Skip to content

Commit 4619689

Browse files
committed
preProcessor: decompose components whose transform overflows F2Dot14
Even though the TTGlyphPen in fonttools already does this for us, it is better to do it upfront so that filters that are performed later on in the preprocessing pipeline (e.g. removeOverlaps, cubicToQuadratic, etc.) operate on the decomposed outlines. This also matches what fontc currently does, and as a bonus give us at least one +1 in the fontc_crater diff (i.e. Ruthie.glyphs).
1 parent 22f466e commit 4619689

File tree

18 files changed

+290
-5
lines changed

18 files changed

+290
-5
lines changed

Lib/ufo2ft/preProcessor.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
DecomposeComponentsIFilter,
1616
)
1717
from ufo2ft.fontInfoData import getAttrWithFallback
18-
from ufo2ft.util import _GlyphSet, zip_strict
18+
from ufo2ft.util import _GlyphSet, _hasOverflowingComponentTransforms, zip_strict
1919

2020
if TYPE_CHECKING:
2121
from ufo2ft.instantiator import Instantiator
@@ -211,8 +211,17 @@ def initDefaultFilters(
211211
_init_explode_color_layer_glyphs_filter(self.ufo, filters)
212212

213213
# len(g) is the number of contours, so we include the all glyphs
214-
# that have both components and at least one contour
215-
filters.append(DecomposeComponentsFilter(include=lambda g: len(g)))
214+
# that have both components and at least one contour.
215+
# We must also decompose composite glyphs with components whose 2x2 transform
216+
# matrix contain values that exceed F2Dot14 range [-2.0, 2.0], as these
217+
# cannot be encoded in the glyf table. The TTGlyphPen would do this
218+
# automatically later in the pipeline, but we do it explicitly in here to
219+
# make sure that any filters that follow will process the decomposed glyphs.
220+
filters.append(
221+
DecomposeComponentsFilter(
222+
include=lambda g: len(g) or _hasOverflowingComponentTransforms(g)
223+
)
224+
)
216225

217226
if flattenComponents:
218227
from ufo2ft.filters.flattenComponents import FlattenComponentsFilter
@@ -467,12 +476,14 @@ def process(self):
467476
self._run(*funcs)
468477

469478
# TrueType fonts cannot mix contours and components, so pick out all glyphs
470-
# that have both contours _and_ components.
479+
# that have both contours _and_ components. Also, decompose components whose
480+
# transform values exceed the F2Dot14 range as they can't be encoded in glyf.
471481
needs_decomposition = {
472482
gname
473483
for glyphSet in self.glyphSets
474484
for gname, glyph in glyphSet.items()
475-
if len(glyph) > 0 and glyph.components
485+
if glyph.components
486+
and (len(glyph) > 0 or _hasOverflowingComponentTransforms(glyph))
476487
}
477488
# Variable fonts can only variate glyf components' x or y offsets, not their
478489
# 2x2 transformation matrix; decompose of these don't match across masters

Lib/ufo2ft/util.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,31 @@ def decomposeCompositeGlyph(
8989
else:
9090
raise
9191
glyph.removeComponent(component)
92+
logger.debug(
93+
"decomposed component '%s' in glyph '%s'",
94+
component.baseGlyph,
95+
glyph.name,
96+
)
97+
98+
99+
def _hasOverflowingComponentTransforms(glyph):
100+
"""Check if all component transform values fit in F2Dot14 range.
101+
102+
F2Dot14 is used to encode component 2x2 matrix in the glyf table.
103+
if any component transform value exceeds this, the glyph must be decomposed
104+
to keep its shape.
105+
106+
This is the same check as in fontTools.pens.ttGlyphPen:
107+
https://github.com/fonttools/fonttools/blob/c4e96980/Lib/fontTools/pens/ttGlyphPen.py#L90-L97
108+
109+
Note that, like in fonttools, the upper bound is 2.0 instead of
110+
1.99993896484375 which is the actual value of F2Dot14::MAX.
111+
The TTGlyphPen will clamp values between F2Dot14::MAX and +2.0 to F2Dot14::MAX.
112+
"""
113+
return any(
114+
any(v < -2.0 or v > 2.0 for v in comp.transformation[:4])
115+
for comp in glyph.components
116+
)
92117

93118

94119
class _GlyphSet(dict):
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?xml version='1.0' encoding='UTF-8'?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>ascender</key>
6+
<integer>800</integer>
7+
<key>capHeight</key>
8+
<integer>700</integer>
9+
<key>descender</key>
10+
<integer>-200</integer>
11+
<key>familyName</key>
12+
<string>Overflowing Component Transforms</string>
13+
<key>openTypeHeadCreated</key>
14+
<string>2025/09/19 11:59:48</string>
15+
<key>openTypeOS2WeightClass</key>
16+
<integer>700</integer>
17+
<key>styleMapFamilyName</key>
18+
<string>Overflowing Component Transforms</string>
19+
<key>styleMapStyleName</key>
20+
<string>bold</string>
21+
<key>styleName</key>
22+
<string>Bold</string>
23+
<key>unitsPerEm</key>
24+
<integer>1000</integer>
25+
<key>versionMajor</key>
26+
<integer>1</integer>
27+
<key>versionMinor</key>
28+
<integer>0</integer>
29+
</dict>
30+
</plist>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?xml version='1.0' encoding='UTF-8'?>
2+
<glyph name="base" format="2">
3+
<advance width="600"/>
4+
<outline>
5+
<contour>
6+
<point x="52" y="404" type="line"/>
7+
<point x="52" y="301" type="line"/>
8+
<point x="550" y="301" type="line"/>
9+
<point x="550" y="404" type="line"/>
10+
</contour>
11+
</outline>
12+
</glyph>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version='1.0' encoding='UTF-8'?>
2+
<glyph name="composite" format="2">
3+
<advance width="1200"/>
4+
<outline>
5+
<component base="base"/>
6+
<component base="base" xScale="0.8811" yScale="6.4512" xOffset="169" yOffset="-1107"/>
7+
</outline>
8+
</glyph>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version='1.0' encoding='UTF-8'?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>base</key>
6+
<string>base.glif</string>
7+
<key>composite</key>
8+
<string>composite.glif</string>
9+
</dict>
10+
</plist>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version='1.0' encoding='UTF-8'?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<array>
5+
<array>
6+
<string>public.default</string>
7+
<string>glyphs</string>
8+
</array>
9+
</array>
10+
</plist>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version='1.0' encoding='UTF-8'?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>public.glyphOrder</key>
6+
<array>
7+
<string>base</string>
8+
<string>composite</string>
9+
</array>
10+
</dict>
11+
</plist>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version='1.0' encoding='UTF-8'?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>creator</key>
6+
<string>com.github.fonttools.ufoLib</string>
7+
<key>formatVersion</key>
8+
<integer>3</integer>
9+
</dict>
10+
</plist>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?xml version='1.0' encoding='UTF-8'?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>ascender</key>
6+
<integer>800</integer>
7+
<key>capHeight</key>
8+
<integer>700</integer>
9+
<key>descender</key>
10+
<integer>-200</integer>
11+
<key>familyName</key>
12+
<string>Overflowing Component Transforms</string>
13+
<key>openTypeHeadCreated</key>
14+
<string>2025/09/19 11:59:48</string>
15+
<key>openTypeOS2WeightClass</key>
16+
<integer>400</integer>
17+
<key>styleMapFamilyName</key>
18+
<string>Overflowing Component Transforms</string>
19+
<key>styleMapStyleName</key>
20+
<string>regular</string>
21+
<key>styleName</key>
22+
<string>Regular</string>
23+
<key>unitsPerEm</key>
24+
<integer>1000</integer>
25+
<key>versionMajor</key>
26+
<integer>1</integer>
27+
<key>versionMinor</key>
28+
<integer>0</integer>
29+
</dict>
30+
</plist>

0 commit comments

Comments
 (0)