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"