From dce15980fd0a614843b4c6fbb1260b3c8bb5aa9c Mon Sep 17 00:00:00 2001 From: Garret Rieger Date: Wed, 15 May 2019 13:33:10 -0700 Subject: [PATCH] Make --retain-gids truncate empty glyphs after the last non-empty glyph. --- Lib/fontTools/subset/__init__.py | 28 ++++---- Lib/fontTools/subset/cff.py | 66 ++++++++++--------- .../subset/data/expect_HVVAR_retain_gids.ttx | 5 -- Tests/subset/subset_test.py | 39 ++++++----- 4 files changed, 73 insertions(+), 65 deletions(-) diff --git a/Lib/fontTools/subset/__init__.py b/Lib/fontTools/subset/__init__.py index efa06c4c0..569137d24 100644 --- a/Lib/fontTools/subset/__init__.py +++ b/Lib/fontTools/subset/__init__.py @@ -2572,17 +2572,19 @@ class Subsetter(object): self.glyphs_retained = frozenset(self.glyphs) - self.glyphs_emptied = frozenset() - if self.options.retain_gids: - self.glyphs_emptied = realGlyphs - self.glyphs_retained - # TODO Drop empty glyphs at the end of GlyphOrder vector. - order = font.getReverseGlyphMap() self.reverseOrigGlyphMap = {g:order[g] for g in self.glyphs_retained} - self.reverseEmptiedGlyphMap = {g:order[g] for g in self.glyphs_emptied} + self.last_retained_order = max(self.reverseOrigGlyphMap.values()) self.last_retained_glyph = font.getGlyphOrder()[self.last_retained_order] + self.glyphs_emptied = frozenset() + if self.options.retain_gids: + self.glyphs_emptied = {g for g in realGlyphs - self.glyphs_retained if order[g] <= self.last_retained_order} + + self.reverseEmptiedGlyphMap = {g:order[g] for g in self.glyphs_emptied} + + log.info("Retaining %d glyphs", len(self.glyphs_retained)) del self.glyphs @@ -2610,12 +2612,16 @@ class Subsetter(object): log.warning("%s NOT subset; don't know how to subset; dropped", tag) del font[tag] - if not self.options.retain_gids: - with timer("subset GlyphOrder"): - glyphOrder = font.getGlyphOrder() + with timer("subset GlyphOrder"): + glyphOrder = font.getGlyphOrder() + if not self.options.retain_gids: glyphOrder = [g for g in glyphOrder if g in self.glyphs_retained] - font.setGlyphOrder(glyphOrder) - font._buildReverseGlyphOrderDict() + else: + glyphOrder = [g for g in glyphOrder if font.getGlyphID(g) <= self.last_retained_order] + + font.setGlyphOrder(glyphOrder) + font._buildReverseGlyphOrderDict() + def _prune_post_subset(self, font): for tag in font.keys(): diff --git a/Lib/fontTools/subset/cff.py b/Lib/fontTools/subset/cff.py index fc29f182a..7db6d880a 100644 --- a/Lib/fontTools/subset/cff.py +++ b/Lib/fontTools/subset/cff.py @@ -113,42 +113,46 @@ def subset_glyphs(self, s): font = cff[fontname] cs = font.CharStrings + glyphs = s.glyphs.union(s.glyphs_emptied) + + # Load all glyphs + for g in font.charset: + if g not in glyphs: continue + c, _ = cs.getItemAndSelector(g) + + if cs.charStringsAreIndexed: + indices = [i for i,g in enumerate(font.charset) if g in glyphs] + csi = cs.charStringsIndex + csi.items = [csi.items[i] for i in indices] + del csi.file, csi.offsets + if hasattr(font, "FDSelect"): + sel = font.FDSelect + # XXX We want to set sel.format to None, such that the + # most compact format is selected. However, OTS was + # broken and couldn't parse a FDSelect format 0 that + # happened before CharStrings. As such, always force + # format 3 until we fix cffLib to always generate + # FDSelect after CharStrings. + # https://github.com/khaledhosny/ots/pull/31 + #sel.format = None + sel.format = 3 + sel.gidArray = [sel.gidArray[i] for i in indices] + cs.charStrings = {g:indices.index(v) + for g,v in cs.charStrings.items() + if g in glyphs} + else: + cs.charStrings = {g:v + for g,v in cs.charStrings.items() + if g in glyphs} + font.charset = [g for g in font.charset if g in glyphs] + font.numGlyphs = len(font.charset) + + if s.options.retain_gids: isCFF2 = cff.major > 1 for g in s.glyphs_emptied: _empty_charstring(font, g, isCFF2=isCFF2, ignoreWidth=True) - else: - # Load all glyphs - for g in font.charset: - if g not in s.glyphs: continue - c, _ = cs.getItemAndSelector(g) - if cs.charStringsAreIndexed: - indices = [i for i,g in enumerate(font.charset) if g in s.glyphs] - csi = cs.charStringsIndex - csi.items = [csi.items[i] for i in indices] - del csi.file, csi.offsets - if hasattr(font, "FDSelect"): - sel = font.FDSelect - # XXX We want to set sel.format to None, such that the - # most compact format is selected. However, OTS was - # broken and couldn't parse a FDSelect format 0 that - # happened before CharStrings. As such, always force - # format 3 until we fix cffLib to always generate - # FDSelect after CharStrings. - # https://github.com/khaledhosny/ots/pull/31 - #sel.format = None - sel.format = 3 - sel.gidArray = [sel.gidArray[i] for i in indices] - cs.charStrings = {g:indices.index(v) - for g,v in cs.charStrings.items() - if g in s.glyphs} - else: - cs.charStrings = {g:v - for g,v in cs.charStrings.items() - if g in s.glyphs} - font.charset = [g for g in font.charset if g in s.glyphs] - font.numGlyphs = len(font.charset) return True # any(cff[fontname].numGlyphs for fontname in cff.keys()) diff --git a/Tests/subset/data/expect_HVVAR_retain_gids.ttx b/Tests/subset/data/expect_HVVAR_retain_gids.ttx index da2eaf4f3..8e51ca7f0 100644 --- a/Tests/subset/data/expect_HVVAR_retain_gids.ttx +++ b/Tests/subset/data/expect_HVVAR_retain_gids.ttx @@ -8,7 +8,6 @@ - @@ -63,7 +62,6 @@ - @@ -71,7 +69,6 @@ - @@ -132,7 +129,6 @@ - @@ -140,7 +136,6 @@ - diff --git a/Tests/subset/subset_test.py b/Tests/subset/subset_test.py index eb632dc09..d2623cf22 100644 --- a/Tests/subset/subset_test.py +++ b/Tests/subset/subset_test.py @@ -530,20 +530,20 @@ class SubsetTest(unittest.TestCase): "--retain-gids", "--output-file=%s" % subsetpath, "--glyph-names", - "A", + "B", ] ) subsetfont = TTFont(subsetpath) - self.assertEqual(subsetfont.getGlyphOrder(), font.getGlyphOrder()) + self.assertEqual(subsetfont.getGlyphOrder(), font.getGlyphOrder()[0:3]) hmtx = subsetfont["hmtx"] - self.assertEqual(hmtx["A"], (500, 132)) - self.assertEqual(hmtx["B"], (0, 0)) + self.assertEqual(hmtx["A"], ( 0, 0)) + self.assertEqual(hmtx["B"], (400, 132)) glyf = subsetfont["glyf"] - self.assertGreater(glyf["A"].numberOfContours, 0) - self.assertEqual(glyf["B"].numberOfContours, 0) + self.assertEqual(glyf["A"].numberOfContours, 0) + self.assertGreater(glyf["B"].numberOfContours, 0) def test_retain_gids_cff(self): _, fontpath = self.compile_font(self.getpath("TestOTF-Regular.ttx"), ".otf") @@ -551,11 +551,13 @@ class SubsetTest(unittest.TestCase): self.assertEqual(font["hmtx"]["A"], (500, 132)) self.assertEqual(font["hmtx"]["B"], (400, 132)) + self.assertEqual(font["hmtx"]["C"], (500, 0)) font["CFF "].cff[0].decompileAllCharStrings() cs = font["CFF "].cff[0].CharStrings self.assertGreater(len(cs["A"].program), 0) self.assertGreater(len(cs["B"].program), 0) + self.assertGreater(len(cs["C"].program), 0) subsetpath = self.temp_path(".otf") subset.main( @@ -564,21 +566,22 @@ class SubsetTest(unittest.TestCase): "--retain-gids", "--output-file=%s" % subsetpath, "--glyph-names", - "A", + "B", ] ) subsetfont = TTFont(subsetpath) - self.assertEqual(subsetfont.getGlyphOrder(), font.getGlyphOrder()) + self.assertEqual(subsetfont.getGlyphOrder(), font.getGlyphOrder()[0:3]) hmtx = subsetfont["hmtx"] - self.assertEqual(hmtx["A"], (500, 132)) - self.assertEqual(hmtx["B"], (0, 0)) + self.assertEqual(hmtx["A"], (0, 0)) + self.assertEqual(hmtx["B"], (400, 132)) subsetfont["CFF "].cff[0].decompileAllCharStrings() cs = subsetfont["CFF "].cff[0].CharStrings - self.assertGreater(len(cs["A"].program), 0) - self.assertEqual(cs["B"].program, ["endchar"]) + + self.assertEqual(cs["A"].program, ["endchar"]) + self.assertGreater(len(cs["B"].program), 0) def test_retain_gids_cff2(self): ttx_path = self.getpath("../../varLib/data/master_ttx_varfont_otf/TestCFF2VF.ttx") @@ -598,21 +601,21 @@ class SubsetTest(unittest.TestCase): fontpath, "--retain-gids", "--output-file=%s" % subsetpath, - "A", + "T", ] ) subsetfont = TTFont(subsetpath) - self.assertEqual(len(subsetfont.getGlyphOrder()), len(font.getGlyphOrder())) + self.assertEqual(len(subsetfont.getGlyphOrder()), len(font.getGlyphOrder()[0:3])) hmtx = subsetfont["hmtx"] - self.assertEqual(hmtx["A"], (600, 31)) - self.assertEqual(hmtx["glyph00002"], (0, 0)) + self.assertEqual(hmtx["glyph00001"], ( 0, 0)) + self.assertEqual(hmtx["T"], (600, 41)) subsetfont["CFF2"].cff[0].decompileAllCharStrings() cs = subsetfont["CFF2"].cff[0].CharStrings - self.assertGreater(len(cs["A"].program), 0) - self.assertEqual(cs["glyph00002"].program, []) + self.assertEqual(cs["glyph00001"].program, []) + self.assertGreater(len(cs["T"].program), 0) def test_HVAR_VVAR(self): _, fontpath = self.compile_font(self.getpath("TestHVVAR.ttx"), ".ttf")