diff --git a/Lib/fontTools/ttLib/tables/otTables.py b/Lib/fontTools/ttLib/tables/otTables.py
index e5a089672..380313d85 100644
--- a/Lib/fontTools/ttLib/tables/otTables.py
+++ b/Lib/fontTools/ttLib/tables/otTables.py
@@ -418,19 +418,19 @@ class ClassDef(FormatSwitchingBaseTable):
assert 0, "unknown format: %s" % self.Format
self.classDefs = classDefs
- def preWrite(self, font):
+ def _getClassRanges(self, font):
classDefs = getattr(self, "classDefs", None)
if classDefs is None:
- classDefs = self.classDefs = {}
- format = 2
- rawTable = {"ClassRangeRecord": []}
+ self.classDefs = {}
+ return
getGlyphID = font.getGlyphID
items = []
for glyphName, cls in classDefs.items():
- if not cls: continue
+ if not cls:
+ continue
items.append((getGlyphID(glyphName), glyphName, cls))
- items.sort()
if items:
+ items.sort()
last, lastName, lastCls = items[0]
ranges = [[lastCls, last, lastName]]
for glyphID, glyphName, cls in items[1:]:
@@ -441,7 +441,13 @@ class ClassDef(FormatSwitchingBaseTable):
lastName = glyphName
lastCls = cls
ranges[-1].extend([last, lastName])
+ return ranges
+ def preWrite(self, font):
+ format = 2
+ rawTable = {"ClassRangeRecord": []}
+ ranges = self._getClassRanges(font)
+ if ranges:
startGlyph = ranges[0][1]
endGlyph = ranges[-1][3]
glyphCount = endGlyph - startGlyph + 1
diff --git a/Lib/fontTools/varLib/merger.py b/Lib/fontTools/varLib/merger.py
index 06a47bdb1..d5f0609d9 100644
--- a/Lib/fontTools/varLib/merger.py
+++ b/Lib/fontTools/varLib/merger.py
@@ -322,10 +322,12 @@ def _ClassDef_merge_classify(lst, allGlyphs=None):
sets = _ClassDef_invert(l)
if allGlyphs is None:
sets = sets[1:]
- else:
+ elif sets:
sets[0] = set(allGlyphs)
for s in sets[1:]:
sets[0].difference_update(s)
+ else:
+ sets = [set(allGlyphs)]
classifier.update(sets)
classes = classifier.getClasses()
@@ -340,6 +342,18 @@ def _ClassDef_merge_classify(lst, allGlyphs=None):
return self, classes
+def _ClassDef_calculate_Format(self, font):
+ fmt = 2
+ ranges = self._getClassRanges(font)
+ if ranges:
+ startGlyph = ranges[0][1]
+ endGlyph = ranges[-1][3]
+ glyphCount = endGlyph - startGlyph + 1
+ if len(ranges) * 3 >= glyphCount + 1:
+ # Format 1 is more compact
+ fmt = 1
+ self.Format = fmt
+
def _PairPosFormat2_merge(self, lst, merger):
merger.mergeObjects(self, lst,
exclude=('Coverage',
@@ -366,6 +380,7 @@ def _PairPosFormat2_merge(self, lst, merger):
# Align first classes
self.ClassDef1, classes = _ClassDef_merge_classify([l.ClassDef1 for l in lst], allGlyphs=glyphSet)
+ _ClassDef_calculate_Format(self.ClassDef1, merger.font)
self.Class1Count = len(classes)
new_matrices = []
for l,matrix in zip(lst, matrices):
@@ -377,11 +392,13 @@ def _PairPosFormat2_merge(self, lst, merger):
exemplarGlyph = next(iter(classSet))
if exemplarGlyph not in coverage:
if nullRow is None:
- rec2 = ot.Class2Record()
- rec2.Value1 = otBase.ValueRecord(l.ValueFormat1) if l.ValueFormat1 else None
- rec2.Value2 = otBase.ValueRecord(l.ValueFormat2) if l.ValueFormat2 else None
nullRow = ot.Class1Record()
- nullRow.Class2Record = [rec2] * l.Class2Count
+ class2records = nullRow.Class2Record = []
+ for _ in range(l.Class2Count):
+ rec2 = ot.Class2Record()
+ rec2.Value1 = otBase.ValueRecord(l.ValueFormat1) if l.ValueFormat1 else None
+ rec2.Value2 = otBase.ValueRecord(l.ValueFormat2) if l.ValueFormat2 else None
+ class2records.append(rec2)
rec1 = nullRow
else:
klass = classDef1.get(exemplarGlyph, 0)
@@ -393,6 +410,7 @@ def _PairPosFormat2_merge(self, lst, merger):
# Align second classes
self.ClassDef2, classes = _ClassDef_merge_classify([l.ClassDef2 for l in lst])
+ _ClassDef_calculate_Format(self.ClassDef2, merger.font)
self.Class2Count = len(classes)
new_matrices = []
for l,matrix in zip(lst, matrices):
@@ -506,15 +524,18 @@ def _Lookup_PairPos_subtables_canonicalize(lst, font):
head = []
tail = []
it = iter(lst)
+ has_Format1 = False
for subtable in it:
if subtable.Format == 1:
head.append(subtable)
+ has_Format1 = True
continue
tail.append(subtable)
break
tail.extend(it)
- # TODO Only do this if at least one font has a Format1.
- tail.insert(0, _Lookup_PairPosFormat1_subtables_merge_overlay(head, font))
+ if has_Format1:
+ # only insert if at least one font has a Format1
+ tail.insert(0, _Lookup_PairPosFormat1_subtables_merge_overlay(head, font))
return tail
@AligningMerger.merger(ot.Lookup)
diff --git a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_class_diff.ttx b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_class_diff.ttx
new file mode 100644
index 000000000..4f94c37bc
--- /dev/null
+++ b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_class_diff.ttx
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_class_diff2.ttx b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_class_diff2.ttx
new file mode 100644
index 000000000..0184886c3
--- /dev/null
+++ b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_class_diff2.ttx
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_class_same.ttx b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_class_same.ttx
new file mode 100644
index 000000000..987253361
--- /dev/null
+++ b/Tests/varLib/data/test_results/InterpolateLayoutGPOS_2_class_same.ttx
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/varLib/interpolate_layout_test.py b/Tests/varLib/interpolate_layout_test.py
index 80251f543..9bebb69e6 100644
--- a/Tests/varLib/interpolate_layout_test.py
+++ b/Tests/varLib/interpolate_layout_test.py
@@ -401,6 +401,104 @@ class InterpolateLayoutTest(unittest.TestCase):
self.check_ttx_dump(instfont, expected_ttx_path, tables, suffix)
+ def test_varlib_interpolate_layout_GPOS_only_LookupType_2_class_pairs_same_val_ttf(self):
+ """Only GPOS; LookupType 2 class pairs; 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 = """
+ feature xxxx {
+ pos [A] [a] -53;
+ } xxxx;
+ """
+ features = [fea_str] * 2
+
+ self.temp_dir()
+ ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily2-')
+ for i, path in enumerate(ttx_paths):
+ self.compile_font(path, suffix, self.tempdir, features[i])
+
+ 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_2_class_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_2_class_pairs_diff_val_ttf(self):
+ """Only GPOS; LookupType 2 class pairs; 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 = """
+ feature xxxx {
+ pos [A] [a] -53;
+ } xxxx;
+ """
+ fea_str_1 = """
+ feature xxxx {
+ pos [A] [a] -27;
+ } xxxx;
+ """
+ features = [fea_str_0, fea_str_1]
+
+ self.temp_dir()
+ ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily2-')
+ for i, path in enumerate(ttx_paths):
+ self.compile_font(path, suffix, self.tempdir, features[i])
+
+ 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_2_class_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_2_class_pairs_diff2_val_ttf(self):
+ """Only GPOS; LookupType 2 class pairs; different values and items 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 = """
+ feature xxxx {
+ pos [A] [a] -53;
+ } xxxx;
+ """
+ fea_str_1 = """
+ feature xxxx {
+ pos [A] [a] -27;
+ pos [a] [a] 19;
+ } xxxx;
+ """
+ features = [fea_str_0, fea_str_1]
+
+ self.temp_dir()
+ ttx_paths = self.get_file_list(ttx_dir, '.ttx', 'TestFamily2-')
+ for i, path in enumerate(ttx_paths):
+ self.compile_font(path, suffix, self.tempdir, features[i])
+
+ 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_2_class_diff2.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_3_same_val_ttf(self):
"""Only GPOS; LookupType 3; same values in all masters.
"""