diff --git a/Lib/fontTools/subset/__init__.py b/Lib/fontTools/subset/__init__.py index da2b8416f..53b440dae 100644 --- a/Lib/fontTools/subset/__init__.py +++ b/Lib/fontTools/subset/__init__.py @@ -2139,6 +2139,20 @@ def subset_glyphs(self, s): from fontTools.colorLib.unbuilder import unbuildColrV1 from fontTools.colorLib.builder import buildColrV1, populateCOLRv0 + # only include glyphs after COLR closure, which in turn comes after cmap and GSUB + # closure, but importantly before glyf/CFF closures. COLR layers can refer to + # composite glyphs, and that's ok, since glyf/CFF closures happen after COLR closure + # and take care of those. If we also included glyphs resulting from glyf/CFF closures + # when deciding which COLR base glyphs to retain, then we may end up with a situation + # whereby a COLR base glyph is kept, not because directly requested (cmap) + # or substituted (GSUB) or referenced by another COLRv1 PaintColrGlyph, but because + # it corresponds to (has same GID as) a non-COLR glyph that happens to be used as a + # component in glyf or CFF table. Best case scenario we retain more glyphs than + # required; worst case we retain incomplete COLR records that try to reference + # glyphs that are no longer in the final subset font. + # https://github.com/fonttools/fonttools/issues/2461 + s.glyphs = s.glyphs_colred + self.ColorLayers = {g: self.ColorLayers[g] for g in s.glyphs if g in self.ColorLayers} if self.version == 0: return bool(self.ColorLayers) @@ -2851,6 +2865,7 @@ class Subsetter(object): log.info("Closed glyph list over '%s': %d glyphs after", table, len(self.glyphs)) log.glyphs(self.glyphs, font=font) + setattr(self, f"glyphs_{table.lower()}ed", frozenset(self.glyphs)) if 'glyf' in font: with timer("close glyph list over 'glyf'"): diff --git a/Tests/subset/data/BungeeColor-Regular.ttx b/Tests/subset/data/BungeeColor-Regular.ttx new file mode 100644 index 000000000..d14c89224 --- /dev/null +++ b/Tests/subset/data/BungeeColor-Regular.ttx @@ -0,0 +1,438 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Copyright 2008 The Bungee Project Authors (david@djr.com) + + + Bungee Color Regular + + + Regular + + + 1.000;djr ;BungeeColor-Regular + + + Bungee Color Regular Regular + + + Version 1.000;PS 1.0;hotconv 1.0.72;makeotf.lib2.5.5900 + + + BungeeColor-Regular + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/subset/subset_test.py b/Tests/subset/subset_test.py index d8f4dac0c..d195af0fe 100644 --- a/Tests/subset/subset_test.py +++ b/Tests/subset/subset_test.py @@ -1352,5 +1352,54 @@ def test_subset_svg_missing_lxml(ttf_path): subset.main([str(ttf_path), "--gids=0,1"]) +def test_subset_COLR_glyph_closure(tmp_path): + # https://github.com/fonttools/fonttools/issues/2461 + font = TTFont() + ttx = pathlib.Path(__file__).parent / "data" / "BungeeColor-Regular.ttx" + font.importXML(ttx) + + color_layers = font["COLR"].ColorLayers + assert ".notdef" in color_layers + assert "Agrave" in color_layers + assert "grave" in color_layers + + font_path = tmp_path / "BungeeColor-Regular.ttf" + subset_path = font_path.with_suffix(".subset.ttf)") + font.save(font_path) + + subset.main( + [ + str(font_path), + "--glyph-names", + f"--output-file={subset_path}", + "--glyphs=Agrave", + ] + ) + subset_font = TTFont(subset_path) + + glyph_order = subset_font.getGlyphOrder() + + assert glyph_order == [ + ".notdef", # '.notdef' is always included automatically + "A", + "grave", + "Agrave", + ".notdef.alt001", + ".notdef.alt002", + "A.alt002", + "Agrave.alt001", + "Agrave.alt002", + "grave.alt002", + ] + + color_layers = subset_font["COLR"].ColorLayers + assert ".notdef" in color_layers + assert "Agrave" in color_layers + # Agrave 'glyf' uses grave. It should be retained in 'glyf' but NOT in + # COLR when we subset down to Agrave. + assert "grave" not in color_layers + + + if __name__ == "__main__": sys.exit(unittest.main())