Parse Compound Glyphs
In addition to simple glyphs, a font can also contain compound glyphs. A compound glyph is a combination of simple glyphs or other compound glyphs. Below are some examples of compoung glyphs.
Compound glyphs start with the number of contours and bounding box like simple glyphs. Start by reading the number of contours in the glyph. A compoiund glyph will have a negative number of contours. After the number of contours is 4 signed 16 bit integers for the min and max points of the glyphs bounding box.
Glyph Font::ParseCompoundGlyph(u16 index, u8* glyf, const vector< u32 >& loca) { u8* glyph = glyf + loca[index]; Glyph result; i16 numberOfContours = (glyph[0] << 8) | glyph[1]; if (numberOfContours >= 0) { return ParseSimpleGlyph(index, glyf, loca); } glyph += 2; result.min.x = read_i16(&glyph); result.min.y = read_i16(&glyph); result.max.x = read_i16(&glyph); result.max.y = read_i16(&glyph);
The number of components in a compound glyph is not stored anywhere (the negative number of components is usually -1). Instead, each component starts with a flag. The 5th bit of this flag is the MORE_COMPONENTS
bit, if it's set, there is another component to parse immediateley after this one. The flag has the following bits:
Bit | Flag | Description |
---|---|---|
0 | ARG_1_AND_2_ARE_WORDS | If set, arguments are 16-bit, otherwise, they are bytes (uint8 or int8). |
1 | ARGS_ARE_XY_VALUES | If set, arguments are signed xy values; otherwise, they are unsigned point numbers. |
2 | ROUND_XY_TO_GRID | For the xy values if the preceding is true. |
3 | WE_HAVE_A_SCALE | If set, there is a simple scale for the component. Otherwise, scale = 1.0. |
4 | RESERVED | Not used, should be 0 |
5 | MORE_COMPONENTS | Indicates that there are more glyphs after this one. |
6 | WE_HAVE_AN_X_AND_Y_SCALE | The x direction will use a different scale from the y direction. |
7 | WE_HAVE_A_TWO_BY_TWO | There is a 2 by 2 transformation that will be used to scale the component. |
8 | WE_HAVE_INSTRUCTIONS | Following the last component are instructions, we will ignore these. |
9 | USE_MY_METRICS | If set, this forces the advance width and left side bearing for the composite to be equal to those from this original glyph. |
Following the flag is the index of the glyph, this index could point to a compound glyph or a simple one.
u16 flags = 0; do { flags = read_u16(&glyph); u16 glyphIndex = read_u16(&glyph);
Each component of a compoung glyph can be transformed, they are usually translated or scaled. The transform data is like matrix, and all points in a component are transformed by the matrix. The equation to transform each component comes from tje true type manual.
The e
and f
values are stored first. If the second bit (ARGS_ARE_XY_VALUES
) is not set, both e
and f
default to zero. If the second bit is set and the first bit (ARG_1_AND_2_ARE_WORDS
) is not, e
and f
are stored as signed 8 bit numbers. If both the first and second bit are set, the e
and f
flags are stored as signed 16 bit numbers.
f32 a, b, c, d, e, f, m, n; if (flags & (1 << 1)) { // ARGS_ARE_XY_VALUES if (flags & 1) { // ARG_1_AND_2_ARE_WORDS e = read_i16(&glyph); f = read_i16(&glyph); } else { e = (i8)(*glyph++); f = (i8)(*glyph++); } } else { e = f = 0.0f; }
The variables a
, b
, c
and d
make up a 2x2 matrix. Bits 3, 6 and 7 determine how these values are stored. If none of the bits are set, default to the identity matrix. If a bit is set, nly one of the three bits will be set. The values for these variables are stored as 16 bit fixed numbers.
If bit 3 (WE_HAVE_A_SCALE
) is set, a
and d
will contain the same scale, which is stored as a 16 bit fixed point number. In this case b
and c
remain 0.
If bit 6 (WE_HAVE_AN_X_AND_Y_SCALE
) is set, a
and d
will contain unique values, both of which are stored as a 16 bit fixed numbers. In this case b
and c
remain 0.
If bit 7 (WE_HAVE_A_TWO_BY_TWO
) is set, a
, b
, c
and d
all have unique values which are stored as 16 bit fixed point numbers.
To convert the fixed point numbers to a floating point number, cast to a float and divide by 16384.0f
, the read_fixed
function already does this.
if (flags & (1 << 3)) { // WE_HAVE_A_SCALE a = d = read_fixed(&glyph); b = c = 0.0f; } else if (flags & (1 << 6)) { // WE_HAVE_AN_X_AND_Y_SCALE a = read_fixed(&glyph); d = read_fixed(&glyph); b = c = 0.0f; } else if (flags & (1 << 7)) { // WE_HAVE_A_TWO_BY_TWO a = read_fixed(&glyph); b = read_fixed(&glyph); c = read_fixed(&glyph); d = read_fixed(&glyph); } else { a = d = 1.0f; b = c = 0.0f; }
The apple true type manual provides the formula for finding \(m\) and \(n\) as:
$$ m = max(|a|, |b|) $$
$$ if (|(|a| - |c|)| < (33 / 65536)) m = 2m $$
$$ n = max(|c|, |d|) $$
$$ if (|(|b| - |d|)| < (33 / 65536)) n = 2n $$
m = fmaxf(fabsf(a), fabsf(b)); n = fmaxf(fabsf(c), fabsf(d)); if (fabsf(fabsf(a) - fabsf(c)) < (33.0f / 65536.0f)) { m = 2.0f * m; } if (fabsf(fabsf(b) - fabsf(d)) < (33.0f / 65536.0f)) { n = 2.0f * n; }
Now that we know how to transform the glyph, call ParseSimpleGlyph
to get the actual gluph data. Transform the resulting glyph by the 3x2 matrix we just parsed, and add the transformed edges to the compound glyphs edge list. A compound glyph could contain other compound glyphs, ParseSimpleGlyph
checks the number of contours of the glyphs being parsed and will call ParseCompoundGlyph
if it's negative. We will write the Transform
function next.
Glyph component = ParseSimpleGlyph(glyphIndex, glyf, loca); component = component.Transform(a, b, c, d, e, f, m, n); for (int c = 0; c < component.edges.size(); ++c) { result.edges.push_back(component.edges[c]); }
To end the loop, check bit 5 (MORE_COMPONENTS) of the glyphs flag, if it's set there are more components. If it's not set, the loop ends and ParseCompoundGlyph
can return.
} while (flags & (1 << 5)); // MORE_COMPONENTS return result; }
Transforming Glyphs
The transformation to apply to each glyph is defined in the True Type Reference Manual, it is:
$$ x' = m(\frac{a}{m}x + \frac{c}{m}y + e) $$
$$ y' = n(\frac{b}{n}x + \frac{d}{n}y + f) $$
The rasterizer we will build cares about the winding order of each edge. Scale is stored in the transforms main diagonal, elements a
and d
. If one of the scales is negative, we need to flip the start and end points of the edge. If both scales are negative, it cancels out. Flip the edge only if the signs of a
and d
are different.
Glyph Glyph::Transform(f32 a, f32 b, f32 c, f32 d, f32 e, f32 f, f32 m, f32 n) { Glyph result = *this; i32 numEdges = result.edges.size(); bool flip = a * d < 0.0f; // If the signs are different, flip for (i32 edge = 0; edge < numEdges; ++edge) { for (int p = 0; p < 3; ++p) { f32 x = edges[edge].points[p].x; f32 y = edges[edge].points[p].y; result.edges[edge].points[p].x = m * ((a / m) * x + (c / m) * y + e); result.edges[edge].points[p].y = n * ((b / n) * x + (d / n) * y + f); } if (flip) { Point tmp = result.edges[edge].start; result.edges[edge].start = result.edges[edge].end; result.edges[edge].end = tmp; } } f32 x = (m * ((a / m) * min.x + (c / m) * min.y + e)); f32 y = (n * ((b / n) * min.x + (d / n) * min.y + f)); result.min.x = x; result.min.y = y; x = (m * ((a / m) * max.x + (c / m) * max.y + e)); y = (n * ((b / n) * max.x + (d / n) * max.y + f)); result.max.x = x; result.max.y = y; return result; }