t1Lib.py revision e9601bf9e1253f77b8a66f27685fae453ce98b14
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
287842e56b97ce677b83bdab09cda48bc2d89ac75aJustif os.name == 'mac':
297842e56b97ce677b83bdab09cda48bc2d89ac75aJust	import Res
307842e56b97ce677b83bdab09cda48bc2d89ac75aJust	import macfs
317842e56b97ce677b83bdab09cda48bc2d89ac75aJust
327842e56b97ce677b83bdab09cda48bc2d89ac75aJusterror = 't1Lib.error'
337842e56b97ce677b83bdab09cda48bc2d89ac75aJust
347842e56b97ce677b83bdab09cda48bc2d89ac75aJust
357842e56b97ce677b83bdab09cda48bc2d89ac75aJustclass T1Font:
367842e56b97ce677b83bdab09cda48bc2d89ac75aJust
373618300613e796ff81abddde967b1092ac1dc915Just	"""Type 1 font class.
383618300613e796ff81abddde967b1092ac1dc915Just
393618300613e796ff81abddde967b1092ac1dc915Just	Uses a minimal interpeter that supports just about enough PS to parse
403618300613e796ff81abddde967b1092ac1dc915Just	Type 1 fonts.
417842e56b97ce677b83bdab09cda48bc2d89ac75aJust	"""
427842e56b97ce677b83bdab09cda48bc2d89ac75aJust
437842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def __init__(self, path=None):
447842e56b97ce677b83bdab09cda48bc2d89ac75aJust		if path is not None:
457842e56b97ce677b83bdab09cda48bc2d89ac75aJust			self.data, type = read(path)
467842e56b97ce677b83bdab09cda48bc2d89ac75aJust		else:
477842e56b97ce677b83bdab09cda48bc2d89ac75aJust			pass # XXX
487842e56b97ce677b83bdab09cda48bc2d89ac75aJust
497842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def saveAs(self, path, type):
507842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.write(path, self.getData(), type)
517842e56b97ce677b83bdab09cda48bc2d89ac75aJust
527842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def getData(self):
533618300613e796ff81abddde967b1092ac1dc915Just		# XXX Todo: if the data has been converted to Python object,
543618300613e796ff81abddde967b1092ac1dc915Just		# recreate the PS stream
557842e56b97ce677b83bdab09cda48bc2d89ac75aJust		return self.data
567842e56b97ce677b83bdab09cda48bc2d89ac75aJust
577842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def __getitem__(self, key):
587842e56b97ce677b83bdab09cda48bc2d89ac75aJust		if not hasattr(self, "font"):
597842e56b97ce677b83bdab09cda48bc2d89ac75aJust			self.parse()
603618300613e796ff81abddde967b1092ac1dc915Just		return self.font[key]
617842e56b97ce677b83bdab09cda48bc2d89ac75aJust
627842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def parse(self):
63528614e6e254dfe3c501ff440c291c6c55de5e6fJust		from fontTools.misc import psLib
64528614e6e254dfe3c501ff440c291c6c55de5e6fJust		from fontTools.misc import psCharStrings
657842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.font = psLib.suckfont(self.data)
667842e56b97ce677b83bdab09cda48bc2d89ac75aJust		charStrings = self.font["CharStrings"]
677842e56b97ce677b83bdab09cda48bc2d89ac75aJust		lenIV = self.font["Private"].get("lenIV", 4)
687842e56b97ce677b83bdab09cda48bc2d89ac75aJust		assert lenIV >= 0
697842e56b97ce677b83bdab09cda48bc2d89ac75aJust		for glyphName, charString in charStrings.items():
70c2be3d982b04c4bbb1c11d9ab8452f78415d6522Just			charString, R = eexec.decrypt(charString, 4330)
717842e56b97ce677b83bdab09cda48bc2d89ac75aJust			charStrings[glyphName] = psCharStrings.T1CharString(charString[lenIV:])
727842e56b97ce677b83bdab09cda48bc2d89ac75aJust		subrs = self.font["Private"]["Subrs"]
737842e56b97ce677b83bdab09cda48bc2d89ac75aJust		for i in range(len(subrs)):
74c2be3d982b04c4bbb1c11d9ab8452f78415d6522Just			charString, R = eexec.decrypt(subrs[i], 4330)
757842e56b97ce677b83bdab09cda48bc2d89ac75aJust			subrs[i] = psCharStrings.T1CharString(charString[lenIV:])
767842e56b97ce677b83bdab09cda48bc2d89ac75aJust		del self.data
777842e56b97ce677b83bdab09cda48bc2d89ac75aJust
787842e56b97ce677b83bdab09cda48bc2d89ac75aJust
797842e56b97ce677b83bdab09cda48bc2d89ac75aJust
803618300613e796ff81abddde967b1092ac1dc915Just# low level T1 data read and write functions
817842e56b97ce677b83bdab09cda48bc2d89ac75aJust
827842e56b97ce677b83bdab09cda48bc2d89ac75aJustdef read(path):
837842e56b97ce677b83bdab09cda48bc2d89ac75aJust	"""reads any Type 1 font file, returns raw data"""
847842e56b97ce677b83bdab09cda48bc2d89ac75aJust	normpath = string.lower(path)
857842e56b97ce677b83bdab09cda48bc2d89ac75aJust	if os.name == 'mac':
867842e56b97ce677b83bdab09cda48bc2d89ac75aJust		fss = macfs.FSSpec(path)
877842e56b97ce677b83bdab09cda48bc2d89ac75aJust		creator, type = fss.GetCreatorType()
887842e56b97ce677b83bdab09cda48bc2d89ac75aJust		if type == 'LWFN':
895810aa9967488207b039cb2d300fa53c91d4df2fJust			return readLWFN(path), 'LWFN'
907842e56b97ce677b83bdab09cda48bc2d89ac75aJust	if normpath[-4:] == '.pfb':
915810aa9967488207b039cb2d300fa53c91d4df2fJust		return readPFB(path), 'PFB'
927842e56b97ce677b83bdab09cda48bc2d89ac75aJust	else:
935810aa9967488207b039cb2d300fa53c91d4df2fJust		return readOther(path), 'OTHER'
947842e56b97ce677b83bdab09cda48bc2d89ac75aJust
957842e56b97ce677b83bdab09cda48bc2d89ac75aJustdef write(path, data, kind='OTHER', dohex=0):
965810aa9967488207b039cb2d300fa53c91d4df2fJust	assertType1(data)
977842e56b97ce677b83bdab09cda48bc2d89ac75aJust	kind = string.upper(kind)
987842e56b97ce677b83bdab09cda48bc2d89ac75aJust	try:
997842e56b97ce677b83bdab09cda48bc2d89ac75aJust		os.remove(path)
1007842e56b97ce677b83bdab09cda48bc2d89ac75aJust	except os.error:
1017842e56b97ce677b83bdab09cda48bc2d89ac75aJust		pass
1027842e56b97ce677b83bdab09cda48bc2d89ac75aJust	err = 1
1037842e56b97ce677b83bdab09cda48bc2d89ac75aJust	try:
1047842e56b97ce677b83bdab09cda48bc2d89ac75aJust		if kind == 'LWFN':
1055810aa9967488207b039cb2d300fa53c91d4df2fJust			writeLWFN(path, data)
1067842e56b97ce677b83bdab09cda48bc2d89ac75aJust		elif kind == 'PFB':
1075810aa9967488207b039cb2d300fa53c91d4df2fJust			writePFB(path, data)
1087842e56b97ce677b83bdab09cda48bc2d89ac75aJust		else:
1095810aa9967488207b039cb2d300fa53c91d4df2fJust			writeOther(path, data, dohex)
1107842e56b97ce677b83bdab09cda48bc2d89ac75aJust		err = 0
1117842e56b97ce677b83bdab09cda48bc2d89ac75aJust	finally:
1127842e56b97ce677b83bdab09cda48bc2d89ac75aJust		if err and not DEBUG:
1137842e56b97ce677b83bdab09cda48bc2d89ac75aJust			try:
1147842e56b97ce677b83bdab09cda48bc2d89ac75aJust				os.remove(path)
1157842e56b97ce677b83bdab09cda48bc2d89ac75aJust			except os.error:
1167842e56b97ce677b83bdab09cda48bc2d89ac75aJust				pass
1177842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1187842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1197842e56b97ce677b83bdab09cda48bc2d89ac75aJust# -- internal --
1207842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1217842e56b97ce677b83bdab09cda48bc2d89ac75aJustLWFNCHUNKSIZE = 2000
1227842e56b97ce677b83bdab09cda48bc2d89ac75aJustHEXLINELENGTH = 80
1237842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1247842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1255810aa9967488207b039cb2d300fa53c91d4df2fJustdef readLWFN(path):
1267842e56b97ce677b83bdab09cda48bc2d89ac75aJust	"""reads an LWFN font file, returns raw data"""
127e9601bf9e1253f77b8a66f27685fae453ce98b14Just	resRef = Res.FSpOpenResFile(path, 1)  # read-only
1287842e56b97ce677b83bdab09cda48bc2d89ac75aJust	try:
129e9601bf9e1253f77b8a66f27685fae453ce98b14Just		Res.UseResFile(resRef)
1307842e56b97ce677b83bdab09cda48bc2d89ac75aJust		n = Res.Count1Resources('POST')
1317842e56b97ce677b83bdab09cda48bc2d89ac75aJust		data = []
1327842e56b97ce677b83bdab09cda48bc2d89ac75aJust		for i in range(501, 501 + n):
1337842e56b97ce677b83bdab09cda48bc2d89ac75aJust			res = Res.Get1Resource('POST', i)
1347842e56b97ce677b83bdab09cda48bc2d89ac75aJust			code = ord(res.data[0])
1357842e56b97ce677b83bdab09cda48bc2d89ac75aJust			if ord(res.data[1]) <> 0:
1367842e56b97ce677b83bdab09cda48bc2d89ac75aJust				raise error, 'corrupt LWFN file'
1377842e56b97ce677b83bdab09cda48bc2d89ac75aJust			if code in [1, 2]:
1387842e56b97ce677b83bdab09cda48bc2d89ac75aJust				data.append(res.data[2:])
1397842e56b97ce677b83bdab09cda48bc2d89ac75aJust			elif code in [3, 5]:
1407842e56b97ce677b83bdab09cda48bc2d89ac75aJust				break
1417842e56b97ce677b83bdab09cda48bc2d89ac75aJust			elif code == 4:
1427842e56b97ce677b83bdab09cda48bc2d89ac75aJust				f = open(path, "rb")
1437842e56b97ce677b83bdab09cda48bc2d89ac75aJust				data.append(f.read())
1447842e56b97ce677b83bdab09cda48bc2d89ac75aJust				f.close()
1457842e56b97ce677b83bdab09cda48bc2d89ac75aJust			elif code == 0:
1467842e56b97ce677b83bdab09cda48bc2d89ac75aJust				pass # comment, ignore
1477842e56b97ce677b83bdab09cda48bc2d89ac75aJust			else:
1487842e56b97ce677b83bdab09cda48bc2d89ac75aJust				raise error, 'bad chunk code: ' + `code`
1497842e56b97ce677b83bdab09cda48bc2d89ac75aJust	finally:
150e9601bf9e1253f77b8a66f27685fae453ce98b14Just		Res.CloseResFile(resRef)
1517842e56b97ce677b83bdab09cda48bc2d89ac75aJust	data = string.join(data, '')
1525810aa9967488207b039cb2d300fa53c91d4df2fJust	assertType1(data)
1537842e56b97ce677b83bdab09cda48bc2d89ac75aJust	return data
1547842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1555810aa9967488207b039cb2d300fa53c91d4df2fJustdef readPFB(path, onlyHeader=0):
1567842e56b97ce677b83bdab09cda48bc2d89ac75aJust	"""reads a PFB font file, returns raw data"""
1577842e56b97ce677b83bdab09cda48bc2d89ac75aJust	f = open(path, "rb")
1587842e56b97ce677b83bdab09cda48bc2d89ac75aJust	data = []
1597842e56b97ce677b83bdab09cda48bc2d89ac75aJust	while 1:
1607842e56b97ce677b83bdab09cda48bc2d89ac75aJust		if f.read(1) <> chr(128):
1617842e56b97ce677b83bdab09cda48bc2d89ac75aJust			raise error, 'corrupt PFB file'
1627842e56b97ce677b83bdab09cda48bc2d89ac75aJust		code = ord(f.read(1))
1637842e56b97ce677b83bdab09cda48bc2d89ac75aJust		if code in [1, 2]:
1645810aa9967488207b039cb2d300fa53c91d4df2fJust			chunklen = stringToLong(f.read(4))
1653618300613e796ff81abddde967b1092ac1dc915Just			chunk = f.read(chunklen)
1663618300613e796ff81abddde967b1092ac1dc915Just			assert len(chunk) == chunklen
1673618300613e796ff81abddde967b1092ac1dc915Just			data.append(chunk)
1687842e56b97ce677b83bdab09cda48bc2d89ac75aJust		elif code == 3:
1697842e56b97ce677b83bdab09cda48bc2d89ac75aJust			break
1707842e56b97ce677b83bdab09cda48bc2d89ac75aJust		else:
1717842e56b97ce677b83bdab09cda48bc2d89ac75aJust			raise error, 'bad chunk code: ' + `code`
1725810aa9967488207b039cb2d300fa53c91d4df2fJust		if onlyHeader:
1735810aa9967488207b039cb2d300fa53c91d4df2fJust			break
1747842e56b97ce677b83bdab09cda48bc2d89ac75aJust	f.close()
1757842e56b97ce677b83bdab09cda48bc2d89ac75aJust	data = string.join(data, '')
1765810aa9967488207b039cb2d300fa53c91d4df2fJust	assertType1(data)
1777842e56b97ce677b83bdab09cda48bc2d89ac75aJust	return data
1787842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1795810aa9967488207b039cb2d300fa53c91d4df2fJustdef readOther(path):
1807842e56b97ce677b83bdab09cda48bc2d89ac75aJust	"""reads any (font) file, returns raw data"""
1817842e56b97ce677b83bdab09cda48bc2d89ac75aJust	f = open(path, "rb")
1827842e56b97ce677b83bdab09cda48bc2d89ac75aJust	data = f.read()
1837842e56b97ce677b83bdab09cda48bc2d89ac75aJust	f.close()
1845810aa9967488207b039cb2d300fa53c91d4df2fJust	assertType1(data)
1857842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1865810aa9967488207b039cb2d300fa53c91d4df2fJust	chunks = findEncryptedChunks(data)
1877842e56b97ce677b83bdab09cda48bc2d89ac75aJust	data = []
1885810aa9967488207b039cb2d300fa53c91d4df2fJust	for isEncrypted, chunk in chunks:
1895810aa9967488207b039cb2d300fa53c91d4df2fJust		if isEncrypted and isHex(chunk[:4]):
1905810aa9967488207b039cb2d300fa53c91d4df2fJust			data.append(deHexString(chunk))
1917842e56b97ce677b83bdab09cda48bc2d89ac75aJust		else:
1927842e56b97ce677b83bdab09cda48bc2d89ac75aJust			data.append(chunk)
1937842e56b97ce677b83bdab09cda48bc2d89ac75aJust	return string.join(data, '')
1947842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1957842e56b97ce677b83bdab09cda48bc2d89ac75aJust# file writing tools
1967842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1975810aa9967488207b039cb2d300fa53c91d4df2fJustdef writeLWFN(path, data):
198e9601bf9e1253f77b8a66f27685fae453ce98b14Just	Res.FSpCreateResFile(path, "just", "LWFN", 0)
199e9601bf9e1253f77b8a66f27685fae453ce98b14Just	resRef = Res.FSpOpenResFile(path, 2)  # write-only
2007842e56b97ce677b83bdab09cda48bc2d89ac75aJust	try:
201e9601bf9e1253f77b8a66f27685fae453ce98b14Just		Res.UseResFile(resRef)
2027842e56b97ce677b83bdab09cda48bc2d89ac75aJust		resID = 501
2035810aa9967488207b039cb2d300fa53c91d4df2fJust		chunks = findEncryptedChunks(data)
2045810aa9967488207b039cb2d300fa53c91d4df2fJust		for isEncrypted, chunk in chunks:
2055810aa9967488207b039cb2d300fa53c91d4df2fJust			if isEncrypted:
2067842e56b97ce677b83bdab09cda48bc2d89ac75aJust				code = 2
2077842e56b97ce677b83bdab09cda48bc2d89ac75aJust			else:
2087842e56b97ce677b83bdab09cda48bc2d89ac75aJust				code = 1
2097842e56b97ce677b83bdab09cda48bc2d89ac75aJust			while chunk:
2107842e56b97ce677b83bdab09cda48bc2d89ac75aJust				res = Res.Resource(chr(code) + '\0' + chunk[:LWFNCHUNKSIZE - 2])
2117842e56b97ce677b83bdab09cda48bc2d89ac75aJust				res.AddResource('POST', resID, '')
2127842e56b97ce677b83bdab09cda48bc2d89ac75aJust				chunk = chunk[LWFNCHUNKSIZE - 2:]
2137842e56b97ce677b83bdab09cda48bc2d89ac75aJust				resID = resID + 1
2147842e56b97ce677b83bdab09cda48bc2d89ac75aJust		res = Res.Resource(chr(5) + '\0')
2157842e56b97ce677b83bdab09cda48bc2d89ac75aJust		res.AddResource('POST', resID, '')
2167842e56b97ce677b83bdab09cda48bc2d89ac75aJust	finally:
217e9601bf9e1253f77b8a66f27685fae453ce98b14Just		Res.CloseResFile(resRef)
2187842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2195810aa9967488207b039cb2d300fa53c91d4df2fJustdef writePFB(path, data):
2205810aa9967488207b039cb2d300fa53c91d4df2fJust	chunks = findEncryptedChunks(data)
2213618300613e796ff81abddde967b1092ac1dc915Just	f = open(path, "wb")
2227842e56b97ce677b83bdab09cda48bc2d89ac75aJust	try:
2235810aa9967488207b039cb2d300fa53c91d4df2fJust		for isEncrypted, chunk in chunks:
2245810aa9967488207b039cb2d300fa53c91d4df2fJust			if isEncrypted:
2257842e56b97ce677b83bdab09cda48bc2d89ac75aJust				code = 2
2267842e56b97ce677b83bdab09cda48bc2d89ac75aJust			else:
2277842e56b97ce677b83bdab09cda48bc2d89ac75aJust				code = 1
2287842e56b97ce677b83bdab09cda48bc2d89ac75aJust			f.write(chr(128) + chr(code))
2295810aa9967488207b039cb2d300fa53c91d4df2fJust			f.write(longToString(len(chunk)))
2307842e56b97ce677b83bdab09cda48bc2d89ac75aJust			f.write(chunk)
2317842e56b97ce677b83bdab09cda48bc2d89ac75aJust		f.write(chr(128) + chr(3))
2327842e56b97ce677b83bdab09cda48bc2d89ac75aJust	finally:
2337842e56b97ce677b83bdab09cda48bc2d89ac75aJust		f.close()
2347842e56b97ce677b83bdab09cda48bc2d89ac75aJust	if os.name == 'mac':
2353618300613e796ff81abddde967b1092ac1dc915Just		fss = macfs.FSSpec(path)
2367842e56b97ce677b83bdab09cda48bc2d89ac75aJust		fss.SetCreatorType('mdos', 'BINA')
2377842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2385810aa9967488207b039cb2d300fa53c91d4df2fJustdef writeOther(path, data, dohex = 0):
2395810aa9967488207b039cb2d300fa53c91d4df2fJust	chunks = findEncryptedChunks(data)
2407842e56b97ce677b83bdab09cda48bc2d89ac75aJust	f = open(path, "wb")
2417842e56b97ce677b83bdab09cda48bc2d89ac75aJust	try:
2427842e56b97ce677b83bdab09cda48bc2d89ac75aJust		hexlinelen = HEXLINELENGTH / 2
2435810aa9967488207b039cb2d300fa53c91d4df2fJust		for isEncrypted, chunk in chunks:
2445810aa9967488207b039cb2d300fa53c91d4df2fJust			if isEncrypted:
2457842e56b97ce677b83bdab09cda48bc2d89ac75aJust				code = 2
2467842e56b97ce677b83bdab09cda48bc2d89ac75aJust			else:
2477842e56b97ce677b83bdab09cda48bc2d89ac75aJust				code = 1
2487842e56b97ce677b83bdab09cda48bc2d89ac75aJust			if code == 2 and dohex:
2497842e56b97ce677b83bdab09cda48bc2d89ac75aJust				while chunk:
250c2be3d982b04c4bbb1c11d9ab8452f78415d6522Just					f.write(eexec.hexString(chunk[:hexlinelen]))
2517842e56b97ce677b83bdab09cda48bc2d89ac75aJust					f.write('\r')
2527842e56b97ce677b83bdab09cda48bc2d89ac75aJust					chunk = chunk[hexlinelen:]
2537842e56b97ce677b83bdab09cda48bc2d89ac75aJust			else:
2547842e56b97ce677b83bdab09cda48bc2d89ac75aJust				f.write(chunk)
2557842e56b97ce677b83bdab09cda48bc2d89ac75aJust	finally:
2567842e56b97ce677b83bdab09cda48bc2d89ac75aJust		f.close()
2577842e56b97ce677b83bdab09cda48bc2d89ac75aJust	if os.name == 'mac':
2587842e56b97ce677b83bdab09cda48bc2d89ac75aJust		fss = macfs.FSSpec(path)
2597842e56b97ce677b83bdab09cda48bc2d89ac75aJust		fss.SetCreatorType('R*ch', 'TEXT') # BBEdit text file
2607842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2617842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2627842e56b97ce677b83bdab09cda48bc2d89ac75aJust# decryption tools
2637842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2647842e56b97ce677b83bdab09cda48bc2d89ac75aJustEEXECBEGIN = "currentfile eexec"
2657842e56b97ce677b83bdab09cda48bc2d89ac75aJustEEXECEND = '0' * 64
2667842e56b97ce677b83bdab09cda48bc2d89ac75aJustEEXECINTERNALEND = "currentfile closefile"
2677842e56b97ce677b83bdab09cda48bc2d89ac75aJustEEXECBEGINMARKER = "%-- eexec start\r"
2687842e56b97ce677b83bdab09cda48bc2d89ac75aJustEEXECENDMARKER = "%-- eexec end\r"
2697842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2707842e56b97ce677b83bdab09cda48bc2d89ac75aJust_ishexRE = re.compile('[0-9A-Fa-f]*$')
2717842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2725810aa9967488207b039cb2d300fa53c91d4df2fJustdef isHex(text):
2737842e56b97ce677b83bdab09cda48bc2d89ac75aJust	return _ishexRE.match(text) is not None
2747842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2757842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2765810aa9967488207b039cb2d300fa53c91d4df2fJustdef decryptType1(data):
2775810aa9967488207b039cb2d300fa53c91d4df2fJust	chunks = findEncryptedChunks(data)
2787842e56b97ce677b83bdab09cda48bc2d89ac75aJust	data = []
2795810aa9967488207b039cb2d300fa53c91d4df2fJust	for isEncrypted, chunk in chunks:
2805810aa9967488207b039cb2d300fa53c91d4df2fJust		if isEncrypted:
2815810aa9967488207b039cb2d300fa53c91d4df2fJust			if isHex(chunk[:4]):
2825810aa9967488207b039cb2d300fa53c91d4df2fJust				chunk = deHexString(chunk)
283c2be3d982b04c4bbb1c11d9ab8452f78415d6522Just			decrypted, R = eexec.decrypt(chunk, 55665)
2847842e56b97ce677b83bdab09cda48bc2d89ac75aJust			decrypted = decrypted[4:]
2857842e56b97ce677b83bdab09cda48bc2d89ac75aJust			if decrypted[-len(EEXECINTERNALEND)-1:-1] <> EEXECINTERNALEND \
2867842e56b97ce677b83bdab09cda48bc2d89ac75aJust					and decrypted[-len(EEXECINTERNALEND)-2:-2] <> EEXECINTERNALEND:
2877842e56b97ce677b83bdab09cda48bc2d89ac75aJust				raise error, "invalid end of eexec part"
2887842e56b97ce677b83bdab09cda48bc2d89ac75aJust			decrypted = decrypted[:-len(EEXECINTERNALEND)-2] + '\r'
2897842e56b97ce677b83bdab09cda48bc2d89ac75aJust			data.append(EEXECBEGINMARKER + decrypted + EEXECENDMARKER)
2907842e56b97ce677b83bdab09cda48bc2d89ac75aJust		else:
2917842e56b97ce677b83bdab09cda48bc2d89ac75aJust			if chunk[-len(EEXECBEGIN)-1:-1] == EEXECBEGIN:
2927842e56b97ce677b83bdab09cda48bc2d89ac75aJust				data.append(chunk[:-len(EEXECBEGIN)-1])
2937842e56b97ce677b83bdab09cda48bc2d89ac75aJust			else:
2947842e56b97ce677b83bdab09cda48bc2d89ac75aJust				data.append(chunk)
2957842e56b97ce677b83bdab09cda48bc2d89ac75aJust	return string.join(data, '')
2967842e56b97ce677b83bdab09cda48bc2d89ac75aJust
2975810aa9967488207b039cb2d300fa53c91d4df2fJustdef findEncryptedChunks(data):
2987842e56b97ce677b83bdab09cda48bc2d89ac75aJust	chunks = []
2997842e56b97ce677b83bdab09cda48bc2d89ac75aJust	while 1:
3005810aa9967488207b039cb2d300fa53c91d4df2fJust		eBegin = string.find(data, EEXECBEGIN)
3015810aa9967488207b039cb2d300fa53c91d4df2fJust		if eBegin < 0:
3027842e56b97ce677b83bdab09cda48bc2d89ac75aJust			break
3035810aa9967488207b039cb2d300fa53c91d4df2fJust		eEnd = string.find(data, EEXECEND, eBegin)
3045810aa9967488207b039cb2d300fa53c91d4df2fJust		if eEnd < 0:
3057842e56b97ce677b83bdab09cda48bc2d89ac75aJust			raise error, "can't find end of eexec part"
3065810aa9967488207b039cb2d300fa53c91d4df2fJust		chunks.append((0, data[:eBegin + len(EEXECBEGIN) + 1]))
3075810aa9967488207b039cb2d300fa53c91d4df2fJust		chunks.append((1, data[eBegin + len(EEXECBEGIN) + 1:eEnd]))
3085810aa9967488207b039cb2d300fa53c91d4df2fJust		data = data[eEnd:]
3097842e56b97ce677b83bdab09cda48bc2d89ac75aJust	chunks.append((0, data))
3107842e56b97ce677b83bdab09cda48bc2d89ac75aJust	return chunks
3117842e56b97ce677b83bdab09cda48bc2d89ac75aJust
3125810aa9967488207b039cb2d300fa53c91d4df2fJustdef deHexString(hexstring):
313c2be3d982b04c4bbb1c11d9ab8452f78415d6522Just	return eexec.deHexString(string.join(string.split(hexstring), ""))
3147842e56b97ce677b83bdab09cda48bc2d89ac75aJust
3157842e56b97ce677b83bdab09cda48bc2d89ac75aJust
3167842e56b97ce677b83bdab09cda48bc2d89ac75aJust# Type 1 assertion
3177842e56b97ce677b83bdab09cda48bc2d89ac75aJust
3187842e56b97ce677b83bdab09cda48bc2d89ac75aJust_fontType1RE = re.compile(r"/FontType\s+1\s+def")
3197842e56b97ce677b83bdab09cda48bc2d89ac75aJust
3205810aa9967488207b039cb2d300fa53c91d4df2fJustdef assertType1(data):
3217842e56b97ce677b83bdab09cda48bc2d89ac75aJust	for head in ['%!PS-AdobeFont', '%!FontType1-1.0']:
3227842e56b97ce677b83bdab09cda48bc2d89ac75aJust		if data[:len(head)] == head:
3237842e56b97ce677b83bdab09cda48bc2d89ac75aJust			break
3247842e56b97ce677b83bdab09cda48bc2d89ac75aJust	else:
3257842e56b97ce677b83bdab09cda48bc2d89ac75aJust		raise error, "not a PostScript font"
3267842e56b97ce677b83bdab09cda48bc2d89ac75aJust	if not _fontType1RE.search(data):
3277842e56b97ce677b83bdab09cda48bc2d89ac75aJust		raise error, "not a Type 1 font"
3287842e56b97ce677b83bdab09cda48bc2d89ac75aJust	if string.find(data, "currentfile eexec") < 0:
3297842e56b97ce677b83bdab09cda48bc2d89ac75aJust		raise error, "not an encrypted Type 1 font"
3307842e56b97ce677b83bdab09cda48bc2d89ac75aJust	# XXX what else?
3317842e56b97ce677b83bdab09cda48bc2d89ac75aJust	return data
3327842e56b97ce677b83bdab09cda48bc2d89ac75aJust
3337842e56b97ce677b83bdab09cda48bc2d89ac75aJust
3347842e56b97ce677b83bdab09cda48bc2d89ac75aJust# pfb helpers
3357842e56b97ce677b83bdab09cda48bc2d89ac75aJust
3365810aa9967488207b039cb2d300fa53c91d4df2fJustdef longToString(long):
3377842e56b97ce677b83bdab09cda48bc2d89ac75aJust	str = ""
3387842e56b97ce677b83bdab09cda48bc2d89ac75aJust	for i in range(4):
3397842e56b97ce677b83bdab09cda48bc2d89ac75aJust		str = str + chr((long & (0xff << (i * 8))) >> i * 8)
3407842e56b97ce677b83bdab09cda48bc2d89ac75aJust	return str
3417842e56b97ce677b83bdab09cda48bc2d89ac75aJust
3425810aa9967488207b039cb2d300fa53c91d4df2fJustdef stringToLong(str):
3437842e56b97ce677b83bdab09cda48bc2d89ac75aJust	if len(str) <> 4:
3447842e56b97ce677b83bdab09cda48bc2d89ac75aJust		raise ValueError, 'string must be 4 bytes long'
3457842e56b97ce677b83bdab09cda48bc2d89ac75aJust	long = 0
3467842e56b97ce677b83bdab09cda48bc2d89ac75aJust	for i in range(4):
3477842e56b97ce677b83bdab09cda48bc2d89ac75aJust		long = long + (ord(str[i]) << (i * 8))
3487842e56b97ce677b83bdab09cda48bc2d89ac75aJust	return long
3495810aa9967488207b039cb2d300fa53c91d4df2fJust
350