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:

BitFlagDescription
0ARG_1_AND_2_ARE_WORDSIf set, arguments are 16-bit, otherwise, they are bytes (uint8 or int8).
1ARGS_ARE_XY_VALUESIf set, arguments are signed xy values; otherwise, they are unsigned point numbers.
2ROUND_XY_TO_GRIDFor the xy values if the preceding is true.
3WE_HAVE_A_SCALEIf set, there is a simple scale for the component. Otherwise, scale = 1.0.
4RESERVEDNot used, should be 0
5MORE_COMPONENTSIndicates that there are more glyphs after this one.
6WE_HAVE_AN_X_AND_Y_SCALEThe x direction will use a different scale from the y direction.
7WE_HAVE_A_TWO_BY_TWOThere is a 2 by 2 transformation that will be used to scale the component.
8WE_HAVE_INSTRUCTIONSFollowing the last component are instructions, we will ignore these.
9USE_MY_METRICSIf 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;
}