diff options
Diffstat (limited to 'Lib/fontTools/subset/cff.py')
-rw-r--r-- | Lib/fontTools/subset/cff.py | 146 |
1 files changed, 133 insertions, 13 deletions
diff --git a/Lib/fontTools/subset/cff.py b/Lib/fontTools/subset/cff.py index 0dcb7975..b59c6b96 100644 --- a/Lib/fontTools/subset/cff.py +++ b/Lib/fontTools/subset/cff.py @@ -2,10 +2,27 @@ 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.varLib.varStore import VarStoreInstancer -from fontTools.subset.util import _add_method, _uniq_sort +def _add_method(*clazzes): + """Returns a decorator function that adds a new method to one or + more classes.""" + def wrapper(method): + done = [] + for clazz in clazzes: + if clazz in done: continue # Support multiple names of a clazz + done.append(clazz) + assert clazz.__name__ != 'DefaultTable', \ + 'Oops, table class not found.' + assert not hasattr(clazz, method.__name__), \ + "Oops, class '%s' has method '%s'." % (clazz.__name__, + method.__name__) + setattr(clazz, method.__name__, method) + return None + return wrapper + +def _uniq_sort(l): + return sorted(set(l)) class _ClosureGlyphsT2Decompiler(psCharStrings.SimpleT2Decompiler): @@ -53,7 +70,8 @@ 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']) + c.decompile() + c.program = [] if isCFF2 else ['endchar'] else: if hasattr(font, 'FDArray') and font.FDArray is not None: private = font.FDArray[fdSelectIndex].Private @@ -119,12 +137,9 @@ def subset_glyphs(self, s): #sel.format = None sel.format = 3 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 + 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() @@ -348,6 +363,86 @@ class _DehintingT2Decompiler(psCharStrings.T2WidthExtractor): hints.status = max(hints.status, subr_hints.status) +class StopHintCountEvent(Exception): + pass + + + + +class _DesubroutinizingT2Decompiler(psCharStrings.SimpleT2Decompiler): + stop_hintcount_ops = ("op_hintmask", "op_cntrmask", "op_rmoveto", "op_hmoveto", + "op_vmoveto") + + def __init__(self, localSubrs, globalSubrs, private=None): + psCharStrings.SimpleT2Decompiler.__init__(self, localSubrs, globalSubrs, + private) + + def execute(self, charString): + self.need_hintcount = True # until proven otherwise + for op_name in self.stop_hintcount_ops: + setattr(self, op_name, self.stop_hint_count) + + if hasattr(charString, '_desubroutinized'): + # If a charstring has already been desubroutinized, we will still + # need to execute it if we need to count hints in order to + # compute the byte length for mask arguments, and haven't finished + # counting hints pairs. + if self.need_hintcount and self.callingStack: + try: + psCharStrings.SimpleT2Decompiler.execute(self, charString) + except StopHintCountEvent: + del self.callingStack[-1] + return + + charString._patches = [] + psCharStrings.SimpleT2Decompiler.execute(self, charString) + desubroutinized = charString.program[:] + for idx, expansion in reversed(charString._patches): + assert idx >= 2 + assert desubroutinized[idx - 1] in ['callsubr', 'callgsubr'], desubroutinized[idx - 1] + assert type(desubroutinized[idx - 2]) == int + if expansion[-1] == 'return': + expansion = expansion[:-1] + desubroutinized[idx-2:idx] = expansion + if not self.private.in_cff2: + if 'endchar' in desubroutinized: + # Cut off after first endchar + desubroutinized = desubroutinized[:desubroutinized.index('endchar') + 1] + else: + if not len(desubroutinized) or desubroutinized[-1] != 'return': + desubroutinized.append('return') + + charString._desubroutinized = desubroutinized + del charString._patches + + def op_callsubr(self, index): + subr = self.localSubrs[self.operandStack[-1]+self.localBias] + psCharStrings.SimpleT2Decompiler.op_callsubr(self, index) + self.processSubr(index, subr) + + def op_callgsubr(self, index): + subr = self.globalSubrs[self.operandStack[-1]+self.globalBias] + psCharStrings.SimpleT2Decompiler.op_callgsubr(self, index) + self.processSubr(index, subr) + + def stop_hint_count(self, *args): + self.need_hintcount = False + for op_name in self.stop_hintcount_ops: + setattr(self, op_name, None) + cs = self.callingStack[-1] + if hasattr(cs, '_desubroutinized'): + raise StopHintCountEvent() + + def op_hintmask(self, index): + psCharStrings.SimpleT2Decompiler.op_hintmask(self, index) + if self.need_hintcount: + self.stop_hint_count() + + def processSubr(self, index, subr): + cs = self.callingStack[-1] + if not hasattr(cs, '_desubroutinized'): + cs._patches.append((index, subr._desubroutinized)) + @_add_method(ttLib.getTableClass('CFF ')) def prune_post_subset(self, ttfFont, options): @@ -367,7 +462,7 @@ def prune_post_subset(self, ttfFont, options): # Desubroutinize if asked for if options.desubroutinize: - cff.desubroutinize() + self.desubroutinize() # Drop hints if not needed if not options.hinting: @@ -383,11 +478,36 @@ def _delete_empty_subrs(private_dict): del private_dict.rawDict['Subrs'] del private_dict.Subrs - -@deprecateFunction("use 'CFFFontSet.desubroutinize()' instead", category=DeprecationWarning) @_add_method(ttLib.getTableClass('CFF ')) def desubroutinize(self): - self.cff.desubroutinize() + cff = self.cff + for fontname in cff.keys(): + font = cff[fontname] + cs = font.CharStrings + for g in font.charset: + c, _ = cs.getItemAndSelector(g) + c.decompile() + subrs = getattr(c.private, "Subrs", []) + decompiler = _DesubroutinizingT2Decompiler(subrs, c.globalSubrs, c.private) + decompiler.execute(c) + c.program = c._desubroutinized + del c._desubroutinized + # Delete all the local subrs + if hasattr(font, 'FDArray'): + for fd in font.FDArray: + pd = fd.Private + if hasattr(pd, 'Subrs'): + del pd.Subrs + if 'Subrs' in pd.rawDict: + del pd.rawDict['Subrs'] + else: + pd = font.Private + if hasattr(pd, 'Subrs'): + del pd.Subrs + if 'Subrs' in pd.rawDict: + del pd.rawDict['Subrs'] + # as well as the global subrs + cff.GlobalSubrs.clear() @_add_method(ttLib.getTableClass('CFF ')) |