Factorial with a X Windows user interface
With this post I looked at programming for the X Window System (X11). X11 is hardware-independent application programming interface (API) for graphical user interface (GUI) programming that includes commands for drawing to a screen and user interaction through key board, mouse or other input devices. The interface provides a means for software to communicate with graphics hardware including over a network. X11 was designed with local GUI interaction for programs running on a remote computer as a major issue. The X Window System was started at Massachusetts Institute of Technology (MIT) in 1984 and quickly became the de facto standard for networked workstations. The X Window System is constantly being improved, but the default X11 is a pixel based system which produces ugly text without anti-aliasing. A pixel based system draws a color to pixels so that object's edges appear jagged. Anti-aliasing reduces the jagged edges by drawing varying colors around the border of objects to blend the object color with the surrounding colors.
To access the code which is amply commented see the post C code for factorial program using X11, Xft and GMP. A screen shot of the program with anti-aliased fonts.
This program started to be a simple factorial program like the prior post but quickly grew. The original concept was a simple line of instructions followed by the "n! = answer" string. However, since the string is drawn into a window, it made sense to draw multiple lines that could fit within the window. Since the answer could have many digits, an automatic reduction of font size and wrapping of the solution was programmed. This involved tracking the window and character sizes. Three font sizes were made available, using the biggest one that fit within the window. Another original concept was to use the Gnu Multi-Precision (GMP) library to calculate larger factorials correctly if it is available.
X11 uses an event loop to wait for user interaction. A means of using this loop to enter numbers was designed. Other options were added by looking for other keys on the keyboard.
The ugly default X11 fonts needed improved so the Xft library was used. When running over a slow network with high latency the Xft library would be slow. Because in order to draw a string, first a snapshot of the background is taken then transferred to the server, then anti-aliased fonts are drawn on the snapshot, then the snapshot is transferred back to the client. A lot of extra network traffic. Both display options were included so they could be toggled to compare network response and appearance.
Several of the prior post also showed the danger of limited integer size. To show the impact of integer size an option to reduce the integer size was added. This option used bit mask to reduce the size so all solutions are non-negative. The integer size limit and resulting numbers can be made clearer in hexadecimal or binary numbers since these better represent the number within the computer.
This program includes many options and optionally depends on two libraries, Xft and GMP. The code also includes lots of comments to be instructional. The Xft library (X FreeType interface) provides nice looking anti-aliased fonts and the GMP (Gnu Multi-Precision) library provides arbitrary precision math.
Preprocessor Directives
Whether or not to use the libraries is controlled by the lines
/** define USE_XFT for antialiased fonts with the Xft library*/
#define USE_XFT
/** define USE_GMP for arbitrary precision math */
#define USE_GMP
Which define 'variables' used by the C preprocessor which changes the code before compiling it based on special commands starting with a hash '#'.
Some common preprocessor commands are:
#include < ... >
#include " ... "
which copies the named file into the source code. The < ... > starts the search for the file in system level directories and the " ... " starts in the source code directories.
#define USE_GMP
#define VALUE 5
#define MIN(X,Y) (((X) < (Y)) ? (X) : (Y))
which define a variable. A variable can optionally be defined with a value as in the second line. With this definition for VALUE the code "y = x + VALUE" would get replaced by "y = x + 5" before compiling. The variable may also be function like, as in the third line where the code "y = 4*MIN(x,5)/3" would get replaced by "y = 4*(((x) < (5)) ? (x) : (5))/3" before compiling. Note that this is a text substitution so it is not a real function and extra use of parenthesis are required to prevent side effects. The tertiary command ( ... )? ... : ... is a short "if else" expression where if the parenthesis contents are true the expression before the colon (:) is evaluated and if the expression is false (0) the expression after the colon is evaluated. These variables can also be defined with compile line arguments "-D...". "gcc -D USE_GMP" would define USE_GMP. The source code #define will override the command line defines and un-defines (-U...).
#ifdef USE_GMP
...
#else
...
#endif
#if (VALUE > 4)
...
#elif VALUE < 6
...
#endif
where the first code block would test USE_GMP and include the code up to an #else, #elif, or #endif if it is defined. The code after the #else would be included if USE_GMP is not defined. Optionally a #ifndef would test if USE_GMP is not defined. The next code block performs test on the value of VALUE and includes the first block if VALUE is greater than 4 and the second block if VALUE is less than 6 but not greater than 4.
Compiling the code
In order to use the libraries, the header files for declaring the variables and routines needs to be included in the source code and the library needs to be included in the last phase of compiling (linking). To link with a shared object (DLL or .so) include a -l option with the library name. For this code including the USE_XFT and USE_GMP defines, " -lX11 -lXft -lgmp " will link with the libraries. When shared object linking is used the system the code is run on needs to have the libraries installed. Another option is to use static linking where the library binary code is included in the executable produced. To use this option include the library archive file on the command line. The library archive file ends with a ".a" for example "/usr/lib64/libgmp.a" is name of the GMP archive library file on my system.
The full compile line on my computer was
gcc factX.c -g -o factX -I/usr/include/freetype2 -lXft -lX11 -lgmp
The -g enables debugging by including references to source code statements in the executable file. The "-o factX" names the output executable file factX. The -I/usr/include/freetype2 adds the specified directory, "/usr/include/freetype2" to the search path for include files. Without the -I a compile error
fatal error: freetype/config/ftheader.h: No such file or directory
was produced. By using
locate ftheader.h
the location of the file was found to be /usr/include/freetype2/freetype/config/ftheader.h
which was used to add the search path to the compile line.Similarly
locate libgmp.a
gave the location of the library archive for GMP. So the compile commandgcc factX.c /usr/lib64/libgmp.a -o factX -I/usr/include/freetype2 -lXft -lX11
static linked the GMP library. The executable file grew in size by over 18 times but the GMP library would not be required on the system the code is run on. Most modern systems include X11 and Xft by default so they would not need to be static linked. Also, these libraries require other libraries not required on the compile statement.
The compile command can also be used to define variables used by the preprocessor with the -D option such as
gcc factX.c /usr/lib64/libgmp.a -o factX -D USE_GMP -I/usr/include/freetype2 -lXft -lX11
however any
#define
within the code will override command line options.GMP factorial
The GMP parts of the code are enclosed between a #ifdef USE_GMP and a matching #else or #endif. Important lines of code for calculating the factorial are:#include <gmp.h>
-mpz_t k;
- mpz_t is the variable type for multi-precision integers. Mathematicians use Z to represent the set of all integers
mpz_inits( k, kt, bmask, NULL );
- Initialize the mpz_t variables in the list ended by a NULL, mpz_t variables must be initialized before they are assigned a value
n = atol(text);
- convert ascii "text" to a long int
mpz_fac_ui( k, n );
- Compute the factorial of the long integer provided and store it into the mpz_t variable
Additional elements for reducing number precision:
switch(precision) {
- Switch to the code with a case of the value of "precision"mpz_set_ui(bmask,0xff);
- Set bmask to the Hex number ff, ff in hexadecimal is 8 1 bits in binary, 11111111, or 255 in decimal.mpz_set_str(bmask,"ffffffffffffffff",16);
- Most 32 bit computers have a long int of only 4 Bytes, therefor a string representation is used for 8 Bytes of 1 bits. Each two hex digits is a Byte.mpz_and(kt,k,bmask);
- Logical bitwise AND, Value in ROP is set to bitwise AND of values in OP1 and OP2. Bitwise AND takes each pair of bits, if both are 1 the resulting bit is set to 1 and 0 otherwise. For example 10101010 & 1111 gives 1010.mpz_set(k,kt);
- This just copies the value of kt into k for later use
And to convert the number to a string in the various number bases:
switch(showBin) {
- Switch to the code with a case of the value of showBincase 0: fact = mpz_get_str(NULL,10,k); break;
- Convert k to a string with base of "base". If the first argument is NULL a newly memory allocated string is returned, otherwise the result is placed into the char * array provided, which the caller must assure is allocated large enough to hold the result.
And don't forget to release the internal memory of the mpz_t variables.
mpz_clears ( k, kt, bmask, NULL);
- Release the internal memory used by the mpz_t variables
X11 Drawing
Drawing text in X11 involves three phases. Initialization, listening + drawing and releasing resources.Key elements of the initialization routine,
init_x()
.dis = XOpenDisplay((char *)0);
-Get the display, An X11 display can consist of multiple screens (monitors) and input devices (keyboard, mouse, touch screen). A display can be thought of as a workstation.
win = XCreateSimpleWindow(dis,DefaultRootWindow(dis),0,0,400, 247, 0, black, white);
- This creates a new window at (0,0) that is 400 pixels wide by 247 pixels high with a 0 width black border and white background. If a window manager is in use, these settings are just suggestions to the window manager.XSetWMProperties(dis,win,winTitle,iconTitle,NULL,0,NULL,NULL,NULL);
- This tells the window manager more information about what is desired for the window. Again these are suggestions or hints about what is desired. The only values set are the window title and icon title.
XSelectInput(dis, win, ExposureMask|KeyPressMask);
- Tell the X11 server we are interested in Key Press and Window Exposure events. Key press events are returned when a key is pressed and exposure events are returned when a portion of the window needs to be redrawn because the window size changed or was raised above an obstruction.
gc=XCreateGC(dis, win, 0,0);
- X11 itself does not store information between draw actions. The graphics context stores color, font and other information required for drawing.
XSetBackground(dis,gc,white);
- This will be the color behind what is being drawn.
XSetForeground(dis,gc,black);
- This will be the color of the shape to be drawn.
XClearWindow(dis, win);
- Reset the window to be empty.
XMapRaised(dis, win);
- Very important - This actually shows the window on the screen.
smallFont = XLoadQueryFont(dis,"-*-*-*-*-*-*-9-*-*-*-m-*-*-*");
- This gets font information from the X11 font server. In this case, a 9 pixel high font that is mono-spaced is requested.
Key elements of the listening part within the main() function
while(1) {
- This begins the main loop which will repeat until some other exit request is encounteredXNextEvent(dis, &event);
- Wait for an event to occur. When it occurs stor information about the event into the structure eventif (event.type == Expose && event.xexpose.count==0) {
- The type of event was an exposure event and it is the last expose event. Wait for the last expose event to avoid excessive redrawing slowing response time. A larger program may try to find what portion of the window was exposed and draw only that portion to improve response time.if (event.type == KeyPress) {
- The type of event was a key presskey = XLookupKeysym(&(event.xkey),0);
- Find out which key was pressedswitch( key ) {
- Got to case statement with the key symbol valuecase XK_???
- Every key has a #defined number, the number is represented by a symbol beginning with XK_. An XK_KP_ is a key pad key. There are case statements for both upper and lower case keys. Since case statements fall through multiple case statements can execute the same code.redraw(text);
- An event has occurred so redraw the window contents. "text" is a character array with the string representing the number.
Key elements of the drawing part in the redraw(text) and draw(numfeq,fact) routines.
void redraw(char * text)
- Computes the factorial, then sets the strings numfeq and fact to contain "n! = " and the solution respectively.draw(numfeq,fact);
- This routine draws the instruction string at the top, the "numfeq" string and the "fact" string in multiple lines.instr = nowShowing();
- Get the instruction string. It simplifies the code to move some parts to other routines.XSetFont(dis,gc,smallFont->fid);
- Put font data into the graphics contextXDrawString(dis,win,gc,x,y, instr, strlen(instr) );
- Draw the string, "instr" at position (x,y)xstart = 5 + XTextWidth(font,numfeq,strlen(numfeq));
- Calculate the x starting position for the multiple lines of the solution string, "fact"nchar = awid/font->max_bounds.width;
- Calculate the number of characters that can fit on a line. "awid" is available width and "maxbounds.width" is the font character maximum width.nlines = totchar/nchar + 1;
- This is where integer division truncation is used to compute the number of lines required to display the solutionif(nlines > maxlines ) {
- The display of the solution will not fit so try a smaller font.for(start = 0; start < totchar; start += nchar) {
- A loop to draw each lineXDrawString(dis,win,gc,xstart,y, fact+start, nchar);
- Draw up to nchar characters starting at the fact[start] character. This uses some pointer math where "fact+start" points "start" characters past the "fact" location.y += font->ascent + font->descent + 5;
- Compute the y position of the next line.
The resources are released in the close_x() routine. Since this is called just before the program exits, these resources don't necessarily need to be released, but it is a good idea to not count on the Operating System to release the resources and it is good practice to release your own resources. The resources released include the font structures, graphics context, window and display. They are released in reverse order from their creation.
Xft Drawing
The Xft drawing routines are on top of the X drawing routines so much of the X drawing activities must still be accomplished.
In addition to the X drawing initialization the following are added:
smallXft = XftFontOpenXlfd(dis,screen,"-*-*-*-*-*-*-9-*-*-*-m-*-*-*");
- Lookup and setup the font, used instead of XLoadQueryFont for X11 drawing. Xft has other font lookup routines that are easier to read because they set values rather than rely on position in a stringcmap = DefaultColormap(dis,screen);
- The color map indicates what colors can be used for drawingdrawXft = XftDrawCreate(dis, win, DefaultVisual(dis, screen), cmap);
- The XftDraw is kind of like the graphics context for X11 combined with the display and windowcolorXrender.red = 0; colorXrender.green = 0; colorXrender.blue = 0; colorXrender.alpha = 0xffff;
- The color for drawing is specified with 2 Byte values for red,green,blue and alpha. Alpha is the opacity so alpha = 0 is transparentXftColorAllocValue(dis,DefaultVisual(dis,screen),cmap,&colorXrender,\
&colorXft);
- Setup the color "colorXft" for actual use later
Replacements for the X11 string drawing and size routines are:
XftDrawString8(drawXft,&colorXft,smallXft,x,y,instr,strlen(instr));
- Draw the string at position (x,y). Other string drawing routines are available for Unicode (16 bit) and larger (32 bit) character types.XftTextExtents8 (dis,font,(FcChar8 *)numfeq,strlen(numfeq),&extents);
textWidth = extents.width;
textHeight = extents.height;
- The function XftTextExtents8(...) computes the bounds of the string if it were drawn in the font on the display and puts them into the structure "extents". The width and height are then available from the structure.
Additional releasing functions are:
XftFontClose(dis,smallXft);
- Free memory used for fontsXftDrawDestroy(drawXft);
- Release memory used for Xft drawing
Code Usage
When the code is started a window appears with limited instructions. It listens for key presses and responds with the following- Any number key (0-9) - adds the digit to the number used for the factorial
- [Delete] or [Backspace] - remove the last digit from the number
- [Return] or [Enter] - Clear the number, display is not refreshed immediately.
- 'A' or 'X' will toggle display text anti-aliasing with the Xft library
- 'B' will cycle through decimal, binary and hex display of the solution
- 'P' will cycle through# of bytes precision of the solution - full,8,4,2,1
- 'Q' or 'Escape' will cleanly exit the program
Assignments
Compile the code without using the GMP library. Test a few numbers. Toggle font anti-aliasing.Compile with the GMP library. Test a few numbers. Change the window size, height and width.
Reduce the precision with 'p'. What is the low precision result for larger number values.
Change the number representation with 'b' and reduce the precision with 'p'. Compare with prior post exercises where fixed size integers were used.
The two draw routines have a lot of code repeated. Rewrite the logic for selecting the font size by adding global character sizes calculated in init_x. Combine the draw routines to simplify the maintenance with useXft logic inside the routines.
No comments:
Post a Comment