RPi Matrix #3: Render vector fonts on a 2D Matrix

Rendering fonts was a pain in the ass! Took me some time to get around with all these glyphs, transformations and bitmaps. Nonetheless, let’s get started!

Setting up FreeType2

Just include

1find_package(Freetype)
2
3include_directories(${FREETYPE_INCLUDE_DIRS})
4target_link_libraries (_target_ ${FREETYPE_LIBRARIES})

and you’re ready to go!

Loading our vector font

For loading I’m going to use the build in font face caching manager. So firt set it up:

1FT_Library library;
2FTC_Manager manager;
3
4FT_Init_FreeType(library);
5FTC_Manager_New(library, 0, 0, 0, face_requester, NULL, manager);

face_requester is the method which gets called if a font isn’t yet in the cache. PCacheFace is contains the key information about a font.

 1typedef struct CacheFace_ {
 2  const char *file_path;
 3  int face_index;
 4
 5} CacheFace, *PCacheFace;
 6
 7static FT_Error face_requester(FTC_FaceID face_id,
 8        FT_Library library,
 9        FT_Pointer req_data,
10        FT_Face *aface) {
11  PCacheFace face = (PCacheFace) face_id;
12
13  FT_Error error = FT_New_Face(library, face->file_path, face->face_index, aface);
14  return error;
15}

Finally we can get our FT!

 1FT_Face get_font_face(FTC_ScalerRec *scaler) {
 2  FT_Size size;
 3  FT_Error error = FTC_Manager_LookupSize(manager, scaler, &size);
 4
 5  if (error) {
 6    return 0; // Font not found or IO error e.g.
 7  }
 8
 9  return size->face;
10}

After building a FTC_ScalerRec :P Note that we’re using the scaler->pixel flag and setting both width and height to size.

 1CacheFace *cache_face = malloc(sizeof(CacheFace));  // How to fuck do I clear this?
 2cache_face->face_index = 0;                         // Face to choose
 3cache_face->file_path = font_file;                  // Path to file
 4
 5FTC_ScalerRec *scaler = malloc(sizeof(FTC_ScalerRec));
 6
 7scaler->face_id = cache_face;
 8scaler->width = size;
 9scaler->height = size;
10scaler->pixel = 1;  
11
12font->scaler = scaler;

Loading glyphs from font face

I really want to use the advantages vector fonts bring. Like Kerning, which sounds absolutely awesome. Maybe we can save some space on our small matrix. Challenge: Setting the coordinate origin to the top-left:

top-left
Source: http://programarcadegames.com/chapters/05_intro_to_graphics/Computer_coordinates_2D.png

Note: You’ll often see something like » 6. A right-bit-shift by 6 is equivalent to dividing by 64. We need to do this as FreeType uses a 26.6 fixed-point format. When FreeType wants a 16.16 fixed-point format we just shift by 10.

First allocate the variables we’re going to use and define our struct:

 1struct lmString_ {
 2  signed int height, width;
 3  FT_Glyph *glyphs;
 4  int num_glyphs;
 5
 6  FT_Pos shiftY;    // Highest glyph height
 7
 8  int use_matrix;  // Use a transformation matrix
 9};
10
11typedef struct lmString_ lmString;
 1static inline void create_string(lmString *string, FT_ULong *text, int length, lmFont *font) {
 2  // Get font face
 3  FT_Error error;
 4  FT_Face face = get_font_face(...);
 5
 6  if (face == 0) {
 7    return;
 8  }
 9
10  FT_GlyphSlot slot = face->glyph;    // a small shortcut
11  FT_UInt glyph_index;                // Current glyph
12  FT_Long use_kerning;                // Whether our font supports kerning
13  FT_UInt previous;                   // The glyph before glyph_index
14  int pen_x, pen_y, n;                // Pen position
15  FT_Glyph *glyphs = malloc(sizeof(FT_Glyph) * length);  // glyphs table
16  FT_Vector pos[length];              // Transformed glyph vectors
17  FT_UInt num_glyphs;                 // Num of glyphs
18
19  pen_x = 0;
20  pen_y = 0;
21
22  num_glyphs = 0;
23  use_kerning = FT_HAS_KERNING(face);
24  previous = 0;
25
26  string->shiftY = 0;

Let’s start now loading each glyph. text[n] is an unsigned long with the FT_LOAD_DEFAULT settings. If you want you can choose a specific char set by using FT_Select_Charmap(face, FT_ENCODING_UNICODE) for example.

1for (n = 0; n < length; n++) {
2  glyph_index = FT_Get_Char_Index(face, text[n]);
3
4  error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT);
5  if (error) {
6    continue;
7  }

Now we come to an interesting part. FreeType usually uses a normal cartesian coordinate system, but we want our origin at the top-left corder. So we need to find the highest bearingY we can find.

Glyphs
Source: https://www.freetype.org/freetype2/docs/glyphs/

(bearingY is basically the height of a character starting at the origin)

1  FT_Pos bearingY = slot->metrics.horiBearingY >> 6;
2
3  if (string->shiftY < bearingY) {
4    string->shiftY = bearingY;
5  }

Storing the glyph in our “glyph output”:

1  error = FT_Get_Glyph(face->glyph, &glyphs[n]);
2
3  if (error) {
4    continue;
5  }

If we want to support kerning, we first check if it’s supported, not the first character and a glyph was loaded and calculate our delta X. Then move our pen by this position.

 1  /* retrieve kerning distance and move pen position */
 2  if (use_kerning && previous && glyph_index) {
 3    FT_Vector delta;
 4
 5
 6    FT_Get_Kerning(face, previous, glyph_index,
 7    FT_KERNING_DEFAULT, &delta);
 8
 9    pen_x += delta.x >> 6;
10  }

Store position in “position output” and go to the next glyph.

 1  pos[n].x = pen_x;
 2  pos[n].y = pen_y;
 3
 4  /* increment pen position */
 5  pen_x += slot->advance.x >> 6;
 6
 7  /* record current glyph index */
 8  previous = glyph_index;
 9
10  /* increment number of glyphs */
11  num_glyphs++;
12}

