Spaces:
Paused
Paused
| from fontTools.misc import psCharStrings | |
| from fontTools import ttLib | |
| from fontTools.pens.basePen import NullPen | |
| from fontTools.misc.roundTools import otRound | |
| from fontTools.misc.loggingTools import deprecateFunction | |
| from fontTools.subset.util import _add_method, _uniq_sort | |
| class _ClosureGlyphsT2Decompiler(psCharStrings.SimpleT2Decompiler): | |
| def __init__(self, components, localSubrs, globalSubrs): | |
| psCharStrings.SimpleT2Decompiler.__init__(self, localSubrs, globalSubrs) | |
| self.components = components | |
| def op_endchar(self, index): | |
| args = self.popall() | |
| if len(args) >= 4: | |
| from fontTools.encodings.StandardEncoding import StandardEncoding | |
| # endchar can do seac accent bulding; The T2 spec says it's deprecated, | |
| # but recent software that shall remain nameless does output it. | |
| adx, ady, bchar, achar = args[-4:] | |
| baseGlyph = StandardEncoding[bchar] | |
| accentGlyph = StandardEncoding[achar] | |
| self.components.add(baseGlyph) | |
| self.components.add(accentGlyph) | |
| def closure_glyphs(self, s): | |
| cff = self.cff | |
| assert len(cff) == 1 | |
| font = cff[cff.keys()[0]] | |
| glyphSet = font.CharStrings | |
| decompose = s.glyphs | |
| while decompose: | |
| components = set() | |
| for g in decompose: | |
| if g not in glyphSet: | |
| continue | |
| gl = glyphSet[g] | |
| subrs = getattr(gl.private, "Subrs", []) | |
| decompiler = _ClosureGlyphsT2Decompiler(components, subrs, gl.globalSubrs) | |
| decompiler.execute(gl) | |
| components -= s.glyphs | |
| 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.setProgram([] 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"] | |
| def prune_pre_subset(self, font, options): | |
| cff = self.cff | |
| # CFF table must have one font only | |
| 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] | |
| _empty_charstring(font, ".notdef", isCFF2=isCFF2) | |
| # Clear useless Encoding | |
| for fontname in cff.keys(): | |
| font = cff[fontname] | |
| # https://github.com/fonttools/fonttools/issues/620 | |
| font.Encoding = "StandardEncoding" | |
| return True # bool(cff.fontNames) | |
| def subset_glyphs(self, s): | |
| cff = self.cff | |
| for fontname in cff.keys(): | |
| 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 | |
| sel.format = None | |
| sel.gidArray = [sel.gidArray[i] for i in indices] | |
| newCharStrings = {} | |
| for indicesIdx, charsetIdx in enumerate(indices): | |
| g = font.charset[charsetIdx] | |
| if g in cs.charStrings: | |
| newCharStrings[g] = indicesIdx | |
| cs.charStrings = newCharStrings | |
| 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) | |
| return True # any(cff[fontname].numGlyphs for fontname in cff.keys()) | |
| def prune_post_subset(self, ttfFont, options): | |
| cff = self.cff | |
| for fontname in cff.keys(): | |
| font = cff[fontname] | |
| cs = font.CharStrings | |
| # Drop unused FontDictionaries | |
| if hasattr(font, "FDSelect"): | |
| sel = font.FDSelect | |
| indices = _uniq_sort(sel.gidArray) | |
| sel.gidArray = [indices.index(ss) for ss in sel.gidArray] | |
| arr = font.FDArray | |
| arr.items = [arr[i] for i in indices] | |
| del arr.file, arr.offsets | |
| # Desubroutinize if asked for | |
| if options.desubroutinize: | |
| cff.desubroutinize() | |
| # Drop hints if not needed | |
| if not options.hinting: | |
| self.remove_hints() | |
| elif not options.desubroutinize: | |
| self.remove_unused_subroutines() | |
| return True | |
| def desubroutinize(self): | |
| self.cff.desubroutinize() | |
| def remove_hints(self): | |
| self.cff.remove_hints() | |
| def remove_unused_subroutines(self): | |
| self.cff.remove_unused_subroutines() | |