TTF parsing requires applying http://www.microsoft.com/typography/otspec/glyf.htm as well as the tech docs about the TTF format from the microsoft site. These tell us that there are two types of points for a curve: on-curve and off-curve points. on-curve points are "real" points, through which a curve passes, and off-curve points are control points that guide the bezier curvature.
Now, what you describe as "a bezier curve" is correct: a single (quadratic) bezier curve goes from 1 real point, guided by 1 control point, to 1 real point (higher order curves like cubics, quartics, etc. have more control points between the real points). However, quadratic curves are generally terrible for design work because they are really bad at approximating circular arcs, but are cheaper to work with than higher order curves, so we're stuck with them for fonts that use TrueType for glyph outlines. To get around the downside of quadratic curves, TrueType outlines generally use sequences of bezier curves rather than single curves in order to get decent-looking uniform curves, and those sequences tend to have a nice property: the on- and off-curve points are spaced in a way that we don't need to record every point in the sequence.
Consider this Bezier sequence:
P1 - C1 - P2 - C2 - P3 - C3 - P4
If we add the on
information, we'd encode it in TTF as:
P1 - C1 - P2 - C2 - P3 - C3 - P4
1 - 0 - 1 - 0 - 1 - 0 - 1
Now for the trick: if each Pn is an on-curve point, and each Cn is a control point, and P2 lies exactly mid-way between C1 and C2, P3 lies exactly mid-way between C2 and C3, and so on, then this curve representation can be compacted a lot, because if we know C1 and C2, we know P2, etc. We don't have to list any of the mid-way points explicitly, we can just leave that up to whatever parses the glyph outline.
So TTF allows you to encode long bezier sequences with the above property as:
P1 - C1 - C2 - C3 - P4
1 - 0 - 0 - 0 - 1
As you can see: we're saving considerable space, without loss of precision. If you look at your TTX dump, you'll see this reflected in the on
values for each point. To get the P2, P3, etc, all we do is this:
def getPoints(glyph):
points = []
previous_point = None;
flags = glyph.flags
for (i, point) in enumerate(glyph.point_array):
(mask_for_point, mask_for_previous_point) = flags[i]
# do we have an implied on-curve point?
if (previous_point && mask_for_point == 0 && mask_for_previous_point == 0):
missing_point = midpoint(point, previous_point)
points.push(missing_point)
# add the explicitly encoded point
points.push(point)
previous_point = point
return points
After running this procedure, the points
array will have alternating on-curve and off-curve points, and the beziers are constructed as:
for i in range(0, len(array), 2):
curve(array[i], array[i+1], array[i+2])
edit after a bit of searching, http://chanae.walon.org/pub/ttf/ttf_glyphs.htm covers how to work with the glyf
table data in pretty good detail (the ascii graphics are a bit silly, but still legible enough)
further edit after several years I managed to find documentation that actually explains (or, at least implies) it in the Apple documentation on TTF, over on https://developer.apple.com/fonts/TrueType-Reference-Manual/RM01/Chap1.html#necessary, which in "Figure 13" states that:
In particular the on-curve points, located at the midpoints of the tangents to the curve, add no extra information and might have been omitted.
even further edit ShreevatsaR points out that the text between Figures 2 and 3 in the apple documentation is also relevant:
It would also be possible to specify the curve shown in FIGURE 2 with one fewer point by removing point p2. Point p2 is not strictly needed to define the curve because its existence implied and its location can be reconstructed from the data given by the other points. After renumbering the remaining points, we have [FIGURE 3].