Figuring Out Fontforge-Python’s Representation of Postscript Curves

You see a young man posing to the right of a whiteboard, looking into the camera. He holds a marker in his right hand as if he is writing, his right foot is planted on the whiteboards horizontal support. He wears black leggings and a salmon pink sleeveless t-shirt. On the whiteboard is constructed a path of curves and lines, showing also the bézier control points. All the points are marked either offCurve or onCurve.

When Ludi and me prepare for a workshop on Open Source typography in Valence, we put together a batch of Python scripts for Fontforge. Among which a port of the script that John and me made in Robofab for a previous workshop, that straightens curves by removing the bézier handles.

Internally, many typefaces are constructed with PostScript, which can be seen as a set of drawing instructions to an imaginary pen.

Postscript has three basic drawing instructions:
moveto(x, y): move the pen to this point to start drawing a path
lineto(x,y): draw a line from the previous point to the point x,y
curveto(x1,y1,x2,y2,x3,y3): draw a curve from the previous point to the point x3,y3, using x1,y1 and x2,y2 as bézier handles, off curve points that control the shape of the curve.

It turns out that Fontforge-Python represents PostScript contours as a set of points. Fair enough. The points have a type property, which can be either onCurve or offCurve. So like in the Robofab script, we first try to remove all the offCurve points.

This turns out to be too radical: a lot more points are removed then seems necessary. Prince Charmant Pierre Marchand to the rescue. It turns out that points that are normally created as part of a lineto, are also called offCurve by FontForge, even if they are strictly speaking neither on or off a curve.

So, how to keep the points called offCurve that are actually part of the contours, and throw away the others? We know that if a point is onCurve, it will always be followed y two control points (offCurve). So instead of checking for offCurves, we simply skip the next two points every time we encounter an onCurve.

Like this:

font = fontforge.activeFont()

for glyph in font.glyphs():
   new_contours = []
   for contour in glyph.layers[1]:
       new_contour = []
       i = 0
       while i < len(contour):
           p = contour[i]
           if p.on_curve:
               i += 2
           new_contour.append((p.x, p.y))
           i += 1
       new_contours.append(new_contour)
   pen = glyph.glyphPen()
   for to_draw in new_contours:
       pen.moveTo(to_draw[0])
       for i in to_draw[1:]:
           pen.lineTo(i)
       pen.closePath()

This won’t work, by the way, if you are dealing with TrueType curves. But that’s a different story altogether.

No Comments

Leave a comment