The PLplot Plotting Library: Programmer's Reference Manual
Prev | Chapter 3. Advanced Use of PLplot | Next

# Adding FreeType Library Support to Bitmap Drivers

Any bitmap driver in the PLplot family should be able to use fonts (TrueType and others) that are rendered by the FreeType library just as long as the device supports setting an individual pixel. Note that drivers interact with FreeType using the support routines `plD_FreeType_init`, `plD_render_freetype_text`, `plD_FreeType_Destroy`, `pl_set_extended_cmap0`, and `pl_RemakeFreeType_text_from_buffer` that are coded in `plfreetype.c`.

The use of these support routines is exemplified by the `gd.c` driver. Here we make some notes to accompany this driver which should make it easier to migrate other drivers to use the FreeType library. Every code fragment we mention below should be surrounded with a `#ifdef HAVE_FREETYPE...#endif` to quarantine these fragments for systems without the FreeType library. For interactive devices that need caching of text drawing, reference should also be made to `wingcc.c`.

## Write a call back function to plot a single pixel

First, write a call back function, of type `plD_pixel_fp`, which specifies how a single pixel is set in the current colour. This can be of type static void. For example, in the `gd.c` driver it looks like this:

    void plD_pixel_gd (PLStream *pls, short x, short y)
    {
    png_Dev *dev=(png_Dev *)pls->dev;

       gdImageSetPixel(dev->im_out, x, y,dev->colour);
    }

## Initialise FreeType

Next, we have to initialise the FreeType library. For the `gd.c` driver this is done via two separate functions due to the order that dependent information is initialised in the driver.

The "level 1" initialisation of FreeType does two things: 1) calls `plD_FreeType_init(pls)`, which in turn allocates memory to the pls->FT structure; and 2) stores the location of the call back routine.

    void init_freetype_lv1 (PLStream *pls)
    {
    FT_Data *FT;

    plD_FreeType_init(pls);

    FT=(FT_Data *)pls->FT;
    FT->pixel= (plD_pixel_fp)plD_pixel_gd;

    }

This initialisation routine is called at the end of `plD_init_png_Dev(PLStream *pls)` in the `gd.c` driver:

    if (freetype)
       {
        pls->dev_text = 1; /* want to draw text */
        init_freetype_lv1(pls);
        FT=(FT_Data *)pls->FT;
        FT->smooth_text=smooth_text;
       }

`"freetype"` is a local variable which is parsed through `plParseDrvOpts` to determine if the user wanted FreeType text. In that case `pls->dev_text` is set to 1 to indicate the driver will be rendering it's own text. After that, we always use `pls->dev_text` to work out if we want FreeType or not.

Similarly, `"smooth_text"` is a local variable passed through `plParseDrvOpts` to find out if the user wants smoothing. Since there is nothing in PLStream to track smoothing, we have to set the FT->smooth_text flag as well at this time.

The "level 2" initialisation function initialises everything else required for using the FreeType library but has to be called after the screen resolution and dpi have been set. Therefore, it is called at the end of `plD_init_png()`, where it looks like:

    if (pls->dev_text)
       {
        init_freetype_lv2(pls);
       }

The actual function looks like this:

    static void init_freetype_lv2 (PLStream *pls)
    {
    png_Dev *dev=(png_Dev *)pls->dev;
    FT_Data *FT=(FT_Data *)pls->FT;

    FT->scale=dev->scale;
    FT->ymax=dev->pngy;
    FT->invert_y=1;

    if (FT->smooth_text==1)
       {
        FT->ncol0_org=pls->ncol0;                                   /* save a copy of the original size of ncol0 */
        FT->ncol0_xtra=NCOLOURS-(pls->ncol1+pls->ncol0);            /* work out how many free slots we have */
        FT->ncol0_width=FT->ncol0_xtra/(pls->ncol0-1);              /* find out how many different shades of anti-aliasing we can do */
        if (FT->ncol0_width>64) FT->ncol0_width=64;                 /* set a maximum number of shades */
        plscmap0n(FT->ncol0_org+(FT->ncol0_width*pls->ncol0));      /* redefine the size of cmap0 */
    /* the level manipulations are to turn off the plP_state(PLSTATE_CMAP0)
     * call in plscmap0 which (a) leads to segfaults since the GD image is
     * not defined at this point and (b) would be inefficient in any case since
     * setcmap is always called later (see plD_bop_png) to update the driver
     * color palette to be consistent with cmap0. */
        {
           PLINT level_save;
           level_save = pls->level;
           pls->level = 0;
           pl_set_extended_cmap0(pls, FT->ncol0_width, FT->ncol0_org); /* call the function to add the extra cmap0 entries and calculate stuff */
           pls->level = level_save;
        }
       }

    }

FT->scale is a scaling factor to convert coordinates. This is used by the `gd.c` and some other drivers to scale back a larger virtual page and this eliminate the "hidden line removal bug". Set it to 1 if your device driver doesn't use any scaling.

Some coordinate systems have zero on the bottom, others have zero on the top. FreeType does it one way, and most everything else does it the other. To make sure everything is working ok, we have to "flip" the coordinates, and to do this we need to know how big in the Y dimension the page is, and whether we have to invert the page or leave it alone.

- FT->ymax specifies the size of the page

- FT->invert_y=1 tells us to invert the y-coordinates, FT->invert_y=0 will not invert the coordinates.

We also do some computational gymnastics to "expand" cmap0 if the user wants anti-aliased text. Basically, you have to work out how many spare colours there are in the driver after cmap0 and cmap1 are done, then set a few variables in FT to let the render know how many colours it's going to have at its disposal, and call plscmap0n to resize cmap0. The call to `pl_set_extended_cmap0` does the remaining part of the work. Note it essential to protect that call by the `pls->level` manipulations for the reasons stated.

## Add A Command to redraw text (interactive drivers only)

Plplot only caches drawing commands, not text plotting commands, so for interactive devices which refresh their display by replaying the plot buffer, a separate function has to be called to redraw the text. plfreetype knows when buffering is being used by a device driver, and will automatically start caching text when necessary. To redraw this cached text, a call to `pl_RemakeFreeType_text_from_buffer` has to be added after the driver has called `plRemakePlot`. The following example is from `wingcc.c`.

                    if (dev->waiting==1)
                    {
                        plRemakePlot(pls);
                        #ifdef HAVE_FREETYPE
                        pl_RemakeFreeType_text_from_buffer(pls);
                        #endif
                     }

## Add Function Prototypes

Next, to the top of the drivers' source file add the prototype definitions for the functions just written.

    static void plD_pixel_gd (PLStream *pls, short x, short y);
    static void init_freetype_lv1 (PLStream *pls);
    static void init_freetype_lv2 (PLStream *pls);

## Add Closing functions

Finally, add a `plD_FreeType_Destroy(pls)` entry to the device "tidy" function; this command deallocates memory allocated to the FT entry in the stream, closes the FreeType library and any open fonts. It is also a good idea to reset CMAP0 back to it's original size here if anti-aliasing was done. For example, in the `gd.c` driver, it looks like this:

    void plD_tidy_png(PLStream *pls)
    {
       fclose(pls->OutFile);

    #ifdef HAVE_FREETYPE
       FT_Data *FT=(FT_Data *)pls->FT;
       plscmap0n(FT->ncol0_org);

       plD_FreeType_Destroy(pls);
    #endif

       free_mem(pls->dev);
    }

Prev | Home | Next
Output Devices | Up | View Surfaces, (Sub-)Pages, Viewports and Windows