Skip to content

Commit d440b5e

Browse files
authored
Merge pull request #946 from googlefonts/decompose-overflowing-transforms
preProcessor: decompose components whose transform overflows F2Dot14
2 parents 22f466e + 4619689 commit d440b5e

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)