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. """