1*e1fe3e4aSElliott Hughesfrom fontTools.misc import psCharStrings 2*e1fe3e4aSElliott Hughesfrom fontTools import ttLib 3*e1fe3e4aSElliott Hughesfrom fontTools.pens.basePen import NullPen 4*e1fe3e4aSElliott Hughesfrom fontTools.misc.roundTools import otRound 5*e1fe3e4aSElliott Hughesfrom fontTools.misc.loggingTools import deprecateFunction 6*e1fe3e4aSElliott Hughesfrom fontTools.subset.util import _add_method, _uniq_sort 7*e1fe3e4aSElliott Hughes 8*e1fe3e4aSElliott Hughes 9*e1fe3e4aSElliott Hughesclass _ClosureGlyphsT2Decompiler(psCharStrings.SimpleT2Decompiler): 10*e1fe3e4aSElliott Hughes def __init__(self, components, localSubrs, globalSubrs): 11*e1fe3e4aSElliott Hughes psCharStrings.SimpleT2Decompiler.__init__(self, localSubrs, globalSubrs) 12*e1fe3e4aSElliott Hughes self.components = components 13*e1fe3e4aSElliott Hughes 14*e1fe3e4aSElliott Hughes def op_endchar(self, index): 15*e1fe3e4aSElliott Hughes args = self.popall() 16*e1fe3e4aSElliott Hughes if len(args) >= 4: 17*e1fe3e4aSElliott Hughes from fontTools.encodings.StandardEncoding import StandardEncoding 18*e1fe3e4aSElliott Hughes 19*e1fe3e4aSElliott Hughes # endchar can do seac accent bulding; The T2 spec says it's deprecated, 20*e1fe3e4aSElliott Hughes # but recent software that shall remain nameless does output it. 21*e1fe3e4aSElliott Hughes adx, ady, bchar, achar = args[-4:] 22*e1fe3e4aSElliott Hughes baseGlyph = StandardEncoding[bchar] 23*e1fe3e4aSElliott Hughes accentGlyph = StandardEncoding[achar] 24*e1fe3e4aSElliott Hughes self.components.add(baseGlyph) 25*e1fe3e4aSElliott Hughes self.components.add(accentGlyph) 26*e1fe3e4aSElliott Hughes 27*e1fe3e4aSElliott Hughes 28*e1fe3e4aSElliott Hughes@_add_method(ttLib.getTableClass("CFF ")) 29*e1fe3e4aSElliott Hughesdef closure_glyphs(self, s): 30*e1fe3e4aSElliott Hughes cff = self.cff 31*e1fe3e4aSElliott Hughes assert len(cff) == 1 32*e1fe3e4aSElliott Hughes font = cff[cff.keys()[0]] 33*e1fe3e4aSElliott Hughes glyphSet = font.CharStrings 34*e1fe3e4aSElliott Hughes 35*e1fe3e4aSElliott Hughes decompose = s.glyphs 36*e1fe3e4aSElliott Hughes while decompose: 37*e1fe3e4aSElliott Hughes components = set() 38*e1fe3e4aSElliott Hughes for g in decompose: 39*e1fe3e4aSElliott Hughes if g not in glyphSet: 40*e1fe3e4aSElliott Hughes continue 41*e1fe3e4aSElliott Hughes gl = glyphSet[g] 42*e1fe3e4aSElliott Hughes 43*e1fe3e4aSElliott Hughes subrs = getattr(gl.private, "Subrs", []) 44*e1fe3e4aSElliott Hughes decompiler = _ClosureGlyphsT2Decompiler(components, subrs, gl.globalSubrs) 45*e1fe3e4aSElliott Hughes decompiler.execute(gl) 46*e1fe3e4aSElliott Hughes components -= s.glyphs 47*e1fe3e4aSElliott Hughes s.glyphs.update(components) 48*e1fe3e4aSElliott Hughes decompose = components 49*e1fe3e4aSElliott Hughes 50*e1fe3e4aSElliott Hughes 51*e1fe3e4aSElliott Hughesdef _empty_charstring(font, glyphName, isCFF2, ignoreWidth=False): 52*e1fe3e4aSElliott Hughes c, fdSelectIndex = font.CharStrings.getItemAndSelector(glyphName) 53*e1fe3e4aSElliott Hughes if isCFF2 or ignoreWidth: 54*e1fe3e4aSElliott Hughes # CFF2 charstrings have no widths nor 'endchar' operators 55*e1fe3e4aSElliott Hughes c.setProgram([] if isCFF2 else ["endchar"]) 56*e1fe3e4aSElliott Hughes else: 57*e1fe3e4aSElliott Hughes if hasattr(font, "FDArray") and font.FDArray is not None: 58*e1fe3e4aSElliott Hughes private = font.FDArray[fdSelectIndex].Private 59*e1fe3e4aSElliott Hughes else: 60*e1fe3e4aSElliott Hughes private = font.Private 61*e1fe3e4aSElliott Hughes dfltWdX = private.defaultWidthX 62*e1fe3e4aSElliott Hughes nmnlWdX = private.nominalWidthX 63*e1fe3e4aSElliott Hughes pen = NullPen() 64*e1fe3e4aSElliott Hughes c.draw(pen) # this will set the charstring's width 65*e1fe3e4aSElliott Hughes if c.width != dfltWdX: 66*e1fe3e4aSElliott Hughes c.program = [c.width - nmnlWdX, "endchar"] 67*e1fe3e4aSElliott Hughes else: 68*e1fe3e4aSElliott Hughes c.program = ["endchar"] 69*e1fe3e4aSElliott Hughes 70*e1fe3e4aSElliott Hughes 71*e1fe3e4aSElliott Hughes@_add_method(ttLib.getTableClass("CFF ")) 72*e1fe3e4aSElliott Hughesdef prune_pre_subset(self, font, options): 73*e1fe3e4aSElliott Hughes cff = self.cff 74*e1fe3e4aSElliott Hughes # CFF table must have one font only 75*e1fe3e4aSElliott Hughes cff.fontNames = cff.fontNames[:1] 76*e1fe3e4aSElliott Hughes 77*e1fe3e4aSElliott Hughes if options.notdef_glyph and not options.notdef_outline: 78*e1fe3e4aSElliott Hughes isCFF2 = cff.major > 1 79*e1fe3e4aSElliott Hughes for fontname in cff.keys(): 80*e1fe3e4aSElliott Hughes font = cff[fontname] 81*e1fe3e4aSElliott Hughes _empty_charstring(font, ".notdef", isCFF2=isCFF2) 82*e1fe3e4aSElliott Hughes 83*e1fe3e4aSElliott Hughes # Clear useless Encoding 84*e1fe3e4aSElliott Hughes for fontname in cff.keys(): 85*e1fe3e4aSElliott Hughes font = cff[fontname] 86*e1fe3e4aSElliott Hughes # https://github.com/fonttools/fonttools/issues/620 87*e1fe3e4aSElliott Hughes font.Encoding = "StandardEncoding" 88*e1fe3e4aSElliott Hughes 89*e1fe3e4aSElliott Hughes return True # bool(cff.fontNames) 90*e1fe3e4aSElliott Hughes 91*e1fe3e4aSElliott Hughes 92*e1fe3e4aSElliott Hughes@_add_method(ttLib.getTableClass("CFF ")) 93*e1fe3e4aSElliott Hughesdef subset_glyphs(self, s): 94*e1fe3e4aSElliott Hughes cff = self.cff 95*e1fe3e4aSElliott Hughes for fontname in cff.keys(): 96*e1fe3e4aSElliott Hughes font = cff[fontname] 97*e1fe3e4aSElliott Hughes cs = font.CharStrings 98*e1fe3e4aSElliott Hughes 99*e1fe3e4aSElliott Hughes glyphs = s.glyphs.union(s.glyphs_emptied) 100*e1fe3e4aSElliott Hughes 101*e1fe3e4aSElliott Hughes # Load all glyphs 102*e1fe3e4aSElliott Hughes for g in font.charset: 103*e1fe3e4aSElliott Hughes if g not in glyphs: 104*e1fe3e4aSElliott Hughes continue 105*e1fe3e4aSElliott Hughes c, _ = cs.getItemAndSelector(g) 106*e1fe3e4aSElliott Hughes 107*e1fe3e4aSElliott Hughes if cs.charStringsAreIndexed: 108*e1fe3e4aSElliott Hughes indices = [i for i, g in enumerate(font.charset) if g in glyphs] 109*e1fe3e4aSElliott Hughes csi = cs.charStringsIndex 110*e1fe3e4aSElliott Hughes csi.items = [csi.items[i] for i in indices] 111*e1fe3e4aSElliott Hughes del csi.file, csi.offsets 112*e1fe3e4aSElliott Hughes if hasattr(font, "FDSelect"): 113*e1fe3e4aSElliott Hughes sel = font.FDSelect 114*e1fe3e4aSElliott Hughes sel.format = None 115*e1fe3e4aSElliott Hughes sel.gidArray = [sel.gidArray[i] for i in indices] 116*e1fe3e4aSElliott Hughes newCharStrings = {} 117*e1fe3e4aSElliott Hughes for indicesIdx, charsetIdx in enumerate(indices): 118*e1fe3e4aSElliott Hughes g = font.charset[charsetIdx] 119*e1fe3e4aSElliott Hughes if g in cs.charStrings: 120*e1fe3e4aSElliott Hughes newCharStrings[g] = indicesIdx 121*e1fe3e4aSElliott Hughes cs.charStrings = newCharStrings 122*e1fe3e4aSElliott Hughes else: 123*e1fe3e4aSElliott Hughes cs.charStrings = {g: v for g, v in cs.charStrings.items() if g in glyphs} 124*e1fe3e4aSElliott Hughes font.charset = [g for g in font.charset if g in glyphs] 125*e1fe3e4aSElliott Hughes font.numGlyphs = len(font.charset) 126*e1fe3e4aSElliott Hughes 127*e1fe3e4aSElliott Hughes if s.options.retain_gids: 128*e1fe3e4aSElliott Hughes isCFF2 = cff.major > 1 129*e1fe3e4aSElliott Hughes for g in s.glyphs_emptied: 130*e1fe3e4aSElliott Hughes _empty_charstring(font, g, isCFF2=isCFF2, ignoreWidth=True) 131*e1fe3e4aSElliott Hughes 132*e1fe3e4aSElliott Hughes return True # any(cff[fontname].numGlyphs for fontname in cff.keys()) 133*e1fe3e4aSElliott Hughes 134*e1fe3e4aSElliott Hughes 135*e1fe3e4aSElliott Hughes@_add_method(psCharStrings.T2CharString) 136*e1fe3e4aSElliott Hughesdef subset_subroutines(self, subrs, gsubrs): 137*e1fe3e4aSElliott Hughes p = self.program 138*e1fe3e4aSElliott Hughes for i in range(1, len(p)): 139*e1fe3e4aSElliott Hughes if p[i] == "callsubr": 140*e1fe3e4aSElliott Hughes assert isinstance(p[i - 1], int) 141*e1fe3e4aSElliott Hughes p[i - 1] = subrs._used.index(p[i - 1] + subrs._old_bias) - subrs._new_bias 142*e1fe3e4aSElliott Hughes elif p[i] == "callgsubr": 143*e1fe3e4aSElliott Hughes assert isinstance(p[i - 1], int) 144*e1fe3e4aSElliott Hughes p[i - 1] = ( 145*e1fe3e4aSElliott Hughes gsubrs._used.index(p[i - 1] + gsubrs._old_bias) - gsubrs._new_bias 146*e1fe3e4aSElliott Hughes ) 147*e1fe3e4aSElliott Hughes 148*e1fe3e4aSElliott Hughes 149*e1fe3e4aSElliott Hughes@_add_method(psCharStrings.T2CharString) 150*e1fe3e4aSElliott Hughesdef drop_hints(self): 151*e1fe3e4aSElliott Hughes hints = self._hints 152*e1fe3e4aSElliott Hughes 153*e1fe3e4aSElliott Hughes if hints.deletions: 154*e1fe3e4aSElliott Hughes p = self.program 155*e1fe3e4aSElliott Hughes for idx in reversed(hints.deletions): 156*e1fe3e4aSElliott Hughes del p[idx - 2 : idx] 157*e1fe3e4aSElliott Hughes 158*e1fe3e4aSElliott Hughes if hints.has_hint: 159*e1fe3e4aSElliott Hughes assert not hints.deletions or hints.last_hint <= hints.deletions[0] 160*e1fe3e4aSElliott Hughes self.program = self.program[hints.last_hint :] 161*e1fe3e4aSElliott Hughes if not self.program: 162*e1fe3e4aSElliott Hughes # TODO CFF2 no need for endchar. 163*e1fe3e4aSElliott Hughes self.program.append("endchar") 164*e1fe3e4aSElliott Hughes if hasattr(self, "width"): 165*e1fe3e4aSElliott Hughes # Insert width back if needed 166*e1fe3e4aSElliott Hughes if self.width != self.private.defaultWidthX: 167*e1fe3e4aSElliott Hughes # For CFF2 charstrings, this should never happen 168*e1fe3e4aSElliott Hughes assert ( 169*e1fe3e4aSElliott Hughes self.private.defaultWidthX is not None 170*e1fe3e4aSElliott Hughes ), "CFF2 CharStrings must not have an initial width value" 171*e1fe3e4aSElliott Hughes self.program.insert(0, self.width - self.private.nominalWidthX) 172*e1fe3e4aSElliott Hughes 173*e1fe3e4aSElliott Hughes if hints.has_hintmask: 174*e1fe3e4aSElliott Hughes i = 0 175*e1fe3e4aSElliott Hughes p = self.program 176*e1fe3e4aSElliott Hughes while i < len(p): 177*e1fe3e4aSElliott Hughes if p[i] in ["hintmask", "cntrmask"]: 178*e1fe3e4aSElliott Hughes assert i + 1 <= len(p) 179*e1fe3e4aSElliott Hughes del p[i : i + 2] 180*e1fe3e4aSElliott Hughes continue 181*e1fe3e4aSElliott Hughes i += 1 182*e1fe3e4aSElliott Hughes 183*e1fe3e4aSElliott Hughes assert len(self.program) 184*e1fe3e4aSElliott Hughes 185*e1fe3e4aSElliott Hughes del self._hints 186*e1fe3e4aSElliott Hughes 187*e1fe3e4aSElliott Hughes 188*e1fe3e4aSElliott Hughesclass _MarkingT2Decompiler(psCharStrings.SimpleT2Decompiler): 189*e1fe3e4aSElliott Hughes def __init__(self, localSubrs, globalSubrs, private): 190*e1fe3e4aSElliott Hughes psCharStrings.SimpleT2Decompiler.__init__( 191*e1fe3e4aSElliott Hughes self, localSubrs, globalSubrs, private 192*e1fe3e4aSElliott Hughes ) 193*e1fe3e4aSElliott Hughes for subrs in [localSubrs, globalSubrs]: 194*e1fe3e4aSElliott Hughes if subrs and not hasattr(subrs, "_used"): 195*e1fe3e4aSElliott Hughes subrs._used = set() 196*e1fe3e4aSElliott Hughes 197*e1fe3e4aSElliott Hughes def op_callsubr(self, index): 198*e1fe3e4aSElliott Hughes self.localSubrs._used.add(self.operandStack[-1] + self.localBias) 199*e1fe3e4aSElliott Hughes psCharStrings.SimpleT2Decompiler.op_callsubr(self, index) 200*e1fe3e4aSElliott Hughes 201*e1fe3e4aSElliott Hughes def op_callgsubr(self, index): 202*e1fe3e4aSElliott Hughes self.globalSubrs._used.add(self.operandStack[-1] + self.globalBias) 203*e1fe3e4aSElliott Hughes psCharStrings.SimpleT2Decompiler.op_callgsubr(self, index) 204*e1fe3e4aSElliott Hughes 205*e1fe3e4aSElliott Hughes 206*e1fe3e4aSElliott Hughesclass _DehintingT2Decompiler(psCharStrings.T2WidthExtractor): 207*e1fe3e4aSElliott Hughes class Hints(object): 208*e1fe3e4aSElliott Hughes def __init__(self): 209*e1fe3e4aSElliott Hughes # Whether calling this charstring produces any hint stems 210*e1fe3e4aSElliott Hughes # Note that if a charstring starts with hintmask, it will 211*e1fe3e4aSElliott Hughes # have has_hint set to True, because it *might* produce an 212*e1fe3e4aSElliott Hughes # implicit vstem if called under certain conditions. 213*e1fe3e4aSElliott Hughes self.has_hint = False 214*e1fe3e4aSElliott Hughes # Index to start at to drop all hints 215*e1fe3e4aSElliott Hughes self.last_hint = 0 216*e1fe3e4aSElliott Hughes # Index up to which we know more hints are possible. 217*e1fe3e4aSElliott Hughes # Only relevant if status is 0 or 1. 218*e1fe3e4aSElliott Hughes self.last_checked = 0 219*e1fe3e4aSElliott Hughes # The status means: 220*e1fe3e4aSElliott Hughes # 0: after dropping hints, this charstring is empty 221*e1fe3e4aSElliott Hughes # 1: after dropping hints, there may be more hints 222*e1fe3e4aSElliott Hughes # continuing after this, or there might be 223*e1fe3e4aSElliott Hughes # other things. Not clear yet. 224*e1fe3e4aSElliott Hughes # 2: no more hints possible after this charstring 225*e1fe3e4aSElliott Hughes self.status = 0 226*e1fe3e4aSElliott Hughes # Has hintmask instructions; not recursive 227*e1fe3e4aSElliott Hughes self.has_hintmask = False 228*e1fe3e4aSElliott Hughes # List of indices of calls to empty subroutines to remove. 229*e1fe3e4aSElliott Hughes self.deletions = [] 230*e1fe3e4aSElliott Hughes 231*e1fe3e4aSElliott Hughes pass 232*e1fe3e4aSElliott Hughes 233*e1fe3e4aSElliott Hughes def __init__( 234*e1fe3e4aSElliott Hughes self, css, localSubrs, globalSubrs, nominalWidthX, defaultWidthX, private=None 235*e1fe3e4aSElliott Hughes ): 236*e1fe3e4aSElliott Hughes self._css = css 237*e1fe3e4aSElliott Hughes psCharStrings.T2WidthExtractor.__init__( 238*e1fe3e4aSElliott Hughes self, localSubrs, globalSubrs, nominalWidthX, defaultWidthX 239*e1fe3e4aSElliott Hughes ) 240*e1fe3e4aSElliott Hughes self.private = private 241*e1fe3e4aSElliott Hughes 242*e1fe3e4aSElliott Hughes def execute(self, charString): 243*e1fe3e4aSElliott Hughes old_hints = charString._hints if hasattr(charString, "_hints") else None 244*e1fe3e4aSElliott Hughes charString._hints = self.Hints() 245*e1fe3e4aSElliott Hughes 246*e1fe3e4aSElliott Hughes psCharStrings.T2WidthExtractor.execute(self, charString) 247*e1fe3e4aSElliott Hughes 248*e1fe3e4aSElliott Hughes hints = charString._hints 249*e1fe3e4aSElliott Hughes 250*e1fe3e4aSElliott Hughes if hints.has_hint or hints.has_hintmask: 251*e1fe3e4aSElliott Hughes self._css.add(charString) 252*e1fe3e4aSElliott Hughes 253*e1fe3e4aSElliott Hughes if hints.status != 2: 254*e1fe3e4aSElliott Hughes # Check from last_check, make sure we didn't have any operators. 255*e1fe3e4aSElliott Hughes for i in range(hints.last_checked, len(charString.program) - 1): 256*e1fe3e4aSElliott Hughes if isinstance(charString.program[i], str): 257*e1fe3e4aSElliott Hughes hints.status = 2 258*e1fe3e4aSElliott Hughes break 259*e1fe3e4aSElliott Hughes else: 260*e1fe3e4aSElliott Hughes hints.status = 1 # There's *something* here 261*e1fe3e4aSElliott Hughes hints.last_checked = len(charString.program) 262*e1fe3e4aSElliott Hughes 263*e1fe3e4aSElliott Hughes if old_hints: 264*e1fe3e4aSElliott Hughes assert hints.__dict__ == old_hints.__dict__ 265*e1fe3e4aSElliott Hughes 266*e1fe3e4aSElliott Hughes def op_callsubr(self, index): 267*e1fe3e4aSElliott Hughes subr = self.localSubrs[self.operandStack[-1] + self.localBias] 268*e1fe3e4aSElliott Hughes psCharStrings.T2WidthExtractor.op_callsubr(self, index) 269*e1fe3e4aSElliott Hughes self.processSubr(index, subr) 270*e1fe3e4aSElliott Hughes 271*e1fe3e4aSElliott Hughes def op_callgsubr(self, index): 272*e1fe3e4aSElliott Hughes subr = self.globalSubrs[self.operandStack[-1] + self.globalBias] 273*e1fe3e4aSElliott Hughes psCharStrings.T2WidthExtractor.op_callgsubr(self, index) 274*e1fe3e4aSElliott Hughes self.processSubr(index, subr) 275*e1fe3e4aSElliott Hughes 276*e1fe3e4aSElliott Hughes def op_hstem(self, index): 277*e1fe3e4aSElliott Hughes psCharStrings.T2WidthExtractor.op_hstem(self, index) 278*e1fe3e4aSElliott Hughes self.processHint(index) 279*e1fe3e4aSElliott Hughes 280*e1fe3e4aSElliott Hughes def op_vstem(self, index): 281*e1fe3e4aSElliott Hughes psCharStrings.T2WidthExtractor.op_vstem(self, index) 282*e1fe3e4aSElliott Hughes self.processHint(index) 283*e1fe3e4aSElliott Hughes 284*e1fe3e4aSElliott Hughes def op_hstemhm(self, index): 285*e1fe3e4aSElliott Hughes psCharStrings.T2WidthExtractor.op_hstemhm(self, index) 286*e1fe3e4aSElliott Hughes self.processHint(index) 287*e1fe3e4aSElliott Hughes 288*e1fe3e4aSElliott Hughes def op_vstemhm(self, index): 289*e1fe3e4aSElliott Hughes psCharStrings.T2WidthExtractor.op_vstemhm(self, index) 290*e1fe3e4aSElliott Hughes self.processHint(index) 291*e1fe3e4aSElliott Hughes 292*e1fe3e4aSElliott Hughes def op_hintmask(self, index): 293*e1fe3e4aSElliott Hughes rv = psCharStrings.T2WidthExtractor.op_hintmask(self, index) 294*e1fe3e4aSElliott Hughes self.processHintmask(index) 295*e1fe3e4aSElliott Hughes return rv 296*e1fe3e4aSElliott Hughes 297*e1fe3e4aSElliott Hughes def op_cntrmask(self, index): 298*e1fe3e4aSElliott Hughes rv = psCharStrings.T2WidthExtractor.op_cntrmask(self, index) 299*e1fe3e4aSElliott Hughes self.processHintmask(index) 300*e1fe3e4aSElliott Hughes return rv 301*e1fe3e4aSElliott Hughes 302*e1fe3e4aSElliott Hughes def processHintmask(self, index): 303*e1fe3e4aSElliott Hughes cs = self.callingStack[-1] 304*e1fe3e4aSElliott Hughes hints = cs._hints 305*e1fe3e4aSElliott Hughes hints.has_hintmask = True 306*e1fe3e4aSElliott Hughes if hints.status != 2: 307*e1fe3e4aSElliott Hughes # Check from last_check, see if we may be an implicit vstem 308*e1fe3e4aSElliott Hughes for i in range(hints.last_checked, index - 1): 309*e1fe3e4aSElliott Hughes if isinstance(cs.program[i], str): 310*e1fe3e4aSElliott Hughes hints.status = 2 311*e1fe3e4aSElliott Hughes break 312*e1fe3e4aSElliott Hughes else: 313*e1fe3e4aSElliott Hughes # We are an implicit vstem 314*e1fe3e4aSElliott Hughes hints.has_hint = True 315*e1fe3e4aSElliott Hughes hints.last_hint = index + 1 316*e1fe3e4aSElliott Hughes hints.status = 0 317*e1fe3e4aSElliott Hughes hints.last_checked = index + 1 318*e1fe3e4aSElliott Hughes 319*e1fe3e4aSElliott Hughes def processHint(self, index): 320*e1fe3e4aSElliott Hughes cs = self.callingStack[-1] 321*e1fe3e4aSElliott Hughes hints = cs._hints 322*e1fe3e4aSElliott Hughes hints.has_hint = True 323*e1fe3e4aSElliott Hughes hints.last_hint = index 324*e1fe3e4aSElliott Hughes hints.last_checked = index 325*e1fe3e4aSElliott Hughes 326*e1fe3e4aSElliott Hughes def processSubr(self, index, subr): 327*e1fe3e4aSElliott Hughes cs = self.callingStack[-1] 328*e1fe3e4aSElliott Hughes hints = cs._hints 329*e1fe3e4aSElliott Hughes subr_hints = subr._hints 330*e1fe3e4aSElliott Hughes 331*e1fe3e4aSElliott Hughes # Check from last_check, make sure we didn't have 332*e1fe3e4aSElliott Hughes # any operators. 333*e1fe3e4aSElliott Hughes if hints.status != 2: 334*e1fe3e4aSElliott Hughes for i in range(hints.last_checked, index - 1): 335*e1fe3e4aSElliott Hughes if isinstance(cs.program[i], str): 336*e1fe3e4aSElliott Hughes hints.status = 2 337*e1fe3e4aSElliott Hughes break 338*e1fe3e4aSElliott Hughes hints.last_checked = index 339*e1fe3e4aSElliott Hughes 340*e1fe3e4aSElliott Hughes if hints.status != 2: 341*e1fe3e4aSElliott Hughes if subr_hints.has_hint: 342*e1fe3e4aSElliott Hughes hints.has_hint = True 343*e1fe3e4aSElliott Hughes 344*e1fe3e4aSElliott Hughes # Decide where to chop off from 345*e1fe3e4aSElliott Hughes if subr_hints.status == 0: 346*e1fe3e4aSElliott Hughes hints.last_hint = index 347*e1fe3e4aSElliott Hughes else: 348*e1fe3e4aSElliott Hughes hints.last_hint = index - 2 # Leave the subr call in 349*e1fe3e4aSElliott Hughes 350*e1fe3e4aSElliott Hughes elif subr_hints.status == 0: 351*e1fe3e4aSElliott Hughes hints.deletions.append(index) 352*e1fe3e4aSElliott Hughes 353*e1fe3e4aSElliott Hughes hints.status = max(hints.status, subr_hints.status) 354*e1fe3e4aSElliott Hughes 355*e1fe3e4aSElliott Hughes 356*e1fe3e4aSElliott Hughes@_add_method(ttLib.getTableClass("CFF ")) 357*e1fe3e4aSElliott Hughesdef prune_post_subset(self, ttfFont, options): 358*e1fe3e4aSElliott Hughes cff = self.cff 359*e1fe3e4aSElliott Hughes for fontname in cff.keys(): 360*e1fe3e4aSElliott Hughes font = cff[fontname] 361*e1fe3e4aSElliott Hughes cs = font.CharStrings 362*e1fe3e4aSElliott Hughes 363*e1fe3e4aSElliott Hughes # Drop unused FontDictionaries 364*e1fe3e4aSElliott Hughes if hasattr(font, "FDSelect"): 365*e1fe3e4aSElliott Hughes sel = font.FDSelect 366*e1fe3e4aSElliott Hughes indices = _uniq_sort(sel.gidArray) 367*e1fe3e4aSElliott Hughes sel.gidArray = [indices.index(ss) for ss in sel.gidArray] 368*e1fe3e4aSElliott Hughes arr = font.FDArray 369*e1fe3e4aSElliott Hughes arr.items = [arr[i] for i in indices] 370*e1fe3e4aSElliott Hughes del arr.file, arr.offsets 371*e1fe3e4aSElliott Hughes 372*e1fe3e4aSElliott Hughes # Desubroutinize if asked for 373*e1fe3e4aSElliott Hughes if options.desubroutinize: 374*e1fe3e4aSElliott Hughes cff.desubroutinize() 375*e1fe3e4aSElliott Hughes 376*e1fe3e4aSElliott Hughes # Drop hints if not needed 377*e1fe3e4aSElliott Hughes if not options.hinting: 378*e1fe3e4aSElliott Hughes self.remove_hints() 379*e1fe3e4aSElliott Hughes elif not options.desubroutinize: 380*e1fe3e4aSElliott Hughes self.remove_unused_subroutines() 381*e1fe3e4aSElliott Hughes return True 382*e1fe3e4aSElliott Hughes 383*e1fe3e4aSElliott Hughes 384*e1fe3e4aSElliott Hughesdef _delete_empty_subrs(private_dict): 385*e1fe3e4aSElliott Hughes if hasattr(private_dict, "Subrs") and not private_dict.Subrs: 386*e1fe3e4aSElliott Hughes if "Subrs" in private_dict.rawDict: 387*e1fe3e4aSElliott Hughes del private_dict.rawDict["Subrs"] 388*e1fe3e4aSElliott Hughes del private_dict.Subrs 389*e1fe3e4aSElliott Hughes 390*e1fe3e4aSElliott Hughes 391*e1fe3e4aSElliott Hughes@deprecateFunction( 392*e1fe3e4aSElliott Hughes "use 'CFFFontSet.desubroutinize()' instead", category=DeprecationWarning 393*e1fe3e4aSElliott Hughes) 394*e1fe3e4aSElliott Hughes@_add_method(ttLib.getTableClass("CFF ")) 395*e1fe3e4aSElliott Hughesdef desubroutinize(self): 396*e1fe3e4aSElliott Hughes self.cff.desubroutinize() 397*e1fe3e4aSElliott Hughes 398*e1fe3e4aSElliott Hughes 399*e1fe3e4aSElliott Hughes@_add_method(ttLib.getTableClass("CFF ")) 400*e1fe3e4aSElliott Hughesdef remove_hints(self): 401*e1fe3e4aSElliott Hughes cff = self.cff 402*e1fe3e4aSElliott Hughes for fontname in cff.keys(): 403*e1fe3e4aSElliott Hughes font = cff[fontname] 404*e1fe3e4aSElliott Hughes cs = font.CharStrings 405*e1fe3e4aSElliott Hughes # This can be tricky, but doesn't have to. What we do is: 406*e1fe3e4aSElliott Hughes # 407*e1fe3e4aSElliott Hughes # - Run all used glyph charstrings and recurse into subroutines, 408*e1fe3e4aSElliott Hughes # - For each charstring (including subroutines), if it has any 409*e1fe3e4aSElliott Hughes # of the hint stem operators, we mark it as such. 410*e1fe3e4aSElliott Hughes # Upon returning, for each charstring we note all the 411*e1fe3e4aSElliott Hughes # subroutine calls it makes that (recursively) contain a stem, 412*e1fe3e4aSElliott Hughes # - Dropping hinting then consists of the following two ops: 413*e1fe3e4aSElliott Hughes # * Drop the piece of the program in each charstring before the 414*e1fe3e4aSElliott Hughes # last call to a stem op or a stem-calling subroutine, 415*e1fe3e4aSElliott Hughes # * Drop all hintmask operations. 416*e1fe3e4aSElliott Hughes # - It's trickier... A hintmask right after hints and a few numbers 417*e1fe3e4aSElliott Hughes # will act as an implicit vstemhm. As such, we track whether 418*e1fe3e4aSElliott Hughes # we have seen any non-hint operators so far and do the right 419*e1fe3e4aSElliott Hughes # thing, recursively... Good luck understanding that :( 420*e1fe3e4aSElliott Hughes css = set() 421*e1fe3e4aSElliott Hughes for g in font.charset: 422*e1fe3e4aSElliott Hughes c, _ = cs.getItemAndSelector(g) 423*e1fe3e4aSElliott Hughes c.decompile() 424*e1fe3e4aSElliott Hughes subrs = getattr(c.private, "Subrs", []) 425*e1fe3e4aSElliott Hughes decompiler = _DehintingT2Decompiler( 426*e1fe3e4aSElliott Hughes css, 427*e1fe3e4aSElliott Hughes subrs, 428*e1fe3e4aSElliott Hughes c.globalSubrs, 429*e1fe3e4aSElliott Hughes c.private.nominalWidthX, 430*e1fe3e4aSElliott Hughes c.private.defaultWidthX, 431*e1fe3e4aSElliott Hughes c.private, 432*e1fe3e4aSElliott Hughes ) 433*e1fe3e4aSElliott Hughes decompiler.execute(c) 434*e1fe3e4aSElliott Hughes c.width = decompiler.width 435*e1fe3e4aSElliott Hughes for charstring in css: 436*e1fe3e4aSElliott Hughes charstring.drop_hints() 437*e1fe3e4aSElliott Hughes del css 438*e1fe3e4aSElliott Hughes 439*e1fe3e4aSElliott Hughes # Drop font-wide hinting values 440*e1fe3e4aSElliott Hughes all_privs = [] 441*e1fe3e4aSElliott Hughes if hasattr(font, "FDArray"): 442*e1fe3e4aSElliott Hughes all_privs.extend(fd.Private for fd in font.FDArray) 443*e1fe3e4aSElliott Hughes else: 444*e1fe3e4aSElliott Hughes all_privs.append(font.Private) 445*e1fe3e4aSElliott Hughes for priv in all_privs: 446*e1fe3e4aSElliott Hughes for k in [ 447*e1fe3e4aSElliott Hughes "BlueValues", 448*e1fe3e4aSElliott Hughes "OtherBlues", 449*e1fe3e4aSElliott Hughes "FamilyBlues", 450*e1fe3e4aSElliott Hughes "FamilyOtherBlues", 451*e1fe3e4aSElliott Hughes "BlueScale", 452*e1fe3e4aSElliott Hughes "BlueShift", 453*e1fe3e4aSElliott Hughes "BlueFuzz", 454*e1fe3e4aSElliott Hughes "StemSnapH", 455*e1fe3e4aSElliott Hughes "StemSnapV", 456*e1fe3e4aSElliott Hughes "StdHW", 457*e1fe3e4aSElliott Hughes "StdVW", 458*e1fe3e4aSElliott Hughes "ForceBold", 459*e1fe3e4aSElliott Hughes "LanguageGroup", 460*e1fe3e4aSElliott Hughes "ExpansionFactor", 461*e1fe3e4aSElliott Hughes ]: 462*e1fe3e4aSElliott Hughes if hasattr(priv, k): 463*e1fe3e4aSElliott Hughes setattr(priv, k, None) 464*e1fe3e4aSElliott Hughes self.remove_unused_subroutines() 465*e1fe3e4aSElliott Hughes 466*e1fe3e4aSElliott Hughes 467*e1fe3e4aSElliott Hughes@_add_method(ttLib.getTableClass("CFF ")) 468*e1fe3e4aSElliott Hughesdef remove_unused_subroutines(self): 469*e1fe3e4aSElliott Hughes cff = self.cff 470*e1fe3e4aSElliott Hughes for fontname in cff.keys(): 471*e1fe3e4aSElliott Hughes font = cff[fontname] 472*e1fe3e4aSElliott Hughes cs = font.CharStrings 473*e1fe3e4aSElliott Hughes # Renumber subroutines to remove unused ones 474*e1fe3e4aSElliott Hughes 475*e1fe3e4aSElliott Hughes # Mark all used subroutines 476*e1fe3e4aSElliott Hughes for g in font.charset: 477*e1fe3e4aSElliott Hughes c, _ = cs.getItemAndSelector(g) 478*e1fe3e4aSElliott Hughes subrs = getattr(c.private, "Subrs", []) 479*e1fe3e4aSElliott Hughes decompiler = _MarkingT2Decompiler(subrs, c.globalSubrs, c.private) 480*e1fe3e4aSElliott Hughes decompiler.execute(c) 481*e1fe3e4aSElliott Hughes 482*e1fe3e4aSElliott Hughes all_subrs = [font.GlobalSubrs] 483*e1fe3e4aSElliott Hughes if hasattr(font, "FDArray"): 484*e1fe3e4aSElliott Hughes all_subrs.extend( 485*e1fe3e4aSElliott Hughes fd.Private.Subrs 486*e1fe3e4aSElliott Hughes for fd in font.FDArray 487*e1fe3e4aSElliott Hughes if hasattr(fd.Private, "Subrs") and fd.Private.Subrs 488*e1fe3e4aSElliott Hughes ) 489*e1fe3e4aSElliott Hughes elif hasattr(font.Private, "Subrs") and font.Private.Subrs: 490*e1fe3e4aSElliott Hughes all_subrs.append(font.Private.Subrs) 491*e1fe3e4aSElliott Hughes 492*e1fe3e4aSElliott Hughes subrs = set(subrs) # Remove duplicates 493*e1fe3e4aSElliott Hughes 494*e1fe3e4aSElliott Hughes # Prepare 495*e1fe3e4aSElliott Hughes for subrs in all_subrs: 496*e1fe3e4aSElliott Hughes if not hasattr(subrs, "_used"): 497*e1fe3e4aSElliott Hughes subrs._used = set() 498*e1fe3e4aSElliott Hughes subrs._used = _uniq_sort(subrs._used) 499*e1fe3e4aSElliott Hughes subrs._old_bias = psCharStrings.calcSubrBias(subrs) 500*e1fe3e4aSElliott Hughes subrs._new_bias = psCharStrings.calcSubrBias(subrs._used) 501*e1fe3e4aSElliott Hughes 502*e1fe3e4aSElliott Hughes # Renumber glyph charstrings 503*e1fe3e4aSElliott Hughes for g in font.charset: 504*e1fe3e4aSElliott Hughes c, _ = cs.getItemAndSelector(g) 505*e1fe3e4aSElliott Hughes subrs = getattr(c.private, "Subrs", None) 506*e1fe3e4aSElliott Hughes c.subset_subroutines(subrs, font.GlobalSubrs) 507*e1fe3e4aSElliott Hughes 508*e1fe3e4aSElliott Hughes # Renumber subroutines themselves 509*e1fe3e4aSElliott Hughes for subrs in all_subrs: 510*e1fe3e4aSElliott Hughes if subrs == font.GlobalSubrs: 511*e1fe3e4aSElliott Hughes if not hasattr(font, "FDArray") and hasattr(font.Private, "Subrs"): 512*e1fe3e4aSElliott Hughes local_subrs = font.Private.Subrs 513*e1fe3e4aSElliott Hughes else: 514*e1fe3e4aSElliott Hughes local_subrs = None 515*e1fe3e4aSElliott Hughes else: 516*e1fe3e4aSElliott Hughes local_subrs = subrs 517*e1fe3e4aSElliott Hughes 518*e1fe3e4aSElliott Hughes subrs.items = [subrs.items[i] for i in subrs._used] 519*e1fe3e4aSElliott Hughes if hasattr(subrs, "file"): 520*e1fe3e4aSElliott Hughes del subrs.file 521*e1fe3e4aSElliott Hughes if hasattr(subrs, "offsets"): 522*e1fe3e4aSElliott Hughes del subrs.offsets 523*e1fe3e4aSElliott Hughes 524*e1fe3e4aSElliott Hughes for subr in subrs.items: 525*e1fe3e4aSElliott Hughes subr.subset_subroutines(local_subrs, font.GlobalSubrs) 526*e1fe3e4aSElliott Hughes 527*e1fe3e4aSElliott Hughes # Delete local SubrsIndex if empty 528*e1fe3e4aSElliott Hughes if hasattr(font, "FDArray"): 529*e1fe3e4aSElliott Hughes for fd in font.FDArray: 530*e1fe3e4aSElliott Hughes _delete_empty_subrs(fd.Private) 531*e1fe3e4aSElliott Hughes else: 532*e1fe3e4aSElliott Hughes _delete_empty_subrs(font.Private) 533*e1fe3e4aSElliott Hughes 534*e1fe3e4aSElliott Hughes # Cleanup 535*e1fe3e4aSElliott Hughes for subrs in all_subrs: 536*e1fe3e4aSElliott Hughes del subrs._used, subrs._old_bias, subrs._new_bias 537