From b945fd63059a482961d71081956ad7a9c5789544 Mon Sep 17 00:00:00 2001 From: Jack McCabe Date: Fri, 30 Sep 2022 11:40:39 +0100 Subject: [PATCH 1/6] [subset] Fix enumeration of palette entry labels --- Lib/fontTools/subset/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/fontTools/subset/__init__.py b/Lib/fontTools/subset/__init__.py index 679ea15ad..1efcd8cef 100644 --- a/Lib/fontTools/subset/__init__.py +++ b/Lib/fontTools/subset/__init__.py @@ -2259,7 +2259,7 @@ def prune_post_subset(self, font, options): if self.version == 1: self.paletteEntryLabels = [ - label for i, label in self.paletteEntryLabels if i in retained_palette_indices + label for i, label in enumerate(self.paletteEntryLabels) if i in retained_palette_indices ] return bool(self.numPaletteEntries) From 4901deab76b01888757e6ea691818720715a9037 Mon Sep 17 00:00:00 2001 From: Jack McCabe Date: Fri, 30 Sep 2022 12:10:56 +0100 Subject: [PATCH 2/6] [subset] Add test for CPALv1 (failing !) --- Tests/subset/subset_test.py | 46 +++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/Tests/subset/subset_test.py b/Tests/subset/subset_test.py index 7efcb698b..7cdeb97e2 100644 --- a/Tests/subset/subset_test.py +++ b/Tests/subset/subset_test.py @@ -1244,6 +1244,28 @@ def colrv1_path(tmp_path): return output_path +@pytest.fixture +def colrv1_cpalv1_path(colrv1_path): + # upgrade CPAL from v0 to v1 by adding labels + font = TTFont(colrv1_path) + fb = FontBuilder(font=font) + fb.setupCPAL( + [ + [ + (1.0, 0.0, 0.0, 1.0), # red + (0.0, 1.0, 0.0, 1.0), # green + (0.0, 0.0, 1.0, 1.0), # blue + ], + ], + paletteLabels=["test palette"], + paletteEntryLabels=["first color", "second color", "third color"] + ) + + output_path = colrv1_path.parent / "TestCOLRv1CPALv1.ttf" + fb.save(output_path) + + return output_path + def test_subset_COLRv1_and_CPAL(colrv1_path): subset_path = colrv1_path.parent / (colrv1_path.name + ".subset") @@ -1321,6 +1343,30 @@ def test_subset_COLRv1_and_CPAL(colrv1_path): (0.0, 0.0, 1.0, 1.0), # blue ] +def test_subset_COLRv1_and_CPALv1(colrv1_cpalv1_path): + subset_path = colrv1_cpalv1_path.parent / (colrv1_cpalv1_path.name + ".subset") + + subset.main( + [ + str(colrv1_cpalv1_path), + "--glyph-names", + f"--output-file={subset_path}", + "--unicodes=E002,E003,E004", + ] + ) + subset_font = TTFont(subset_path) + + assert "CPAL" in subset_font + cpal = subset_font["CPAL"] + name_table = subset_font["name"] + import pdb + pdb.set_trace() + assert [name_table.getDebugName(name_id) for name_id in cpal.paletteEntryLabels] == [ + # "first color", # The first color was pruned + "second color", + "third color", + ] + def test_subset_COLRv1_and_CPAL_drop_empty(colrv1_path): subset_path = colrv1_path.parent / (colrv1_path.name + ".subset") From 70112b947dfbf1d53d105739ec9e57504b70a939 Mon Sep 17 00:00:00 2001 From: Jack McCabe Date: Fri, 30 Sep 2022 12:36:28 +0100 Subject: [PATCH 3/6] [subset] prevent CPAL nameIDs from being dropped --- Lib/fontTools/subset/__init__.py | 4 ++++ Tests/subset/subset_test.py | 2 -- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Lib/fontTools/subset/__init__.py b/Lib/fontTools/subset/__init__.py index 1efcd8cef..4d5fa74c2 100644 --- a/Lib/fontTools/subset/__init__.py +++ b/Lib/fontTools/subset/__init__.py @@ -2553,6 +2553,10 @@ def prune_pre_subset(self, font, options): if stat.table.AxisValueArray: nameIDs.update([val_rec.ValueNameID for val_rec in stat.table.AxisValueArray.AxisValue]) nameIDs.update([axis_rec.AxisNameID for axis_rec in stat.table.DesignAxisRecord.Axis]) + cpal = font.get('CPAL') + if cpal and cpal.version == 1: + nameIDs.update(cpal.paletteLabels) + nameIDs.update(cpal.paletteEntryLabels) if '*' not in options.name_IDs: self.names = [n for n in self.names if n.nameID in nameIDs] if not options.name_legacy: diff --git a/Tests/subset/subset_test.py b/Tests/subset/subset_test.py index 7cdeb97e2..321edd0e4 100644 --- a/Tests/subset/subset_test.py +++ b/Tests/subset/subset_test.py @@ -1359,8 +1359,6 @@ def test_subset_COLRv1_and_CPALv1(colrv1_cpalv1_path): assert "CPAL" in subset_font cpal = subset_font["CPAL"] name_table = subset_font["name"] - import pdb - pdb.set_trace() assert [name_table.getDebugName(name_id) for name_id in cpal.paletteEntryLabels] == [ # "first color", # The first color was pruned "second color", From 14ce08bd7550092d8daee7dd6cacfc7f0f7d505e Mon Sep 17 00:00:00 2001 From: Jack McCabe Date: Fri, 30 Sep 2022 15:41:14 +0100 Subject: [PATCH 4/6] [subset] extend CPALv1 test --- Lib/fontTools/subset/__init__.py | 19 +++++- Tests/subset/subset_test.py | 101 ++++++++++++++++++++++++++++++- 2 files changed, 116 insertions(+), 4 deletions(-) diff --git a/Lib/fontTools/subset/__init__.py b/Lib/fontTools/subset/__init__.py index 4d5fa74c2..e13215bd0 100644 --- a/Lib/fontTools/subset/__init__.py +++ b/Lib/fontTools/subset/__init__.py @@ -2258,8 +2258,23 @@ def prune_post_subset(self, font, options): self.numPaletteEntries = len(self.palettes[0]) if self.version == 1: - self.paletteEntryLabels = [ - label for i, label in enumerate(self.paletteEntryLabels) if i in retained_palette_indices + kept_labels = [] + dropped_labels = [] + for i, label in enumerate(self.paletteEntryLabels): + if i in retained_palette_indices: + kept_labels.append(label) + else: + dropped_labels.append(label) + self.paletteEntryLabels = kept_labels + # Remove dropped labels from the name table. + name_table = font["name"] + name_table.names = [ + n for n in name_table.names + if ( + n.nameID not in dropped_labels or + # Only remove nameIDs in the user range and if they're not explicitly kept + n.nameID < 256 or n.nameID in options.name_IDs + ) ] return bool(self.numPaletteEntries) diff --git a/Tests/subset/subset_test.py b/Tests/subset/subset_test.py index 321edd0e4..add0b8e59 100644 --- a/Tests/subset/subset_test.py +++ b/Tests/subset/subset_test.py @@ -1260,12 +1260,37 @@ def colrv1_cpalv1_path(colrv1_path): paletteLabels=["test palette"], paletteEntryLabels=["first color", "second color", "third color"] ) - + output_path = colrv1_path.parent / "TestCOLRv1CPALv1.ttf" fb.save(output_path) return output_path - + +@pytest.fixture +def colrv1_cpalv1_share_nameID_path(colrv1_path): + # upgrade CPAL from v0 to v1 by adding labels + font = TTFont(colrv1_path) + fb = FontBuilder(font=font) + fb.setupCPAL( + [ + [ + (1.0, 0.0, 0.0, 1.0), # red + (0.0, 1.0, 0.0, 1.0), # green + (0.0, 0.0, 1.0, 1.0), # blue + ], + ], + paletteLabels=["test palette"], + paletteEntryLabels=["first color", "second color", "third color"] + ) + + # Set the name ID of the first color to use nameID 1 = familyName = "TestCOLRv1" + fb.font["CPAL"].paletteEntryLabels[0] = 1 + + output_path = colrv1_path.parent / "TestCOLRv1CPALv1.ttf" + fb.save(output_path) + + return output_path + def test_subset_COLRv1_and_CPAL(colrv1_path): subset_path = colrv1_path.parent / (colrv1_path.name + ".subset") @@ -1343,6 +1368,7 @@ def test_subset_COLRv1_and_CPAL(colrv1_path): (0.0, 0.0, 1.0, 1.0), # blue ] + def test_subset_COLRv1_and_CPALv1(colrv1_cpalv1_path): subset_path = colrv1_cpalv1_path.parent / (colrv1_cpalv1_path.name + ".subset") @@ -1364,6 +1390,77 @@ def test_subset_COLRv1_and_CPALv1(colrv1_cpalv1_path): "second color", "third color", ] + # check that the "first color" name is dropped from name table + font = TTFont(colrv1_cpalv1_path) + for n in font["name"].names: + if n.toUnicode() == "first color": + first_color_nameID = n.nameID + break + assert first_color_nameID is not None + assert all(n.nameID != first_color_nameID for n in name_table.names) + + +def test_subset_COLRv1_and_CPALv1_keep_nameID(colrv1_cpalv1_path): + subset_path = colrv1_cpalv1_path.parent / (colrv1_cpalv1_path.name + ".subset") + + # figure out the name ID of first color so we can keep it + font = TTFont(colrv1_cpalv1_path) + + for n in font["name"].names: + if n.toUnicode() == "first color": + first_color_nameID = n.nameID + break + assert first_color_nameID is not None + + subset.main( + [ + str(colrv1_cpalv1_path), + "--glyph-names", + f"--output-file={subset_path}", + "--unicodes=E002,E003,E004", + f"--name-IDs={first_color_nameID}", + + ] + ) + subset_font = TTFont(subset_path) + + assert "CPAL" in subset_font + cpal = subset_font["CPAL"] + name_table = subset_font["name"] + assert [name_table.getDebugName(name_id) for name_id in cpal.paletteEntryLabels] == [ + # "first color", # The first color was pruned + "second color", + "third color", + ] + + # Check that the name ID is kept + assert any(n.nameID == first_color_nameID for n in name_table.names) + + +def test_subset_COLRv1_and_CPALv1_share_nameID(colrv1_cpalv1_share_nameID_path): + subset_path = colrv1_cpalv1_share_nameID_path.parent / (colrv1_cpalv1_share_nameID_path.name + ".subset") + + subset.main( + [ + str(colrv1_cpalv1_share_nameID_path), + "--glyph-names", + f"--output-file={subset_path}", + "--unicodes=E002,E003,E004", + ] + ) + subset_font = TTFont(subset_path) + + assert "CPAL" in subset_font + cpal = subset_font["CPAL"] + name_table = subset_font["name"] + assert [name_table.getDebugName(name_id) for name_id in cpal.paletteEntryLabels] == [ + # "first color", # The first color was pruned + "second color", + "third color", + ] + + # Check that the name ID 1 is kept + assert any(n.nameID == 1 for n in name_table.names) def test_subset_COLRv1_and_CPAL_drop_empty(colrv1_path): From eeba234ee037b2c99ea9c5c9e3f04e95f46dac15 Mon Sep 17 00:00:00 2001 From: Jack McCabe Date: Fri, 30 Sep 2022 15:59:11 +0100 Subject: [PATCH 5/6] [subset] PR feedback implementation --- Lib/fontTools/subset/__init__.py | 5 +++-- Tests/subset/subset_test.py | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Lib/fontTools/subset/__init__.py b/Lib/fontTools/subset/__init__.py index e13215bd0..b309f7760 100644 --- a/Lib/fontTools/subset/__init__.py +++ b/Lib/fontTools/subset/__init__.py @@ -2271,9 +2271,10 @@ def prune_post_subset(self, font, options): name_table.names = [ n for n in name_table.names if ( - n.nameID not in dropped_labels or + n.nameID not in dropped_labels # Only remove nameIDs in the user range and if they're not explicitly kept - n.nameID < 256 or n.nameID in options.name_IDs + or n.nameID < 256 + or n.nameID in options.name_IDs ) ] return bool(self.numPaletteEntries) diff --git a/Tests/subset/subset_test.py b/Tests/subset/subset_test.py index add0b8e59..1b551ac29 100644 --- a/Tests/subset/subset_test.py +++ b/Tests/subset/subset_test.py @@ -1268,7 +1268,6 @@ def colrv1_cpalv1_path(colrv1_path): @pytest.fixture def colrv1_cpalv1_share_nameID_path(colrv1_path): - # upgrade CPAL from v0 to v1 by adding labels font = TTFont(colrv1_path) fb = FontBuilder(font=font) fb.setupCPAL( @@ -1392,6 +1391,8 @@ def test_subset_COLRv1_and_CPALv1(colrv1_cpalv1_path): ] # check that the "first color" name is dropped from name table font = TTFont(colrv1_cpalv1_path) + + first_color_nameID = None for n in font["name"].names: if n.toUnicode() == "first color": first_color_nameID = n.nameID @@ -1406,6 +1407,7 @@ def test_subset_COLRv1_and_CPALv1_keep_nameID(colrv1_cpalv1_path): # figure out the name ID of first color so we can keep it font = TTFont(colrv1_cpalv1_path) + first_color_nameID = None for n in font["name"].names: if n.toUnicode() == "first color": first_color_nameID = n.nameID From a01e20aa8632fdb881e2bb7e75011a0809273e9d Mon Sep 17 00:00:00 2001 From: Jack McCabe Date: Fri, 30 Sep 2022 16:11:41 +0100 Subject: [PATCH 6/6] Update news --- NEWS.rst | 46 +++++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/NEWS.rst b/NEWS.rst index 9b13e8ba6..fe0bbbdd7 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -1,3 +1,11 @@ +4.37.4 (released 2022-09-30) +---------------------------- + +- [subset] Keep nameIDs used by CPAL palette entry labels (#2837). +- [varLib] Avoid negative hmtx values when creating font from variable CFF2 font (#2827). +- [instancer] Don't prune stat.ElidedFallbackNameID (#2828). +- [unicodedata] Update Scripts/Blocks to Unicode 15.0 (#2833). + 4.37.3 (released 2022-09-20) ---------------------------- @@ -218,30 +226,30 @@ - [OS/2 / merge] Automatically recalculate ``OS/2.xAvgCharWidth`` after merging fonts with ``fontTools.merge`` (#2591, #2538). - [misc/config] Added ``fontTools.misc.configTools`` module, a generic configuration - system (#2416, #2439). + system (#2416, #2439). Added ``fontTools.config`` module, a fontTools-specific configuration - system using ``configTools`` above. + system using ``configTools`` above. Attached a ``Config`` object to ``TTFont``. - [otlLib] Replaced environment variable for GPOS compression level with an equivalent option using the new config system. -- [designspaceLib] Incremented format version to 5.0 (#2436). +- [designspaceLib] Incremented format version to 5.0 (#2436). Added discrete axes, variable fonts, STAT information, either design- or - user-space location on instances. + user-space location on instances. Added ``fontTools.designspaceLib.split`` module to split a designspace into sub-spaces that interpolate and that represent the variable fonts - listed in the document. + listed in the document. Made instance names optional and allow computing them from STAT data instead. - Added ``fontTools.designspaceLib.statNames`` module. - Allow instances to have the same location as a previously defined STAT label. - Deprecated some attributes: - ``SourceDescriptor``: ``copyLib``, ``copyInfo``, ``copyGroups``, ``copyFeatures``. + Added ``fontTools.designspaceLib.statNames`` module. + Allow instances to have the same location as a previously defined STAT label. + Deprecated some attributes: + ``SourceDescriptor``: ``copyLib``, ``copyInfo``, ``copyGroups``, ``copyFeatures``. ``InstanceDescriptor``: ``kerning``, ``info``; ``glyphs``: use rules or sparse - sources. - For both, ``location``: use the more explicit designLocation. - Note: all are soft deprecations and existing code should keep working. + sources. + For both, ``location``: use the more explicit designLocation. + Note: all are soft deprecations and existing code should keep working. Updated documentation for Python methods and the XML format. - [varLib] Added ``build_many`` to build several variable fonts from a single - designspace document (#2436). + designspace document (#2436). Added ``fontTools.varLib.stat`` module to build STAT tables from a designspace document. - [otBase] Try to use the Harfbuzz Repacker for packing GSUB/GPOS tables when @@ -432,12 +440,12 @@ 4.25.2 (released 2021-07-26) ---------------------------- -- [COLRv1] Various changes to sync with the latest CORLv1 draft spec. In particular: - define COLR.VarIndexMap, remove/inline ColorIndex struct, add VarIndexBase to ``PaintVar*`` tables (#2372); - add reduced-precicion specialized transform Paints; - define Angle as fraction of half circle encoded as F2Dot14; - use FWORD (int16) for all Paint center coordinates; - change PaintTransform to have an offset to Affine2x3; +- [COLRv1] Various changes to sync with the latest CORLv1 draft spec. In particular: + define COLR.VarIndexMap, remove/inline ColorIndex struct, add VarIndexBase to ``PaintVar*`` tables (#2372); + add reduced-precicion specialized transform Paints; + define Angle as fraction of half circle encoded as F2Dot14; + use FWORD (int16) for all Paint center coordinates; + change PaintTransform to have an offset to Affine2x3; - [ttLib] when importing XML, only set sfntVersion if the font has no reader and is empty (#2376) 4.25.1 (released 2021-07-16)