diff --git a/Lib/fontTools/subset/cff.py b/Lib/fontTools/subset/cff.py index 9a2b77e4d..96dc3210f 100644 --- a/Lib/fontTools/subset/cff.py +++ b/Lib/fontTools/subset/cff.py @@ -66,6 +66,26 @@ def closure_glyphs(self, s): s.glyphs.update(components) decompose = components +def _empty_charstring(font, glyphName, isCFF2, ignoreWidth=False): + c, fdSelectIndex = font.CharStrings.getItemAndSelector(glyphName) + if isCFF2 or ignoreWidth: + # CFF2 charstrings have no widths nor 'endchar' operators + c.decompile() + c.program = [] if isCFF2 else ['endchar'] + else: + if hasattr(font, 'FDArray') and font.FDArray is not None: + private = font.FDArray[fdSelectIndex].Private + else: + private = font.Private + dfltWdX = private.defaultWidthX + nmnlWdX = private.nominalWidthX + pen = NullPen() + c.draw(pen) # this will set the charstring's width + if c.width != dfltWdX: + c.program = [c.width - nmnlWdX, 'endchar'] + else: + c.program = ['endchar'] + @_add_method(ttLib.getTableClass('CFF ')) def prune_pre_subset(self, font, options): cff = self.cff @@ -73,21 +93,10 @@ def prune_pre_subset(self, font, options): cff.fontNames = cff.fontNames[:1] if options.notdef_glyph and not options.notdef_outline: + isCFF2 = cff.major > 1 for fontname in cff.keys(): font = cff[fontname] - c, fdSelectIndex = font.CharStrings.getItemAndSelector('.notdef') - if hasattr(font, 'FDArray') and font.FDArray is not None: - private = font.FDArray[fdSelectIndex].Private - else: - private = font.Private - dfltWdX = private.defaultWidthX - nmnlWdX = private.nominalWidthX - pen = NullPen() - c.draw(pen) # this will set the charstring's width - if c.width != dfltWdX: - c.program = [c.width - nmnlWdX, 'endchar'] - else: - c.program = ['endchar'] + _empty_charstring(font, ".notdef", isCFF2=isCFF2) # Clear useless Encoding for fontname in cff.keys(): @@ -104,37 +113,42 @@ def subset_glyphs(self, s): font = cff[fontname] cs = font.CharStrings - # 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} + if s.options.retain_gids: + isCFF2 = cff.major > 1 + for g in s.glyphs_emptied: + _empty_charstring(font, g, isCFF2=isCFF2, ignoreWidth=True) 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) + # 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/subset_test.py b/Tests/subset/subset_test.py index 030caa136..72fee41b4 100644 --- a/Tests/subset/subset_test.py +++ b/Tests/subset/subset_test.py @@ -475,7 +475,7 @@ class SubsetTest(unittest.TestCase): subset.main([fontpath, "--recalc-timestamp", "--output-file=%s" % subsetpath, "*"]) self.assertLess(modified, TTFont(subsetpath)['head'].modified) - def test_retain_gids(self): + def test_retain_gids_ttf(self): _, fontpath = self.compile_font(self.getpath("TestTTF-Regular.ttx"), ".ttf") font = TTFont(fontpath) @@ -507,6 +507,75 @@ class SubsetTest(unittest.TestCase): self.assertGreater(glyf["A"].numberOfContours, 0) self.assertEqual(glyf["B"].numberOfContours, 0) + def test_retain_gids_cff(self): + _, fontpath = self.compile_font(self.getpath("TestOTF-Regular.ttx"), ".otf") + font = TTFont(fontpath) + + self.assertEqual(font["hmtx"]["A"], (500, 132)) + self.assertEqual(font["hmtx"]["B"], (400, 132)) + + 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) + + subsetpath = self.temp_path(".otf") + subset.main( + [ + fontpath, + "--retain-gids", + "--output-file=%s" % subsetpath, + "--glyph-names", + "A", + ] + ) + subsetfont = TTFont(subsetpath) + + self.assertEqual(subsetfont.getGlyphOrder(), font.getGlyphOrder()) + + hmtx = subsetfont["hmtx"] + self.assertEqual(hmtx["A"], (500, 132)) + self.assertEqual(hmtx["B"], (0, 0)) + + subsetfont["CFF "].cff[0].decompileAllCharStrings() + cs = subsetfont["CFF "].cff[0].CharStrings + self.assertGreater(len(cs["A"].program), 0) + self.assertEqual(cs["B"].program, ["endchar"]) + + def test_retain_gids_cff2(self): + fontpath = self.getpath("../../varLib/data/TestCFF2VF.otf") + font = TTFont(fontpath) + + self.assertEqual(font["hmtx"]["A"], (600, 31)) + self.assertEqual(font["hmtx"]["T"], (600, 41)) + + font["CFF2"].cff[0].decompileAllCharStrings() + cs = font["CFF2"].cff[0].CharStrings + self.assertGreater(len(cs["A"].program), 0) + self.assertGreater(len(cs["T"].program), 0) + + subsetpath = self.temp_path(".otf") + subset.main( + [ + fontpath, + "--retain-gids", + "--output-file=%s" % subsetpath, + "A", + ] + ) + subsetfont = TTFont(subsetpath) + + self.assertEqual(len(subsetfont.getGlyphOrder()), len(font.getGlyphOrder())) + + hmtx = subsetfont["hmtx"] + self.assertEqual(hmtx["A"], (600, 31)) + self.assertEqual(hmtx["glyph00002"], (0, 0)) + + subsetfont["CFF2"].cff[0].decompileAllCharStrings() + cs = subsetfont["CFF2"].cff[0].CharStrings + self.assertGreater(len(cs["A"].program), 0) + self.assertEqual(cs["glyph00002"].program, []) + if __name__ == "__main__": sys.exit(unittest.main())