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