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