1*e1fe3e4aSElliott Hughesfrom fontTools.misc.textTools import bytechr, byteord, bytesjoin, tobytes, tostr 2*e1fe3e4aSElliott Hughesfrom fontTools.misc import eexec 3*e1fe3e4aSElliott Hughesfrom .psOperators import ( 4*e1fe3e4aSElliott Hughes PSOperators, 5*e1fe3e4aSElliott Hughes ps_StandardEncoding, 6*e1fe3e4aSElliott Hughes ps_array, 7*e1fe3e4aSElliott Hughes ps_boolean, 8*e1fe3e4aSElliott Hughes ps_dict, 9*e1fe3e4aSElliott Hughes ps_integer, 10*e1fe3e4aSElliott Hughes ps_literal, 11*e1fe3e4aSElliott Hughes ps_mark, 12*e1fe3e4aSElliott Hughes ps_name, 13*e1fe3e4aSElliott Hughes ps_operator, 14*e1fe3e4aSElliott Hughes ps_procedure, 15*e1fe3e4aSElliott Hughes ps_procmark, 16*e1fe3e4aSElliott Hughes ps_real, 17*e1fe3e4aSElliott Hughes ps_string, 18*e1fe3e4aSElliott Hughes) 19*e1fe3e4aSElliott Hughesimport re 20*e1fe3e4aSElliott Hughesfrom collections.abc import Callable 21*e1fe3e4aSElliott Hughesfrom string import whitespace 22*e1fe3e4aSElliott Hughesimport logging 23*e1fe3e4aSElliott Hughes 24*e1fe3e4aSElliott Hughes 25*e1fe3e4aSElliott Hugheslog = logging.getLogger(__name__) 26*e1fe3e4aSElliott Hughes 27*e1fe3e4aSElliott Hughesps_special = b"()<>[]{}%" # / is one too, but we take care of that one differently 28*e1fe3e4aSElliott Hughes 29*e1fe3e4aSElliott HughesskipwhiteRE = re.compile(bytesjoin([b"[", whitespace, b"]*"])) 30*e1fe3e4aSElliott HughesendofthingPat = bytesjoin([b"[^][(){}<>/%", whitespace, b"]*"]) 31*e1fe3e4aSElliott HughesendofthingRE = re.compile(endofthingPat) 32*e1fe3e4aSElliott HughescommentRE = re.compile(b"%[^\n\r]*") 33*e1fe3e4aSElliott Hughes 34*e1fe3e4aSElliott Hughes# XXX This not entirely correct as it doesn't allow *nested* embedded parens: 35*e1fe3e4aSElliott HughesstringPat = rb""" 36*e1fe3e4aSElliott Hughes \( 37*e1fe3e4aSElliott Hughes ( 38*e1fe3e4aSElliott Hughes ( 39*e1fe3e4aSElliott Hughes [^()]* \ [()] 40*e1fe3e4aSElliott Hughes ) 41*e1fe3e4aSElliott Hughes | 42*e1fe3e4aSElliott Hughes ( 43*e1fe3e4aSElliott Hughes [^()]* \( [^()]* \) 44*e1fe3e4aSElliott Hughes ) 45*e1fe3e4aSElliott Hughes )* 46*e1fe3e4aSElliott Hughes [^()]* 47*e1fe3e4aSElliott Hughes \) 48*e1fe3e4aSElliott Hughes""" 49*e1fe3e4aSElliott HughesstringPat = b"".join(stringPat.split()) 50*e1fe3e4aSElliott HughesstringRE = re.compile(stringPat) 51*e1fe3e4aSElliott Hughes 52*e1fe3e4aSElliott HugheshexstringRE = re.compile(bytesjoin([b"<[", whitespace, b"0-9A-Fa-f]*>"])) 53*e1fe3e4aSElliott Hughes 54*e1fe3e4aSElliott Hughes 55*e1fe3e4aSElliott Hughesclass PSTokenError(Exception): 56*e1fe3e4aSElliott Hughes pass 57*e1fe3e4aSElliott Hughes 58*e1fe3e4aSElliott Hughes 59*e1fe3e4aSElliott Hughesclass PSError(Exception): 60*e1fe3e4aSElliott Hughes pass 61*e1fe3e4aSElliott Hughes 62*e1fe3e4aSElliott Hughes 63*e1fe3e4aSElliott Hughesclass PSTokenizer(object): 64*e1fe3e4aSElliott Hughes def __init__(self, buf=b"", encoding="ascii"): 65*e1fe3e4aSElliott Hughes # Force self.buf to be a byte string 66*e1fe3e4aSElliott Hughes buf = tobytes(buf) 67*e1fe3e4aSElliott Hughes self.buf = buf 68*e1fe3e4aSElliott Hughes self.len = len(buf) 69*e1fe3e4aSElliott Hughes self.pos = 0 70*e1fe3e4aSElliott Hughes self.closed = False 71*e1fe3e4aSElliott Hughes self.encoding = encoding 72*e1fe3e4aSElliott Hughes 73*e1fe3e4aSElliott Hughes def read(self, n=-1): 74*e1fe3e4aSElliott Hughes """Read at most 'n' bytes from the buffer, or less if the read 75*e1fe3e4aSElliott Hughes hits EOF before obtaining 'n' bytes. 76*e1fe3e4aSElliott Hughes If 'n' is negative or omitted, read all data until EOF is reached. 77*e1fe3e4aSElliott Hughes """ 78*e1fe3e4aSElliott Hughes if self.closed: 79*e1fe3e4aSElliott Hughes raise ValueError("I/O operation on closed file") 80*e1fe3e4aSElliott Hughes if n is None or n < 0: 81*e1fe3e4aSElliott Hughes newpos = self.len 82*e1fe3e4aSElliott Hughes else: 83*e1fe3e4aSElliott Hughes newpos = min(self.pos + n, self.len) 84*e1fe3e4aSElliott Hughes r = self.buf[self.pos : newpos] 85*e1fe3e4aSElliott Hughes self.pos = newpos 86*e1fe3e4aSElliott Hughes return r 87*e1fe3e4aSElliott Hughes 88*e1fe3e4aSElliott Hughes def close(self): 89*e1fe3e4aSElliott Hughes if not self.closed: 90*e1fe3e4aSElliott Hughes self.closed = True 91*e1fe3e4aSElliott Hughes del self.buf, self.pos 92*e1fe3e4aSElliott Hughes 93*e1fe3e4aSElliott Hughes def getnexttoken( 94*e1fe3e4aSElliott Hughes self, 95*e1fe3e4aSElliott Hughes # localize some stuff, for performance 96*e1fe3e4aSElliott Hughes len=len, 97*e1fe3e4aSElliott Hughes ps_special=ps_special, 98*e1fe3e4aSElliott Hughes stringmatch=stringRE.match, 99*e1fe3e4aSElliott Hughes hexstringmatch=hexstringRE.match, 100*e1fe3e4aSElliott Hughes commentmatch=commentRE.match, 101*e1fe3e4aSElliott Hughes endmatch=endofthingRE.match, 102*e1fe3e4aSElliott Hughes ): 103*e1fe3e4aSElliott Hughes self.skipwhite() 104*e1fe3e4aSElliott Hughes if self.pos >= self.len: 105*e1fe3e4aSElliott Hughes return None, None 106*e1fe3e4aSElliott Hughes pos = self.pos 107*e1fe3e4aSElliott Hughes buf = self.buf 108*e1fe3e4aSElliott Hughes char = bytechr(byteord(buf[pos])) 109*e1fe3e4aSElliott Hughes if char in ps_special: 110*e1fe3e4aSElliott Hughes if char in b"{}[]": 111*e1fe3e4aSElliott Hughes tokentype = "do_special" 112*e1fe3e4aSElliott Hughes token = char 113*e1fe3e4aSElliott Hughes elif char == b"%": 114*e1fe3e4aSElliott Hughes tokentype = "do_comment" 115*e1fe3e4aSElliott Hughes _, nextpos = commentmatch(buf, pos).span() 116*e1fe3e4aSElliott Hughes token = buf[pos:nextpos] 117*e1fe3e4aSElliott Hughes elif char == b"(": 118*e1fe3e4aSElliott Hughes tokentype = "do_string" 119*e1fe3e4aSElliott Hughes m = stringmatch(buf, pos) 120*e1fe3e4aSElliott Hughes if m is None: 121*e1fe3e4aSElliott Hughes raise PSTokenError("bad string at character %d" % pos) 122*e1fe3e4aSElliott Hughes _, nextpos = m.span() 123*e1fe3e4aSElliott Hughes token = buf[pos:nextpos] 124*e1fe3e4aSElliott Hughes elif char == b"<": 125*e1fe3e4aSElliott Hughes tokentype = "do_hexstring" 126*e1fe3e4aSElliott Hughes m = hexstringmatch(buf, pos) 127*e1fe3e4aSElliott Hughes if m is None: 128*e1fe3e4aSElliott Hughes raise PSTokenError("bad hexstring at character %d" % pos) 129*e1fe3e4aSElliott Hughes _, nextpos = m.span() 130*e1fe3e4aSElliott Hughes token = buf[pos:nextpos] 131*e1fe3e4aSElliott Hughes else: 132*e1fe3e4aSElliott Hughes raise PSTokenError("bad token at character %d" % pos) 133*e1fe3e4aSElliott Hughes else: 134*e1fe3e4aSElliott Hughes if char == b"/": 135*e1fe3e4aSElliott Hughes tokentype = "do_literal" 136*e1fe3e4aSElliott Hughes m = endmatch(buf, pos + 1) 137*e1fe3e4aSElliott Hughes else: 138*e1fe3e4aSElliott Hughes tokentype = "" 139*e1fe3e4aSElliott Hughes m = endmatch(buf, pos) 140*e1fe3e4aSElliott Hughes if m is None: 141*e1fe3e4aSElliott Hughes raise PSTokenError("bad token at character %d" % pos) 142*e1fe3e4aSElliott Hughes _, nextpos = m.span() 143*e1fe3e4aSElliott Hughes token = buf[pos:nextpos] 144*e1fe3e4aSElliott Hughes self.pos = pos + len(token) 145*e1fe3e4aSElliott Hughes token = tostr(token, encoding=self.encoding) 146*e1fe3e4aSElliott Hughes return tokentype, token 147*e1fe3e4aSElliott Hughes 148*e1fe3e4aSElliott Hughes def skipwhite(self, whitematch=skipwhiteRE.match): 149*e1fe3e4aSElliott Hughes _, nextpos = whitematch(self.buf, self.pos).span() 150*e1fe3e4aSElliott Hughes self.pos = nextpos 151*e1fe3e4aSElliott Hughes 152*e1fe3e4aSElliott Hughes def starteexec(self): 153*e1fe3e4aSElliott Hughes self.pos = self.pos + 1 154*e1fe3e4aSElliott Hughes self.dirtybuf = self.buf[self.pos :] 155*e1fe3e4aSElliott Hughes self.buf, R = eexec.decrypt(self.dirtybuf, 55665) 156*e1fe3e4aSElliott Hughes self.len = len(self.buf) 157*e1fe3e4aSElliott Hughes self.pos = 4 158*e1fe3e4aSElliott Hughes 159*e1fe3e4aSElliott Hughes def stopeexec(self): 160*e1fe3e4aSElliott Hughes if not hasattr(self, "dirtybuf"): 161*e1fe3e4aSElliott Hughes return 162*e1fe3e4aSElliott Hughes self.buf = self.dirtybuf 163*e1fe3e4aSElliott Hughes del self.dirtybuf 164*e1fe3e4aSElliott Hughes 165*e1fe3e4aSElliott Hughes 166*e1fe3e4aSElliott Hughesclass PSInterpreter(PSOperators): 167*e1fe3e4aSElliott Hughes def __init__(self, encoding="ascii"): 168*e1fe3e4aSElliott Hughes systemdict = {} 169*e1fe3e4aSElliott Hughes userdict = {} 170*e1fe3e4aSElliott Hughes self.encoding = encoding 171*e1fe3e4aSElliott Hughes self.dictstack = [systemdict, userdict] 172*e1fe3e4aSElliott Hughes self.stack = [] 173*e1fe3e4aSElliott Hughes self.proclevel = 0 174*e1fe3e4aSElliott Hughes self.procmark = ps_procmark() 175*e1fe3e4aSElliott Hughes self.fillsystemdict() 176*e1fe3e4aSElliott Hughes 177*e1fe3e4aSElliott Hughes def fillsystemdict(self): 178*e1fe3e4aSElliott Hughes systemdict = self.dictstack[0] 179*e1fe3e4aSElliott Hughes systemdict["["] = systemdict["mark"] = self.mark = ps_mark() 180*e1fe3e4aSElliott Hughes systemdict["]"] = ps_operator("]", self.do_makearray) 181*e1fe3e4aSElliott Hughes systemdict["true"] = ps_boolean(1) 182*e1fe3e4aSElliott Hughes systemdict["false"] = ps_boolean(0) 183*e1fe3e4aSElliott Hughes systemdict["StandardEncoding"] = ps_array(ps_StandardEncoding) 184*e1fe3e4aSElliott Hughes systemdict["FontDirectory"] = ps_dict({}) 185*e1fe3e4aSElliott Hughes self.suckoperators(systemdict, self.__class__) 186*e1fe3e4aSElliott Hughes 187*e1fe3e4aSElliott Hughes def suckoperators(self, systemdict, klass): 188*e1fe3e4aSElliott Hughes for name in dir(klass): 189*e1fe3e4aSElliott Hughes attr = getattr(self, name) 190*e1fe3e4aSElliott Hughes if isinstance(attr, Callable) and name[:3] == "ps_": 191*e1fe3e4aSElliott Hughes name = name[3:] 192*e1fe3e4aSElliott Hughes systemdict[name] = ps_operator(name, attr) 193*e1fe3e4aSElliott Hughes for baseclass in klass.__bases__: 194*e1fe3e4aSElliott Hughes self.suckoperators(systemdict, baseclass) 195*e1fe3e4aSElliott Hughes 196*e1fe3e4aSElliott Hughes def interpret(self, data, getattr=getattr): 197*e1fe3e4aSElliott Hughes tokenizer = self.tokenizer = PSTokenizer(data, self.encoding) 198*e1fe3e4aSElliott Hughes getnexttoken = tokenizer.getnexttoken 199*e1fe3e4aSElliott Hughes do_token = self.do_token 200*e1fe3e4aSElliott Hughes handle_object = self.handle_object 201*e1fe3e4aSElliott Hughes try: 202*e1fe3e4aSElliott Hughes while 1: 203*e1fe3e4aSElliott Hughes tokentype, token = getnexttoken() 204*e1fe3e4aSElliott Hughes if not token: 205*e1fe3e4aSElliott Hughes break 206*e1fe3e4aSElliott Hughes if tokentype: 207*e1fe3e4aSElliott Hughes handler = getattr(self, tokentype) 208*e1fe3e4aSElliott Hughes object = handler(token) 209*e1fe3e4aSElliott Hughes else: 210*e1fe3e4aSElliott Hughes object = do_token(token) 211*e1fe3e4aSElliott Hughes if object is not None: 212*e1fe3e4aSElliott Hughes handle_object(object) 213*e1fe3e4aSElliott Hughes tokenizer.close() 214*e1fe3e4aSElliott Hughes self.tokenizer = None 215*e1fe3e4aSElliott Hughes except: 216*e1fe3e4aSElliott Hughes if self.tokenizer is not None: 217*e1fe3e4aSElliott Hughes log.debug( 218*e1fe3e4aSElliott Hughes "ps error:\n" 219*e1fe3e4aSElliott Hughes "- - - - - - -\n" 220*e1fe3e4aSElliott Hughes "%s\n" 221*e1fe3e4aSElliott Hughes ">>>\n" 222*e1fe3e4aSElliott Hughes "%s\n" 223*e1fe3e4aSElliott Hughes "- - - - - - -", 224*e1fe3e4aSElliott Hughes self.tokenizer.buf[self.tokenizer.pos - 50 : self.tokenizer.pos], 225*e1fe3e4aSElliott Hughes self.tokenizer.buf[self.tokenizer.pos : self.tokenizer.pos + 50], 226*e1fe3e4aSElliott Hughes ) 227*e1fe3e4aSElliott Hughes raise 228*e1fe3e4aSElliott Hughes 229*e1fe3e4aSElliott Hughes def handle_object(self, object): 230*e1fe3e4aSElliott Hughes if not (self.proclevel or object.literal or object.type == "proceduretype"): 231*e1fe3e4aSElliott Hughes if object.type != "operatortype": 232*e1fe3e4aSElliott Hughes object = self.resolve_name(object.value) 233*e1fe3e4aSElliott Hughes if object.literal: 234*e1fe3e4aSElliott Hughes self.push(object) 235*e1fe3e4aSElliott Hughes else: 236*e1fe3e4aSElliott Hughes if object.type == "proceduretype": 237*e1fe3e4aSElliott Hughes self.call_procedure(object) 238*e1fe3e4aSElliott Hughes else: 239*e1fe3e4aSElliott Hughes object.function() 240*e1fe3e4aSElliott Hughes else: 241*e1fe3e4aSElliott Hughes self.push(object) 242*e1fe3e4aSElliott Hughes 243*e1fe3e4aSElliott Hughes def call_procedure(self, proc): 244*e1fe3e4aSElliott Hughes handle_object = self.handle_object 245*e1fe3e4aSElliott Hughes for item in proc.value: 246*e1fe3e4aSElliott Hughes handle_object(item) 247*e1fe3e4aSElliott Hughes 248*e1fe3e4aSElliott Hughes def resolve_name(self, name): 249*e1fe3e4aSElliott Hughes dictstack = self.dictstack 250*e1fe3e4aSElliott Hughes for i in range(len(dictstack) - 1, -1, -1): 251*e1fe3e4aSElliott Hughes if name in dictstack[i]: 252*e1fe3e4aSElliott Hughes return dictstack[i][name] 253*e1fe3e4aSElliott Hughes raise PSError("name error: " + str(name)) 254*e1fe3e4aSElliott Hughes 255*e1fe3e4aSElliott Hughes def do_token( 256*e1fe3e4aSElliott Hughes self, 257*e1fe3e4aSElliott Hughes token, 258*e1fe3e4aSElliott Hughes int=int, 259*e1fe3e4aSElliott Hughes float=float, 260*e1fe3e4aSElliott Hughes ps_name=ps_name, 261*e1fe3e4aSElliott Hughes ps_integer=ps_integer, 262*e1fe3e4aSElliott Hughes ps_real=ps_real, 263*e1fe3e4aSElliott Hughes ): 264*e1fe3e4aSElliott Hughes try: 265*e1fe3e4aSElliott Hughes num = int(token) 266*e1fe3e4aSElliott Hughes except (ValueError, OverflowError): 267*e1fe3e4aSElliott Hughes try: 268*e1fe3e4aSElliott Hughes num = float(token) 269*e1fe3e4aSElliott Hughes except (ValueError, OverflowError): 270*e1fe3e4aSElliott Hughes if "#" in token: 271*e1fe3e4aSElliott Hughes hashpos = token.find("#") 272*e1fe3e4aSElliott Hughes try: 273*e1fe3e4aSElliott Hughes base = int(token[:hashpos]) 274*e1fe3e4aSElliott Hughes num = int(token[hashpos + 1 :], base) 275*e1fe3e4aSElliott Hughes except (ValueError, OverflowError): 276*e1fe3e4aSElliott Hughes return ps_name(token) 277*e1fe3e4aSElliott Hughes else: 278*e1fe3e4aSElliott Hughes return ps_integer(num) 279*e1fe3e4aSElliott Hughes else: 280*e1fe3e4aSElliott Hughes return ps_name(token) 281*e1fe3e4aSElliott Hughes else: 282*e1fe3e4aSElliott Hughes return ps_real(num) 283*e1fe3e4aSElliott Hughes else: 284*e1fe3e4aSElliott Hughes return ps_integer(num) 285*e1fe3e4aSElliott Hughes 286*e1fe3e4aSElliott Hughes def do_comment(self, token): 287*e1fe3e4aSElliott Hughes pass 288*e1fe3e4aSElliott Hughes 289*e1fe3e4aSElliott Hughes def do_literal(self, token): 290*e1fe3e4aSElliott Hughes return ps_literal(token[1:]) 291*e1fe3e4aSElliott Hughes 292*e1fe3e4aSElliott Hughes def do_string(self, token): 293*e1fe3e4aSElliott Hughes return ps_string(token[1:-1]) 294*e1fe3e4aSElliott Hughes 295*e1fe3e4aSElliott Hughes def do_hexstring(self, token): 296*e1fe3e4aSElliott Hughes hexStr = "".join(token[1:-1].split()) 297*e1fe3e4aSElliott Hughes if len(hexStr) % 2: 298*e1fe3e4aSElliott Hughes hexStr = hexStr + "0" 299*e1fe3e4aSElliott Hughes cleanstr = [] 300*e1fe3e4aSElliott Hughes for i in range(0, len(hexStr), 2): 301*e1fe3e4aSElliott Hughes cleanstr.append(chr(int(hexStr[i : i + 2], 16))) 302*e1fe3e4aSElliott Hughes cleanstr = "".join(cleanstr) 303*e1fe3e4aSElliott Hughes return ps_string(cleanstr) 304*e1fe3e4aSElliott Hughes 305*e1fe3e4aSElliott Hughes def do_special(self, token): 306*e1fe3e4aSElliott Hughes if token == "{": 307*e1fe3e4aSElliott Hughes self.proclevel = self.proclevel + 1 308*e1fe3e4aSElliott Hughes return self.procmark 309*e1fe3e4aSElliott Hughes elif token == "}": 310*e1fe3e4aSElliott Hughes proc = [] 311*e1fe3e4aSElliott Hughes while 1: 312*e1fe3e4aSElliott Hughes topobject = self.pop() 313*e1fe3e4aSElliott Hughes if topobject == self.procmark: 314*e1fe3e4aSElliott Hughes break 315*e1fe3e4aSElliott Hughes proc.append(topobject) 316*e1fe3e4aSElliott Hughes self.proclevel = self.proclevel - 1 317*e1fe3e4aSElliott Hughes proc.reverse() 318*e1fe3e4aSElliott Hughes return ps_procedure(proc) 319*e1fe3e4aSElliott Hughes elif token == "[": 320*e1fe3e4aSElliott Hughes return self.mark 321*e1fe3e4aSElliott Hughes elif token == "]": 322*e1fe3e4aSElliott Hughes return ps_name("]") 323*e1fe3e4aSElliott Hughes else: 324*e1fe3e4aSElliott Hughes raise PSTokenError("huh?") 325*e1fe3e4aSElliott Hughes 326*e1fe3e4aSElliott Hughes def push(self, object): 327*e1fe3e4aSElliott Hughes self.stack.append(object) 328*e1fe3e4aSElliott Hughes 329*e1fe3e4aSElliott Hughes def pop(self, *types): 330*e1fe3e4aSElliott Hughes stack = self.stack 331*e1fe3e4aSElliott Hughes if not stack: 332*e1fe3e4aSElliott Hughes raise PSError("stack underflow") 333*e1fe3e4aSElliott Hughes object = stack[-1] 334*e1fe3e4aSElliott Hughes if types: 335*e1fe3e4aSElliott Hughes if object.type not in types: 336*e1fe3e4aSElliott Hughes raise PSError( 337*e1fe3e4aSElliott Hughes "typecheck, expected %s, found %s" % (repr(types), object.type) 338*e1fe3e4aSElliott Hughes ) 339*e1fe3e4aSElliott Hughes del stack[-1] 340*e1fe3e4aSElliott Hughes return object 341*e1fe3e4aSElliott Hughes 342*e1fe3e4aSElliott Hughes def do_makearray(self): 343*e1fe3e4aSElliott Hughes array = [] 344*e1fe3e4aSElliott Hughes while 1: 345*e1fe3e4aSElliott Hughes topobject = self.pop() 346*e1fe3e4aSElliott Hughes if topobject == self.mark: 347*e1fe3e4aSElliott Hughes break 348*e1fe3e4aSElliott Hughes array.append(topobject) 349*e1fe3e4aSElliott Hughes array.reverse() 350*e1fe3e4aSElliott Hughes self.push(ps_array(array)) 351*e1fe3e4aSElliott Hughes 352*e1fe3e4aSElliott Hughes def close(self): 353*e1fe3e4aSElliott Hughes """Remove circular references.""" 354*e1fe3e4aSElliott Hughes del self.stack 355*e1fe3e4aSElliott Hughes del self.dictstack 356*e1fe3e4aSElliott Hughes 357*e1fe3e4aSElliott Hughes 358*e1fe3e4aSElliott Hughesdef unpack_item(item): 359*e1fe3e4aSElliott Hughes tp = type(item.value) 360*e1fe3e4aSElliott Hughes if tp == dict: 361*e1fe3e4aSElliott Hughes newitem = {} 362*e1fe3e4aSElliott Hughes for key, value in item.value.items(): 363*e1fe3e4aSElliott Hughes newitem[key] = unpack_item(value) 364*e1fe3e4aSElliott Hughes elif tp == list: 365*e1fe3e4aSElliott Hughes newitem = [None] * len(item.value) 366*e1fe3e4aSElliott Hughes for i in range(len(item.value)): 367*e1fe3e4aSElliott Hughes newitem[i] = unpack_item(item.value[i]) 368*e1fe3e4aSElliott Hughes if item.type == "proceduretype": 369*e1fe3e4aSElliott Hughes newitem = tuple(newitem) 370*e1fe3e4aSElliott Hughes else: 371*e1fe3e4aSElliott Hughes newitem = item.value 372*e1fe3e4aSElliott Hughes return newitem 373*e1fe3e4aSElliott Hughes 374*e1fe3e4aSElliott Hughes 375*e1fe3e4aSElliott Hughesdef suckfont(data, encoding="ascii"): 376*e1fe3e4aSElliott Hughes m = re.search(rb"/FontName\s+/([^ \t\n\r]+)\s+def", data) 377*e1fe3e4aSElliott Hughes if m: 378*e1fe3e4aSElliott Hughes fontName = m.group(1) 379*e1fe3e4aSElliott Hughes fontName = fontName.decode() 380*e1fe3e4aSElliott Hughes else: 381*e1fe3e4aSElliott Hughes fontName = None 382*e1fe3e4aSElliott Hughes interpreter = PSInterpreter(encoding=encoding) 383*e1fe3e4aSElliott Hughes interpreter.interpret( 384*e1fe3e4aSElliott Hughes b"/Helvetica 4 dict dup /Encoding StandardEncoding put definefont pop" 385*e1fe3e4aSElliott Hughes ) 386*e1fe3e4aSElliott Hughes interpreter.interpret(data) 387*e1fe3e4aSElliott Hughes fontdir = interpreter.dictstack[0]["FontDirectory"].value 388*e1fe3e4aSElliott Hughes if fontName in fontdir: 389*e1fe3e4aSElliott Hughes rawfont = fontdir[fontName] 390*e1fe3e4aSElliott Hughes else: 391*e1fe3e4aSElliott Hughes # fall back, in case fontName wasn't found 392*e1fe3e4aSElliott Hughes fontNames = list(fontdir.keys()) 393*e1fe3e4aSElliott Hughes if len(fontNames) > 1: 394*e1fe3e4aSElliott Hughes fontNames.remove("Helvetica") 395*e1fe3e4aSElliott Hughes fontNames.sort() 396*e1fe3e4aSElliott Hughes rawfont = fontdir[fontNames[0]] 397*e1fe3e4aSElliott Hughes interpreter.close() 398*e1fe3e4aSElliott Hughes return unpack_item(rawfont) 399