[otlib] Add a config option to write GPOS 7 lookups

See the inline comment and option documentation
This commit is contained in:
Khaled Hosny 2023-03-13 16:29:59 +02:00
parent 580e3b3b50
commit fd6c81585f
5 changed files with 348 additions and 2 deletions

View File

@ -57,3 +57,18 @@ Config.register_option(
parse=Option.parse_optional_bool,
validate=Option.validate_optional_bool,
)
Config.register_option(
name="fontTools.otlLib.builder:WRITE_GPOS7",
help=dedent(
"""\
macOS before 13.2 didnt support GPOS LookupType 7 (none-chaining
ContextPos lookups), so FontTools.otLib.builder disables a file size
optimisation that would use LookupType 7 instead of 8 when there is no
chaining (no prefix or suffix). Set to True to enable the optimization.
"""
),
default=False,
parse=Option.parse_optional_bool,
validate=Option.validate_optional_bool,
)

View File

@ -369,10 +369,19 @@ class ChainContextualBuilder(LookupBuilder):
rulesets = self.rulesets()
chaining = any(ruleset.hasPrefixOrSuffix for ruleset in rulesets)
# https://github.com/fonttools/fonttools/issues/2539
#
# Unfortunately, as of 2022-03-07, Apple's CoreText renderer does not
# correctly process GPOS7 lookups, so for now we force contextual
# positioning lookups to be chaining (GPOS8).
if self.subtable_type == "Pos": # horrible separation of concerns breach
#
# This seems to be fixed as of macOS 13.2, but we keep disabling this
# for now until we are no longer concerned about old macOS versions.
# But we allow people to opt-out of this with the config key below.
write_gpos7 = self.font.cfg.get("fontTools.otlLib.builder:WRITE_GPOS7")
# horrible separation of concerns breach
if not write_gpos7 and self.subtable_type == "Pos":
chaining = True
for ruleset in rulesets:

View File

@ -0,0 +1,116 @@
<?xml version="1.0" encoding="UTF-8"?>
<ttFont>
<GPOS>
<Version value="0x00010000"/>
<ScriptList>
<!-- ScriptCount=1 -->
<ScriptRecord index="0">
<ScriptTag value="DFLT"/>
<Script>
<DefaultLangSys>
<ReqFeatureIndex value="65535"/>
<!-- FeatureCount=1 -->
<FeatureIndex index="0" value="0"/>
</DefaultLangSys>
<!-- LangSysCount=0 -->
</Script>
</ScriptRecord>
</ScriptList>
<FeatureList>
<!-- FeatureCount=1 -->
<FeatureRecord index="0">
<FeatureTag value="xxxx"/>
<Feature>
<!-- LookupCount=1 -->
<LookupListIndex index="0" value="2"/>
</Feature>
</FeatureRecord>
</FeatureList>
<LookupList>
<!-- LookupCount=3 -->
<Lookup index="0">
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
<Coverage>
<Glyph value="A"/>
</Coverage>
<ValueFormat1 value="4"/>
<ValueFormat2 value="0"/>
<!-- PairSetCount=1 -->
<PairSet index="0">
<!-- PairValueCount=1 -->
<PairValueRecord index="0">
<SecondGlyph value="a"/>
<Value1 XAdvance="17"/>
</PairValueRecord>
</PairSet>
</PairPos>
</Lookup>
<Lookup index="1">
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<MarkBasePos index="0" Format="1">
<MarkCoverage>
<Glyph value="uni0303"/>
</MarkCoverage>
<BaseCoverage>
<Glyph value="a"/>
</BaseCoverage>
<!-- ClassCount=1 -->
<MarkArray>
<!-- MarkCount=1 -->
<MarkRecord index="0">
<Class value="0"/>
<MarkAnchor Format="1">
<XCoordinate value="0"/>
<YCoordinate value="510"/>
</MarkAnchor>
</MarkRecord>
</MarkArray>
<BaseArray>
<!-- BaseCount=1 -->
<BaseRecord index="0">
<BaseAnchor index="0" Format="1">
<XCoordinate value="273"/>
<YCoordinate value="510"/>
</BaseAnchor>
</BaseRecord>
</BaseArray>
</MarkBasePos>
</Lookup>
<Lookup index="2">
<LookupType value="7"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextPos index="0" Format="1">
<Coverage>
<Glyph value="A"/>
</Coverage>
<!-- PosRuleSetCount=1 -->
<PosRuleSet index="0">
<!-- PosRuleCount=1 -->
<PosRule index="0">
<!-- GlyphCount=3 -->
<!-- PosCount=2 -->
<Input index="0" value="a"/>
<Input index="1" value="uni0303"/>
<PosLookupRecord index="0">
<SequenceIndex value="0"/>
<LookupListIndex value="0"/>
</PosLookupRecord>
<PosLookupRecord index="1">
<SequenceIndex value="2"/>
<LookupListIndex value="1"/>
</PosLookupRecord>
</PosRule>
</PosRuleSet>
</ContextPos>
</Lookup>
</LookupList>
</GPOS>
</ttFont>

