From fd6c81585f6fe81fccc1b8d4bf6b81f7d2e92772 Mon Sep 17 00:00:00 2001 From: Khaled Hosny Date: Mon, 13 Mar 2023 16:29:59 +0200 Subject: [PATCH] [otlib] Add a config option to write GPOS 7 lookups See the inline comment and option documentation --- Lib/fontTools/config/__init__.py | 15 +++ Lib/fontTools/otlLib/builder.py | 11 +- .../InterpolateLayoutGPOS_7_diff.ttx | 116 ++++++++++++++++++ .../InterpolateLayoutGPOS_7_same.ttx | 116 ++++++++++++++++++ Tests/varLib/interpolate_layout_test.py | 92 +++++++++++++- 5 files changed, 348 insertions(+), 2 deletions(-) create mode 100644 Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_diff.ttx create mode 100644 Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_same.ttx diff --git a/Lib/fontTools/config/__init__.py b/Lib/fontTools/config/__init__.py index f5a62eaf1..29774fcad 100644 --- a/Lib/fontTools/config/__init__.py +++ b/Lib/fontTools/config/__init__.py @@ -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 didn’t 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, +) diff --git a/Lib/fontTools/otlLib/builder.py b/Lib/fontTools/otlLib/builder.py index a72aa9fdb..7a5848ad8 100644 --- a/Lib/fontTools/otlLib/builder.py +++ b/Lib/fontTools/otlLib/builder.py @@ -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: diff --git a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_diff.ttx b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_diff.ttx new file mode 100644 index 000000000..8a73402ea --- /dev/null +++ b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_diff.ttx @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_same.ttx b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_same.ttx new file mode 100644 index 000000000..17636512f --- /dev/null +++ b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_7_same.ttx @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/varLib/interpolate_layout_test.py b/Tests/varLib/interpolate_layout_test.py index 9d14617a0..1844e3b16 100644 --- a/Tests/varLib/interpolate_layout_test.py +++ b/Tests/varLib/interpolate_layout_test.py @@ -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 @MARKS_ABOVE; + lookup CNTXT_PAIR_POS { + pos A a -23; + } CNTXT_PAIR_POS; + + lookup CNTXT_MARK_TO_BASE { + pos base a 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 @MARKS_ABOVE; + lookup CNTXT_PAIR_POS { + pos A a -23; + } CNTXT_PAIR_POS; + + lookup CNTXT_MARK_TO_BASE { + pos base a 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 @MARKS_ABOVE; + lookup CNTXT_PAIR_POS { + pos A a 57; + } CNTXT_PAIR_POS; + + lookup CNTXT_MARK_TO_BASE { + pos base a 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"