Fonts

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:

Symbol metrics

Let's start with some definitions:


Symbol metrics


Symbol metrics.

Advance
The amount by which the pen x position advances after drawing the symbol. This might not be the full width of the character (especially in an italic font) to implement kerning.
Ascender
The height from the baseline to the top of the character.
Bearing x or left bearing
The amount of the character to the left of where the character is deemed to start.
Descender
The height from the bottom of the character to the baseline.
Extent
The width of the symbol. Depending on the font, this might include some white space.
Origin
The lower left corner of the character
X Max
The width of the symbol, not including the bearing x.

Note: To save time and memory, kerning isn't supported.

Font function libraries

There are two libraries of font-related functions shipped with Neutrino:

The Photon library font functions reference libfont functions using global contexts.


ph font server


Font architecture using io-graphics with a resource manager font instance

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.


many


Every application with its own private font server.

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.


common


Applications sharing a common font server.

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.

Font names

A font is identified by its name, which can be in one of these forms:

foundry name
The name assigned by the font foundry to identify the font family, such as Helvetica, Comic Sans MS, and PrimaSans BT. Note the use of capitals.

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).

stem name
A unique identifier that includes an abbreviation of the foundry name, as well as the style (e.g. b, i) and the size. For example, helv12 is the stem name for 12-point Helvetica, and helv12b is the stem name for 12-point bold Helvetica.

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/.

Querying available fonts

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.


Note: 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.

FontDetails structure

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:

FontDescription desc
The foundry name or full descriptive name of the font, such as Helvetica or Charter.
FontName stem
The short form. This provides a part of the stem name used by the Photon API calls. For example, helv and char correspond to Helvetica and Charter.

Generating font names

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 );

Example

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 rectangular area

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 );
}

Repairing damage to proportional text

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 );
}