TrueType Font's glyph are made of quadratic Bezier. Why do more than one consecutive off-curve points appear in glyph outline?
Asked Answered
R

1

29

I'm writing a TTF parser. For a better understanding of the TTF format, I used TTX to extract the ".notdef" glyph data of C:\Windows\calibri.ttf as follow.

<TTGlyph name=".notdef" xMin="0" yMin="-397" xMax="978" yMax="1294">
      <contour>
        <pt x="978" y="1294" on="1"/>
        <pt x="978" y="0" on="1"/>
        <pt x="44" y="0" on="1"/>
        <pt x="44" y="1294" on="1"/>
      </contour>
      <contour>
        <pt x="891" y="81" on="1"/>
        <pt x="891" y="1213" on="1"/>
        <pt x="129" y="1213" on="1"/>
        <pt x="129" y="81" on="1"/>
      </contour>
      <contour>
        <pt x="767" y="855" on="1"/>
        <pt x="767" y="796" on="0"/>
        <pt x="732" y="704" on="0"/>
        <pt x="669" y="641" on="0"/>
        <pt x="583" y="605" on="0"/>
        <pt x="532" y="602" on="1"/>
        <pt x="527" y="450" on="1"/>
        many more points
     </contour>
     ...some other xml
</TTGlyph>

You can see more than one off-curve control points in a row. But I've learned that TrueType Font are made of Quadratic Beziers, each of which has two on-curve points (end points) and only one off-curve point (control point). How to interpret these consecutive off-curve points?

Respond answered 22/12, 2013 at 20:33 Comment(2)
They are off-curve control-points for the Bezier curve. Points P1 and P2 in the Wikipedia article.Integrator
Hi Hans. Thanks for your reply. The Points P1 and P2 you mentioned are for cubic Bezier. I know n-order Bezier has (n-1) control-points. Particularly, quadratic Bezier only needs one control-point. Why TTF, which is made by quadratic Bezier, has more than one control-points?Respond
F
53

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].

Frequentation answered 25/12, 2013 at 11:59 Comment(21)
Wow! Thanks for your answer! I guessed the outline is somehow compressed but didn't know exactly how. Your answer seems to be right. I used the your method and got the outline interpreted correctly. Do you know where this trick is officially documented? I didn't find this out in TTF spec(I think the spec is not very specific).Respond
I cannot find the trick in developer.apple.com/fonts/TTRefMan/RM06/Chap6glyf.html or microsoft.com/typography/otspec/glyf.htm.Respond
hm, I can't remember which documents I used to implement TTF parsing myself, it might have been explained on usenet or the opentype mailing list when I ran into this way back when, too.Marcellmarcella
Thank you very much! Rather than the caption to Figure 13 which seems to be about that figure specifically, the text between Figures 2 and 3 in your reference (developer.apple.com/fonts/TrueType-Reference-Manual/RM01/…) seems more 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]."Morn
further amended =)Marcellmarcella
:-) Thanks. I just encountered fontforge.github.io/python.html which says "two adjacent on-curve points yield a line between those points. Two on-curve points with an off-curve point between them yields a quadratic bezier curve. However if there are two adjacent off-curve points then an on-curve point will be interpolated between them. (This should be familiar to anyone who has read the truetype 'glyf' table docs)" but I don't know what docs are meant. Anyway, by now it seems pretty sure that this is the standard behaviour.Morn
Those apple pages are, to the best of my knowledge (and anyone I asked in the font engineering world), the "docs".Marcellmarcella
@Morn How to calculate p2 in [FIGURE2]?Oakie
@Oakie p2 in FIGURE 2 is the midpoint of points p1 and p3. That is the main lesson from the answer you are commenting on :-)Morn
Are these mid-points created before or after hinting?Dree
hinting is applied to the outline, so: before.Marcellmarcella
@Mike'Pomax'Kamermans In link you provided, it says only during bitmap rendering.Dree
All rendering is bitmap rendering: except for maybe oscilloscopes, there are no vector monitors. In order to form the vector outline, the glyph is evaluated and any implied points are inserted because otherwise you can't form the outline. That outline (which uses font unit numbers, not "real" units like pixels or cm etc) is then rasterized to a bitmap at some point size, with hinting applied to (try to) overcome the problems that come with rasterizationMarcellmarcella
@Mike'Pomax'Kamermans its actually after hinting, on point is added between two grid fitted off points and outline is rasterized.Dree
... I really don't understand what you're asking, or arguing at this point: the true quadratic curves are formed first, because you literally can't do anything curve related until you have those modelled, and then that outline gets rasterised, with TT hinting getting applied (if it exists) during the rasterisation pass, in order realign the outline to fit the desired pixel grid as best as possible. You asked "Are these mid-points created before or after hinting" and the answer is "before". You can't model a curve otherwise, and without a curve model, you can't (re)compute points on it.Marcellmarcella
@Mike'Pomax'Kamermans sorry for misunderstanding, its just when I add points before hinting the font engine interpreter indexes points as they should not have been added before but after, In here it says finally, two successive "off" points forces the rasterizer to create (only during bitmap rendering) a virtual "on" point but rasterization is after hinting, right? Im just trying to make sure I understood everything correcrtly.Dree
@Mike'Pomax'Kamermans And it seems like TrueType instructions work only with points? so they dont need curves at this stage, and moreover the types of points in glyf are Funit's which are int16 and when you add mid-point it becomes float of course you can floor but you lose fractional part, but after hinting points are f26d6 fixed point.Dree
hinting happens after the glyph's vector outline is parsed, during the rasterizing step, and before the actual rasterizing of individual closed paths to (anti-aliased) pixels. I think the miscommunication came from me talking about rasterizing as the entire procedure that has to happen to turn an "outline" into a "bitmap", while you were thinking of it as the result of that process.Marcellmarcella
So I think the question is: when you ask "Are these mid-points created before or after hinting", are you talking about whether numbered points in hint programs apply to the points in the glyph's TT paths, or to the path after turning the virtual points into real points", so that on1 off1 off2 on2 with the last point being p4, becomes on1 off1 on2 off2 on3 with the last point being p5. In that case: hinting only works with the points specified in the TT path.Marcellmarcella
I'm not sure how old this answer is, but I came across it when doing my own project and it answers my own questions perfectly!Ulaulah
Of course you do, it's right there in the post. Dec 25, 2013 =)Marcellmarcella

© 2022 - 2024 — McMap. All rights reserved.