View File

@ -0,0 +1,116 @@
<?xml version="1.0" encoding="UTF-8"?>
<ttFont>
<GPOS>
<Version value="0x00010000"/>
<ScriptList>
<!-- ScriptCount=1 -->
<ScriptRecord index="0">
<ScriptTag value="DFLT"/>
<Script>
<DefaultLangSys>
<ReqFeatureIndex value="65535"/>
<!-- FeatureCount=1 -->
<FeatureIndex index="0" value="0"/>
</DefaultLangSys>
<!-- LangSysCount=0 -->
</Script>
</ScriptRecord>
</ScriptList>
<FeatureList>
<!-- FeatureCount=1 -->
<FeatureRecord index="0">
<FeatureTag value="xxxx"/>
<Feature>
<!-- LookupCount=1 -->
<LookupListIndex index="0" value="2"/>
</Feature>
</FeatureRecord>
</FeatureList>
<LookupList>
<!-- LookupCount=3 -->
<Lookup index="0">
<LookupType value="2"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<PairPos index="0" Format="1">
<Coverage>
<Glyph value="A"/>
</Coverage>
<ValueFormat1 value="4"/>
<ValueFormat2 value="0"/>
<!-- PairSetCount=1 -->
<PairSet index="0">
<!-- PairValueCount=1 -->
<PairValueRecord index="0">
<SecondGlyph value="a"/>
<Value1 XAdvance="-23"/>
</PairValueRecord>
</PairSet>
</PairPos>
</Lookup>
<Lookup index="1">
<LookupType value="4"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<MarkBasePos index="0" Format="1">
<MarkCoverage>
<Glyph value="uni0303"/>
</MarkCoverage>
<BaseCoverage>
<Glyph value="a"/>
</BaseCoverage>
<!-- ClassCount=1 -->
<MarkArray>
<!-- MarkCount=1 -->
<MarkRecord index="0">
<Class value="0"/>
<MarkAnchor Format="1">
<XCoordinate value="0"/>
<YCoordinate value="500"/>
</MarkAnchor>
</MarkRecord>
</MarkArray>
<BaseArray>
<!-- BaseCount=1 -->
<BaseRecord index="0">
<BaseAnchor index="0" Format="1">
<XCoordinate value="260"/>
<YCoordinate value="500"/>
</BaseAnchor>
</BaseRecord>
</BaseArray>
</MarkBasePos>
</Lookup>
<Lookup index="2">
<LookupType value="7"/>
<LookupFlag value="0"/>
<!-- SubTableCount=1 -->
<ContextPos index="0" Format="1">
<Coverage>
<Glyph value="A"/>
</Coverage>
<!-- PosRuleSetCount=1 -->
<PosRuleSet index="0">
<!-- PosRuleCount=1 -->
<PosRule index="0">
<!-- GlyphCount=3 -->
<!-- PosCount=2 -->
<Input index="0" value="a"/>
<Input index="1" value="uni0303"/>
<PosLookupRecord index="0">
<SequenceIndex value="0"/>
<LookupListIndex value="0"/>
</PosLookupRecord>
<PosLookupRecord index="1">
<SequenceIndex value="2"/>
<LookupListIndex value="1"/>
</PosLookupRecord>
</PosRule>
</PosRuleSet>
</ContextPos>
</Lookup>
</LookupList>
</GPOS>
</ttFont>

View File

