4 freetyp2.c - font support via the FreeType library version 2.
8 if (!i_ft2_init()) { error }
10 font = i_ft2_new(name, index);
11 if (!i_ft2_setdpi(font, xdpi, ydpi)) { error }
12 if (!i_ft2_getdpi(font, &xdpi, &ydpi)) { error }
14 if (!i_ft2_settransform(font, matrix)) { error }
15 int bbox[BOUNDING_BOX_COUNT];
16 if (!i_ft2_bbox(font, cheight, cwidth, text, length, bbox, utf8)) { error }
19 if (!i_ft2_text(font, im, tx, ty, cl, cheight, cwidth, text, length, align,
21 if (!i_ft2_cp(font, im, tx, ty, channel, cheight, cwidth, text, length,
27 Implements Imager font support using the FreeType2 library.
29 The FreeType2 library understands several font file types, including
30 Truetype, Type1 and Windows FNT.
40 #include FT_FREETYPE_H
42 static void ft2_push_message(int code);
44 static FT_Library library;
47 =item i_ft2_init(void)
49 Initializes the Freetype 2 library.
51 Returns true on success, false on failure.
60 error = FT_Init_FreeType(&library);
62 ft2_push_message(error);
63 i_push_error(0, "Initializing Freetype2");
69 struct FT2_Fonthandle {
75 /* used to adjust so we can align the draw point to the top-left */
79 /* the following is used to select a "best" encoding */
80 static struct enc_score {
85 /* the selections here are fairly arbitrary
86 ideally we need to give the user a list of encodings available
87 and a mechanism to choose one */
88 { ft_encoding_unicode, 10 },
89 { ft_encoding_sjis, 8 },
90 { ft_encoding_gb2312, 8 },
91 { ft_encoding_big5, 8 },
92 { ft_encoding_wansung, 8 },
93 { ft_encoding_johab, 8 },
94 { ft_encoding_latin_2, 6 },
95 { ft_encoding_apple_roman, 6 },
96 { ft_encoding_adobe_standard, 6 },
97 { ft_encoding_adobe_expert, 6 },
101 =item i_ft2_new(char *name, int index)
103 Creates a new font object, from the file given by I<name>. I<index>
104 is the index of the font in a file with multiple fonts, where 0 is the
107 Return NULL on failure.
113 i_ft2_new(char *name, int index) {
115 FT2_Fonthandle *result;
117 double matrix[6] = { 1, 0, 0,
120 FT_Encoding encoding;
123 mm_log((1, "i_ft2_new(name %p, index %d)\n", name, index));
126 error = FT_New_Face(library, name, index, &face);
128 ft2_push_message(error);
129 i_push_error(error, "Opening face");
133 encoding = face->num_charmaps ? face->charmaps[0]->encoding : ft_encoding_unicode;
135 for (i = 0; i < face->num_charmaps; ++i) {
136 FT_Encoding enc_entry = face->charmaps[i]->encoding;
137 mm_log((2, "i_ft2_new, encoding %lX platform %u encoding %u\n",
138 enc_entry, face->charmaps[i]->platform_id,
139 face->charmaps[i]->encoding_id));
140 for (j = 0; j < sizeof(enc_scores) / sizeof(*enc_scores); ++j) {
141 if (enc_scores[j].encoding == enc_entry && enc_scores[j].score > score) {
142 encoding = enc_entry;
143 score = enc_scores[j].score;
148 FT_Select_Charmap(face, encoding);
149 mm_log((2, "i_ft2_new, selected encoding %lX\n", encoding));
151 result = mymalloc(sizeof(FT2_Fonthandle));
153 result->xdpi = result->ydpi = 72;
154 result->encoding = encoding;
156 /* by default we disable hinting on a call to i_ft2_settransform()
157 if we don't do this, then the hinting can the untransformed text
158 to be a different size to the transformed text.
159 Obviously we have it initially enabled.
163 /* I originally forgot this: :/ */
164 /*i_ft2_settransform(result, matrix); */
165 result->matrix[0] = 1; result->matrix[1] = 0; result->matrix[2] = 0;
166 result->matrix[3] = 0; result->matrix[4] = 1; result->matrix[5] = 0;
172 =item i_ft2_destroy(FT2_Fonthandle *handle)
174 Destroys a font object, which must have been the return value of
180 i_ft2_destroy(FT2_Fonthandle *handle) {
181 FT_Done_Face(handle->face);
186 =item i_ft2_setdpi(FT2_Fonthandle *handle, int xdpi, int ydpi)
188 Sets the resolution in dots per inch at which point sizes scaled, by
189 default xdpi and ydpi are 72, so that 1 point maps to 1 pixel.
191 Both xdpi and ydpi should be positive.
193 Return true on success.
198 i_ft2_setdpi(FT2_Fonthandle *handle, int xdpi, int ydpi) {
200 if (xdpi > 0 && ydpi > 0) {
206 i_push_error(0, "resolutions must be positive");
212 =item i_ft2_getdpi(FT2_Fonthandle *handle, int *xdpi, int *ydpi)
214 Retrieves the current horizontal and vertical resolutions at which
215 point sizes are scaled.
220 i_ft2_getdpi(FT2_Fonthandle *handle, int *xdpi, int *ydpi) {
221 *xdpi = handle->xdpi;
222 *ydpi = handle->ydpi;
228 =item i_ft2_settransform(FT2_FontHandle *handle, double *matrix)
230 Sets a transormation matrix for output.
232 This should be a 2 x 3 matrix like:
234 matrix[0] matrix[1] matrix[2]
235 matrix[3] matrix[4] matrix[5]
240 i_ft2_settransform(FT2_Fonthandle *handle, double *matrix) {
245 m.xx = matrix[0] * 65536;
246 m.xy = matrix[1] * 65536;
247 v.x = matrix[2]; /* this could be pels of 26.6 fixed - not sure */
248 m.yx = matrix[3] * 65536;
249 m.yy = matrix[4] * 65536;
250 v.y = matrix[5]; /* see just above */
252 FT_Set_Transform(handle->face, &m, &v);
254 for (i = 0; i < 6; ++i)
255 handle->matrix[i] = matrix[i];
262 =item i_ft2_sethinting(FT2_Fonthandle *handle, int hinting)
264 If hinting is non-zero then glyph hinting is enabled, otherwise disabled.
266 i_ft2_settransform() disables hinting to prevent distortions in
267 gradual text transformations.
271 int i_ft2_sethinting(FT2_Fonthandle *handle, int hinting) {
272 handle->hint = hinting;
277 =item i_ft2_bbox(FT2_Fonthandle *handle, double cheight, double cwidth, char *text, int len, int *bbox)
279 Retrieves bounding box information for the font at the given
280 character width and height. This ignores the transformation matrix.
282 Returns non-zero on success.
287 i_ft2_bbox(FT2_Fonthandle *handle, double cheight, double cwidth,
288 char const *text, int len, int *bbox, int utf8) {
293 int ascent = 0, descent = 0;
294 int glyph_ascent, glyph_descent;
295 FT_Glyph_Metrics *gm;
297 int loadFlags = FT_LOAD_DEFAULT;
300 mm_log((1, "i_ft2_bbox(handle %p, cheight %f, cwidth %f, text %p, len %d, bbox %p)\n",
301 handle, cheight, cwidth, text, len, bbox));
303 error = FT_Set_Char_Size(handle->face, cwidth*64, cheight*64,
304 handle->xdpi, handle->ydpi);
306 ft2_push_message(error);
307 i_push_error(0, "setting size");
311 loadFlags |= FT_LOAD_NO_HINTING;
318 c = i_utf8_advance(&text, &len);
320 i_push_error(0, "invalid UTF8 character");
325 c = (unsigned char)*text++;
329 index = FT_Get_Char_Index(handle->face, c);
330 error = FT_Load_Glyph(handle->face, index, loadFlags);
332 ft2_push_message(error);
333 i_push_errorf(0, "loading glyph for character \\x%02x (glyph 0x%04X)",
337 gm = &handle->face->glyph->metrics;
338 glyph_ascent = gm->horiBearingY / 64;
339 glyph_descent = glyph_ascent - gm->height/64;
341 start = gm->horiBearingX / 64;
342 /* handles -ve values properly */
343 ascent = glyph_ascent;
344 descent = glyph_descent;
348 if (glyph_ascent > ascent)
349 ascent = glyph_ascent;
350 if (glyph_descent < descent)
351 descent = glyph_descent;
353 width += gm->horiAdvance / 64;
357 handle the case where the right the of the character overlaps the
359 rightb = gm->horiAdvance - gm->horiBearingX - gm->width;
365 bbox[BBOX_NEG_WIDTH] = start;
366 bbox[BBOX_GLOBAL_DESCENT] = handle->face->size->metrics.descender / 64;
367 bbox[BBOX_POS_WIDTH] = width - rightb;
368 bbox[BBOX_GLOBAL_ASCENT] = handle->face->size->metrics.ascender / 64;
369 bbox[BBOX_DESCENT] = descent;
370 bbox[BBOX_ASCENT] = ascent;
371 bbox[BBOX_ADVANCE_WIDTH] = width;
373 return BBOX_ADVANCE_WIDTH + 1;
377 =item transform_box(FT2_FontHandle *handle, int bbox[4])
379 bbox contains coorinates of a the top-left and bottom-right of a bounding
380 box relative to a point.
382 This is then transformed and the values in bbox[4] are the top-left
383 and bottom-right of the new bounding box.
385 This is meant to provide the bounding box of a transformed character
386 box. The problem is that if the character was round and is rotated,
387 the real bounding box isn't going to be much different from the
388 original, but this function will return a _bigger_ bounding box. I
389 suppose I could work my way through the glyph outline, but that's
394 void ft2_transform_box(FT2_Fonthandle *handle, int bbox[4]) {
396 double *matrix = handle->matrix;
399 work[0] = matrix[0] * bbox[0] + matrix[1] * bbox[1];
400 work[1] = matrix[3] * bbox[0] + matrix[4] * bbox[1];
401 work[2] = matrix[0] * bbox[2] + matrix[1] * bbox[1];
402 work[3] = matrix[3] * bbox[2] + matrix[4] * bbox[1];
403 work[4] = matrix[0] * bbox[0] + matrix[1] * bbox[3];
404 work[5] = matrix[3] * bbox[0] + matrix[4] * bbox[3];
405 work[6] = matrix[0] * bbox[2] + matrix[1] * bbox[3];
406 work[7] = matrix[3] * bbox[2] + matrix[4] * bbox[3];
408 bbox[0] = floor(i_min(i_min(work[0], work[2]),i_min(work[4], work[6])));
409 bbox[1] = floor(i_min(i_min(work[1], work[3]),i_min(work[5], work[7])));
410 bbox[2] = ceil(i_max(i_max(work[0], work[2]),i_max(work[4], work[6])));
411 bbox[3] = ceil(i_max(i_max(work[1], work[3]),i_max(work[5], work[7])));
415 =item expand_bounds(int bbox[4], int bbox2[4])
417 Treating bbox[] and bbox2[] as 2 bounding boxes, produces a new
418 bounding box in bbox[] that encloses both.
422 static void expand_bounds(int bbox[4], int bbox2[4]) {
423 bbox[0] = i_min(bbox[0], bbox2[0]);
424 bbox[1] = i_min(bbox[1], bbox2[1]);
425 bbox[2] = i_max(bbox[2], bbox2[2]);
426 bbox[3] = i_max(bbox[3], bbox2[3]);
430 =item i_ft2_bbox_r(FT2_Fonthandle *handle, double cheight, double cwidth, char *text, int len, int vlayout, int utf8, int *bbox)
432 Retrieves bounding box information for the font at the given
433 character width and height.
435 This version finds the rectangular bounding box of the glyphs, with
436 the text as transformed by the transformation matrix. As with
437 i_ft2_bbox (bbox[0], bbox[1]) will the the offset from the start of
438 the topline to the top-left of the bounding box. Unlike i_ft2_bbox()
439 this could be near the bottom left corner of the box.
441 (bbox[4], bbox[5]) is the offset to the start of the baseline.
442 (bbox[6], bbox[7]) is the offset from the start of the baseline to the
445 Returns non-zero on success.
450 i_ft2_bbox_r(FT2_Fonthandle *handle, double cheight, double cwidth,
451 char const *text, int len, int vlayout, int utf8, int *bbox) {
456 int ascent = 0, descent = 0;
457 int glyph_ascent, glyph_descent;
458 FT_Glyph_Metrics *gm;
466 int loadFlags = FT_LOAD_DEFAULT;
469 loadFlags |= FT_LOAD_VERTICAL_LAYOUT;
471 loadFlags |= FT_LOAD_NO_HINTING;
473 error = FT_Set_Char_Size(handle->face, cwidth*64, cheight*64,
474 handle->xdpi, handle->ydpi);
476 ft2_push_message(error);
477 i_push_error(0, "setting size");
485 c = i_utf8_advance(&text, &len);
487 i_push_error(0, "invalid UTF8 character");
492 c = (unsigned char)*text++;
496 index = FT_Get_Char_Index(handle->face, c);
497 error = FT_Load_Glyph(handle->face, index, loadFlags);
499 ft2_push_message(error);
500 i_push_errorf(0, "loading glyph for character \\x%02x (glyph 0x%04X)",
504 slot = handle->face->glyph;
507 /* these probably don't mean much for vertical layouts */
508 glyph_ascent = gm->horiBearingY / 64;
509 glyph_descent = glyph_ascent - gm->height/64;
511 work[0] = gm->vertBearingX;
512 work[1] = gm->vertBearingY;
515 work[0] = gm->horiBearingX;
516 work[1] = gm->horiBearingY;
518 work[2] = gm->width + work[0];
519 work[3] = work[1] - gm->height;
521 bbox[4] = work[0] * handle->matrix[0] + work[1] * handle->matrix[1] + handle->matrix[2];
522 bbox[5] = work[0] * handle->matrix[3] + work[1] * handle->matrix[4] + handle->matrix[5];
523 bbox[4] = bbox[4] < 0 ? -(-bbox[4] + 32)/64 : (bbox[4] + 32) / 64;
526 ft2_transform_box(handle, work);
527 for (i = 0; i < 4; ++i)
534 for (i = 0; i < 4; ++i)
536 ascent = glyph_ascent;
537 descent = glyph_descent;
541 expand_bounds(bounds, work);
543 x += slot->advance.x / 64;
544 y += slot->advance.y / 64;
546 if (glyph_ascent > ascent)
547 ascent = glyph_ascent;
548 if (glyph_descent > descent)
549 descent = glyph_descent;
553 handle the case where the right the of the character overlaps the
555 /*int rightb = gm->horiAdvance - gm->horiBearingX - gm->width;
557 width -= rightb / 64;*/
561 /* at this point bounds contains the bounds relative to the CP,
562 and x, y hold the final position relative to the CP */
569 bbox[1] = -bounds[3];
571 bbox[3] = -bounds[1];
579 make_bmp_map(FT_Bitmap *bitmap, unsigned char *map);
582 =item i_ft2_text(FT2_Fonthandle *handle, i_img *im, int tx, int ty, i_color *cl, double cheight, double cwidth, char *text, int len, int align, int aa)
584 Renders I<text> to (I<tx>, I<ty>) in I<im> using color I<cl> at the given
585 I<cheight> and I<cwidth>.
587 If align is 0, then the text is rendered with the top-left of the
588 first character at (I<tx>, I<ty>). If align is non-zero then the text
589 is rendered with (I<tx>, I<ty>) aligned with the base-line of the
592 If aa is non-zero then the text is anti-aliased.
594 Returns non-zero on success.
599 i_ft2_text(FT2_Fonthandle *handle, i_img *im, int tx, int ty, i_color *cl,
600 double cheight, double cwidth, char const *text, int len, int align,
601 int aa, int vlayout, int utf8) {
604 FT_Glyph_Metrics *gm;
605 int bbox[BOUNDING_BOX_COUNT];
609 unsigned char map[256];
610 char last_mode = ft_pixel_mode_none;
614 int loadFlags = FT_LOAD_DEFAULT;
616 mm_log((1, "i_ft2_text(handle %p, im %p, tx %d, ty %d, cl %p, cheight %f, cwidth %f, text %p, len %d, align %d, aa %d)\n",
617 handle, im, tx, ty, cl, cheight, cwidth, text, align, aa));
620 if (!FT_HAS_VERTICAL(handle->face)) {
621 i_push_error(0, "face has no vertical metrics");
624 loadFlags |= FT_LOAD_VERTICAL_LAYOUT;
627 loadFlags |= FT_LOAD_NO_HINTING;
629 /* set the base-line based on the string ascent */
630 if (!i_ft2_bbox(handle, cheight, cwidth, text, len, bbox, utf8))
634 /* this may need adjustment */
635 tx -= bbox[0] * handle->matrix[0] + bbox[5] * handle->matrix[1] + handle->matrix[2];
636 ty += bbox[0] * handle->matrix[3] + bbox[5] * handle->matrix[4] + handle->matrix[5];
641 c = i_utf8_advance(&text, &len);
643 i_push_error(0, "invalid UTF8 character");
648 c = (unsigned char)*text++;
652 index = FT_Get_Char_Index(handle->face, c);
653 error = FT_Load_Glyph(handle->face, index, loadFlags);
655 ft2_push_message(error);
656 i_push_errorf(0, "loading glyph for character \\x%02x (glyph 0x%04X)",
660 slot = handle->face->glyph;
663 error = FT_Render_Glyph(slot, aa ? ft_render_mode_normal : ft_render_mode_mono);
665 ft2_push_message(error);
666 i_push_errorf(0, "rendering glyph 0x%04X (character \\x%02X)");
669 if (slot->bitmap.pixel_mode == ft_pixel_mode_mono) {
670 bmp = slot->bitmap.buffer;
671 for (y = 0; y < slot->bitmap.rows; ++y) {
674 for (x = 0; x < slot->bitmap.width; ++x) {
676 i_ppix(im, tx+x+slot->bitmap_left, ty+y-slot->bitmap_top, cl);
684 bmp += slot->bitmap.pitch;
688 /* grey scale or something we can treat as greyscale */
689 /* we create a map to convert from the bitmap values to 0-255 */
690 if (last_mode != slot->bitmap.pixel_mode
691 || last_grays != slot->bitmap.num_grays) {
692 if (!make_bmp_map(&slot->bitmap, map))
694 last_mode = slot->bitmap.pixel_mode;
695 last_grays = slot->bitmap.num_grays;
698 bmp = slot->bitmap.buffer;
699 for (y = 0; y < slot->bitmap.rows; ++y) {
700 for (x = 0; x < slot->bitmap.width; ++x) {
701 int value = map[bmp[x]];
703 i_gpix(im, tx+x+slot->bitmap_left, ty+y-slot->bitmap_top, &pel);
704 for (ch = 0; ch < im->channels; ++ch) {
706 ((255-value)*pel.channel[ch] + value * cl->channel[ch]) / 255;
708 i_ppix(im, tx+x+slot->bitmap_left, ty+y-slot->bitmap_top, &pel);
711 bmp += slot->bitmap.pitch;
715 tx += slot->advance.x / 64;
716 ty -= slot->advance.y / 64;
723 =item i_ft2_cp(FT2_Fonthandle *handle, i_img *im, int tx, int ty, int channel, double cheight, double cwidth, char *text, int len, int align, int aa)
725 Renders I<text> to (I<tx>, I<ty>) in I<im> to I<channel> at the given
726 I<cheight> and I<cwidth>.
728 If align is 0, then the text is rendered with the top-left of the
729 first character at (I<tx>, I<ty>). If align is non-zero then the text
730 is rendered with (I<tx>, I<ty>) aligned with the base-line of the
733 If aa is non-zero then the text is anti-aliased.
735 Returns non-zero on success.
740 i_ft2_cp(FT2_Fonthandle *handle, i_img *im, int tx, int ty, int channel,
741 double cheight, double cwidth, char const *text, int len, int align,
742 int aa, int vlayout, int utf8) {
748 mm_log((1, "i_ft2_cp(handle %p, im %p, tx %d, ty %d, channel %d, cheight %f, cwidth %f, text %p, len %d, ...)\n",
749 handle, im, tx, ty, channel, cheight, cwidth, text, len));
751 if (vlayout && !FT_HAS_VERTICAL(handle->face)) {
752 i_push_error(0, "face has no vertical metrics");
756 if (!i_ft2_bbox_r(handle, cheight, cwidth, text, len, vlayout, utf8, bbox))
759 work = i_img_empty_ch(NULL, bbox[2]-bbox[0]+1, bbox[3]-bbox[1]+1, 1);
761 if (!i_ft2_text(handle, work, -bbox[0], -bbox[1], &cl, cheight, cwidth,
762 text, len, 1, aa, vlayout, utf8))
770 /* render to the specified channel */
771 /* this will be sped up ... */
772 for (y = 0; y < work->ysize; ++y) {
773 for (x = 0; x < work->xsize; ++x) {
774 i_gpix(work, x, y, &cl);
775 i_gpix(im, tx + x + bbox[0], ty + y + bbox[1], &cl2);
776 cl2.channel[channel] = cl.channel[0];
777 i_ppix(im, tx + x + bbox[0], ty + y + bbox[1], &cl2);
785 =item i_ft2_has_chars(handle, char *text, int len, int utf8, char *out)
787 Check if the given characters are defined by the font.
789 Returns the number of characters that were checked.
793 int i_ft2_has_chars(FT2_Fonthandle *handle, char const *text, int len,
794 int utf8, char *out) {
796 mm_log((1, "i_ft2_has_chars(handle %p, text %p, len %d, utf8 %d)\n",
797 handle, text, len, utf8));
803 c = i_utf8_advance(&text, &len);
805 i_push_error(0, "invalid UTF8 character");
810 c = (unsigned char)*text++;
814 index = FT_Get_Char_Index(handle->face, c);
822 /* uses a method described in fterrors.h to build an error translation
825 #undef __FT_ERRORS_H__
826 #define FT_ERRORDEF(e, v, s) case v: i_push_error(code, s); return;
827 #define FT_ERROR_START_LIST
828 #define FT_ERROR_END_LIST
833 =head2 Internal Functions
835 These functions are used in the implementation of freetyp2.c and should not
836 (usually cannot) be called from outside it.
840 =item ft2_push_message(int code)
842 Pushes an error message corresponding to code onto the error stack.
846 static void ft2_push_message(int code) {
853 sprintf(unknown, "Unknown Freetype2 error code 0x%04X\n", code);
854 i_push_error(code, unknown);
858 =item make_bmp_map(FT_Bitmap *bitmap, unsigned char *map)
860 Creates a map to convert grey levels from the glyphs bitmap into
861 values scaled 0..255.
866 make_bmp_map(FT_Bitmap *bitmap, unsigned char *map) {
870 switch (bitmap->pixel_mode) {
871 case ft_pixel_mode_grays:
872 scale = bitmap->num_grays;
876 i_push_errorf(0, "I can't handle pixel mode %d", bitmap->pixel_mode);
880 /* build the table */
881 for (i = 0; i < scale; ++i)
882 map[i] = i * 255 / (bitmap->num_grays - 1);
888 i_ft2_face_name(FT2_Fonthandle *handle, char *name_buf, size_t name_buf_size) {
889 char const *name = FT_Get_Postscript_Name(handle->face);
894 strncpy(name_buf, name, name_buf_size);
895 name_buf[name_buf_size-1] = '\0';
897 return strlen(name) + 1;
900 i_push_error(0, "no face name available");
907 /* FT_Has_PS_Glyph_Names() was introduced in FT2.1.1 */
908 /* well, I assume FREETYPE_MAJOR is 2, since we're here */
909 #if FREETYPE_MINOR < 1 || (FREETYPE_MINOR == 1 && FREETYPE_PATCH < 1)
910 #define FT_Has_PS_Glyph_Names(face) (FT_HAS_GLYPH_NAMES(face))
914 i_ft2_glyph_name(FT2_Fonthandle *handle, unsigned char ch, char *name_buf,
915 size_t name_buf_size) {
916 #ifdef FT_CONFIG_OPTION_NO_GLYPH_NAMES
919 i_push_error(0, "FT2 configured without glyph name support");
925 if (FT_Has_PS_Glyph_Names(handle->face)) {
926 FT_UInt index = FT_Get_Char_Index(handle->face, ch);
929 FT_Error error = FT_Get_Glyph_Name(handle->face, index, name_buf,
932 ft2_push_message(error);
937 return strlen(name_buf) + 1;
944 i_push_error(0, "no glyph for that character");
950 i_push_error(0, "no glyph names in font");
958 i_ft2_can_do_glyph_names(void) {
959 #ifdef FT_CONFIG_OPTION_NO_GLYPH_NAMES
967 i_ft2_face_has_glyph_names(FT2_Fonthandle *handle) {
968 #ifdef FT_CONFIG_OPTION_NO_GLYPH_NAMES
971 return FT_Has_PS_Glyph_Names(handle->face);
980 Tony Cook <tony@develop-help.com>, with a fair amount of help from
981 reading the code in font.c.
985 font.c, Imager::Font(3), Imager(3)
987 http://www.freetype.org/