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:
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.
(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.