Home | History | Annotate | Download | only in misc
      1 from __future__ import print_function, division, absolute_import
      2 from fontTools.misc.py23 import *
      3 from fontTools.misc import eexec
      4 from .psOperators import *
      5 import re
      6 import collections
      7 from string import whitespace
      8 
      9 
     10 ps_special = '()<>[]{}%'	# / is one too, but we take care of that one differently
     11 
     12 skipwhiteRE = re.compile("[%s]*" % whitespace)
     13 endofthingPat = "[^][(){}<>/%%%s]*" % whitespace
     14 endofthingRE = re.compile(endofthingPat)
     15 commentRE = re.compile("%[^\n\r]*")
     16 
     17 # XXX This not entirely correct as it doesn't allow *nested* embedded parens:
     18 stringPat = r"""
     19 	\(
     20 		(
     21 			(
     22 				[^()]*   \   [()]
     23 			)
     24 			|
     25 			(
     26 				[^()]*  \(   [^()]*  \)
     27 			)
     28 		)*
     29 		[^()]*
     30 	\)
     31 """
     32 stringPat = "".join(stringPat.split())
     33 stringRE = re.compile(stringPat)
     34 
     35 hexstringRE = re.compile("<[%s0-9A-Fa-f]*>" % whitespace)
     36 
     37 class PSTokenError(Exception): pass
     38 class PSError(Exception): pass
     39 
     40 
     41 class 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 
    122 class 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 
    305 def 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 
    321 def 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 
    345 if __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