• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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