afdko

Notes on the OTF autohinter

otfautohint contains a Python port of the “Automatic Coloring” code originally written in C by Bill Paxton. That code was most recently distributed as “psautohint” in a PyPI package of the same name.

Changes Summary:

Most of the algorithms are unchanged but not all. Some notable differences are:

  1. Because all code is now in Python there is no role for the bez charstring interchange format. CharStrings are now directly unpacked into glyphData objects. These are capable of representing any (decompiled) CharString with the caveats that the particular spline operator (e.g. rCurveTo vs curveTo) is not recorded.
  2. Intermediate hint state is now maintained in hintState objects, one per glyph, rather than in globals.
  3. The C code algorithms for vertical and horizontal hinting were mostly implemented as separate functions. In the port there is typically one function shared by both dimensions. The glyphData/pt 2-tuple object has special a and o value accessors and a class-level switch as part of this unification. (a is meant to suggest “aligned” and o is meant to suggest “opposite”, referring to the relation between the value and the chosen alignment; a is x and o is y when horizontal alignment is chosen, and vice-versa when vertical alignment is chosen.) Some bug fixes and improvements that were only added to one dimension in the past now work in both.
  4. In the C code spline characteristics such as bounding boxes and measures of flatness were calculated by approximating spline curves with line segments. In the Python code these calculations are closed-form and implemented with fontTools’ quadratic and cubic root finders.
  5. The C code had special functions for handling a spline with an inflection point. The new code copies and splits such splines at their inflection points, analyzes the copies, and copies the resulting analysis back to the inflected splines.
  6. The mask distribution algorithm (which is equivalent to what used to be called “hint substitution” is implemeneted somewhat differently.

There are also some features that are not (yet) ported:

  1. The C code for hint substitution “promoted” hints to earlier splines under some circumstances. This was not included because testing did not indicate it was of noticable benefit in current renderers.
  2. Previous versions of psautohint would clean up duplicate moveTo operators and warn about duplicate subpaths or unusually large glyphs. It is now more appropriate to check for these characteristics using sanitizers at earlier stages in the development process.
  3. Under some circumstances the C autohinter would divide curved splines at t == .5 when the allow-changes option was active. The primary reason for doing so was when a single spline “wanted” conflicting hints at its start and end points. This is a relatively rare circumstance and the Adobe maintainers are evaluating what, if anything, should be done in this case.
  4. The C autohinter would add a new hint mask before each subpath in the “percent” and “perthousand” glyphs but did not offer this option for other glyphs. This code was not ported.

Most functions are now documented in-line. Adapter code in autohint.py calls hint() on glyphHinter in hinter.py, which in turn calls into the dimension-specific hhinter and vhinter objects in the same file.

The Python code is slower when hinting individual glyphs, often 5 or more times as slow or more compared with the C code on the same machine. It also uses more memory. However, by default glyphs are now hinted in parallel using the Python multiprocessing module when multiple CPU cores are available. As part of this change the glyphs are also unpacked just before hinting and updated right after hinting in order to lower the total memory used by the process at a given time. As a result the overall hinting process is often slightly faster on contemporary machines.

Some notes on hinting glyphs in a variable font

Stem ordering

The initial CFF2 specification, and all revisions at the time of writing, require that stems are defined in increasing order by their (lower) starting locations and then, if two stems start at the same place, their ending locations (which are higher except in the case of ghost hints). Duplicate stems are disallowed. Stems that would be specified out of order in a particular master (relative to the default master ordering) must therefore be removed.

As long as a design space is defined by interpolation only (rather than extrapolation) the extremes of stem ordering are represented by the (sorted) orders in the individual masters. Consider the bottom edge of stem i in a variable glyph with n masters. It’s location at some point in design space can be represented as

c1*si1 + c2*si2 + … + cn*sin

where sik is the position of the edge in that glyph in master k and each c value is some interpolation coefficient, so that c1 + c2 + … + cn == 1 and 0 <= ck <= 1

The signed distance between the bottom edges of two stems i and j is accordingly

c1*si1 + c2*si2 + … + cn*sin - c1*sj1 + c2*sj2 + … + cn*sjn

or

c1*(si1 - sj1) + c2*(si2 - sj2) + … + cn*(sin - sjn)

The minimum/maximum distance in the space will therefore be sik

Stem overlap

Suppose that stems i and j are in the same order across all masters with stem i before stem j (so either sik < sjk or (sik == sjk and eik < ejk)). Whether i overlaps with j in a given master k is defined by whether sjk - eik < Om (the overlap margin). Therefore, by reasoning analogous to the above, the question of whether two consistently ordered stems overlap in design space is equivalent to whether they overlap in at least one master.

Any two stems that change order between two masters overlap at some point in design space interpolated between those masters. The question of whether two stems overlap in the general is therefore equivalent to:

  1. Do the stems change order in any master relative to the default master? If yes, then they overlap.
  2. If no, then is there any master k in which sjk - eik < Om? If yes, then they overlap.