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 PL_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 color. 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 initialize the FreeType library. For the
gd.c
driver this is done via two separate functions due
to the order that dependent information is initialized in the driver.
The "level 1" initialization 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 initialization 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" initialization function initializes 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
colors 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 colors 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 PL_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 PL_HAVE_FREETYPE FT_Data *FT=(FT_Data *)pls->FT; plscmap0n(FT->ncol0_org); plD_FreeType_Destroy(pls); #endif free_mem(pls->dev); }