t1Lib.py revision 05a16f2310e26193557a3dc223ac0efeb166789f
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
115810aa9967488207b039cb2d300fa53c91d4df2fJustwrite(path, data, kind='OTHER', dohex=0)
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"""
187842e56b97ce677b83bdab09cda48bc2d89ac75aJust
197842e56b97ce677b83bdab09cda48bc2d89ac75aJust__author__ = "jvr"
207842e56b97ce677b83bdab09cda48bc2d89ac75aJust__version__ = "1.0b2"
217842e56b97ce677b83bdab09cda48bc2d89ac75aJustDEBUG = 0
227842e56b97ce677b83bdab09cda48bc2d89ac75aJust
23c2be3d982b04c4bbb1c11d9ab8452f78415d6522Justfrom fontTools.misc import eexec
247842e56b97ce677b83bdab09cda48bc2d89ac75aJustimport string
257842e56b97ce677b83bdab09cda48bc2d89ac75aJustimport re
267842e56b97ce677b83bdab09cda48bc2d89ac75aJustimport os
277842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2822433b1f52c34a04cf13e21103ee978f7f5b501cjvr
2922433b1f52c34a04cf13e21103ee978f7f5b501cjvrtry:
3025ccb9c3460b05522eae9283a4c9ea783da87aa4jvr	try:
3125ccb9c3460b05522eae9283a4c9ea783da87aa4jvr		from Carbon import Res
3225ccb9c3460b05522eae9283a4c9ea783da87aa4jvr	except ImportError:
3325ccb9c3460b05522eae9283a4c9ea783da87aa4jvr		import Res  # MacPython < 2.2
34e568dc77d3baccc303d5115e2686f126c96c8a26jvrexcept ImportError:
35e568dc77d3baccc303d5115e2686f126c96c8a26jvr	haveMacSupport = 0
36e568dc77d3baccc303d5115e2686f126c96c8a26jvrelse:
37e568dc77d3baccc303d5115e2686f126c96c8a26jvr	haveMacSupport = 1
387842e56b97ce677b83bdab09cda48bc2d89ac75aJust	import macfs
397842e56b97ce677b83bdab09cda48bc2d89ac75aJust
40e568dc77d3baccc303d5115e2686f126c96c8a26jvrclass T1Error(Exception): pass
417842e56b97ce677b83bdab09cda48bc2d89ac75aJust
427842e56b97ce677b83bdab09cda48bc2d89ac75aJust
437842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass T1Font:
447842e56b97ce677b83bdab09cda48bc2d89ac75aJust
453618300613e796ff81abddde967b1092ac1dc915Just	"""Type 1 font class.
463618300613e796ff81abddde967b1092ac1dc915Just
473618300613e796ff81abddde967b1092ac1dc915Just	Uses a minimal interpeter that supports just about enough PS to parse
483618300613e796ff81abddde967b1092ac1dc915Just	Type 1 fonts.
497842e56b97ce677b83bdab09cda48bc2d89ac75aJust	"""
507842e56b97ce677b83bdab09cda48bc2d89ac75aJust
517842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def __init__(self, path=None):
527842e56b97ce677b83bdab09cda48bc2d89ac75aJust		if path is not None:
537842e56b97ce677b83bdab09cda48bc2d89ac75aJust			self.data, type = read(path)
547842e56b97ce677b83bdab09cda48bc2d89ac75aJust		else:
557842e56b97ce677b83bdab09cda48bc2d89ac75aJust			pass # XXX
567842e56b97ce677b83bdab09cda48bc2d89ac75aJust
577842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def saveAs(self, path, type):
588c74f4639a4494d9d84d1220cbe895de4ffb8aacjvr		write(path, self.getData(), type)
597842e56b97ce677b83bdab09cda48bc2d89ac75aJust
607842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def getData(self):
613618300613e796ff81abddde967b1092ac1dc915Just		# XXX Todo: if the data has been converted to Python object,
623618300613e796ff81abddde967b1092ac1dc915Just		# recreate the PS stream
637842e56b97ce677b83bdab09cda48bc2d89ac75aJust		return self.data
647842e56b97ce677b83bdab09cda48bc2d89ac75aJust
657842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def __getitem__(self, key):
667842e56b97ce677b83bdab09cda48bc2d89ac75aJust		if not hasattr(self, "font"):
677842e56b97ce677b83bdab09cda48bc2d89ac75aJust			self.parse()
683618300613e796ff81abddde967b1092ac1dc915Just		return self.font[key]
697842e56b97ce677b83bdab09cda48bc2d89ac75aJust
707842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def parse(self):
71528614e6e254dfe3c501ff440c291c6c55de5e6fJust		from fontTools.misc import psLib
72528614e6e254dfe3c501ff440c291c6c55de5e6fJust		from fontTools.misc import psCharStrings
737842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.font = psLib.suckfont(self.data)
747842e56b97ce677b83bdab09cda48bc2d89ac75aJust		charStrings = self.font["CharStrings"]
757842e56b97ce677b83bdab09cda48bc2d89ac75aJust		lenIV = self.font["Private"].get("lenIV", 4)
767842e56b97ce677b83bdab09cda48bc2d89ac75aJust		assert lenIV >= 0
777842e56b97ce677b83bdab09cda48bc2d89ac75aJust		for glyphName, charString in charStrings.items():
78c2be3d982b04c4bbb1c11d9ab8452f78415d6522Just			charString, R = eexec.decrypt(charString, 4330)
797842e56b97ce677b83bdab09cda48bc2d89ac75aJust			charStrings[glyphName] = psCharStrings.T1CharString(charString[lenIV:])
807842e56b97ce677b83bdab09cda48bc2d89ac75aJust		subrs = self.font["Private"]["Subrs"]
817842e56b97ce677b83bdab09cda48bc2d89ac75aJust		for i in range(len(subrs)):
82c2be3d982b04c4bbb1c11d9ab8452f78415d6522Just			charString, R = eexec.decrypt(subrs[i], 4330)
837842e56b97ce677b83bdab09cda48bc2d89ac75aJust			subrs[i] = psCharStrings.T1CharString(charString[lenIV:])
847842e56b97ce677b83bdab09cda48bc2d89ac75aJust		del self.data
857842e56b97ce677b83bdab09cda48bc2d89ac75aJust
867842e56b97ce677b83bdab09cda48bc2d89ac75aJust
877842e56b97ce677b83bdab09cda48bc2d89ac75aJust
883618300613e796ff81abddde967b1092ac1dc915Just# low level T1 data read and write functions
897842e56b97ce677b83bdab09cda48bc2d89ac75aJust
907842e56b97ce677b83bdab09cda48bc2d89ac75aJustdef read(path):
917842e56b97ce677b83bdab09cda48bc2d89ac75aJust	"""reads any Type 1 font file, returns raw data"""
927842e56b97ce677b83bdab09cda48bc2d89ac75aJust	normpath = string.lower(path)
9322433b1f52c34a04cf13e21103ee978f7f5b501cjvr	if haveMacSupport:
947842e56b97ce677b83bdab09cda48bc2d89ac75aJust		fss = macfs.FSSpec(path)
957842e56b97ce677b83bdab09cda48bc2d89ac75aJust		creator, type = fss.GetCreatorType()
967842e56b97ce677b83bdab09cda48bc2d89ac75aJust		if type == 'LWFN':
975810aa9967488207b039cb2d300fa53c91d4df2fJust			return readLWFN(path), 'LWFN'
987842e56b97ce677b83bdab09cda48bc2d89ac75aJust	if normpath[-4:] == '.pfb':
995810aa9967488207b039cb2d300fa53c91d4df2fJust		return readPFB(path), 'PFB'
1007842e56b97ce677b83bdab09cda48bc2d89ac75aJust	else:
1015810aa9967488207b039cb2d300fa53c91d4df2fJust		return readOther(path), 'OTHER'
1027842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1037842e56b97ce677b83bdab09cda48bc2d89ac75aJustdef write(path, data, kind='OTHER', dohex=0):
1045810aa9967488207b039cb2d300fa53c91d4df2fJust	assertType1(data)
1057842e56b97ce677b83bdab09cda48bc2d89ac75aJust	kind = string.upper(kind)
1067842e56b97ce677b83bdab09cda48bc2d89ac75aJust	try:
1077842e56b97ce677b83bdab09cda48bc2d89ac75aJust		os.remove(path)
1087842e56b97ce677b83bdab09cda48bc2d89ac75aJust	except os.error:
1097842e56b97ce677b83bdab09cda48bc2d89ac75aJust		pass
1107842e56b97ce677b83bdab09cda48bc2d89ac75aJust	err = 1
1117842e56b97ce677b83bdab09cda48bc2d89ac75aJust	try:
1127842e56b97ce677b83bdab09cda48bc2d89ac75aJust		if kind == 'LWFN':
1135810aa9967488207b039cb2d300fa53c91d4df2fJust			writeLWFN(path, data)
1147842e56b97ce677b83bdab09cda48bc2d89ac75aJust		elif kind == 'PFB':
1155810aa9967488207b039cb2d300fa53c91d4df2fJust			writePFB(path, data)
1167842e56b97ce677b83bdab09cda48bc2d89ac75aJust		else:
1175810aa9967488207b039cb2d300fa53c91d4df2fJust			writeOther(path, data, dohex)
1187842e56b97ce677b83bdab09cda48bc2d89ac75aJust		err = 0
1197842e56b97ce677b83bdab09cda48bc2d89ac75aJust	finally:
1207842e56b97ce677b83bdab09cda48bc2d89ac75aJust		if err and not DEBUG:
1217842e56b97ce677b83bdab09cda48bc2d89ac75aJust			try:
1227842e56b97ce677b83bdab09cda48bc2d89ac75aJust				os.remove(path)
1237842e56b97ce677b83bdab09cda48bc2d89ac75aJust			except os.error:
1247842e56b97ce677b83bdab09cda48bc2d89ac75aJust				pass
1257842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1267842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1277842e56b97ce677b83bdab09cda48bc2d89ac75aJust# -- internal --
1287842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1297842e56b97ce677b83bdab09cda48bc2d89ac75aJustLWFNCHUNKSIZE = 2000
1307842e56b97ce677b83bdab09cda48bc2d89ac75aJustHEXLINELENGTH = 80
1317842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1327842e56b97ce677b83bdab09cda48bc2d89ac75aJust
133da0d805d2603dd9b83c695006521f887b68d5505jvrdef readLWFN(path, onlyHeader=0):
1347842e56b97ce677b83bdab09cda48bc2d89ac75aJust	"""reads an LWFN font file, returns raw data"""
135e9601bf9e1253f77b8a66f27685fae453ce98b14Just	resRef = Res.FSpOpenResFile(path, 1)  # read-only
1367842e56b97ce677b83bdab09cda48bc2d89ac75aJust	try:
137e9601bf9e1253f77b8a66f27685fae453ce98b14Just		Res.UseResFile(resRef)
1387842e56b97ce677b83bdab09cda48bc2d89ac75aJust		n = Res.Count1Resources('POST')
1397842e56b97ce677b83bdab09cda48bc2d89ac75aJust		data = []
1407842e56b97ce677b83bdab09cda48bc2d89ac75aJust		for i in range(501, 501 + n):
1417842e56b97ce677b83bdab09cda48bc2d89ac75aJust			res = Res.Get1Resource('POST', i)
1427842e56b97ce677b83bdab09cda48bc2d89ac75aJust			code = ord(res.data[0])
1437842e56b97ce677b83bdab09cda48bc2d89ac75aJust			if ord(res.data[1]) <> 0:
144e568dc77d3baccc303d5115e2686f126c96c8a26jvr				raise T1Error, 'corrupt LWFN file'
1457842e56b97ce677b83bdab09cda48bc2d89ac75aJust			if code in [1, 2]:
146da0d805d2603dd9b83c695006521f887b68d5505jvr				if onlyHeader and code == 2:
147da0d805d2603dd9b83c695006521f887b68d5505jvr					break
14805a16f2310e26193557a3dc223ac0efeb166789fjvr				data.append(res.data[2:])
1497842e56b97ce677b83bdab09cda48bc2d89ac75aJust			elif code in [3, 5]:
1507842e56b97ce677b83bdab09cda48bc2d89ac75aJust				break
1517842e56b97ce677b83bdab09cda48bc2d89ac75aJust			elif code == 4:
1527842e56b97ce677b83bdab09cda48bc2d89ac75aJust				f = open(path, "rb")
1537842e56b97ce677b83bdab09cda48bc2d89ac75aJust				data.append(f.read())
1547842e56b97ce677b83bdab09cda48bc2d89ac75aJust				f.close()
1557842e56b97ce677b83bdab09cda48bc2d89ac75aJust			elif code == 0:
1567842e56b97ce677b83bdab09cda48bc2d89ac75aJust				pass # comment, ignore
1577842e56b97ce677b83bdab09cda48bc2d89ac75aJust			else:
158e568dc77d3baccc303d5115e2686f126c96c8a26jvr				raise T1Error, 'bad chunk code: ' + `code`
1597842e56b97ce677b83bdab09cda48bc2d89ac75aJust	finally:
160e9601bf9e1253f77b8a66f27685fae453ce98b14Just		Res.CloseResFile(resRef)
1617842e56b97ce677b83bdab09cda48bc2d89ac75aJust	data = string.join(data, '')
1625810aa9967488207b039cb2d300fa53c91d4df2fJust	assertType1(data)
1637842e56b97ce677b83bdab09cda48bc2d89ac75aJust	return data
1647842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1655810aa9967488207b039cb2d300fa53c91d4df2fJustdef readPFB(path, onlyHeader=0):
1667842e56b97ce677b83bdab09cda48bc2d89ac75aJust	"""reads a PFB font file, returns raw data"""
1677842e56b97ce677b83bdab09cda48bc2d89ac75aJust	f = open(path, "rb")
1687842e56b97ce677b83bdab09cda48bc2d89ac75aJust	data = []
1697842e56b97ce677b83bdab09cda48bc2d89ac75aJust	while 1:
1707842e56b97ce677b83bdab09cda48bc2d89ac75aJust		if f.read(1) <> chr(128):
171e568dc77d3baccc303d5115e2686f126c96c8a26jvr			raise T1Error, 'corrupt PFB file'
1727842e56b97ce677b83bdab09cda48bc2d89ac75aJust		code = ord(f.read(1))
1737842e56b97ce677b83bdab09cda48bc2d89ac75aJust		if code in [1, 2]:
1745810aa9967488207b039cb2d300fa53c91d4df2fJust			chunklen = stringToLong(f.read(4))
1753618300613e796ff81abddde967b1092ac1dc915Just			chunk = f.read(chunklen)
1763618300613e796ff81abddde967b1092ac1dc915Just			assert len(chunk) == chunklen
1773618300613e796ff81abddde967b1092ac1dc915Just			data.append(chunk)
1787842e56b97ce677b83bdab09cda48bc2d89ac75aJust		elif code == 3:
1797842e56b97ce677b83bdab09cda48bc2d89ac75aJust			break
1807842e56b97ce677b83bdab09cda48bc2d89ac75aJust		else:
181e568dc77d3baccc303d5115e2686f126c96c8a26jvr			raise T1Error, 'bad chunk code: ' + `code`
1825810aa9967488207b039cb2d300fa53c91d4df2fJust		if onlyHeader:
1835810aa9967488207b039cb2d300fa53c91d4df2fJust			break
1847842e56b97ce677b83bdab09cda48bc2d89ac75aJust	f.close()
1857842e56b97ce677b83bdab09cda48bc2d89ac75aJust	data = string.join(data, '')
1865810aa9967488207b039cb2d300fa53c91d4df2fJust	assertType1(data)
1877842e56b97ce677b83bdab09cda48bc2d89ac75aJust	return data
1887842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1895810aa9967488207b039cb2d300fa53c91d4df2fJustdef readOther(path):
1907842e56b97ce677b83bdab09cda48bc2d89ac75aJust	"""reads any (font) file, returns raw data"""
1917842e56b97ce677b83bdab09cda48bc2d89ac75aJust	f = open(path, "rb")
1927842e56b97ce677b83bdab09cda48bc2d89ac75aJust	data = f.read()
1937842e56b97ce677b83bdab09cda48bc2d89ac75aJust	f.close()
1945810aa9967488207b039cb2d300fa53c91d4df2fJust	assertType1(data)
1957842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1965810aa9967488207b039cb2d300fa53c91d4df2fJust	chunks = findEncryptedChunks(data)
1977842e56b97ce677b83bdab09cda48bc2d89ac75aJust	data = []
1985810aa9967488207b039cb2d300fa53c91d4df2fJust	for isEncrypted, chunk in chunks:
1995810aa9967488207b039cb2d300fa53c91d4df2fJust		if isEncrypted and isHex(chunk[:4]):
2005810aa9967488207b039cb2d300fa53c91d4df2fJust			data.append(deHexString(chunk))
2017842e56b97ce677b83bdab09cda48bc2d89ac75aJust		else:
2027842e56b97ce677b83bdab09cda48bc2d89ac75aJust			data.append(chunk)
2037842e56b97ce677b83bdab09cda48bc2d89ac75aJust	return string.join(data, '')
2047842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2057842e56b97ce677b83bdab09cda48bc2d89ac75aJust# file writing tools
2067842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2075810aa9967488207b039cb2d300fa53c91d4df2fJustdef writeLWFN(path, data):
208e9601bf9e1253f77b8a66f27685fae453ce98b14Just	Res.FSpCreateResFile(path, "just", "LWFN", 0)
209e9601bf9e1253f77b8a66f27685fae453ce98b14Just	resRef = Res.FSpOpenResFile(path, 2)  # write-only
2107842e56b97ce677b83bdab09cda48bc2d89ac75aJust	try:
211e9601bf9e1253f77b8a66f27685fae453ce98b14Just		Res.UseResFile(resRef)
2127842e56b97ce677b83bdab09cda48bc2d89ac75aJust		resID = 501
2135810aa9967488207b039cb2d300fa53c91d4df2fJust		chunks = findEncryptedChunks(data)
2145810aa9967488207b039cb2d300fa53c91d4df2fJust		for isEncrypted, chunk in chunks:
2155810aa9967488207b039cb2d300fa53c91d4df2fJust			if isEncrypted:
2167842e56b97ce677b83bdab09cda48bc2d89ac75aJust				code = 2
2177842e56b97ce677b83bdab09cda48bc2d89ac75aJust			else:
2187842e56b97ce677b83bdab09cda48bc2d89ac75aJust				code = 1
2197842e56b97ce677b83bdab09cda48bc2d89ac75aJust			while chunk:
2207842e56b97ce677b83bdab09cda48bc2d89ac75aJust				res = Res.Resource(chr(code) + '\0' + chunk[:LWFNCHUNKSIZE - 2])
2217842e56b97ce677b83bdab09cda48bc2d89ac75aJust				res.AddResource('POST', resID, '')
2227842e56b97ce677b83bdab09cda48bc2d89ac75aJust				chunk = chunk[LWFNCHUNKSIZE - 2:]
2237842e56b97ce677b83bdab09cda48bc2d89ac75aJust				resID = resID + 1
2247842e56b97ce677b83bdab09cda48bc2d89ac75aJust		res = Res.Resource(chr(5) + '\0')
2257842e56b97ce677b83bdab09cda48bc2d89ac75aJust		res.AddResource('POST', resID, '')
2267842e56b97ce677b83bdab09cda48bc2d89ac75aJust	finally:
227e9601bf9e1253f77b8a66f27685fae453ce98b14Just		Res.CloseResFile(resRef)
2287842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2295810aa9967488207b039cb2d300fa53c91d4df2fJustdef writePFB(path, data):
2305810aa9967488207b039cb2d300fa53c91d4df2fJust	chunks = findEncryptedChunks(data)
2313618300613e796ff81abddde967b1092ac1dc915Just	f = open(path, "wb")
2327842e56b97ce677b83bdab09cda48bc2d89ac75aJust	try:
2335810aa9967488207b039cb2d300fa53c91d4df2fJust		for isEncrypted, chunk in chunks:
2345810aa9967488207b039cb2d300fa53c91d4df2fJust			if isEncrypted:
2357842e56b97ce677b83bdab09cda48bc2d89ac75aJust				code = 2
2367842e56b97ce677b83bdab09cda48bc2d89ac75aJust			else:
2377842e56b97ce677b83bdab09cda48bc2d89ac75aJust				code = 1
2387842e56b97ce677b83bdab09cda48bc2d89ac75aJust			f.write(chr(128) + chr(code))
2395810aa9967488207b039cb2d300fa53c91d4df2fJust			f.write(longToString(len(chunk)))
2407842e56b97ce677b83bdab09cda48bc2d89ac75aJust			f.write(chunk)
2417842e56b97ce677b83bdab09cda48bc2d89ac75aJust		f.write(chr(128) + chr(3))
2427842e56b97ce677b83bdab09cda48bc2d89ac75aJust	finally:
2437842e56b97ce677b83bdab09cda48bc2d89ac75aJust		f.close()
24422433b1f52c34a04cf13e21103ee978f7f5b501cjvr	if haveMacSupport:
2453618300613e796ff81abddde967b1092ac1dc915Just		fss = macfs.FSSpec(path)
2467842e56b97ce677b83bdab09cda48bc2d89ac75aJust		fss.SetCreatorType('mdos', 'BINA')
2477842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2485810aa9967488207b039cb2d300fa53c91d4df2fJustdef writeOther(path, data, dohex = 0):
2495810aa9967488207b039cb2d300fa53c91d4df2fJust	chunks = findEncryptedChunks(data)
2507842e56b97ce677b83bdab09cda48bc2d89ac75aJust	f = open(path, "wb")
2517842e56b97ce677b83bdab09cda48bc2d89ac75aJust	try:
2527842e56b97ce677b83bdab09cda48bc2d89ac75aJust		hexlinelen = HEXLINELENGTH / 2
2535810aa9967488207b039cb2d300fa53c91d4df2fJust		for isEncrypted, chunk in chunks:
2545810aa9967488207b039cb2d300fa53c91d4df2fJust			if isEncrypted:
2557842e56b97ce677b83bdab09cda48bc2d89ac75aJust				code = 2
2567842e56b97ce677b83bdab09cda48bc2d89ac75aJust			else:
2577842e56b97ce677b83bdab09cda48bc2d89ac75aJust				code = 1
2587842e56b97ce677b83bdab09cda48bc2d89ac75aJust			if code == 2 and dohex:
2597842e56b97ce677b83bdab09cda48bc2d89ac75aJust				while chunk:
260c2be3d982b04c4bbb1c11d9ab8452f78415d6522Just					f.write(eexec.hexString(chunk[:hexlinelen]))
2617842e56b97ce677b83bdab09cda48bc2d89ac75aJust					f.write('\r')
2627842e56b97ce677b83bdab09cda48bc2d89ac75aJust					chunk = chunk[hexlinelen:]
2637842e56b97ce677b83bdab09cda48bc2d89ac75aJust			else:
2647842e56b97ce677b83bdab09cda48bc2d89ac75aJust				f.write(chunk)
2657842e56b97ce677b83bdab09cda48bc2d89ac75aJust	finally:
2667842e56b97ce677b83bdab09cda48bc2d89ac75aJust		f.close()
26722433b1f52c34a04cf13e21103ee978f7f5b501cjvr	if haveMacSupport:
2687842e56b97ce677b83bdab09cda48bc2d89ac75aJust		fss = macfs.FSSpec(path)
2697842e56b97ce677b83bdab09cda48bc2d89ac75aJust		fss.SetCreatorType('R*ch', 'TEXT') # BBEdit text file
2707842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2717842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2727842e56b97ce677b83bdab09cda48bc2d89ac75aJust# decryption tools
2737842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2747842e56b97ce677b83bdab09cda48bc2d89ac75aJustEEXECBEGIN = "currentfile eexec"
2757842e56b97ce677b83bdab09cda48bc2d89ac75aJustEEXECEND = '0' * 64
2767842e56b97ce677b83bdab09cda48bc2d89ac75aJustEEXECINTERNALEND = "currentfile closefile"
2777842e56b97ce677b83bdab09cda48bc2d89ac75aJustEEXECBEGINMARKER = "%-- eexec start\r"
2787842e56b97ce677b83bdab09cda48bc2d89ac75aJustEEXECENDMARKER = "%-- eexec end\r"
2797842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2807842e56b97ce677b83bdab09cda48bc2d89ac75aJust_ishexRE = re.compile('[0-9A-Fa-f]*$')
2817842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2825810aa9967488207b039cb2d300fa53c91d4df2fJustdef isHex(text):
2837842e56b97ce677b83bdab09cda48bc2d89ac75aJust	return _ishexRE.match(text) is not None
2847842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2857842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2865810aa9967488207b039cb2d300fa53c91d4df2fJustdef decryptType1(data):
2875810aa9967488207b039cb2d300fa53c91d4df2fJust	chunks = findEncryptedChunks(data)
2887842e56b97ce677b83bdab09cda48bc2d89ac75aJust	data = []
2895810aa9967488207b039cb2d300fa53c91d4df2fJust	for isEncrypted, chunk in chunks:
2905810aa9967488207b039cb2d300fa53c91d4df2fJust		if isEncrypted:
2915810aa9967488207b039cb2d300fa53c91d4df2fJust			if isHex(chunk[:4]):
2925810aa9967488207b039cb2d300fa53c91d4df2fJust				chunk = deHexString(chunk)
293c2be3d982b04c4bbb1c11d9ab8452f78415d6522Just			decrypted, R = eexec.decrypt(chunk, 55665)
2947842e56b97ce677b83bdab09cda48bc2d89ac75aJust			decrypted = decrypted[4:]
2957842e56b97ce677b83bdab09cda48bc2d89ac75aJust			if decrypted[-len(EEXECINTERNALEND)-1:-1] <> EEXECINTERNALEND \
2967842e56b97ce677b83bdab09cda48bc2d89ac75aJust					and decrypted[-len(EEXECINTERNALEND)-2:-2] <> EEXECINTERNALEND:
297e568dc77d3baccc303d5115e2686f126c96c8a26jvr				raise T1Error, "invalid end of eexec part"
2987842e56b97ce677b83bdab09cda48bc2d89ac75aJust			decrypted = decrypted[:-len(EEXECINTERNALEND)-2] + '\r'
2997842e56b97ce677b83bdab09cda48bc2d89ac75aJust			data.append(EEXECBEGINMARKER + decrypted + EEXECENDMARKER)
3007842e56b97ce677b83bdab09cda48bc2d89ac75aJust		else:
3017842e56b97ce677b83bdab09cda48bc2d89ac75aJust			if chunk[-len(EEXECBEGIN)-1:-1] == EEXECBEGIN:
3027842e56b97ce677b83bdab09cda48bc2d89ac75aJust				data.append(chunk[:-len(EEXECBEGIN)-1])
3037842e56b97ce677b83bdab09cda48bc2d89ac75aJust			else:
3047842e56b97ce677b83bdab09cda48bc2d89ac75aJust				data.append(chunk)
3057842e56b97ce677b83bdab09cda48bc2d89ac75aJust	return string.join(data, '')
3067842e56b97ce677b83bdab09cda48bc2d89ac75aJust
3075810aa9967488207b039cb2d300fa53c91d4df2fJustdef findEncryptedChunks(data):
3087842e56b97ce677b83bdab09cda48bc2d89ac75aJust	chunks = []
3097842e56b97ce677b83bdab09cda48bc2d89ac75aJust	while 1:
3105810aa9967488207b039cb2d300fa53c91d4df2fJust		eBegin = string.find(data, EEXECBEGIN)
3115810aa9967488207b039cb2d300fa53c91d4df2fJust		if eBegin < 0:
3127842e56b97ce677b83bdab09cda48bc2d89ac75aJust			break
31390290b7bd9d8d1e31d98637126aafc7dd8d0dfacjvr		eBegin = eBegin + len(EEXECBEGIN) + 1
3145810aa9967488207b039cb2d300fa53c91d4df2fJust		eEnd = string.find(data, EEXECEND, eBegin)
3155810aa9967488207b039cb2d300fa53c91d4df2fJust		if eEnd < 0:
316e568dc77d3baccc303d5115e2686f126c96c8a26jvr			raise T1Error, "can't find end of eexec part"
317db1f2800e1fe8eedb102c997e9f133ed74b3af13jvr		cypherText = data[eBegin:eEnd + 2]
318db1f2800e1fe8eedb102c997e9f133ed74b3af13jvr		plainText, R = eexec.decrypt(cypherText, 55665)
319db1f2800e1fe8eedb102c997e9f133ed74b3af13jvr		eEndLocal = string.find(plainText, EEXECINTERNALEND)
320db1f2800e1fe8eedb102c997e9f133ed74b3af13jvr		if eEndLocal < 0:
321db1f2800e1fe8eedb102c997e9f133ed74b3af13jvr			raise T1Error, "can't find end of eexec part"
322db1f2800e1fe8eedb102c997e9f133ed74b3af13jvr		eEnd = eBegin + eEndLocal + len(EEXECINTERNALEND) + 1
32390290b7bd9d8d1e31d98637126aafc7dd8d0dfacjvr		chunks.append((0, data[:eBegin]))
32490290b7bd9d8d1e31d98637126aafc7dd8d0dfacjvr		chunks.append((1, data[eBegin:eEnd]))
3255810aa9967488207b039cb2d300fa53c91d4df2fJust		data = data[eEnd:]
3267842e56b97ce677b83bdab09cda48bc2d89ac75aJust	chunks.append((0, data))
3277842e56b97ce677b83bdab09cda48bc2d89ac75aJust	return chunks
3287842e56b97ce677b83bdab09cda48bc2d89ac75aJust
3295810aa9967488207b039cb2d300fa53c91d4df2fJustdef deHexString(hexstring):
330c2be3d982b04c4bbb1c11d9ab8452f78415d6522Just	return eexec.deHexString(string.join(string.split(hexstring), ""))
3317842e56b97ce677b83bdab09cda48bc2d89ac75aJust
3327842e56b97ce677b83bdab09cda48bc2d89ac75aJust
3337842e56b97ce677b83bdab09cda48bc2d89ac75aJust# Type 1 assertion
3347842e56b97ce677b83bdab09cda48bc2d89ac75aJust
3357842e56b97ce677b83bdab09cda48bc2d89ac75aJust_fontType1RE = re.compile(r"/FontType\s+1\s+def")
3367842e56b97ce677b83bdab09cda48bc2d89ac75aJust
3375810aa9967488207b039cb2d300fa53c91d4df2fJustdef assertType1(data):
3387842e56b97ce677b83bdab09cda48bc2d89ac75aJust	for head in ['%!PS-AdobeFont', '%!FontType1-1.0']:
3397842e56b97ce677b83bdab09cda48bc2d89ac75aJust		if data[:len(head)] == head:
3407842e56b97ce677b83bdab09cda48bc2d89ac75aJust			break
3417842e56b97ce677b83bdab09cda48bc2d89ac75aJust	else:
342e568dc77d3baccc303d5115e2686f126c96c8a26jvr		raise T1Error, "not a PostScript font"
3437842e56b97ce677b83bdab09cda48bc2d89ac75aJust	if not _fontType1RE.search(data):
344e568dc77d3baccc303d5115e2686f126c96c8a26jvr		raise T1Error, "not a Type 1 font"
3457842e56b97ce677b83bdab09cda48bc2d89ac75aJust	if string.find(data, "currentfile eexec") < 0:
346e568dc77d3baccc303d5115e2686f126c96c8a26jvr		raise T1Error, "not an encrypted Type 1 font"
3477842e56b97ce677b83bdab09cda48bc2d89ac75aJust	# XXX what else?
3487842e56b97ce677b83bdab09cda48bc2d89ac75aJust	return data
3497842e56b97ce677b83bdab09cda48bc2d89ac75aJust
3507842e56b97ce677b83bdab09cda48bc2d89ac75aJust
3517842e56b97ce677b83bdab09cda48bc2d89ac75aJust# pfb helpers
3527842e56b97ce677b83bdab09cda48bc2d89ac75aJust
3535810aa9967488207b039cb2d300fa53c91d4df2fJustdef longToString(long):
3547842e56b97ce677b83bdab09cda48bc2d89ac75aJust	str = ""
3557842e56b97ce677b83bdab09cda48bc2d89ac75aJust	for i in range(4):
3567842e56b97ce677b83bdab09cda48bc2d89ac75aJust		str = str + chr((long & (0xff << (i * 8))) >> i * 8)
3577842e56b97ce677b83bdab09cda48bc2d89ac75aJust	return str
3587842e56b97ce677b83bdab09cda48bc2d89ac75aJust
3595810aa9967488207b039cb2d300fa53c91d4df2fJustdef stringToLong(str):
3607842e56b97ce677b83bdab09cda48bc2d89ac75aJust	if len(str) <> 4:
3617842e56b97ce677b83bdab09cda48bc2d89ac75aJust		raise ValueError, 'string must be 4 bytes long'
3627842e56b97ce677b83bdab09cda48bc2d89ac75aJust	long = 0
3637842e56b97ce677b83bdab09cda48bc2d89ac75aJust	for i in range(4):
3647842e56b97ce677b83bdab09cda48bc2d89ac75aJust		long = long + (ord(str[i]) << (i * 8))
3657842e56b97ce677b83bdab09cda48bc2d89ac75aJust	return long
3665810aa9967488207b039cb2d300fa53c91d4df2fJust
367