As we want to render a whole string we have to compute a bounding box somehow. You can read more about this in the FreeType documentation 4b’s compute_string_bbox.

 1static void compute_string_bbox(int num_glyphs, FT_Glyph *glyphs, FT_Vector *pos, FT_BBox *abbox) {
 2		int n;
 3		FT_BBox bbox;
 4		FT_BBox glyph_bbox;
 5
 6
 7		/* initialize string bbox to "empty" values */
 8		bbox.xMin = bbox.yMin = 32000;
 9		bbox.xMax = bbox.yMax = -32000;
10
11		/* for each glyph image, compute its bounding box, */
12		/* translate it, and grow the string bbox          */
13		for (n = 0; n < num_glyphs; n++) {
14				FT_Glyph_Get_CBox(glyphs[n], FT_GLYPH_BBOX_PIXELS,
15								&glyph_bbox);
16
17				glyph_bbox.xMin += pos[n].x;
18				glyph_bbox.xMax += pos[n].x;
19				glyph_bbox.yMin += pos[n].y;
20				glyph_bbox.yMax += pos[n].y;
21
22				if (glyph_bbox.xMin < bbox.xMin)
23						bbox.xMin = glyph_bbox.xMin;
24
25				if (glyph_bbox.yMin < bbox.yMin)
26						bbox.yMin = glyph_bbox.yMin;
27
28				if (glyph_bbox.xMax > bbox.xMax)
29						bbox.xMax = glyph_bbox.xMax;
30
31				if (glyph_bbox.yMax > bbox.yMax)
32						bbox.yMax = glyph_bbox.yMax;
33		}
34
35		/* check that we really grew the string bbox */
36		if (bbox.xMin > bbox.xMax) {
37				bbox.xMin = 0;
38				bbox.yMin = 0;
39				bbox.xMax = 0;
40				bbox.yMax = 0;
41		}
42
43		/* return string bbox */
44		*abbox = bbox;
45}

Last step is to populate our lmString struct.

1  // Compute box
2  FT_BBox string_bbox;
3  compute_string_bbox(num_glyphs, glyphs, pos, &string_bbox);
4
5  string->width = (int) (string_bbox.xMax - string_bbox.xMin);
6  string->height = (int) (string_bbox.yMax - string_bbox.yMin);
7  string->glyphs = glyphs;
8  string->num_glyphs = num_glyphs;

Rendering!

We have now all information we need to render a glyph to a bitmap and copying this to our matrix buffer.

We start again by defining a few variables.

1int n;
2FT_Error error;
3FT_Glyph image;
4FT_Vector pen;
5pen.x = 0;
6pen.y = 0;
7
8FT_Pos shiftY = string->shiftY;

And iterate over all glyphs.

1for (n = 0; n < string->num_glyphs; n++) {
2  image = string->glyphs[n];

Each glyph needs to be transformed now. We also allow an optional matrix which can rotate each glyph or scale it.

 1  FT_Vector delta;
 2  delta.x = x << 6;
 3  delta.y = -y << 6;
 4
 5
 6  FT_Matrix *ft_matrix = 0;
 7
 8  if (string->use_matrix > 0) {
 9    FT_Matrix m;
10
11    m.xx = string->matrix.xx * 0x10000L; // pixel format to 16.16 fixed float format
12    m.xy = string->matrix.xy * 0x10000L;
13    m.yx = string->matrix.yx * 0x10000L;
14    m.yy = string->matrix.yy * 0x10000L;
15
16    ft_matrix = &m;
17  }
18
19  FT_Glyph_Transform(image, ft_matrix, &delta);

Each glyph needs to be rendered to a monochrome bitmap in our case, as we want to rasterize it later on a 2D matrix.

1  error = FT_Glyph_To_Bitmap(
2  &image,
3  FT_RENDER_MODE_MONO,
4  &pen,     // Apply pen
5  1);       // Do destroy!
6
7  string->glyphs[n] = image;

Last step is to copy our bitmap to the matrix. There comes our shiftY, which is the max. bearingY, into play. We need it to define our top-left corner of our string. Else the lower corner will be used.

 1  if (!error) {
 2    FT_BitmapGlyph bit = (FT_BitmapGlyph) image;
 3
 4    render_bitmap(matrix, bit->bitmap,
 5    bit->left,
 6    shiftY - bit->top,
 7    rgb);
 8
 9    /* increment pen position --                       */
10    /* we don't have access to a slot structure,       */
11    /* so we have to use advances from glyph structure */
12    /* (which are in 16.16 fixed float format)         */
13    pen.x += image->advance.x >> 10;
14    pen.y += image->advance.y >> 10;
15  }

That’s it! If you want to view the full source follow this link with all the matrix rendering stuff.

TODO: Apply rotation matrix to rotate about the origin in the top-left corner.

Do you have questions? Send an email to max@maxammann.org