@ -85,10 +85,12 @@ class InterpolateLayoutTest(unittest.TestCase):
font.save(path)
self.expect_ttx(TTFont(path), expected_ttx, tables)
def compile_font(self, path, suffix, temp_dir, features=None):
def compile_font(self, path, suffix, temp_dir, features=None, cfg=None):
ttx_filename = os.path.basename(path)
savepath = os.path.join(temp_dir, ttx_filename.replace(".ttx", suffix))
font = TTFont(recalcBBoxes=False, recalcTimestamp=False)
if cfg:
font.cfg.update(cfg)
font.importXML(path)
if features:
addOpenTypeFeaturesFromString(font, features)
@ -734,6 +736,94 @@ class InterpolateLayoutTest(unittest.TestCase):
self.expect_ttx(instfont, expected_ttx_path, tables)
self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
def test_varlib_interpolate_layout_GPOS_only_LookupType_7_same_val_ttf(self):
"""Only GPOS; LookupType 7; same values in all masters."""
suffix = ".ttf"
ds_path = self.get_test_input("InterpolateLayout.designspace")
ufo_dir = self.get_test_input("master_ufo")
ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
fea_str = """
markClass uni0303 <anchor 0 500> @MARKS_ABOVE;
lookup CNTXT_PAIR_POS {
pos A a -23;
} CNTXT_PAIR_POS;
lookup CNTXT_MARK_TO_BASE {
pos base a <anchor 260 500> mark @MARKS_ABOVE;
} CNTXT_MARK_TO_BASE;
feature xxxx {
pos A' lookup CNTXT_PAIR_POS a' @MARKS_ABOVE' lookup CNTXT_MARK_TO_BASE;
} xxxx;
"""
features = [fea_str] * 2
self.temp_dir()
ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
cfg = {"fontTools.otlLib.builder:WRITE_GPOS7": True}
for i, path in enumerate(ttx_paths):
self.compile_font(path, suffix, self.tempdir, features[i], cfg)
finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
tables = ["GPOS"]
expected_ttx_path = self.get_test_output("InterpolateLayoutGPOS_7_same.ttx")
self.expect_ttx(instfont, expected_ttx_path, tables)
self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
def test_varlib_interpolate_layout_GPOS_only_LookupType_7_diff_val_ttf(self):
"""Only GPOS; LookupType 7; different values in each master."""
suffix = ".ttf"
ds_path = self.get_test_input("InterpolateLayout.designspace")
ufo_dir = self.get_test_input("master_ufo")
ttx_dir = self.get_test_input("master_ttx_interpolatable_ttf")
fea_str_0 = """
markClass uni0303 <anchor 0 500> @MARKS_ABOVE;
lookup CNTXT_PAIR_POS {
pos A a -23;
} CNTXT_PAIR_POS;
lookup CNTXT_MARK_TO_BASE {
pos base a <anchor 260 500> mark @MARKS_ABOVE;
} CNTXT_MARK_TO_BASE;
feature xxxx {
pos A' lookup CNTXT_PAIR_POS a' @MARKS_ABOVE' lookup CNTXT_MARK_TO_BASE;
} xxxx;
"""
fea_str_1 = """
markClass uni0303 <anchor 0 520> @MARKS_ABOVE;
lookup CNTXT_PAIR_POS {
pos A a 57;
} CNTXT_PAIR_POS;
lookup CNTXT_MARK_TO_BASE {
pos base a <anchor 285 520> mark @MARKS_ABOVE;
} CNTXT_MARK_TO_BASE;
feature xxxx {
pos A' lookup CNTXT_PAIR_POS a' @MARKS_ABOVE' lookup CNTXT_MARK_TO_BASE;
} xxxx;
"""
features = [fea_str_0, fea_str_1]
self.temp_dir()
ttx_paths = self.get_file_list(ttx_dir, ".ttx", "TestFamily2-")
cfg = {"fontTools.otlLib.builder:WRITE_GPOS7": True}
for i, path in enumerate(ttx_paths):
self.compile_font(path, suffix, self.tempdir, features[i], cfg)
finder = lambda s: s.replace(ufo_dir, self.tempdir).replace(".ufo", suffix)
instfont = interpolate_layout(ds_path, {"weight": 500}, finder)
tables = ["GPOS"]
expected_ttx_path = self.get_test_output("InterpolateLayoutGPOS_7_diff.ttx")
self.expect_ttx(instfont, expected_ttx_path, tables)
self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
def test_varlib_interpolate_layout_GPOS_only_LookupType_8_same_val_ttf(self):
"""Only GPOS; LookupType 8; same values in all masters."""
suffix = ".ttf"