Although the Photon and font libraries provide many functions that deal with fonts (see the Pf—Font Server chapter of the Photon Library Reference), most of them are low-level routines that you probably don't need to use. This chapter describes the basics of using fonts.
This chapter includes:
Let's start with some definitions:
To save time and memory, kerning isn't supported. |
There are two libraries of font-related functions shipped with Neutrino:
The Photon library font functions reference libfont functions using global contexts.
The font library, libfont, offers three different methods for obtaining font services:
These methods are made possible through the font server plugin, phfont.so, which contains all common font server code. This allows the memory footprint of the font server to be potentially much smaller than it was before. The font library also allows you to fine-tune your system's memory requirements for your particular embedded environment.
For example, if your system had maximum resources, you could run a private font server instance for every application. Each application would use PfAttachLocalDLL(), passing either your own schema or NULL.
Now say you had only minimal resources. Your applications would use the external font server phfont, or io-graphics that uses phfont.so, with each application performing a message pass to process fonts, which would require minimal memory but higher CPU usage. In the case of io-graphics, font rendering is done locally with no memory passing.
The libfont library DLL functions introduce the concept of a schema, which is a configuration file you can use to override the default settings for private font server instances.
A font is identified by its name, which can be in one of these forms:
The foundry name doesn't include information about the style (e.g. bold, italic) or the size. This name is universal across operating environments (e.g. X, Photon).
To specify a font in the Photon API, you always use a stem name. You should consider stem names to be constant identifiers, not modifiable strings.
You can hard-code all references to fonts in a Photon application. But your application can be more flexible if it uses the foundry name to choose the best match from whatever fonts are available. That way, there isn't a problem if a particular font is eventually renamed, removed, or replaced.
For example, the following call to PtAlert() uses the hard-coded stem name helv14 to specify 14-point Helvetica:
answer = PtAlert( base_wgt, NULL, "File Not Saved", NULL, "File has not been saved.\nSave it?", "helv14", 3, btns, NULL, 1, 3, Pt_MODAL );
You can get the available stem names from the names of the files in ${PHOTON_PATH}/font_repository — just remove any file extension (e.g. .phf).
Alternately, if you have a $HOME/.ph directory, check in $HOME/.ph/font/.
The above example takes a shortcut by using a hard-coded stem name ( helv14). And, like any shortcut, this approach has trade-offs. First, stem names are subject to change. More importantly, all versions of the Photon microGUI up to and including 1.13 have only 16 characters available for the stem name. This isn't always enough to give each font a unique stem. The current version of the Photon microGUI allows 80 characters.
We've defined the FontName data type for you to use for the buffer you pass to PfGenerateFontName(). It's an array of size MAX_FONT_TAG. For successful font programming, don't use a font identifier storage buffer that's smaller than FontName. |
To get around hard-coded stem name issues, you can use PfQueryFonts() to determine which fonts are available and provide the information needed to build a stem name. This function queries the font server, and protects you from future changes.
Once you've got the list of fonts, you need to examine each FontDetails structure in it to find the font you need and determine the string to use as the stem name.
The FontDetails structure is defined in <photon/Pf.h>, and contains at least these elements:
As described earlier, the Photon API requires a stem name to identify a font, but if you want to be flexible, you should use a font foundry name.
The easiest way to get a stem name, given the font foundry name, desired point size, and style, is to call PfGenerateFontName(). It creates, in a buffer that you supply, a unique stem name for the font. (You can use this approach even if you don't use PfQueryFonts() to find all the available fonts.)
Here's the same call to PtAlert() as shown earlier, but this time it calls PfGenerateFontName():
char Helvetica14[MAX_FONT_TAG]; if ( PfGenerateFontName("Helvetica", 0, 14, Helvetica14) == NULL ) { /* Couldn't find the font! */ … } answer = PtAlert( base_wgt, NULL, "File Not Saved", NULL, "File has not been saved.\nSave it?", Helvetica14, 3, btns, NULL, 1, 3, Pt_MODAL );
Now that we've looked at the pieces involved, it's fairly simple to follow the steps needed to build up the correct stem name for a given font.
Keep these things in mind:
You'll probably want to do this work in the initialization function for your application, or perhaps in the base window setup function. Define the FontName buffer as a global variable; you can then use it as needed throughout your application.
Here's a sample application-initialization function:
/*************************** *** global variables *** ***************************/ FontName GcaCharter14Bold; int fcnAppInit( int argc, char *argv[] ) { /* Local variables */ FontDetails tsFontList [nFONTLIST_SIZE]; short sCurrFont = 0; char caBuff[20]; /* Get a description of the available fonts */ if (PfQueryFonts (PHFONT_ALL_SYMBOLS, PHFONT_ALL_FONTS, tsFontList, nFONTLIST_SIZE) == -1) { perror ("PfQueryFonts() failed: "); return (Pt_CONTINUE); } /* Search among them for the font that matches our specifications */ for (sCurrFont = 0; sCurrFont < nFONTLIST_SIZE; sCurrFont++) { if ( !strcmp (tsFontList[sCurrFont].desc, "Charter") ) break; /* we've found it */ } /* Overrun check */ if (sCurrFont == nFONTLIST_SIZE) { /* check for a partial match */ for (sCurrFont = 0; sCurrFont < nFONTLIST_SIZE; sCurrFont++) { if ( !strncmp (tsFontList[sCurrFont].desc, "Charter", strlen ("Charter") ) ) break; /* found a partial match */ } if (sCurrFont == nFONTLIST_SIZE) { printf ("Charter not in %d fonts checked.\n", sCurrFont); return (Pt_CONTINUE); } else printf ("Using partial match -- 'Charter'.\n"); } /* Does it have bold? */ if (!(tsFontList[sCurrFont].flags & PHFONT_INFO_BOLD)) { printf ("Charter not available in bold font.\n"); return (Pt_CONTINUE); } /* Is 14-point available? */ if ( !( (tsFontList[sCurrFont].losize == tsFontList[sCurrFont].hisize == 0) /* proportional font -- it can be shown in 14-point*/ || ( (tsFontList[sCurrFont].losize <= 14 ) && (tsFontList[sCurrFont].hisize >= 14 ) ) ) ) /* 14-point fits between smallest and largest available size */ { printf ("Charter not available in 14-point.\n"); return (Pt_CONTINUE); } /* Generate the stem name */ if (PfGenerateFontName( tsFontList[sCurrFont].desc, PF_STYLE_BOLD, 14, GcaCharter14Bol) == NULL) { perror ("PfGenerateFontName() failed: "); return (Pt_CONTINUE); } /* You can now use GcaCharter14Bold as an argument to PtAlert(), etc. */ /* Eliminate 'unreferenced' warnings */ argc = argc, argv = argv; return( Pt_CONTINUE ); }
For the above code to work, you must declare the following information in the application's global header file. To do this, use PhAB's Startup Info/Modules dialog (accessed from the Application menu).
/********************************* *** user-defined constants *** *********************************/ #define nFONTLIST_SIZE 100 /* an arbitrary choice of size */ /*************************** *** global variables *** ***************************/ extern FontName GcaCharter14Bold;
You can avoid using a specific size for the list by calling PfQueryFonts() with n set to 0 and list set to NULL. If you do this, PfQueryFonts() returns the number of matching fonts but doesn't try to fill in the list. You can use this feature to determine the number of items to allocate.
Remember to define this header before you start adding callbacks and setup functions — that way, it's automatically included as a #define. If you forget, you'll have to go back and add the statement manually. For more information, see “Specifying a global header file” in the Working with Applications chapter.
And last of all, here's a sample callback that uses our stem name string:
int fcnbase_btn_showdlg_ActivateCB( PtWidget_t *widget, ApInfo_t *apinfo, PtCallbackInfo_t *cbinfo ) /* This callback is used to launch a dialog box with the intent of exercising the global variable GcaCharter14Bold */ { PtNotice (ABW_base, NULL, "Font Demonstration", NULL, "This sentence is in 14-pt. Charter bold", GcaCharter14Bold, "OK", NULL, 0); /* Eliminate 'unreferenced' warnings */ widget = widget, apinfo = apinfo, cbinfo = cbinfo; return( Pt_CONTINUE ); }
Writing text in a rectangle of a specific size can be tricky if the string size is unknown.
Consider a rectangle of fixed dimensions, for example a cell in a spreadsheet. How do you determine how many characters can successfully be displayed in this cell without clipping? Call PfExtentTextToRect(). Give it a clipping rectangle, a font identifier, a string, and the maximum number of bytes within the string, and it tells you the number and extent of the characters that fit within the clipping rectangle.
This is useful for placing an ellipsis (…) after a truncated string and avoiding partially clipped characters. Currently this routine supports clipping only along the horizontal axis.
Here's an example:
/* PfExtentTextToRect */ #include <stdlib.h> #include <stdio.h> #include <string.h> #include <Ap.h> #include <Ph.h> #include <Pt.h> #include <errno.h> PtWidget_t * pwndMain = NULL, * pbtn = NULL, * pobjRaw = NULL; char * pcText = "pAfaBfbfffffffffffffffCfcXfxYfyZfzf"; char * pcGB = "\323\316\317\267"; char ** ppcData = NULL; int fnDrawCanvas( PtWidget_t * ptsWidget, PhTile_t * ptsDamage ); #define FALSE 0 FontName szFont; char * pmbGB = NULL; struct PxTransCtrl * ptsTrans = NULL; int iTemp1 = 0, iTemp2 = 0; #define BUFFER_SIZE 256 int main (int argc, char *argv[]) { PtArg_t args[4]; PhPoint_t win_size, pntPOS, pntDIM; short nArgs = 0; if((pmbGB = calloc(BUFFER_SIZE, sizeof(char))) == NULL) return(EXIT_FAILURE); PtInit (NULL); if(argc > 1) { if(PfGenerateFontName(argv[1], 0, 9, szFont) == NULL) PfGenerateFontName("TextFont", 0, 9, szFont); } else PfGenerateFontName("TextFont", 0, 9, szFont); if((ptsTrans = PxTranslateSet(NULL, "GB2312-80")) == NULL) return(EXIT_FAILURE); if(PxTranslateToUTF(ptsTrans, pcGB, 4, &iTemp1, pmbGB, BUFFER_SIZE, &iTemp2) == -1) printf("Could not translate from GB to UTF.\n"); if(argc > 2) pcText = pmbGB; /* Set the base pwndMain parameters. */ win_size.x = 450; win_size.y = 450; PtSetArg(&args[0],Pt_ARG_DIM, &win_size, 0); PtSetArg(&args[1],Pt_ARG_WINDOW_TITLE, "PfExtentTextToRect", 0); pwndMain = PtCreateWidget (PtWindow, Pt_NO_PARENT, 2, args); nArgs = 0; pntPOS.x = 100; pntPOS.y = 10; PtSetArg(&args[nArgs], Pt_ARG_POS, &pntPOS, 0); nArgs++; PtSetArg(&args[nArgs], Pt_ARG_TEXT_STRING, pcText, NULL); nArgs++; PtSetArg(&args[nArgs], Pt_ARG_TEXT_FONT, szFont, NULL); nArgs++; pbtn = PtCreateWidget(PtButton, pwndMain, nArgs, args); PtRealizeWidget(pbtn); pntPOS.y = 100; pntPOS.x = 75; pntDIM.x = 300; pntDIM.y = 300; PtSetArg(&args[0], Pt_ARG_POS, &pntPOS, 0); PtSetArg(&args[1], Pt_ARG_DIM, &pntDIM, 0); PtSetArg(&args[2], Pt_ARG_RAW_DRAW_F, fnDrawCanvas, 0L); pobjRaw = PtCreateWidget(PtRaw, pwndMain, 3, args); PtRealizeWidget(pwndMain); PtMainLoop (); return(0); } #define ASCENDER tsExtent.ul.y #define DESCENDER tsExtent.lr.y int fnDrawCanvas( PtWidget_t * ptsWidget, PhTile_t * ptsDamage ) { PhRect_t tsExtentClip; PhRect_t rect; PhPoint_t pnt; PhRect_t tsExtent; PgColor_t old; PhPoint_t pnt2; PhPoint_t tsPos = {0, 0}; int iRet = 0; int iBytes = 0; /* Find our canvas. */ PtBasicWidgetCanvas(pobjRaw, &rect); PtSuperClassDraw( PtBasic, ptsWidget, ptsDamage ); old = PgSetStrokeColor(Pg_BLACK); PfExtentText(&tsExtent, &tsPos, szFont, pcText, strlen(pcText)); /* Draw the text. */ pnt.x = 10 + rect.ul.x; pnt.y = 100 + rect.ul.y; PgSetFont(szFont); PgSetTextColor(Pg_BLACK); PgDrawText(pcText, strlen(pcText), &pnt, 0); pnt.x -= 10; pnt2.x = pnt.x + tsExtent.lr.x + 20; pnt2.y = pnt.y; PgSetStrokeColor(Pg_BLUE); PgDrawLine(&pnt, &pnt2); pnt.x = 10 + rect.ul.x; pnt.y = 100 + rect.ul.y; PgSetStrokeColor(Pg_RED); PgDrawIRect(tsExtent.ul.x + pnt.x, tsExtent.ul.y + pnt.y, (tsExtent.lr.x - min(tsExtent.ul.x, 0) + 1) + pnt.x, tsExtent.lr.y + pnt.y, Pg_DRAW_STROKE); if((iRet = PfExtentTextToRect(&tsExtentClip, szFont, &tsExtent, pcText, strlen(pcText))) == -1) printf("PfExtentTextToRect failed 1.\n"); else { printf("lrx == %d, %d characters in string.\n", tsExtent.lr.x, utf8strlen(pcText, &iBytes)); printf("PfExtentTextToRect lrx == %d, %d characters will\ fit in clip of %d.\n", tsExtentClip.lr.x, iRet, tsExtent.lr.x); } tsExtent.lr.x /= 2; if((iRet = PfExtentTextToRect(&tsExtentClip, szFont, &tsExtent, pcText, strlen(pcText))) == -1) printf("PfExtentTextToRect failed 2.\n"); else { printf("lrx == %d, %d characters in string.\n", tsExtent.lr.x, utf8strlen(pcText, &iBytes)); printf("PfExtentTextToRect lrx == %d, %d characters will\ fit in clip of %d.\n", tsExtentClip.lr.x, iRet, tsExtent.lr.x); } pnt.x = 10 + rect.ul.x; pnt.y = 150 + rect.ul.y; PgDrawText(pcText, iRet, &pnt, 0); PgDrawIRect(tsExtentClip.ul.x + pnt.x, tsExtentClip.ul.y + pnt.y, (tsExtentClip.lr.x - min(tsExtentClip.ul.x, 0) + 1) + pnt.x, tsExtentClip.lr.y + pnt.y, Pg_DRAW_STROKE); tsExtent.lr.x /= 2; if((iRet = PfExtentTextToRect(&tsExtentClip, szFont, &tsExtent, pcText, strlen(pcText))) == -1) printf("PfExtentTextToRect failed 3.\n"); else { printf("lrx == %d, %d characters in string.\n", tsExtent.lr.x, utf8strlen(pcText, &iBytes)); printf("PfExtentTextToRect lrx == %d, %d characters will\ fit in clip of %d.\n", tsExtentClip.lr.x, iRet, tsExtent.lr.x); } pnt.x = 10 + rect.ul.x; pnt.y = 200 + rect.ul.y; PgDrawText(pcText, iRet, &pnt, 0); PgDrawIRect(tsExtentClip.ul.x + pnt.x, tsExtentClip.ul.y + pnt.y, (tsExtentClip.lr.x - min(tsExtentClip.ul.x, 0) + 1) + pnt.x, tsExtentClip.lr.y + pnt.y, Pg_DRAW_STROKE); PgSetStrokeColor(old); return( Pt_CONTINUE ); }
When dealing with proportional fonts, sometimes the vectors of one glyph run into the vectors of another. This is especially evident when using a font such as Nuptial BT. You need to take special care when repairing damage to such fonts.
PfExtentTextCharPositions() addresses this issue. You can use this routine to obtain the position after each character, incorporating the bearing x of the following character. This position is where you should draw the next character.
If you use the PF_CHAR_DRAW_POSITIONS flag, the bearing x of the following character isn't applied to the position, which is useful when you're placing cursors.
For example:
#include <stdlib.h> #include <stdio.h> #include <string.h> #include <Ap.h> #include <Ph.h> #include <Pt.h> #include <errno.h> PtWidget_t * pwndMain = NULL, * pbtn = NULL, * pobjRaw = NULL, * pobjLabel = NULL; char ** ppcData = NULL; int fnDrawCanvas( PtWidget_t * ptsWidget, PhTile_t * ptsDamage ); #define FALSE 0 #define __WIN_SIZE_X_ 1000 FontName szFont; int main (int argc, char *argv[]) { PtArg_t args[8]; PhPoint_t win_size, pntPOS, pntDIM; short nArgs = 0; char caTitle[50]; if(argc < 2) { printf("Usage: pen text_string\n"); exit(EXIT_FAILURE); } PtInit (NULL); ppcData = argv; PfGenerateFontName("TextFont", 0, 9, szFont); /* Set the base pwndMain parms. */ win_size.x = 800; win_size.y = 600; sprintf(caTitle, "Get the pen position"); PtSetArg(&args[0],Pt_ARG_DIM, &win_size, 0); PtSetArg(&args[1],Pt_ARG_WINDOW_TITLE, caTitle, 0); pwndMain = PtCreateWidget (PtWindow, Pt_NO_PARENT, 2, args); nArgs = 0; pntDIM.x = 80; pntDIM.y = 20; PtSetArg(&args[nArgs], Pt_ARG_DIM, &pntDIM, 0); nArgs++; pntPOS.x = 100; pntPOS.y = 10; PtSetArg(&args[nArgs], Pt_ARG_POS, &pntPOS, 0); nArgs++; PtSetArg(&args[nArgs], Pt_ARG_TEXT_STRING, argv[1], NULL); nArgs++; pbtn = PtCreateWidget(PtButton, pwndMain, nArgs, args); PtRealizeWidget(pbtn); nArgs = 0; pntDIM.x = 80; pntDIM.y = 20; PtSetArg(&args[nArgs], Pt_ARG_DIM, &pntDIM, 0); nArgs++; pntPOS.x = 100; pntPOS.y = 600; PtSetArg(&args[nArgs], Pt_ARG_POS, &pntPOS, 0); nArgs++; PtSetArg(&args[nArgs], Pt_ARG_TEXT_STRING, argv[1], NULL); nArgs++; PtSetArg(&args[nArgs], Pt_ARG_RESIZE_FLAGS, Pt_RESIZE_XY_ALWAYS, Pt_RESIZE_XY_ALWAYS); nArgs++; PtSetArg(&args[nArgs], Pt_ARG_BORDER_WIDTH, 0L, 0L); nArgs++; PtSetArg(&args[nArgs], Pt_ARG_MARGIN_LEFT, 0L, 0L); nArgs++; PtSetArg(&args[nArgs], Pt_ARG_MARGIN_RIGHT, 0L, 0L); nArgs++; pobjLabel = PtCreateWidget(PtLabel, pwndMain, nArgs, args); PtRealizeWidget(pobjLabel); pntPOS.y = 100; pntPOS.x = 75; pntDIM.x = __WIN_SIZE_X_ - 75 - 10; pntDIM.y = 300; PtSetArg(&args[0], Pt_ARG_POS, &pntPOS, 0); PtSetArg(&args[1], Pt_ARG_DIM, &pntDIM, 0); PtSetArg(&args[2], Pt_ARG_RAW_DRAW_F, fnDrawCanvas, 0L); pobjRaw = PtCreateWidget(PtRaw, pwndMain, 3, args); (void) PtRealizeWidget(pwndMain); PtMainLoop (); return(0); } int fnDrawCanvas( PtWidget_t * ptsWidget, PhTile_t * ptsDamage ) { unsigned char const * pucFont = NULL; int * piIndx = NULL; int * piPos = NULL; char ** argv = (char **)ppcData; PhRect_t rect; PhPoint_t pnt; PhPoint_t tsPos = {0, 0}; PhRect_t tsExtent; short n = 0; char * pc = NULL; PgColor_t old; pucFont = szFont; pc = argv[1]; piIndx = (int *)calloc(50, sizeof(int)); piPos = (int *)calloc(50, sizeof(int)); if(strlen(pc) < 4) { printf("Pick a longer string, must be at least\ 4 characters.\n"); exit(EXIT_SUCCESS); } for(n = 0; n < strlen(pc); n++) piIndx[n] = n + 1; /* Find our canvas. */ PtBasicWidgetCanvas(pobjRaw, &rect); old = PgSetStrokeColor(Pg_BLACK); PfExtentText(&tsExtent, &tsPos, pucFont, pc, strlen(pc)); PgSetFont(pucFont); PgSetTextColor(Pg_BLACK); for(n = 0; n < strlen(pc); n++) piIndx[n] = n + 1; /* Draw the string, one character at a time. */ PfExtentTextCharPositions(&tsExtent, &tsPos, pc, pucFont, piIndx, piPos, strlen(pc), 0L, 0, 0, NULL); pnt.x = 10 + rect.ul.x; pnt.y = 200 + rect.ul.y; PgDrawIRect(tsExtent.ul.x + pnt.x, tsExtent.ul.y + pnt.y, (tsExtent.lr.x - min(tsExtent.ul.x, 0) + 1) + pnt.x, tsExtent.lr.y + pnt.y, Pg_DRAW_STROKE); for(n = 0; n < strlen(pc); n++) { PgDrawText(pc + n, 1, &pnt, 0); pnt.x = 10 + rect.ul.x + piPos[n]; printf("Single[%d]: %d\n", n, piPos[n]); } /* End draw one character at a time. */ /* Draw the string, then overlay individual characters on top from right to left. */ printf("Overlay test.\n"); PfExtentText(&tsExtent, &tsPos, pucFont, pc, strlen(pc)); pnt.x = 10 + rect.ul.x; pnt.y = 400 + rect.ul.y; PgDrawIRect(tsExtent.ul.x + pnt.x, tsExtent.ul.y + pnt.y, (tsExtent.lr.x - min(tsExtent.ul.x, 0) + 1) + pnt.x, tsExtent.lr.y + pnt.y, Pg_DRAW_STROKE); PgSetFont(pucFont); PgSetTextColor(Pg_BLACK); PgDrawText(pc, strlen(pc), &pnt, 0); for(n = strlen(pc) - 1; n >= 0; n--) { switch(n) { case 0: pnt.x = 10 + rect.ul.x; PgDrawText(pc + 0, strlen(pc), &pnt, 0); break; default: piIndx[0] = n; PfExtentTextCharPositions(&tsExtent, &tsPos, pc, pucFont, piIndx, piPos, 1, 0L, 0, 0, NULL); printf("Position: %d\n", piPos[0]); pnt.x = 10 + rect.ul.x + piPos[0]; PgDrawText(pc + n, strlen(pc) - n, &pnt, 0); PgFlush(); sleep(1); break; } } /* End draw string, then overlay individual characters on top from right to left. */ PgSetStrokeColor(old); free(piPos); free(piIndx); return( Pt_CONTINUE ); }