17842e56b97ce677b83bdab09cda48bc2d89ac75aJust"""fontTools.t1Lib.py -- Tools for PostScript Type 1 fonts
27842e56b97ce677b83bdab09cda48bc2d89ac75aJust
37842e56b97ce677b83bdab09cda48bc2d89ac75aJustFunctions for reading and writing raw Type 1 data:
47842e56b97ce677b83bdab09cda48bc2d89ac75aJust
57842e56b97ce677b83bdab09cda48bc2d89ac75aJustread(path)
67842e56b97ce677b83bdab09cda48bc2d89ac75aJust	reads any Type 1 font file, returns the raw data and a type indicator:
77842e56b97ce677b83bdab09cda48bc2d89ac75aJust	'LWFN', 'PFB' or 'OTHER', depending on the format of the file pointed
87842e56b97ce677b83bdab09cda48bc2d89ac75aJust	to by 'path'.
97842e56b97ce677b83bdab09cda48bc2d89ac75aJust	Raises an error when the file does not contain valid Type 1 data.
107842e56b97ce677b83bdab09cda48bc2d89ac75aJust
11dc87372c88dfd3bb4418c4113d9301102324359eBehdad Esfahbodwrite(path, data, kind='OTHER', dohex=False)
127842e56b97ce677b83bdab09cda48bc2d89ac75aJust	writes raw Type 1 data to the file pointed to by 'path'.
137842e56b97ce677b83bdab09cda48bc2d89ac75aJust	'kind' can be one of 'LWFN', 'PFB' or 'OTHER'; it defaults to 'OTHER'.
147842e56b97ce677b83bdab09cda48bc2d89ac75aJust	'dohex' is a flag which determines whether the eexec encrypted
157842e56b97ce677b83bdab09cda48bc2d89ac75aJust	part should be written as hexadecimal or binary, but only if kind
167842e56b97ce677b83bdab09cda48bc2d89ac75aJust	is 'LWFN' or 'PFB'.
177842e56b97ce677b83bdab09cda48bc2d89ac75aJust"""
181ae29591efbb29492ce05378909ccf4028d7c1eeBehdad Esfahbodfrom __future__ import print_function, division, absolute_import
1930e691edd056ba22fa8970280e986747817bec3dBehdad Esfahbodfrom fontTools.misc.py23 import *
20c2be3d982b04c4bbb1c11d9ab8452f78415d6522Justfrom fontTools.misc import eexec
2145d1f3b3b552297484bc2b8e9a2e999630bb5e50jvrfrom fontTools.misc.macCreatorType import getMacCreatorAndType
227842e56b97ce677b83bdab09cda48bc2d89ac75aJustimport os
2330e691edd056ba22fa8970280e986747817bec3dBehdad Esfahbodimport re
247842e56b97ce677b83bdab09cda48bc2d89ac75aJust
25af1c9968b2edc827ee9115355a72b3c56c4fbe61Denis Jacquerye__author__ = "jvr"
26af1c9968b2edc827ee9115355a72b3c56c4fbe61Denis Jacquerye__version__ = "1.0b2"
27af1c9968b2edc827ee9115355a72b3c56c4fbe61Denis JacqueryeDEBUG = 0
28af1c9968b2edc827ee9115355a72b3c56c4fbe61Denis Jacquerye
2922433b1f52c34a04cf13e21103ee978f7f5b501cjvr
3022433b1f52c34a04cf13e21103ee978f7f5b501cjvrtry:
3125ccb9c3460b05522eae9283a4c9ea783da87aa4jvr	try:
3225ccb9c3460b05522eae9283a4c9ea783da87aa4jvr		from Carbon import Res
3325ccb9c3460b05522eae9283a4c9ea783da87aa4jvr	except ImportError:
3425ccb9c3460b05522eae9283a4c9ea783da87aa4jvr		import Res  # MacPython < 2.2
35e568dc77d3baccc303d5115e2686f126c96c8a26jvrexcept ImportError:
36e568dc77d3baccc303d5115e2686f126c96c8a26jvr	haveMacSupport = 0
37e568dc77d3baccc303d5115e2686f126c96c8a26jvrelse:
38e568dc77d3baccc303d5115e2686f126c96c8a26jvr	haveMacSupport = 1
39b19141e48930ddd3106a6fe303fa2e6b76d35f47jvr	import MacOS
4059afba7684d4c2f7760be5dbc1aeffceb8ced46fjvr
417842e56b97ce677b83bdab09cda48bc2d89ac75aJust
42e568dc77d3baccc303d5115e2686f126c96c8a26jvrclass T1Error(Exception): pass
437842e56b97ce677b83bdab09cda48bc2d89ac75aJust
447842e56b97ce677b83bdab09cda48bc2d89ac75aJust
45e388db566b9ba42669c7e353db4293cf27bc2a5bBehdad Esfahbodclass T1Font(object):
467842e56b97ce677b83bdab09cda48bc2d89ac75aJust
473618300613e796ff81abddde967b1092ac1dc915Just	"""Type 1 font class.
483618300613e796ff81abddde967b1092ac1dc915Just
493618300613e796ff81abddde967b1092ac1dc915Just	Uses a minimal interpeter that supports just about enough PS to parse
503618300613e796ff81abddde967b1092ac1dc915Just	Type 1 fonts.
517842e56b97ce677b83bdab09cda48bc2d89ac75aJust	"""
527842e56b97ce677b83bdab09cda48bc2d89ac75aJust
537842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def __init__(self, path=None):
547842e56b97ce677b83bdab09cda48bc2d89ac75aJust		if path is not None:
557842e56b97ce677b83bdab09cda48bc2d89ac75aJust			self.data, type = read(path)
567842e56b97ce677b83bdab09cda48bc2d89ac75aJust		else:
577842e56b97ce677b83bdab09cda48bc2d89ac75aJust			pass # XXX
587842e56b97ce677b83bdab09cda48bc2d89ac75aJust
597842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def saveAs(self, path, type):
608c74f4639a4494d9d84d1220cbe895de4ffb8aacjvr		write(path, self.getData(), type)
617842e56b97ce677b83bdab09cda48bc2d89ac75aJust
627842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def getData(self):
633618300613e796ff81abddde967b1092ac1dc915Just		# XXX Todo: if the data has been converted to Python object,
643618300613e796ff81abddde967b1092ac1dc915Just		# recreate the PS stream
657842e56b97ce677b83bdab09cda48bc2d89ac75aJust		return self.data
667842e56b97ce677b83bdab09cda48bc2d89ac75aJust
677d4b693627c128bea3ad0c6dd49e923fe5ca4947jvr	def getGlyphSet(self):
687d4b693627c128bea3ad0c6dd49e923fe5ca4947jvr		"""Return a generic GlyphSet, which is a dict-like object
697d4b693627c128bea3ad0c6dd49e923fe5ca4947jvr		mapping glyph names to glyph objects. The returned glyph objects
707d4b693627c128bea3ad0c6dd49e923fe5ca4947jvr		have a .draw() method that supports the Pen protocol, and will
717d4b693627c128bea3ad0c6dd49e923fe5ca4947jvr		have an attribute named 'width', but only *after* the .draw() method
727d4b693627c128bea3ad0c6dd49e923fe5ca4947jvr		has been called.
737d4b693627c128bea3ad0c6dd49e923fe5ca4947jvr
747d4b693627c128bea3ad0c6dd49e923fe5ca4947jvr		In the case of Type 1, the GlyphSet is simply the CharStrings dict.
757d4b693627c128bea3ad0c6dd49e923fe5ca4947jvr		"""
767d4b693627c128bea3ad0c6dd49e923fe5ca4947jvr		return self["CharStrings"]
777d4b693627c128bea3ad0c6dd49e923fe5ca4947jvr
787842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def __getitem__(self, key):
797842e56b97ce677b83bdab09cda48bc2d89ac75aJust		if not hasattr(self, "font"):
807842e56b97ce677b83bdab09cda48bc2d89ac75aJust			self.parse()
813618300613e796ff81abddde967b1092ac1dc915Just		return self.font[key]
827842e56b97ce677b83bdab09cda48bc2d89ac75aJust
837842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def parse(self):
84528614e6e254dfe3c501ff440c291c6c55de5e6fJust		from fontTools.misc import psLib
85528614e6e254dfe3c501ff440c291c6c55de5e6fJust		from fontTools.misc import psCharStrings
867842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.font = psLib.suckfont(self.data)
877842e56b97ce677b83bdab09cda48bc2d89ac75aJust		charStrings = self.font["CharStrings"]
887842e56b97ce677b83bdab09cda48bc2d89ac75aJust		lenIV = self.font["Private"].get("lenIV", 4)
897842e56b97ce677b83bdab09cda48bc2d89ac75aJust		assert lenIV >= 0
90489d76a340845361def6af9ab7d9152f8e66f417jvr		subrs = self.font["Private"]["Subrs"]
917842e56b97ce677b83bdab09cda48bc2d89ac75aJust		for glyphName, charString in charStrings.items():
92c2be3d982b04c4bbb1c11d9ab8452f78415d6522Just			charString, R = eexec.decrypt(charString, 4330)
93489d76a340845361def6af9ab7d9152f8e66f417jvr			charStrings[glyphName] = psCharStrings.T1CharString(charString[lenIV:],
94489d76a340845361def6af9ab7d9152f8e66f417jvr					subrs=subrs)
957842e56b97ce677b83bdab09cda48bc2d89ac75aJust		for i in range(len(subrs)):
96c2be3d982b04c4bbb1c11d9ab8452f78415d6522Just			charString, R = eexec.decrypt(subrs[i], 4330)
97489d76a340845361def6af9ab7d9152f8e66f417jvr			subrs[i] = psCharStrings.T1CharString(charString[lenIV:], subrs=subrs)
987842e56b97ce677b83bdab09cda48bc2d89ac75aJust		del self.data
997842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1007842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1013618300613e796ff81abddde967b1092ac1dc915Just# low level T1 data read and write functions
1027842e56b97ce677b83bdab09cda48bc2d89ac75aJust
103dc87372c88dfd3bb4418c4113d9301102324359eBehdad Esfahboddef read(path, onlyHeader=False):
1047842e56b97ce677b83bdab09cda48bc2d89ac75aJust	"""reads any Type 1 font file, returns raw data"""
10514fb031125b773f0a15eb19be4f02ed8540b2db6Behdad Esfahbod	normpath = path.lower()
106153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod	creator, typ = getMacCreatorAndType(path)
107153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod	if typ == 'LWFN':
10845d1f3b3b552297484bc2b8e9a2e999630bb5e50jvr		return readLWFN(path, onlyHeader), 'LWFN'
1097842e56b97ce677b83bdab09cda48bc2d89ac75aJust	if normpath[-4:] == '.pfb':
11010fd22a9890ce20091a20e70660677db779d4b64jvr		return readPFB(path, onlyHeader), 'PFB'
1117842e56b97ce677b83bdab09cda48bc2d89ac75aJust	else:
1125810aa9967488207b039cb2d300fa53c91d4df2fJust		return readOther(path), 'OTHER'
1137842e56b97ce677b83bdab09cda48bc2d89ac75aJust
114dc87372c88dfd3bb4418c4113d9301102324359eBehdad Esfahboddef write(path, data, kind='OTHER', dohex=False):
1155810aa9967488207b039cb2d300fa53c91d4df2fJust	assertType1(data)
11614fb031125b773f0a15eb19be4f02ed8540b2db6Behdad Esfahbod	kind = kind.upper()
1177842e56b97ce677b83bdab09cda48bc2d89ac75aJust	try:
1187842e56b97ce677b83bdab09cda48bc2d89ac75aJust		os.remove(path)
1197842e56b97ce677b83bdab09cda48bc2d89ac75aJust	except os.error:
1207842e56b97ce677b83bdab09cda48bc2d89ac75aJust		pass
1217842e56b97ce677b83bdab09cda48bc2d89ac75aJust	err = 1
1227842e56b97ce677b83bdab09cda48bc2d89ac75aJust	try:
1237842e56b97ce677b83bdab09cda48bc2d89ac75aJust		if kind == 'LWFN':
1245810aa9967488207b039cb2d300fa53c91d4df2fJust			writeLWFN(path, data)
1257842e56b97ce677b83bdab09cda48bc2d89ac75aJust		elif kind == 'PFB':
1265810aa9967488207b039cb2d300fa53c91d4df2fJust			writePFB(path, data)
1277842e56b97ce677b83bdab09cda48bc2d89ac75aJust		else:
1285810aa9967488207b039cb2d300fa53c91d4df2fJust			writeOther(path, data, dohex)
1297842e56b97ce677b83bdab09cda48bc2d89ac75aJust		err = 0
1307842e56b97ce677b83bdab09cda48bc2d89ac75aJust	finally:
1317842e56b97ce677b83bdab09cda48bc2d89ac75aJust		if err and not DEBUG:
1327842e56b97ce677b83bdab09cda48bc2d89ac75aJust			try:
1337842e56b97ce677b83bdab09cda48bc2d89ac75aJust				os.remove(path)
1347842e56b97ce677b83bdab09cda48bc2d89ac75aJust			except os.error:
1357842e56b97ce677b83bdab09cda48bc2d89ac75aJust				pass
1367842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1377842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1387842e56b97ce677b83bdab09cda48bc2d89ac75aJust# -- internal --
1397842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1407842e56b97ce677b83bdab09cda48bc2d89ac75aJustLWFNCHUNKSIZE = 2000
1417842e56b97ce677b83bdab09cda48bc2d89ac75aJustHEXLINELENGTH = 80
1427842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1437842e56b97ce677b83bdab09cda48bc2d89ac75aJust
144dc87372c88dfd3bb4418c4113d9301102324359eBehdad Esfahboddef readLWFN(path, onlyHeader=False):
1457842e56b97ce677b83bdab09cda48bc2d89ac75aJust	"""reads an LWFN font file, returns raw data"""
14691bca4244286fb519c93fe92329da96b0e6f32eejvr	resRef = Res.FSOpenResFile(path, 1)  # read-only
1477842e56b97ce677b83bdab09cda48bc2d89ac75aJust	try:
148e9601bf9e1253f77b8a66f27685fae453ce98b14Just		Res.UseResFile(resRef)
1497842e56b97ce677b83bdab09cda48bc2d89ac75aJust		n = Res.Count1Resources('POST')
1507842e56b97ce677b83bdab09cda48bc2d89ac75aJust		data = []
1517842e56b97ce677b83bdab09cda48bc2d89ac75aJust		for i in range(501, 501 + n):
1527842e56b97ce677b83bdab09cda48bc2d89ac75aJust			res = Res.Get1Resource('POST', i)
153319c5fd10e2ea84304bd299b7483e05b5b0d5480Behdad Esfahbod			code = byteord(res.data[0])
154319c5fd10e2ea84304bd299b7483e05b5b0d5480Behdad Esfahbod			if byteord(res.data[1]) != 0:
155cd5aad92f23737ff93a110d5c73d624658a28da8Behdad Esfahbod				raise T1Error('corrupt LWFN file')
1567842e56b97ce677b83bdab09cda48bc2d89ac75aJust			if code in [1, 2]:
157da0d805d2603dd9b83c695006521f887b68d5505jvr				if onlyHeader and code == 2:
158da0d805d2603dd9b83c695006521f887b68d5505jvr					break
15905a16f2310e26193557a3dc223ac0efeb166789fjvr				data.append(res.data[2:])
1607842e56b97ce677b83bdab09cda48bc2d89ac75aJust			elif code in [3, 5]:
1617842e56b97ce677b83bdab09cda48bc2d89ac75aJust				break
1627842e56b97ce677b83bdab09cda48bc2d89ac75aJust			elif code == 4:
1637842e56b97ce677b83bdab09cda48bc2d89ac75aJust				f = open(path, "rb")
1647842e56b97ce677b83bdab09cda48bc2d89ac75aJust				data.append(f.read())
1657842e56b97ce677b83bdab09cda48bc2d89ac75aJust				f.close()
1667842e56b97ce677b83bdab09cda48bc2d89ac75aJust			elif code == 0:
1677842e56b97ce677b83bdab09cda48bc2d89ac75aJust				pass # comment, ignore
1687842e56b97ce677b83bdab09cda48bc2d89ac75aJust			else:
169dc7e6f3e5563a853477ebe26166b002c158dbe8bBehdad Esfahbod				raise T1Error('bad chunk code: ' + repr(code))
1707842e56b97ce677b83bdab09cda48bc2d89ac75aJust	finally:
171e9601bf9e1253f77b8a66f27685fae453ce98b14Just		Res.CloseResFile(resRef)
17218316aa769566eeb6f3f4a6ed2685fa8f8e861c2Behdad Esfahbod	data = bytesjoin(data)
1735810aa9967488207b039cb2d300fa53c91d4df2fJust	assertType1(data)
1747842e56b97ce677b83bdab09cda48bc2d89ac75aJust	return data
1757842e56b97ce677b83bdab09cda48bc2d89ac75aJust
176dc87372c88dfd3bb4418c4113d9301102324359eBehdad Esfahboddef readPFB(path, onlyHeader=False):
1777842e56b97ce677b83bdab09cda48bc2d89ac75aJust	"""reads a PFB font file, returns raw data"""
1787842e56b97ce677b83bdab09cda48bc2d89ac75aJust	f = open(path, "rb")
1797842e56b97ce677b83bdab09cda48bc2d89ac75aJust	data = []
180ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod	while True:
181b7a2d797a40fb658d1e6dca6c08c9d2e1d83e78aBehdad Esfahbod		if f.read(1) != bytechr(128):
182cd5aad92f23737ff93a110d5c73d624658a28da8Behdad Esfahbod			raise T1Error('corrupt PFB file')
183319c5fd10e2ea84304bd299b7483e05b5b0d5480Behdad Esfahbod		code = byteord(f.read(1))
1847842e56b97ce677b83bdab09cda48bc2d89ac75aJust		if code in [1, 2]:
1855810aa9967488207b039cb2d300fa53c91d4df2fJust			chunklen = stringToLong(f.read(4))
1863618300613e796ff81abddde967b1092ac1dc915Just			chunk = f.read(chunklen)
1873618300613e796ff81abddde967b1092ac1dc915Just			assert len(chunk) == chunklen
1883618300613e796ff81abddde967b1092ac1dc915Just			data.append(chunk)
1897842e56b97ce677b83bdab09cda48bc2d89ac75aJust		elif code == 3:
1907842e56b97ce677b83bdab09cda48bc2d89ac75aJust			break
1917842e56b97ce677b83bdab09cda48bc2d89ac75aJust		else:
192dc7e6f3e5563a853477ebe26166b002c158dbe8bBehdad Esfahbod			raise T1Error('bad chunk code: ' + repr(code))
1935810aa9967488207b039cb2d300fa53c91d4df2fJust		if onlyHeader:
1945810aa9967488207b039cb2d300fa53c91d4df2fJust			break
1957842e56b97ce677b83bdab09cda48bc2d89ac75aJust	f.close()
19618316aa769566eeb6f3f4a6ed2685fa8f8e861c2Behdad Esfahbod	data = bytesjoin(data)
1975810aa9967488207b039cb2d300fa53c91d4df2fJust	assertType1(data)
1987842e56b97ce677b83bdab09cda48bc2d89ac75aJust	return data
1997842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2005810aa9967488207b039cb2d300fa53c91d4df2fJustdef readOther(path):
2017842e56b97ce677b83bdab09cda48bc2d89ac75aJust	"""reads any (font) file, returns raw data"""
2027842e56b97ce677b83bdab09cda48bc2d89ac75aJust	f = open(path, "rb")
2037842e56b97ce677b83bdab09cda48bc2d89ac75aJust	data = f.read()
2047842e56b97ce677b83bdab09cda48bc2d89ac75aJust	f.close()
2055810aa9967488207b039cb2d300fa53c91d4df2fJust	assertType1(data)
2067842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2075810aa9967488207b039cb2d300fa53c91d4df2fJust	chunks = findEncryptedChunks(data)
2087842e56b97ce677b83bdab09cda48bc2d89ac75aJust	data = []
2095810aa9967488207b039cb2d300fa53c91d4df2fJust	for isEncrypted, chunk in chunks:
2105810aa9967488207b039cb2d300fa53c91d4df2fJust		if isEncrypted and isHex(chunk[:4]):
2115810aa9967488207b039cb2d300fa53c91d4df2fJust			data.append(deHexString(chunk))
2127842e56b97ce677b83bdab09cda48bc2d89ac75aJust		else:
2137842e56b97ce677b83bdab09cda48bc2d89ac75aJust			data.append(chunk)
21418316aa769566eeb6f3f4a6ed2685fa8f8e861c2Behdad Esfahbod	return bytesjoin(data)
2157842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2167842e56b97ce677b83bdab09cda48bc2d89ac75aJust# file writing tools
2177842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2185810aa9967488207b039cb2d300fa53c91d4df2fJustdef writeLWFN(path, data):
219e9601bf9e1253f77b8a66f27685fae453ce98b14Just	Res.FSpCreateResFile(path, "just", "LWFN", 0)
22091bca4244286fb519c93fe92329da96b0e6f32eejvr	resRef = Res.FSOpenResFile(path, 2)  # write-only
2217842e56b97ce677b83bdab09cda48bc2d89ac75aJust	try:
222e9601bf9e1253f77b8a66f27685fae453ce98b14Just		Res.UseResFile(resRef)
2237842e56b97ce677b83bdab09cda48bc2d89ac75aJust		resID = 501
2245810aa9967488207b039cb2d300fa53c91d4df2fJust		chunks = findEncryptedChunks(data)
2255810aa9967488207b039cb2d300fa53c91d4df2fJust		for isEncrypted, chunk in chunks:
2265810aa9967488207b039cb2d300fa53c91d4df2fJust			if isEncrypted:
2277842e56b97ce677b83bdab09cda48bc2d89ac75aJust				code = 2
2287842e56b97ce677b83bdab09cda48bc2d89ac75aJust			else:
2297842e56b97ce677b83bdab09cda48bc2d89ac75aJust				code = 1
2307842e56b97ce677b83bdab09cda48bc2d89ac75aJust			while chunk:
231b7a2d797a40fb658d1e6dca6c08c9d2e1d83e78aBehdad Esfahbod				res = Res.Resource(bytechr(code) + '\0' + chunk[:LWFNCHUNKSIZE - 2])
2327842e56b97ce677b83bdab09cda48bc2d89ac75aJust				res.AddResource('POST', resID, '')
2337842e56b97ce677b83bdab09cda48bc2d89ac75aJust				chunk = chunk[LWFNCHUNKSIZE - 2:]
2347842e56b97ce677b83bdab09cda48bc2d89ac75aJust				resID = resID + 1
235b7a2d797a40fb658d1e6dca6c08c9d2e1d83e78aBehdad Esfahbod		res = Res.Resource(bytechr(5) + '\0')
2367842e56b97ce677b83bdab09cda48bc2d89ac75aJust		res.AddResource('POST', resID, '')
2377842e56b97ce677b83bdab09cda48bc2d89ac75aJust	finally:
238e9601bf9e1253f77b8a66f27685fae453ce98b14Just		Res.CloseResFile(resRef)
2397842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2405810aa9967488207b039cb2d300fa53c91d4df2fJustdef writePFB(path, data):
2415810aa9967488207b039cb2d300fa53c91d4df2fJust	chunks = findEncryptedChunks(data)
2423618300613e796ff81abddde967b1092ac1dc915Just	f = open(path, "wb")
2437842e56b97ce677b83bdab09cda48bc2d89ac75aJust	try:
2445810aa9967488207b039cb2d300fa53c91d4df2fJust		for isEncrypted, chunk in chunks:
2455810aa9967488207b039cb2d300fa53c91d4df2fJust			if isEncrypted:
2467842e56b97ce677b83bdab09cda48bc2d89ac75aJust				code = 2
2477842e56b97ce677b83bdab09cda48bc2d89ac75aJust			else:
2487842e56b97ce677b83bdab09cda48bc2d89ac75aJust				code = 1
249b7a2d797a40fb658d1e6dca6c08c9d2e1d83e78aBehdad Esfahbod			f.write(bytechr(128) + bytechr(code))
2505810aa9967488207b039cb2d300fa53c91d4df2fJust			f.write(longToString(len(chunk)))
2517842e56b97ce677b83bdab09cda48bc2d89ac75aJust			f.write(chunk)
252b7a2d797a40fb658d1e6dca6c08c9d2e1d83e78aBehdad Esfahbod		f.write(bytechr(128) + bytechr(3))
2537842e56b97ce677b83bdab09cda48bc2d89ac75aJust	finally:
2547842e56b97ce677b83bdab09cda48bc2d89ac75aJust		f.close()
2557842e56b97ce677b83bdab09cda48bc2d89ac75aJust
256dc87372c88dfd3bb4418c4113d9301102324359eBehdad Esfahboddef writeOther(path, data, dohex=False):
2575810aa9967488207b039cb2d300fa53c91d4df2fJust	chunks = findEncryptedChunks(data)
2587842e56b97ce677b83bdab09cda48bc2d89ac75aJust	f = open(path, "wb")
2597842e56b97ce677b83bdab09cda48bc2d89ac75aJust	try:
26032c10eecffb4923e0721c395e4b80fb732543f18Behdad Esfahbod		hexlinelen = HEXLINELENGTH // 2
2615810aa9967488207b039cb2d300fa53c91d4df2fJust		for isEncrypted, chunk in chunks:
2625810aa9967488207b039cb2d300fa53c91d4df2fJust			if isEncrypted:
2637842e56b97ce677b83bdab09cda48bc2d89ac75aJust				code = 2
2647842e56b97ce677b83bdab09cda48bc2d89ac75aJust			else:
2657842e56b97ce677b83bdab09cda48bc2d89ac75aJust				code = 1
2667842e56b97ce677b83bdab09cda48bc2d89ac75aJust			if code == 2 and dohex:
2677842e56b97ce677b83bdab09cda48bc2d89ac75aJust				while chunk:
268c2be3d982b04c4bbb1c11d9ab8452f78415d6522Just					f.write(eexec.hexString(chunk[:hexlinelen]))
2697842e56b97ce677b83bdab09cda48bc2d89ac75aJust					f.write('\r')
2707842e56b97ce677b83bdab09cda48bc2d89ac75aJust					chunk = chunk[hexlinelen:]
2717842e56b97ce677b83bdab09cda48bc2d89ac75aJust			else:
2727842e56b97ce677b83bdab09cda48bc2d89ac75aJust				f.write(chunk)
2737842e56b97ce677b83bdab09cda48bc2d89ac75aJust	finally:
2747842e56b97ce677b83bdab09cda48bc2d89ac75aJust		f.close()
2757842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2767842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2777842e56b97ce677b83bdab09cda48bc2d89ac75aJust# decryption tools
2787842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2797842e56b97ce677b83bdab09cda48bc2d89ac75aJustEEXECBEGIN = "currentfile eexec"
2807842e56b97ce677b83bdab09cda48bc2d89ac75aJustEEXECEND = '0' * 64
2817842e56b97ce677b83bdab09cda48bc2d89ac75aJustEEXECINTERNALEND = "currentfile closefile"
2827842e56b97ce677b83bdab09cda48bc2d89ac75aJustEEXECBEGINMARKER = "%-- eexec start\r"
2837842e56b97ce677b83bdab09cda48bc2d89ac75aJustEEXECENDMARKER = "%-- eexec end\r"
2847842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2857842e56b97ce677b83bdab09cda48bc2d89ac75aJust_ishexRE = re.compile('[0-9A-Fa-f]*$')
2867842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2875810aa9967488207b039cb2d300fa53c91d4df2fJustdef isHex(text):
2887842e56b97ce677b83bdab09cda48bc2d89ac75aJust	return _ishexRE.match(text) is not None
2897842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2907842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2915810aa9967488207b039cb2d300fa53c91d4df2fJustdef decryptType1(data):
2925810aa9967488207b039cb2d300fa53c91d4df2fJust	chunks = findEncryptedChunks(data)
2937842e56b97ce677b83bdab09cda48bc2d89ac75aJust	data = []
2945810aa9967488207b039cb2d300fa53c91d4df2fJust	for isEncrypted, chunk in chunks:
2955810aa9967488207b039cb2d300fa53c91d4df2fJust		if isEncrypted:
2965810aa9967488207b039cb2d300fa53c91d4df2fJust			if isHex(chunk[:4]):
2975810aa9967488207b039cb2d300fa53c91d4df2fJust				chunk = deHexString(chunk)
298c2be3d982b04c4bbb1c11d9ab8452f78415d6522Just			decrypted, R = eexec.decrypt(chunk, 55665)
2997842e56b97ce677b83bdab09cda48bc2d89ac75aJust			decrypted = decrypted[4:]
300180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod			if decrypted[-len(EEXECINTERNALEND)-1:-1] != EEXECINTERNALEND \
301180ace6a5ff1399ec53bc696e8bef7cce6eef39aBehdad Esfahbod					and decrypted[-len(EEXECINTERNALEND)-2:-2] != EEXECINTERNALEND:
302cd5aad92f23737ff93a110d5c73d624658a28da8Behdad Esfahbod				raise T1Error("invalid end of eexec part")
3037842e56b97ce677b83bdab09cda48bc2d89ac75aJust			decrypted = decrypted[:-len(EEXECINTERNALEND)-2] + '\r'
3047842e56b97ce677b83bdab09cda48bc2d89ac75aJust			data.append(EEXECBEGINMARKER + decrypted + EEXECENDMARKER)
3057842e56b97ce677b83bdab09cda48bc2d89ac75aJust		else:
3067842e56b97ce677b83bdab09cda48bc2d89ac75aJust			if chunk[-len(EEXECBEGIN)-1:-1] == EEXECBEGIN:
3077842e56b97ce677b83bdab09cda48bc2d89ac75aJust				data.append(chunk[:-len(EEXECBEGIN)-1])
3087842e56b97ce677b83bdab09cda48bc2d89ac75aJust			else:
3097842e56b97ce677b83bdab09cda48bc2d89ac75aJust				data.append(chunk)
31018316aa769566eeb6f3f4a6ed2685fa8f8e861c2Behdad Esfahbod	return bytesjoin(data)
3117842e56b97ce677b83bdab09cda48bc2d89ac75aJust
3125810aa9967488207b039cb2d300fa53c91d4df2fJustdef findEncryptedChunks(data):
3137842e56b97ce677b83bdab09cda48bc2d89ac75aJust	chunks = []
314ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod	while True:
31514fb031125b773f0a15eb19be4f02ed8540b2db6Behdad Esfahbod		eBegin = data.find(EEXECBEGIN)
3165810aa9967488207b039cb2d300fa53c91d4df2fJust		if eBegin < 0:
3177842e56b97ce677b83bdab09cda48bc2d89ac75aJust			break
31890290b7bd9d8d1e31d98637126aafc7dd8d0dfacjvr		eBegin = eBegin + len(EEXECBEGIN) + 1
31914fb031125b773f0a15eb19be4f02ed8540b2db6Behdad Esfahbod		eEnd = data.find(EEXECEND, eBegin)
3205810aa9967488207b039cb2d300fa53c91d4df2fJust		if eEnd < 0:
321cd5aad92f23737ff93a110d5c73d624658a28da8Behdad Esfahbod			raise T1Error("can't find end of eexec part")
322db1f2800e1fe8eedb102c997e9f133ed74b3af13jvr		cypherText = data[eBegin:eEnd + 2]
323e56bc902cf6a707349ae6ddfe8a83a1bd7b155b9jvr		if isHex(cypherText[:4]):
324e56bc902cf6a707349ae6ddfe8a83a1bd7b155b9jvr			cypherText = deHexString(cypherText)
325db1f2800e1fe8eedb102c997e9f133ed74b3af13jvr		plainText, R = eexec.decrypt(cypherText, 55665)
32614fb031125b773f0a15eb19be4f02ed8540b2db6Behdad Esfahbod		eEndLocal = plainText.find(EEXECINTERNALEND)
327db1f2800e1fe8eedb102c997e9f133ed74b3af13jvr		if eEndLocal < 0:
328cd5aad92f23737ff93a110d5c73d624658a28da8Behdad Esfahbod			raise T1Error("can't find end of eexec part")
32990290b7bd9d8d1e31d98637126aafc7dd8d0dfacjvr		chunks.append((0, data[:eBegin]))
330e56bc902cf6a707349ae6ddfe8a83a1bd7b155b9jvr		chunks.append((1, cypherText[:eEndLocal + len(EEXECINTERNALEND) + 1]))
3315810aa9967488207b039cb2d300fa53c91d4df2fJust		data = data[eEnd:]
3327842e56b97ce677b83bdab09cda48bc2d89ac75aJust	chunks.append((0, data))
3337842e56b97ce677b83bdab09cda48bc2d89ac75aJust	return chunks
3347842e56b97ce677b83bdab09cda48bc2d89ac75aJust
3355810aa9967488207b039cb2d300fa53c91d4df2fJustdef deHexString(hexstring):
33618316aa769566eeb6f3f4a6ed2685fa8f8e861c2Behdad Esfahbod	return eexec.deHexString(strjoin(hexstring.split()))
3377842e56b97ce677b83bdab09cda48bc2d89ac75aJust
3387842e56b97ce677b83bdab09cda48bc2d89ac75aJust
3397842e56b97ce677b83bdab09cda48bc2d89ac75aJust# Type 1 assertion
3407842e56b97ce677b83bdab09cda48bc2d89ac75aJust
341dc0ce0b498912287a557bdad58301ec7611c569eBehdad Esfahbod_fontType1RE = re.compile(br"/FontType\s+1\s+def")
3427842e56b97ce677b83bdab09cda48bc2d89ac75aJust
3435810aa9967488207b039cb2d300fa53c91d4df2fJustdef assertType1(data):
344dc0ce0b498912287a557bdad58301ec7611c569eBehdad Esfahbod	for head in [b'%!PS-AdobeFont', b'%!FontType1']:
3457842e56b97ce677b83bdab09cda48bc2d89ac75aJust		if data[:len(head)] == head:
3467842e56b97ce677b83bdab09cda48bc2d89ac75aJust			break
3477842e56b97ce677b83bdab09cda48bc2d89ac75aJust	else:
348cd5aad92f23737ff93a110d5c73d624658a28da8Behdad Esfahbod		raise T1Error("not a PostScript font")
3497842e56b97ce677b83bdab09cda48bc2d89ac75aJust	if not _fontType1RE.search(data):
350cd5aad92f23737ff93a110d5c73d624658a28da8Behdad Esfahbod		raise T1Error("not a Type 1 font")
351dc0ce0b498912287a557bdad58301ec7611c569eBehdad Esfahbod	if data.find(b"currentfile eexec") < 0:
352cd5aad92f23737ff93a110d5c73d624658a28da8Behdad Esfahbod		raise T1Error("not an encrypted Type 1 font")
3537842e56b97ce677b83bdab09cda48bc2d89ac75aJust	# XXX what else?
3547842e56b97ce677b83bdab09cda48bc2d89ac75aJust	return data
3557842e56b97ce677b83bdab09cda48bc2d89ac75aJust
3567842e56b97ce677b83bdab09cda48bc2d89ac75aJust
3577842e56b97ce677b83bdab09cda48bc2d89ac75aJust# pfb helpers
3587842e56b97ce677b83bdab09cda48bc2d89ac75aJust
3595810aa9967488207b039cb2d300fa53c91d4df2fJustdef longToString(long):
360153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod	s = ""
3617842e56b97ce677b83bdab09cda48bc2d89ac75aJust	for i in range(4):
362153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod		s += bytechr((long & (0xff << (i * 8))) >> i * 8)
363153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod	return s
3647842e56b97ce677b83bdab09cda48bc2d89ac75aJust
365153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahboddef stringToLong(s):
366153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod	if len(s) != 4:
367cd5aad92f23737ff93a110d5c73d624658a28da8Behdad Esfahbod		raise ValueError('string must be 4 bytes long')
368153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod	l = 0
3697842e56b97ce677b83bdab09cda48bc2d89ac75aJust	for i in range(4):
370153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod		l += byteord(s[i]) << (i * 8)
371153ec402094adbea673e914385b87f1d99191d0bBehdad Esfahbod	return l
3725810aa9967488207b039cb2d300fa53c91d4df2fJust
373