1*e1fe3e4aSElliott Hughes# -*- coding: utf-8 -*- 2*e1fe3e4aSElliott Hughes 3*e1fe3e4aSElliott Hughes"""T2CharString operator specializer and generalizer. 4*e1fe3e4aSElliott Hughes 5*e1fe3e4aSElliott HughesPostScript glyph drawing operations can be expressed in multiple different 6*e1fe3e4aSElliott Hughesways. For example, as well as the ``lineto`` operator, there is also a 7*e1fe3e4aSElliott Hughes``hlineto`` operator which draws a horizontal line, removing the need to 8*e1fe3e4aSElliott Hughesspecify a ``dx`` coordinate, and a ``vlineto`` operator which draws a 9*e1fe3e4aSElliott Hughesvertical line, removing the need to specify a ``dy`` coordinate. As well 10*e1fe3e4aSElliott Hughesas decompiling :class:`fontTools.misc.psCharStrings.T2CharString` objects 11*e1fe3e4aSElliott Hughesinto lists of operations, this module allows for conversion between general 12*e1fe3e4aSElliott Hughesand specific forms of the operation. 13*e1fe3e4aSElliott Hughes 14*e1fe3e4aSElliott Hughes""" 15*e1fe3e4aSElliott Hughes 16*e1fe3e4aSElliott Hughesfrom fontTools.cffLib import maxStackLimit 17*e1fe3e4aSElliott Hughes 18*e1fe3e4aSElliott Hughes 19*e1fe3e4aSElliott Hughesdef stringToProgram(string): 20*e1fe3e4aSElliott Hughes if isinstance(string, str): 21*e1fe3e4aSElliott Hughes string = string.split() 22*e1fe3e4aSElliott Hughes program = [] 23*e1fe3e4aSElliott Hughes for token in string: 24*e1fe3e4aSElliott Hughes try: 25*e1fe3e4aSElliott Hughes token = int(token) 26*e1fe3e4aSElliott Hughes except ValueError: 27*e1fe3e4aSElliott Hughes try: 28*e1fe3e4aSElliott Hughes token = float(token) 29*e1fe3e4aSElliott Hughes except ValueError: 30*e1fe3e4aSElliott Hughes pass 31*e1fe3e4aSElliott Hughes program.append(token) 32*e1fe3e4aSElliott Hughes return program 33*e1fe3e4aSElliott Hughes 34*e1fe3e4aSElliott Hughes 35*e1fe3e4aSElliott Hughesdef programToString(program): 36*e1fe3e4aSElliott Hughes return " ".join(str(x) for x in program) 37*e1fe3e4aSElliott Hughes 38*e1fe3e4aSElliott Hughes 39*e1fe3e4aSElliott Hughesdef programToCommands(program, getNumRegions=None): 40*e1fe3e4aSElliott Hughes """Takes a T2CharString program list and returns list of commands. 41*e1fe3e4aSElliott Hughes Each command is a two-tuple of commandname,arg-list. The commandname might 42*e1fe3e4aSElliott Hughes be empty string if no commandname shall be emitted (used for glyph width, 43*e1fe3e4aSElliott Hughes hintmask/cntrmask argument, as well as stray arguments at the end of the 44*e1fe3e4aSElliott Hughes program (). 45*e1fe3e4aSElliott Hughes 'getNumRegions' may be None, or a callable object. It must return the 46*e1fe3e4aSElliott Hughes number of regions. 'getNumRegions' takes a single argument, vsindex. If 47*e1fe3e4aSElliott Hughes the vsindex argument is None, getNumRegions returns the default number 48*e1fe3e4aSElliott Hughes of regions for the charstring, else it returns the numRegions for 49*e1fe3e4aSElliott Hughes the vsindex. 50*e1fe3e4aSElliott Hughes The Charstring may or may not start with a width value. If the first 51*e1fe3e4aSElliott Hughes non-blend operator has an odd number of arguments, then the first argument is 52*e1fe3e4aSElliott Hughes a width, and is popped off. This is complicated with blend operators, as 53*e1fe3e4aSElliott Hughes there may be more than one before the first hint or moveto operator, and each 54*e1fe3e4aSElliott Hughes one reduces several arguments to just one list argument. We have to sum the 55*e1fe3e4aSElliott Hughes number of arguments that are not part of the blend arguments, and all the 56*e1fe3e4aSElliott Hughes 'numBlends' values. We could instead have said that by definition, if there 57*e1fe3e4aSElliott Hughes is a blend operator, there is no width value, since CFF2 Charstrings don't 58*e1fe3e4aSElliott Hughes have width values. I discussed this with Behdad, and we are allowing for an 59*e1fe3e4aSElliott Hughes initial width value in this case because developers may assemble a CFF2 60*e1fe3e4aSElliott Hughes charstring from CFF Charstrings, which could have width values. 61*e1fe3e4aSElliott Hughes """ 62*e1fe3e4aSElliott Hughes 63*e1fe3e4aSElliott Hughes seenWidthOp = False 64*e1fe3e4aSElliott Hughes vsIndex = None 65*e1fe3e4aSElliott Hughes lenBlendStack = 0 66*e1fe3e4aSElliott Hughes lastBlendIndex = 0 67*e1fe3e4aSElliott Hughes commands = [] 68*e1fe3e4aSElliott Hughes stack = [] 69*e1fe3e4aSElliott Hughes it = iter(program) 70*e1fe3e4aSElliott Hughes 71*e1fe3e4aSElliott Hughes for token in it: 72*e1fe3e4aSElliott Hughes if not isinstance(token, str): 73*e1fe3e4aSElliott Hughes stack.append(token) 74*e1fe3e4aSElliott Hughes continue 75*e1fe3e4aSElliott Hughes 76*e1fe3e4aSElliott Hughes if token == "blend": 77*e1fe3e4aSElliott Hughes assert getNumRegions is not None 78*e1fe3e4aSElliott Hughes numSourceFonts = 1 + getNumRegions(vsIndex) 79*e1fe3e4aSElliott Hughes # replace the blend op args on the stack with a single list 80*e1fe3e4aSElliott Hughes # containing all the blend op args. 81*e1fe3e4aSElliott Hughes numBlends = stack[-1] 82*e1fe3e4aSElliott Hughes numBlendArgs = numBlends * numSourceFonts + 1 83*e1fe3e4aSElliott Hughes # replace first blend op by a list of the blend ops. 84*e1fe3e4aSElliott Hughes stack[-numBlendArgs:] = [stack[-numBlendArgs:]] 85*e1fe3e4aSElliott Hughes lenBlendStack += numBlends + len(stack) - 1 86*e1fe3e4aSElliott Hughes lastBlendIndex = len(stack) 87*e1fe3e4aSElliott Hughes # if a blend op exists, this is or will be a CFF2 charstring. 88*e1fe3e4aSElliott Hughes continue 89*e1fe3e4aSElliott Hughes 90*e1fe3e4aSElliott Hughes elif token == "vsindex": 91*e1fe3e4aSElliott Hughes vsIndex = stack[-1] 92*e1fe3e4aSElliott Hughes assert type(vsIndex) is int 93*e1fe3e4aSElliott Hughes 94*e1fe3e4aSElliott Hughes elif (not seenWidthOp) and token in { 95*e1fe3e4aSElliott Hughes "hstem", 96*e1fe3e4aSElliott Hughes "hstemhm", 97*e1fe3e4aSElliott Hughes "vstem", 98*e1fe3e4aSElliott Hughes "vstemhm", 99*e1fe3e4aSElliott Hughes "cntrmask", 100*e1fe3e4aSElliott Hughes "hintmask", 101*e1fe3e4aSElliott Hughes "hmoveto", 102*e1fe3e4aSElliott Hughes "vmoveto", 103*e1fe3e4aSElliott Hughes "rmoveto", 104*e1fe3e4aSElliott Hughes "endchar", 105*e1fe3e4aSElliott Hughes }: 106*e1fe3e4aSElliott Hughes seenWidthOp = True 107*e1fe3e4aSElliott Hughes parity = token in {"hmoveto", "vmoveto"} 108*e1fe3e4aSElliott Hughes if lenBlendStack: 109*e1fe3e4aSElliott Hughes # lenBlendStack has the number of args represented by the last blend 110*e1fe3e4aSElliott Hughes # arg and all the preceding args. We need to now add the number of 111*e1fe3e4aSElliott Hughes # args following the last blend arg. 112*e1fe3e4aSElliott Hughes numArgs = lenBlendStack + len(stack[lastBlendIndex:]) 113*e1fe3e4aSElliott Hughes else: 114*e1fe3e4aSElliott Hughes numArgs = len(stack) 115*e1fe3e4aSElliott Hughes if numArgs and (numArgs % 2) ^ parity: 116*e1fe3e4aSElliott Hughes width = stack.pop(0) 117*e1fe3e4aSElliott Hughes commands.append(("", [width])) 118*e1fe3e4aSElliott Hughes 119*e1fe3e4aSElliott Hughes if token in {"hintmask", "cntrmask"}: 120*e1fe3e4aSElliott Hughes if stack: 121*e1fe3e4aSElliott Hughes commands.append(("", stack)) 122*e1fe3e4aSElliott Hughes commands.append((token, [])) 123*e1fe3e4aSElliott Hughes commands.append(("", [next(it)])) 124*e1fe3e4aSElliott Hughes else: 125*e1fe3e4aSElliott Hughes commands.append((token, stack)) 126*e1fe3e4aSElliott Hughes stack = [] 127*e1fe3e4aSElliott Hughes if stack: 128*e1fe3e4aSElliott Hughes commands.append(("", stack)) 129*e1fe3e4aSElliott Hughes return commands 130*e1fe3e4aSElliott Hughes 131*e1fe3e4aSElliott Hughes 132*e1fe3e4aSElliott Hughesdef _flattenBlendArgs(args): 133*e1fe3e4aSElliott Hughes token_list = [] 134*e1fe3e4aSElliott Hughes for arg in args: 135*e1fe3e4aSElliott Hughes if isinstance(arg, list): 136*e1fe3e4aSElliott Hughes token_list.extend(arg) 137*e1fe3e4aSElliott Hughes token_list.append("blend") 138*e1fe3e4aSElliott Hughes else: 139*e1fe3e4aSElliott Hughes token_list.append(arg) 140*e1fe3e4aSElliott Hughes return token_list 141*e1fe3e4aSElliott Hughes 142*e1fe3e4aSElliott Hughes 143*e1fe3e4aSElliott Hughesdef commandsToProgram(commands): 144*e1fe3e4aSElliott Hughes """Takes a commands list as returned by programToCommands() and converts 145*e1fe3e4aSElliott Hughes it back to a T2CharString program list.""" 146*e1fe3e4aSElliott Hughes program = [] 147*e1fe3e4aSElliott Hughes for op, args in commands: 148*e1fe3e4aSElliott Hughes if any(isinstance(arg, list) for arg in args): 149*e1fe3e4aSElliott Hughes args = _flattenBlendArgs(args) 150*e1fe3e4aSElliott Hughes program.extend(args) 151*e1fe3e4aSElliott Hughes if op: 152*e1fe3e4aSElliott Hughes program.append(op) 153*e1fe3e4aSElliott Hughes return program 154*e1fe3e4aSElliott Hughes 155*e1fe3e4aSElliott Hughes 156*e1fe3e4aSElliott Hughesdef _everyN(el, n): 157*e1fe3e4aSElliott Hughes """Group the list el into groups of size n""" 158*e1fe3e4aSElliott Hughes if len(el) % n != 0: 159*e1fe3e4aSElliott Hughes raise ValueError(el) 160*e1fe3e4aSElliott Hughes for i in range(0, len(el), n): 161*e1fe3e4aSElliott Hughes yield el[i : i + n] 162*e1fe3e4aSElliott Hughes 163*e1fe3e4aSElliott Hughes 164*e1fe3e4aSElliott Hughesclass _GeneralizerDecombinerCommandsMap(object): 165*e1fe3e4aSElliott Hughes @staticmethod 166*e1fe3e4aSElliott Hughes def rmoveto(args): 167*e1fe3e4aSElliott Hughes if len(args) != 2: 168*e1fe3e4aSElliott Hughes raise ValueError(args) 169*e1fe3e4aSElliott Hughes yield ("rmoveto", args) 170*e1fe3e4aSElliott Hughes 171*e1fe3e4aSElliott Hughes @staticmethod 172*e1fe3e4aSElliott Hughes def hmoveto(args): 173*e1fe3e4aSElliott Hughes if len(args) != 1: 174*e1fe3e4aSElliott Hughes raise ValueError(args) 175*e1fe3e4aSElliott Hughes yield ("rmoveto", [args[0], 0]) 176*e1fe3e4aSElliott Hughes 177*e1fe3e4aSElliott Hughes @staticmethod 178*e1fe3e4aSElliott Hughes def vmoveto(args): 179*e1fe3e4aSElliott Hughes if len(args) != 1: 180*e1fe3e4aSElliott Hughes raise ValueError(args) 181*e1fe3e4aSElliott Hughes yield ("rmoveto", [0, args[0]]) 182*e1fe3e4aSElliott Hughes 183*e1fe3e4aSElliott Hughes @staticmethod 184*e1fe3e4aSElliott Hughes def rlineto(args): 185*e1fe3e4aSElliott Hughes if not args: 186*e1fe3e4aSElliott Hughes raise ValueError(args) 187*e1fe3e4aSElliott Hughes for args in _everyN(args, 2): 188*e1fe3e4aSElliott Hughes yield ("rlineto", args) 189*e1fe3e4aSElliott Hughes 190*e1fe3e4aSElliott Hughes @staticmethod 191*e1fe3e4aSElliott Hughes def hlineto(args): 192*e1fe3e4aSElliott Hughes if not args: 193*e1fe3e4aSElliott Hughes raise ValueError(args) 194*e1fe3e4aSElliott Hughes it = iter(args) 195*e1fe3e4aSElliott Hughes try: 196*e1fe3e4aSElliott Hughes while True: 197*e1fe3e4aSElliott Hughes yield ("rlineto", [next(it), 0]) 198*e1fe3e4aSElliott Hughes yield ("rlineto", [0, next(it)]) 199*e1fe3e4aSElliott Hughes except StopIteration: 200*e1fe3e4aSElliott Hughes pass 201*e1fe3e4aSElliott Hughes 202*e1fe3e4aSElliott Hughes @staticmethod 203*e1fe3e4aSElliott Hughes def vlineto(args): 204*e1fe3e4aSElliott Hughes if not args: 205*e1fe3e4aSElliott Hughes raise ValueError(args) 206*e1fe3e4aSElliott Hughes it = iter(args) 207*e1fe3e4aSElliott Hughes try: 208*e1fe3e4aSElliott Hughes while True: 209*e1fe3e4aSElliott Hughes yield ("rlineto", [0, next(it)]) 210*e1fe3e4aSElliott Hughes yield ("rlineto", [next(it), 0]) 211*e1fe3e4aSElliott Hughes except StopIteration: 212*e1fe3e4aSElliott Hughes pass 213*e1fe3e4aSElliott Hughes 214*e1fe3e4aSElliott Hughes @staticmethod 215*e1fe3e4aSElliott Hughes def rrcurveto(args): 216*e1fe3e4aSElliott Hughes if not args: 217*e1fe3e4aSElliott Hughes raise ValueError(args) 218*e1fe3e4aSElliott Hughes for args in _everyN(args, 6): 219*e1fe3e4aSElliott Hughes yield ("rrcurveto", args) 220*e1fe3e4aSElliott Hughes 221*e1fe3e4aSElliott Hughes @staticmethod 222*e1fe3e4aSElliott Hughes def hhcurveto(args): 223*e1fe3e4aSElliott Hughes if len(args) < 4 or len(args) % 4 > 1: 224*e1fe3e4aSElliott Hughes raise ValueError(args) 225*e1fe3e4aSElliott Hughes if len(args) % 2 == 1: 226*e1fe3e4aSElliott Hughes yield ("rrcurveto", [args[1], args[0], args[2], args[3], args[4], 0]) 227*e1fe3e4aSElliott Hughes args = args[5:] 228*e1fe3e4aSElliott Hughes for args in _everyN(args, 4): 229*e1fe3e4aSElliott Hughes yield ("rrcurveto", [args[0], 0, args[1], args[2], args[3], 0]) 230*e1fe3e4aSElliott Hughes 231*e1fe3e4aSElliott Hughes @staticmethod 232*e1fe3e4aSElliott Hughes def vvcurveto(args): 233*e1fe3e4aSElliott Hughes if len(args) < 4 or len(args) % 4 > 1: 234*e1fe3e4aSElliott Hughes raise ValueError(args) 235*e1fe3e4aSElliott Hughes if len(args) % 2 == 1: 236*e1fe3e4aSElliott Hughes yield ("rrcurveto", [args[0], args[1], args[2], args[3], 0, args[4]]) 237*e1fe3e4aSElliott Hughes args = args[5:] 238*e1fe3e4aSElliott Hughes for args in _everyN(args, 4): 239*e1fe3e4aSElliott Hughes yield ("rrcurveto", [0, args[0], args[1], args[2], 0, args[3]]) 240*e1fe3e4aSElliott Hughes 241*e1fe3e4aSElliott Hughes @staticmethod 242*e1fe3e4aSElliott Hughes def hvcurveto(args): 243*e1fe3e4aSElliott Hughes if len(args) < 4 or len(args) % 8 not in {0, 1, 4, 5}: 244*e1fe3e4aSElliott Hughes raise ValueError(args) 245*e1fe3e4aSElliott Hughes last_args = None 246*e1fe3e4aSElliott Hughes if len(args) % 2 == 1: 247*e1fe3e4aSElliott Hughes lastStraight = len(args) % 8 == 5 248*e1fe3e4aSElliott Hughes args, last_args = args[:-5], args[-5:] 249*e1fe3e4aSElliott Hughes it = _everyN(args, 4) 250*e1fe3e4aSElliott Hughes try: 251*e1fe3e4aSElliott Hughes while True: 252*e1fe3e4aSElliott Hughes args = next(it) 253*e1fe3e4aSElliott Hughes yield ("rrcurveto", [args[0], 0, args[1], args[2], 0, args[3]]) 254*e1fe3e4aSElliott Hughes args = next(it) 255*e1fe3e4aSElliott Hughes yield ("rrcurveto", [0, args[0], args[1], args[2], args[3], 0]) 256*e1fe3e4aSElliott Hughes except StopIteration: 257*e1fe3e4aSElliott Hughes pass 258*e1fe3e4aSElliott Hughes if last_args: 259*e1fe3e4aSElliott Hughes args = last_args 260*e1fe3e4aSElliott Hughes if lastStraight: 261*e1fe3e4aSElliott Hughes yield ("rrcurveto", [args[0], 0, args[1], args[2], args[4], args[3]]) 262*e1fe3e4aSElliott Hughes else: 263*e1fe3e4aSElliott Hughes yield ("rrcurveto", [0, args[0], args[1], args[2], args[3], args[4]]) 264*e1fe3e4aSElliott Hughes 265*e1fe3e4aSElliott Hughes @staticmethod 266*e1fe3e4aSElliott Hughes def vhcurveto(args): 267*e1fe3e4aSElliott Hughes if len(args) < 4 or len(args) % 8 not in {0, 1, 4, 5}: 268*e1fe3e4aSElliott Hughes raise ValueError(args) 269*e1fe3e4aSElliott Hughes last_args = None 270*e1fe3e4aSElliott Hughes if len(args) % 2 == 1: 271*e1fe3e4aSElliott Hughes lastStraight = len(args) % 8 == 5 272*e1fe3e4aSElliott Hughes args, last_args = args[:-5], args[-5:] 273*e1fe3e4aSElliott Hughes it = _everyN(args, 4) 274*e1fe3e4aSElliott Hughes try: 275*e1fe3e4aSElliott Hughes while True: 276*e1fe3e4aSElliott Hughes args = next(it) 277*e1fe3e4aSElliott Hughes yield ("rrcurveto", [0, args[0], args[1], args[2], args[3], 0]) 278*e1fe3e4aSElliott Hughes args = next(it) 279*e1fe3e4aSElliott Hughes yield ("rrcurveto", [args[0], 0, args[1], args[2], 0, args[3]]) 280*e1fe3e4aSElliott Hughes except StopIteration: 281*e1fe3e4aSElliott Hughes pass 282*e1fe3e4aSElliott Hughes if last_args: 283*e1fe3e4aSElliott Hughes args = last_args 284*e1fe3e4aSElliott Hughes if lastStraight: 285*e1fe3e4aSElliott Hughes yield ("rrcurveto", [0, args[0], args[1], args[2], args[3], args[4]]) 286*e1fe3e4aSElliott Hughes else: 287*e1fe3e4aSElliott Hughes yield ("rrcurveto", [args[0], 0, args[1], args[2], args[4], args[3]]) 288*e1fe3e4aSElliott Hughes 289*e1fe3e4aSElliott Hughes @staticmethod 290*e1fe3e4aSElliott Hughes def rcurveline(args): 291*e1fe3e4aSElliott Hughes if len(args) < 8 or len(args) % 6 != 2: 292*e1fe3e4aSElliott Hughes raise ValueError(args) 293*e1fe3e4aSElliott Hughes args, last_args = args[:-2], args[-2:] 294*e1fe3e4aSElliott Hughes for args in _everyN(args, 6): 295*e1fe3e4aSElliott Hughes yield ("rrcurveto", args) 296*e1fe3e4aSElliott Hughes yield ("rlineto", last_args) 297*e1fe3e4aSElliott Hughes 298*e1fe3e4aSElliott Hughes @staticmethod 299*e1fe3e4aSElliott Hughes def rlinecurve(args): 300*e1fe3e4aSElliott Hughes if len(args) < 8 or len(args) % 2 != 0: 301*e1fe3e4aSElliott Hughes raise ValueError(args) 302*e1fe3e4aSElliott Hughes args, last_args = args[:-6], args[-6:] 303*e1fe3e4aSElliott Hughes for args in _everyN(args, 2): 304*e1fe3e4aSElliott Hughes yield ("rlineto", args) 305*e1fe3e4aSElliott Hughes yield ("rrcurveto", last_args) 306*e1fe3e4aSElliott Hughes 307*e1fe3e4aSElliott Hughes 308*e1fe3e4aSElliott Hughesdef _convertBlendOpToArgs(blendList): 309*e1fe3e4aSElliott Hughes # args is list of blend op args. Since we are supporting 310*e1fe3e4aSElliott Hughes # recursive blend op calls, some of these args may also 311*e1fe3e4aSElliott Hughes # be a list of blend op args, and need to be converted before 312*e1fe3e4aSElliott Hughes # we convert the current list. 313*e1fe3e4aSElliott Hughes if any([isinstance(arg, list) for arg in blendList]): 314*e1fe3e4aSElliott Hughes args = [ 315*e1fe3e4aSElliott Hughes i 316*e1fe3e4aSElliott Hughes for e in blendList 317*e1fe3e4aSElliott Hughes for i in (_convertBlendOpToArgs(e) if isinstance(e, list) else [e]) 318*e1fe3e4aSElliott Hughes ] 319*e1fe3e4aSElliott Hughes else: 320*e1fe3e4aSElliott Hughes args = blendList 321*e1fe3e4aSElliott Hughes 322*e1fe3e4aSElliott Hughes # We now know that blendList contains a blend op argument list, even if 323*e1fe3e4aSElliott Hughes # some of the args are lists that each contain a blend op argument list. 324*e1fe3e4aSElliott Hughes # Convert from: 325*e1fe3e4aSElliott Hughes # [default font arg sequence x0,...,xn] + [delta tuple for x0] + ... + [delta tuple for xn] 326*e1fe3e4aSElliott Hughes # to: 327*e1fe3e4aSElliott Hughes # [ [x0] + [delta tuple for x0], 328*e1fe3e4aSElliott Hughes # ..., 329*e1fe3e4aSElliott Hughes # [xn] + [delta tuple for xn] ] 330*e1fe3e4aSElliott Hughes numBlends = args[-1] 331*e1fe3e4aSElliott Hughes # Can't use args.pop() when the args are being used in a nested list 332*e1fe3e4aSElliott Hughes # comprehension. See calling context 333*e1fe3e4aSElliott Hughes args = args[:-1] 334*e1fe3e4aSElliott Hughes 335*e1fe3e4aSElliott Hughes numRegions = len(args) // numBlends - 1 336*e1fe3e4aSElliott Hughes if not (numBlends * (numRegions + 1) == len(args)): 337*e1fe3e4aSElliott Hughes raise ValueError(blendList) 338*e1fe3e4aSElliott Hughes 339*e1fe3e4aSElliott Hughes defaultArgs = [[arg] for arg in args[:numBlends]] 340*e1fe3e4aSElliott Hughes deltaArgs = args[numBlends:] 341*e1fe3e4aSElliott Hughes numDeltaValues = len(deltaArgs) 342*e1fe3e4aSElliott Hughes deltaList = [ 343*e1fe3e4aSElliott Hughes deltaArgs[i : i + numRegions] for i in range(0, numDeltaValues, numRegions) 344*e1fe3e4aSElliott Hughes ] 345*e1fe3e4aSElliott Hughes blend_args = [a + b + [1] for a, b in zip(defaultArgs, deltaList)] 346*e1fe3e4aSElliott Hughes return blend_args 347*e1fe3e4aSElliott Hughes 348*e1fe3e4aSElliott Hughes 349*e1fe3e4aSElliott Hughesdef generalizeCommands(commands, ignoreErrors=False): 350*e1fe3e4aSElliott Hughes result = [] 351*e1fe3e4aSElliott Hughes mapping = _GeneralizerDecombinerCommandsMap 352*e1fe3e4aSElliott Hughes for op, args in commands: 353*e1fe3e4aSElliott Hughes # First, generalize any blend args in the arg list. 354*e1fe3e4aSElliott Hughes if any([isinstance(arg, list) for arg in args]): 355*e1fe3e4aSElliott Hughes try: 356*e1fe3e4aSElliott Hughes args = [ 357*e1fe3e4aSElliott Hughes n 358*e1fe3e4aSElliott Hughes for arg in args 359*e1fe3e4aSElliott Hughes for n in ( 360*e1fe3e4aSElliott Hughes _convertBlendOpToArgs(arg) if isinstance(arg, list) else [arg] 361*e1fe3e4aSElliott Hughes ) 362*e1fe3e4aSElliott Hughes ] 363*e1fe3e4aSElliott Hughes except ValueError: 364*e1fe3e4aSElliott Hughes if ignoreErrors: 365*e1fe3e4aSElliott Hughes # Store op as data, such that consumers of commands do not have to 366*e1fe3e4aSElliott Hughes # deal with incorrect number of arguments. 367*e1fe3e4aSElliott Hughes result.append(("", args)) 368*e1fe3e4aSElliott Hughes result.append(("", [op])) 369*e1fe3e4aSElliott Hughes else: 370*e1fe3e4aSElliott Hughes raise 371*e1fe3e4aSElliott Hughes 372*e1fe3e4aSElliott Hughes func = getattr(mapping, op, None) 373*e1fe3e4aSElliott Hughes if not func: 374*e1fe3e4aSElliott Hughes result.append((op, args)) 375*e1fe3e4aSElliott Hughes continue 376*e1fe3e4aSElliott Hughes try: 377*e1fe3e4aSElliott Hughes for command in func(args): 378*e1fe3e4aSElliott Hughes result.append(command) 379*e1fe3e4aSElliott Hughes except ValueError: 380*e1fe3e4aSElliott Hughes if ignoreErrors: 381*e1fe3e4aSElliott Hughes # Store op as data, such that consumers of commands do not have to 382*e1fe3e4aSElliott Hughes # deal with incorrect number of arguments. 383*e1fe3e4aSElliott Hughes result.append(("", args)) 384*e1fe3e4aSElliott Hughes result.append(("", [op])) 385*e1fe3e4aSElliott Hughes else: 386*e1fe3e4aSElliott Hughes raise 387*e1fe3e4aSElliott Hughes return result 388*e1fe3e4aSElliott Hughes 389*e1fe3e4aSElliott Hughes 390*e1fe3e4aSElliott Hughesdef generalizeProgram(program, getNumRegions=None, **kwargs): 391*e1fe3e4aSElliott Hughes return commandsToProgram( 392*e1fe3e4aSElliott Hughes generalizeCommands(programToCommands(program, getNumRegions), **kwargs) 393*e1fe3e4aSElliott Hughes ) 394*e1fe3e4aSElliott Hughes 395*e1fe3e4aSElliott Hughes 396*e1fe3e4aSElliott Hughesdef _categorizeVector(v): 397*e1fe3e4aSElliott Hughes """ 398*e1fe3e4aSElliott Hughes Takes X,Y vector v and returns one of r, h, v, or 0 depending on which 399*e1fe3e4aSElliott Hughes of X and/or Y are zero, plus tuple of nonzero ones. If both are zero, 400*e1fe3e4aSElliott Hughes it returns a single zero still. 401*e1fe3e4aSElliott Hughes 402*e1fe3e4aSElliott Hughes >>> _categorizeVector((0,0)) 403*e1fe3e4aSElliott Hughes ('0', (0,)) 404*e1fe3e4aSElliott Hughes >>> _categorizeVector((1,0)) 405*e1fe3e4aSElliott Hughes ('h', (1,)) 406*e1fe3e4aSElliott Hughes >>> _categorizeVector((0,2)) 407*e1fe3e4aSElliott Hughes ('v', (2,)) 408*e1fe3e4aSElliott Hughes >>> _categorizeVector((1,2)) 409*e1fe3e4aSElliott Hughes ('r', (1, 2)) 410*e1fe3e4aSElliott Hughes """ 411*e1fe3e4aSElliott Hughes if not v[0]: 412*e1fe3e4aSElliott Hughes if not v[1]: 413*e1fe3e4aSElliott Hughes return "0", v[:1] 414*e1fe3e4aSElliott Hughes else: 415*e1fe3e4aSElliott Hughes return "v", v[1:] 416*e1fe3e4aSElliott Hughes else: 417*e1fe3e4aSElliott Hughes if not v[1]: 418*e1fe3e4aSElliott Hughes return "h", v[:1] 419*e1fe3e4aSElliott Hughes else: 420*e1fe3e4aSElliott Hughes return "r", v 421*e1fe3e4aSElliott Hughes 422*e1fe3e4aSElliott Hughes 423*e1fe3e4aSElliott Hughesdef _mergeCategories(a, b): 424*e1fe3e4aSElliott Hughes if a == "0": 425*e1fe3e4aSElliott Hughes return b 426*e1fe3e4aSElliott Hughes if b == "0": 427*e1fe3e4aSElliott Hughes return a 428*e1fe3e4aSElliott Hughes if a == b: 429*e1fe3e4aSElliott Hughes return a 430*e1fe3e4aSElliott Hughes return None 431*e1fe3e4aSElliott Hughes 432*e1fe3e4aSElliott Hughes 433*e1fe3e4aSElliott Hughesdef _negateCategory(a): 434*e1fe3e4aSElliott Hughes if a == "h": 435*e1fe3e4aSElliott Hughes return "v" 436*e1fe3e4aSElliott Hughes if a == "v": 437*e1fe3e4aSElliott Hughes return "h" 438*e1fe3e4aSElliott Hughes assert a in "0r" 439*e1fe3e4aSElliott Hughes return a 440*e1fe3e4aSElliott Hughes 441*e1fe3e4aSElliott Hughes 442*e1fe3e4aSElliott Hughesdef _convertToBlendCmds(args): 443*e1fe3e4aSElliott Hughes # return a list of blend commands, and 444*e1fe3e4aSElliott Hughes # the remaining non-blended args, if any. 445*e1fe3e4aSElliott Hughes num_args = len(args) 446*e1fe3e4aSElliott Hughes stack_use = 0 447*e1fe3e4aSElliott Hughes new_args = [] 448*e1fe3e4aSElliott Hughes i = 0 449*e1fe3e4aSElliott Hughes while i < num_args: 450*e1fe3e4aSElliott Hughes arg = args[i] 451*e1fe3e4aSElliott Hughes if not isinstance(arg, list): 452*e1fe3e4aSElliott Hughes new_args.append(arg) 453*e1fe3e4aSElliott Hughes i += 1 454*e1fe3e4aSElliott Hughes stack_use += 1 455*e1fe3e4aSElliott Hughes else: 456*e1fe3e4aSElliott Hughes prev_stack_use = stack_use 457*e1fe3e4aSElliott Hughes # The arg is a tuple of blend values. 458*e1fe3e4aSElliott Hughes # These are each (master 0,delta 1..delta n, 1) 459*e1fe3e4aSElliott Hughes # Combine as many successive tuples as we can, 460*e1fe3e4aSElliott Hughes # up to the max stack limit. 461*e1fe3e4aSElliott Hughes num_sources = len(arg) - 1 462*e1fe3e4aSElliott Hughes blendlist = [arg] 463*e1fe3e4aSElliott Hughes i += 1 464*e1fe3e4aSElliott Hughes stack_use += 1 + num_sources # 1 for the num_blends arg 465*e1fe3e4aSElliott Hughes while (i < num_args) and isinstance(args[i], list): 466*e1fe3e4aSElliott Hughes blendlist.append(args[i]) 467*e1fe3e4aSElliott Hughes i += 1 468*e1fe3e4aSElliott Hughes stack_use += num_sources 469*e1fe3e4aSElliott Hughes if stack_use + num_sources > maxStackLimit: 470*e1fe3e4aSElliott Hughes # if we are here, max stack is the CFF2 max stack. 471*e1fe3e4aSElliott Hughes # I use the CFF2 max stack limit here rather than 472*e1fe3e4aSElliott Hughes # the 'maxstack' chosen by the client, as the default 473*e1fe3e4aSElliott Hughes # maxstack may have been used unintentionally. For all 474*e1fe3e4aSElliott Hughes # the other operators, this just produces a little less 475*e1fe3e4aSElliott Hughes # optimization, but here it puts a hard (and low) limit 476*e1fe3e4aSElliott Hughes # on the number of source fonts that can be used. 477*e1fe3e4aSElliott Hughes break 478*e1fe3e4aSElliott Hughes # blendList now contains as many single blend tuples as can be 479*e1fe3e4aSElliott Hughes # combined without exceeding the CFF2 stack limit. 480*e1fe3e4aSElliott Hughes num_blends = len(blendlist) 481*e1fe3e4aSElliott Hughes # append the 'num_blends' default font values 482*e1fe3e4aSElliott Hughes blend_args = [] 483*e1fe3e4aSElliott Hughes for arg in blendlist: 484*e1fe3e4aSElliott Hughes blend_args.append(arg[0]) 485*e1fe3e4aSElliott Hughes for arg in blendlist: 486*e1fe3e4aSElliott Hughes assert arg[-1] == 1 487*e1fe3e4aSElliott Hughes blend_args.extend(arg[1:-1]) 488*e1fe3e4aSElliott Hughes blend_args.append(num_blends) 489*e1fe3e4aSElliott Hughes new_args.append(blend_args) 490*e1fe3e4aSElliott Hughes stack_use = prev_stack_use + num_blends 491*e1fe3e4aSElliott Hughes 492*e1fe3e4aSElliott Hughes return new_args 493*e1fe3e4aSElliott Hughes 494*e1fe3e4aSElliott Hughes 495*e1fe3e4aSElliott Hughesdef _addArgs(a, b): 496*e1fe3e4aSElliott Hughes if isinstance(b, list): 497*e1fe3e4aSElliott Hughes if isinstance(a, list): 498*e1fe3e4aSElliott Hughes if len(a) != len(b) or a[-1] != b[-1]: 499*e1fe3e4aSElliott Hughes raise ValueError() 500*e1fe3e4aSElliott Hughes return [_addArgs(va, vb) for va, vb in zip(a[:-1], b[:-1])] + [a[-1]] 501*e1fe3e4aSElliott Hughes else: 502*e1fe3e4aSElliott Hughes a, b = b, a 503*e1fe3e4aSElliott Hughes if isinstance(a, list): 504*e1fe3e4aSElliott Hughes assert a[-1] == 1 505*e1fe3e4aSElliott Hughes return [_addArgs(a[0], b)] + a[1:] 506*e1fe3e4aSElliott Hughes return a + b 507*e1fe3e4aSElliott Hughes 508*e1fe3e4aSElliott Hughes 509*e1fe3e4aSElliott Hughesdef specializeCommands( 510*e1fe3e4aSElliott Hughes commands, 511*e1fe3e4aSElliott Hughes ignoreErrors=False, 512*e1fe3e4aSElliott Hughes generalizeFirst=True, 513*e1fe3e4aSElliott Hughes preserveTopology=False, 514*e1fe3e4aSElliott Hughes maxstack=48, 515*e1fe3e4aSElliott Hughes): 516*e1fe3e4aSElliott Hughes # We perform several rounds of optimizations. They are carefully ordered and are: 517*e1fe3e4aSElliott Hughes # 518*e1fe3e4aSElliott Hughes # 0. Generalize commands. 519*e1fe3e4aSElliott Hughes # This ensures that they are in our expected simple form, with each line/curve only 520*e1fe3e4aSElliott Hughes # having arguments for one segment, and using the generic form (rlineto/rrcurveto). 521*e1fe3e4aSElliott Hughes # If caller is sure the input is in this form, they can turn off generalization to 522*e1fe3e4aSElliott Hughes # save time. 523*e1fe3e4aSElliott Hughes # 524*e1fe3e4aSElliott Hughes # 1. Combine successive rmoveto operations. 525*e1fe3e4aSElliott Hughes # 526*e1fe3e4aSElliott Hughes # 2. Specialize rmoveto/rlineto/rrcurveto operators into horizontal/vertical variants. 527*e1fe3e4aSElliott Hughes # We specialize into some, made-up, variants as well, which simplifies following 528*e1fe3e4aSElliott Hughes # passes. 529*e1fe3e4aSElliott Hughes # 530*e1fe3e4aSElliott Hughes # 3. Merge or delete redundant operations, to the extent requested. 531*e1fe3e4aSElliott Hughes # OpenType spec declares point numbers in CFF undefined. As such, we happily 532*e1fe3e4aSElliott Hughes # change topology. If client relies on point numbers (in GPOS anchors, or for 533*e1fe3e4aSElliott Hughes # hinting purposes(what?)) they can turn this off. 534*e1fe3e4aSElliott Hughes # 535*e1fe3e4aSElliott Hughes # 4. Peephole optimization to revert back some of the h/v variants back into their 536*e1fe3e4aSElliott Hughes # original "relative" operator (rline/rrcurveto) if that saves a byte. 537*e1fe3e4aSElliott Hughes # 538*e1fe3e4aSElliott Hughes # 5. Combine adjacent operators when possible, minding not to go over max stack size. 539*e1fe3e4aSElliott Hughes # 540*e1fe3e4aSElliott Hughes # 6. Resolve any remaining made-up operators into real operators. 541*e1fe3e4aSElliott Hughes # 542*e1fe3e4aSElliott Hughes # I have convinced myself that this produces optimal bytecode (except for, possibly 543*e1fe3e4aSElliott Hughes # one byte each time maxstack size prohibits combining.) YMMV, but you'd be wrong. :-) 544*e1fe3e4aSElliott Hughes # A dynamic-programming approach can do the same but would be significantly slower. 545*e1fe3e4aSElliott Hughes # 546*e1fe3e4aSElliott Hughes # 7. For any args which are blend lists, convert them to a blend command. 547*e1fe3e4aSElliott Hughes 548*e1fe3e4aSElliott Hughes # 0. Generalize commands. 549*e1fe3e4aSElliott Hughes if generalizeFirst: 550*e1fe3e4aSElliott Hughes commands = generalizeCommands(commands, ignoreErrors=ignoreErrors) 551*e1fe3e4aSElliott Hughes else: 552*e1fe3e4aSElliott Hughes commands = list(commands) # Make copy since we modify in-place later. 553*e1fe3e4aSElliott Hughes 554*e1fe3e4aSElliott Hughes # 1. Combine successive rmoveto operations. 555*e1fe3e4aSElliott Hughes for i in range(len(commands) - 1, 0, -1): 556*e1fe3e4aSElliott Hughes if "rmoveto" == commands[i][0] == commands[i - 1][0]: 557*e1fe3e4aSElliott Hughes v1, v2 = commands[i - 1][1], commands[i][1] 558*e1fe3e4aSElliott Hughes commands[i - 1] = ("rmoveto", [v1[0] + v2[0], v1[1] + v2[1]]) 559*e1fe3e4aSElliott Hughes del commands[i] 560*e1fe3e4aSElliott Hughes 561*e1fe3e4aSElliott Hughes # 2. Specialize rmoveto/rlineto/rrcurveto operators into horizontal/vertical variants. 562*e1fe3e4aSElliott Hughes # 563*e1fe3e4aSElliott Hughes # We, in fact, specialize into more, made-up, variants that special-case when both 564*e1fe3e4aSElliott Hughes # X and Y components are zero. This simplifies the following optimization passes. 565*e1fe3e4aSElliott Hughes # This case is rare, but OCD does not let me skip it. 566*e1fe3e4aSElliott Hughes # 567*e1fe3e4aSElliott Hughes # After this round, we will have four variants that use the following mnemonics: 568*e1fe3e4aSElliott Hughes # 569*e1fe3e4aSElliott Hughes # - 'r' for relative, ie. non-zero X and non-zero Y, 570*e1fe3e4aSElliott Hughes # - 'h' for horizontal, ie. zero X and non-zero Y, 571*e1fe3e4aSElliott Hughes # - 'v' for vertical, ie. non-zero X and zero Y, 572*e1fe3e4aSElliott Hughes # - '0' for zeros, ie. zero X and zero Y. 573*e1fe3e4aSElliott Hughes # 574*e1fe3e4aSElliott Hughes # The '0' pseudo-operators are not part of the spec, but help simplify the following 575*e1fe3e4aSElliott Hughes # optimization rounds. We resolve them at the end. So, after this, we will have four 576*e1fe3e4aSElliott Hughes # moveto and four lineto variants: 577*e1fe3e4aSElliott Hughes # 578*e1fe3e4aSElliott Hughes # - 0moveto, 0lineto 579*e1fe3e4aSElliott Hughes # - hmoveto, hlineto 580*e1fe3e4aSElliott Hughes # - vmoveto, vlineto 581*e1fe3e4aSElliott Hughes # - rmoveto, rlineto 582*e1fe3e4aSElliott Hughes # 583*e1fe3e4aSElliott Hughes # and sixteen curveto variants. For example, a '0hcurveto' operator means a curve 584*e1fe3e4aSElliott Hughes # dx0,dy0,dx1,dy1,dx2,dy2,dx3,dy3 where dx0, dx1, and dy3 are zero but not dx3. 585*e1fe3e4aSElliott Hughes # An 'rvcurveto' means dx3 is zero but not dx0,dy0,dy3. 586*e1fe3e4aSElliott Hughes # 587*e1fe3e4aSElliott Hughes # There are nine different variants of curves without the '0'. Those nine map exactly 588*e1fe3e4aSElliott Hughes # to the existing curve variants in the spec: rrcurveto, and the four variants hhcurveto, 589*e1fe3e4aSElliott Hughes # vvcurveto, hvcurveto, and vhcurveto each cover two cases, one with an odd number of 590*e1fe3e4aSElliott Hughes # arguments and one without. Eg. an hhcurveto with an extra argument (odd number of 591*e1fe3e4aSElliott Hughes # arguments) is in fact an rhcurveto. The operators in the spec are designed such that 592*e1fe3e4aSElliott Hughes # all four of rhcurveto, rvcurveto, hrcurveto, and vrcurveto are encodable for one curve. 593*e1fe3e4aSElliott Hughes # 594*e1fe3e4aSElliott Hughes # Of the curve types with '0', the 00curveto is equivalent to a lineto variant. The rest 595*e1fe3e4aSElliott Hughes # of the curve types with a 0 need to be encoded as a h or v variant. Ie. a '0' can be 596*e1fe3e4aSElliott Hughes # thought of a "don't care" and can be used as either an 'h' or a 'v'. As such, we always 597*e1fe3e4aSElliott Hughes # encode a number 0 as argument when we use a '0' variant. Later on, we can just substitute 598*e1fe3e4aSElliott Hughes # the '0' with either 'h' or 'v' and it works. 599*e1fe3e4aSElliott Hughes # 600*e1fe3e4aSElliott Hughes # When we get to curve splines however, things become more complicated... XXX finish this. 601*e1fe3e4aSElliott Hughes # There's one more complexity with splines. If one side of the spline is not horizontal or 602*e1fe3e4aSElliott Hughes # vertical (or zero), ie. if it's 'r', then it limits which spline types we can encode. 603*e1fe3e4aSElliott Hughes # Only hhcurveto and vvcurveto operators can encode a spline starting with 'r', and 604*e1fe3e4aSElliott Hughes # only hvcurveto and vhcurveto operators can encode a spline ending with 'r'. 605*e1fe3e4aSElliott Hughes # This limits our merge opportunities later. 606*e1fe3e4aSElliott Hughes # 607*e1fe3e4aSElliott Hughes for i in range(len(commands)): 608*e1fe3e4aSElliott Hughes op, args = commands[i] 609*e1fe3e4aSElliott Hughes 610*e1fe3e4aSElliott Hughes if op in {"rmoveto", "rlineto"}: 611*e1fe3e4aSElliott Hughes c, args = _categorizeVector(args) 612*e1fe3e4aSElliott Hughes commands[i] = c + op[1:], args 613*e1fe3e4aSElliott Hughes continue 614*e1fe3e4aSElliott Hughes 615*e1fe3e4aSElliott Hughes if op == "rrcurveto": 616*e1fe3e4aSElliott Hughes c1, args1 = _categorizeVector(args[:2]) 617*e1fe3e4aSElliott Hughes c2, args2 = _categorizeVector(args[-2:]) 618*e1fe3e4aSElliott Hughes commands[i] = c1 + c2 + "curveto", args1 + args[2:4] + args2 619*e1fe3e4aSElliott Hughes continue 620*e1fe3e4aSElliott Hughes 621*e1fe3e4aSElliott Hughes # 3. Merge or delete redundant operations, to the extent requested. 622*e1fe3e4aSElliott Hughes # 623*e1fe3e4aSElliott Hughes # TODO 624*e1fe3e4aSElliott Hughes # A 0moveto that comes before all other path operations can be removed. 625*e1fe3e4aSElliott Hughes # though I find conflicting evidence for this. 626*e1fe3e4aSElliott Hughes # 627*e1fe3e4aSElliott Hughes # TODO 628*e1fe3e4aSElliott Hughes # "If hstem and vstem hints are both declared at the beginning of a 629*e1fe3e4aSElliott Hughes # CharString, and this sequence is followed directly by the hintmask or 630*e1fe3e4aSElliott Hughes # cntrmask operators, then the vstem hint operator (or, if applicable, 631*e1fe3e4aSElliott Hughes # the vstemhm operator) need not be included." 632*e1fe3e4aSElliott Hughes # 633*e1fe3e4aSElliott Hughes # "The sequence and form of a CFF2 CharString program may be represented as: 634*e1fe3e4aSElliott Hughes # {hs* vs* cm* hm* mt subpath}? {mt subpath}*" 635*e1fe3e4aSElliott Hughes # 636*e1fe3e4aSElliott Hughes # https://www.microsoft.com/typography/otspec/cff2charstr.htm#section3.1 637*e1fe3e4aSElliott Hughes # 638*e1fe3e4aSElliott Hughes # For Type2 CharStrings the sequence is: 639*e1fe3e4aSElliott Hughes # w? {hs* vs* cm* hm* mt subpath}? {mt subpath}* endchar" 640*e1fe3e4aSElliott Hughes 641*e1fe3e4aSElliott Hughes # Some other redundancies change topology (point numbers). 642*e1fe3e4aSElliott Hughes if not preserveTopology: 643*e1fe3e4aSElliott Hughes for i in range(len(commands) - 1, -1, -1): 644*e1fe3e4aSElliott Hughes op, args = commands[i] 645*e1fe3e4aSElliott Hughes 646*e1fe3e4aSElliott Hughes # A 00curveto is demoted to a (specialized) lineto. 647*e1fe3e4aSElliott Hughes if op == "00curveto": 648*e1fe3e4aSElliott Hughes assert len(args) == 4 649*e1fe3e4aSElliott Hughes c, args = _categorizeVector(args[1:3]) 650*e1fe3e4aSElliott Hughes op = c + "lineto" 651*e1fe3e4aSElliott Hughes commands[i] = op, args 652*e1fe3e4aSElliott Hughes # and then... 653*e1fe3e4aSElliott Hughes 654*e1fe3e4aSElliott Hughes # A 0lineto can be deleted. 655*e1fe3e4aSElliott Hughes if op == "0lineto": 656*e1fe3e4aSElliott Hughes del commands[i] 657*e1fe3e4aSElliott Hughes continue 658*e1fe3e4aSElliott Hughes 659*e1fe3e4aSElliott Hughes # Merge adjacent hlineto's and vlineto's. 660*e1fe3e4aSElliott Hughes # In CFF2 charstrings from variable fonts, each 661*e1fe3e4aSElliott Hughes # arg item may be a list of blendable values, one from 662*e1fe3e4aSElliott Hughes # each source font. 663*e1fe3e4aSElliott Hughes if i and op in {"hlineto", "vlineto"} and (op == commands[i - 1][0]): 664*e1fe3e4aSElliott Hughes _, other_args = commands[i - 1] 665*e1fe3e4aSElliott Hughes assert len(args) == 1 and len(other_args) == 1 666*e1fe3e4aSElliott Hughes try: 667*e1fe3e4aSElliott Hughes new_args = [_addArgs(args[0], other_args[0])] 668*e1fe3e4aSElliott Hughes except ValueError: 669*e1fe3e4aSElliott Hughes continue 670*e1fe3e4aSElliott Hughes commands[i - 1] = (op, new_args) 671*e1fe3e4aSElliott Hughes del commands[i] 672*e1fe3e4aSElliott Hughes continue 673*e1fe3e4aSElliott Hughes 674*e1fe3e4aSElliott Hughes # 4. Peephole optimization to revert back some of the h/v variants back into their 675*e1fe3e4aSElliott Hughes # original "relative" operator (rline/rrcurveto) if that saves a byte. 676*e1fe3e4aSElliott Hughes for i in range(1, len(commands) - 1): 677*e1fe3e4aSElliott Hughes op, args = commands[i] 678*e1fe3e4aSElliott Hughes prv, nxt = commands[i - 1][0], commands[i + 1][0] 679*e1fe3e4aSElliott Hughes 680*e1fe3e4aSElliott Hughes if op in {"0lineto", "hlineto", "vlineto"} and prv == nxt == "rlineto": 681*e1fe3e4aSElliott Hughes assert len(args) == 1 682*e1fe3e4aSElliott Hughes args = [0, args[0]] if op[0] == "v" else [args[0], 0] 683*e1fe3e4aSElliott Hughes commands[i] = ("rlineto", args) 684*e1fe3e4aSElliott Hughes continue 685*e1fe3e4aSElliott Hughes 686*e1fe3e4aSElliott Hughes if op[2:] == "curveto" and len(args) == 5 and prv == nxt == "rrcurveto": 687*e1fe3e4aSElliott Hughes assert (op[0] == "r") ^ (op[1] == "r") 688*e1fe3e4aSElliott Hughes if op[0] == "v": 689*e1fe3e4aSElliott Hughes pos = 0 690*e1fe3e4aSElliott Hughes elif op[0] != "r": 691*e1fe3e4aSElliott Hughes pos = 1 692*e1fe3e4aSElliott Hughes elif op[1] == "v": 693*e1fe3e4aSElliott Hughes pos = 4 694*e1fe3e4aSElliott Hughes else: 695*e1fe3e4aSElliott Hughes pos = 5 696*e1fe3e4aSElliott Hughes # Insert, while maintaining the type of args (can be tuple or list). 697*e1fe3e4aSElliott Hughes args = args[:pos] + type(args)((0,)) + args[pos:] 698*e1fe3e4aSElliott Hughes commands[i] = ("rrcurveto", args) 699*e1fe3e4aSElliott Hughes continue 700*e1fe3e4aSElliott Hughes 701*e1fe3e4aSElliott Hughes # 5. Combine adjacent operators when possible, minding not to go over max stack size. 702*e1fe3e4aSElliott Hughes for i in range(len(commands) - 1, 0, -1): 703*e1fe3e4aSElliott Hughes op1, args1 = commands[i - 1] 704*e1fe3e4aSElliott Hughes op2, args2 = commands[i] 705*e1fe3e4aSElliott Hughes new_op = None 706*e1fe3e4aSElliott Hughes 707*e1fe3e4aSElliott Hughes # Merge logic... 708*e1fe3e4aSElliott Hughes if {op1, op2} <= {"rlineto", "rrcurveto"}: 709*e1fe3e4aSElliott Hughes if op1 == op2: 710*e1fe3e4aSElliott Hughes new_op = op1 711*e1fe3e4aSElliott Hughes else: 712*e1fe3e4aSElliott Hughes if op2 == "rrcurveto" and len(args2) == 6: 713*e1fe3e4aSElliott Hughes new_op = "rlinecurve" 714*e1fe3e4aSElliott Hughes elif len(args2) == 2: 715*e1fe3e4aSElliott Hughes new_op = "rcurveline" 716*e1fe3e4aSElliott Hughes 717*e1fe3e4aSElliott Hughes elif (op1, op2) in {("rlineto", "rlinecurve"), ("rrcurveto", "rcurveline")}: 718*e1fe3e4aSElliott Hughes new_op = op2 719*e1fe3e4aSElliott Hughes 720*e1fe3e4aSElliott Hughes elif {op1, op2} == {"vlineto", "hlineto"}: 721*e1fe3e4aSElliott Hughes new_op = op1 722*e1fe3e4aSElliott Hughes 723*e1fe3e4aSElliott Hughes elif "curveto" == op1[2:] == op2[2:]: 724*e1fe3e4aSElliott Hughes d0, d1 = op1[:2] 725*e1fe3e4aSElliott Hughes d2, d3 = op2[:2] 726*e1fe3e4aSElliott Hughes 727*e1fe3e4aSElliott Hughes if d1 == "r" or d2 == "r" or d0 == d3 == "r": 728*e1fe3e4aSElliott Hughes continue 729*e1fe3e4aSElliott Hughes 730*e1fe3e4aSElliott Hughes d = _mergeCategories(d1, d2) 731*e1fe3e4aSElliott Hughes if d is None: 732*e1fe3e4aSElliott Hughes continue 733*e1fe3e4aSElliott Hughes if d0 == "r": 734*e1fe3e4aSElliott Hughes d = _mergeCategories(d, d3) 735*e1fe3e4aSElliott Hughes if d is None: 736*e1fe3e4aSElliott Hughes continue 737*e1fe3e4aSElliott Hughes new_op = "r" + d + "curveto" 738*e1fe3e4aSElliott Hughes elif d3 == "r": 739*e1fe3e4aSElliott Hughes d0 = _mergeCategories(d0, _negateCategory(d)) 740*e1fe3e4aSElliott Hughes if d0 is None: 741*e1fe3e4aSElliott Hughes continue 742*e1fe3e4aSElliott Hughes new_op = d0 + "r" + "curveto" 743*e1fe3e4aSElliott Hughes else: 744*e1fe3e4aSElliott Hughes d0 = _mergeCategories(d0, d3) 745*e1fe3e4aSElliott Hughes if d0 is None: 746*e1fe3e4aSElliott Hughes continue 747*e1fe3e4aSElliott Hughes new_op = d0 + d + "curveto" 748*e1fe3e4aSElliott Hughes 749*e1fe3e4aSElliott Hughes # Make sure the stack depth does not exceed (maxstack - 1), so 750*e1fe3e4aSElliott Hughes # that subroutinizer can insert subroutine calls at any point. 751*e1fe3e4aSElliott Hughes if new_op and len(args1) + len(args2) < maxstack: 752*e1fe3e4aSElliott Hughes commands[i - 1] = (new_op, args1 + args2) 753*e1fe3e4aSElliott Hughes del commands[i] 754*e1fe3e4aSElliott Hughes 755*e1fe3e4aSElliott Hughes # 6. Resolve any remaining made-up operators into real operators. 756*e1fe3e4aSElliott Hughes for i in range(len(commands)): 757*e1fe3e4aSElliott Hughes op, args = commands[i] 758*e1fe3e4aSElliott Hughes 759*e1fe3e4aSElliott Hughes if op in {"0moveto", "0lineto"}: 760*e1fe3e4aSElliott Hughes commands[i] = "h" + op[1:], args 761*e1fe3e4aSElliott Hughes continue 762*e1fe3e4aSElliott Hughes 763*e1fe3e4aSElliott Hughes if op[2:] == "curveto" and op[:2] not in {"rr", "hh", "vv", "vh", "hv"}: 764*e1fe3e4aSElliott Hughes op0, op1 = op[:2] 765*e1fe3e4aSElliott Hughes if (op0 == "r") ^ (op1 == "r"): 766*e1fe3e4aSElliott Hughes assert len(args) % 2 == 1 767*e1fe3e4aSElliott Hughes if op0 == "0": 768*e1fe3e4aSElliott Hughes op0 = "h" 769*e1fe3e4aSElliott Hughes if op1 == "0": 770*e1fe3e4aSElliott Hughes op1 = "h" 771*e1fe3e4aSElliott Hughes if op0 == "r": 772*e1fe3e4aSElliott Hughes op0 = op1 773*e1fe3e4aSElliott Hughes if op1 == "r": 774*e1fe3e4aSElliott Hughes op1 = _negateCategory(op0) 775*e1fe3e4aSElliott Hughes assert {op0, op1} <= {"h", "v"}, (op0, op1) 776*e1fe3e4aSElliott Hughes 777*e1fe3e4aSElliott Hughes if len(args) % 2: 778*e1fe3e4aSElliott Hughes if op0 != op1: # vhcurveto / hvcurveto 779*e1fe3e4aSElliott Hughes if (op0 == "h") ^ (len(args) % 8 == 1): 780*e1fe3e4aSElliott Hughes # Swap last two args order 781*e1fe3e4aSElliott Hughes args = args[:-2] + args[-1:] + args[-2:-1] 782*e1fe3e4aSElliott Hughes else: # hhcurveto / vvcurveto 783*e1fe3e4aSElliott Hughes if op0 == "h": # hhcurveto 784*e1fe3e4aSElliott Hughes # Swap first two args order 785*e1fe3e4aSElliott Hughes args = args[1:2] + args[:1] + args[2:] 786*e1fe3e4aSElliott Hughes 787*e1fe3e4aSElliott Hughes commands[i] = op0 + op1 + "curveto", args 788*e1fe3e4aSElliott Hughes continue 789*e1fe3e4aSElliott Hughes 790*e1fe3e4aSElliott Hughes # 7. For any series of args which are blend lists, convert the series to a single blend arg. 791*e1fe3e4aSElliott Hughes for i in range(len(commands)): 792*e1fe3e4aSElliott Hughes op, args = commands[i] 793*e1fe3e4aSElliott Hughes if any(isinstance(arg, list) for arg in args): 794*e1fe3e4aSElliott Hughes commands[i] = op, _convertToBlendCmds(args) 795*e1fe3e4aSElliott Hughes 796*e1fe3e4aSElliott Hughes return commands 797*e1fe3e4aSElliott Hughes 798*e1fe3e4aSElliott Hughes 799*e1fe3e4aSElliott Hughesdef specializeProgram(program, getNumRegions=None, **kwargs): 800*e1fe3e4aSElliott Hughes return commandsToProgram( 801*e1fe3e4aSElliott Hughes specializeCommands(programToCommands(program, getNumRegions), **kwargs) 802*e1fe3e4aSElliott Hughes ) 803*e1fe3e4aSElliott Hughes 804*e1fe3e4aSElliott Hughes 805*e1fe3e4aSElliott Hughesif __name__ == "__main__": 806*e1fe3e4aSElliott Hughes import sys 807*e1fe3e4aSElliott Hughes 808*e1fe3e4aSElliott Hughes if len(sys.argv) == 1: 809*e1fe3e4aSElliott Hughes import doctest 810*e1fe3e4aSElliott Hughes 811*e1fe3e4aSElliott Hughes sys.exit(doctest.testmod().failed) 812*e1fe3e4aSElliott Hughes 813*e1fe3e4aSElliott Hughes import argparse 814*e1fe3e4aSElliott Hughes 815*e1fe3e4aSElliott Hughes parser = argparse.ArgumentParser( 816*e1fe3e4aSElliott Hughes "fonttools cffLib.specialer", 817*e1fe3e4aSElliott Hughes description="CFF CharString generalizer/specializer", 818*e1fe3e4aSElliott Hughes ) 819*e1fe3e4aSElliott Hughes parser.add_argument("program", metavar="command", nargs="*", help="Commands.") 820*e1fe3e4aSElliott Hughes parser.add_argument( 821*e1fe3e4aSElliott Hughes "--num-regions", 822*e1fe3e4aSElliott Hughes metavar="NumRegions", 823*e1fe3e4aSElliott Hughes nargs="*", 824*e1fe3e4aSElliott Hughes default=None, 825*e1fe3e4aSElliott Hughes help="Number of variable-font regions for blend opertaions.", 826*e1fe3e4aSElliott Hughes ) 827*e1fe3e4aSElliott Hughes 828*e1fe3e4aSElliott Hughes options = parser.parse_args(sys.argv[1:]) 829*e1fe3e4aSElliott Hughes 830*e1fe3e4aSElliott Hughes getNumRegions = ( 831*e1fe3e4aSElliott Hughes None 832*e1fe3e4aSElliott Hughes if options.num_regions is None 833*e1fe3e4aSElliott Hughes else lambda vsIndex: int(options.num_regions[0 if vsIndex is None else vsIndex]) 834*e1fe3e4aSElliott Hughes ) 835*e1fe3e4aSElliott Hughes 836*e1fe3e4aSElliott Hughes program = stringToProgram(options.program) 837*e1fe3e4aSElliott Hughes print("Program:") 838*e1fe3e4aSElliott Hughes print(programToString(program)) 839*e1fe3e4aSElliott Hughes commands = programToCommands(program, getNumRegions) 840*e1fe3e4aSElliott Hughes print("Commands:") 841*e1fe3e4aSElliott Hughes print(commands) 842*e1fe3e4aSElliott Hughes program2 = commandsToProgram(commands) 843*e1fe3e4aSElliott Hughes print("Program from commands:") 844*e1fe3e4aSElliott Hughes print(programToString(program2)) 845*e1fe3e4aSElliott Hughes assert program == program2 846*e1fe3e4aSElliott Hughes print("Generalized program:") 847*e1fe3e4aSElliott Hughes print(programToString(generalizeProgram(program, getNumRegions))) 848*e1fe3e4aSElliott Hughes print("Specialized program:") 849*e1fe3e4aSElliott Hughes print(programToString(specializeProgram(program, getNumRegions))) 850