# Source code for fontTools.t1Lib

    """fontTools.t1Lib.py -- Tools for PostScript Type 1 fonts (Python2 only)

    Functions for reading and writing raw Type 1 data:

    read(path)
    	reads any Type 1 font file, returns the raw data and a type indicator:
    	'LWFN', 'PFB' or 'OTHER', depending on the format of the file pointed
    	to by 'path'.
    	Raises an error when the file does not contain valid Type 1 data.

    write(path, data, kind='OTHER', dohex=False)
    	writes raw Type 1 data to the file pointed to by 'path'.
    	'kind' can be one of 'LWFN', 'PFB' or 'OTHER'; it defaults to 'OTHER'.
    	'dohex' is a flag which determines whether the eexec encrypted
    	part should be written as hexadecimal or binary, but only if kind
    	is 'OTHER'.
    """
    import fontTools
    from fontTools.misc import eexec
    from fontTools.misc.macCreatorType import getMacCreatorAndType
    from fontTools.misc.textTools import bytechr, byteord, bytesjoin, tobytes
    from fontTools.misc.psOperators import _type1_pre_eexec_order, _type1_fontinfo_order, _type1_post_eexec_order
    from fontTools.encodings.StandardEncoding import StandardEncoding
    import os
    import re

    __author__ = "jvr"
    __version__ = "1.0b3"
    DEBUG = 0

    try:
    	try:
    		from Carbon import Res
    	except ImportError:
    		import Res  # MacPython < 2.2
    except ImportError:
    	haveMacSupport = 0
    else:
    	haveMacSupport = 1

    class T1Error(Exception): pass

    class T1Font(object):

    	"""Type 1 font class.

    	Uses a minimal interpeter that supports just about enough PS to parse
    	Type 1 fonts.
    	"""

    	def __init__(self, path, encoding="ascii", kind=None):
    		if kind is None:
    			self.data, _ = read(path)
    		elif kind == "LWFN":
    			self.data = readLWFN(path)
    		elif kind == "PFB":
    			self.data = readPFB(path)
    		elif kind == "OTHER":
    			self.data = readOther(path)
    		else:
    			raise ValueError(kind)
    		self.encoding = encoding

    	def saveAs(self, path, type, dohex=False):
    		write(path, self.getData(), type, dohex)

    	def getData(self):
    		if not hasattr(self, "data"):
    			self.data = self.createData()
    		return self.data

    	def getGlyphSet(self):
    		"""Return a generic GlyphSet, which is a dict-like object
    		mapping glyph names to glyph objects. The returned glyph objects
    		have a .draw() method that supports the Pen protocol, and will
    		have an attribute named 'width', but only *after* the .draw() method
    		has been called.

    		In the case of Type 1, the GlyphSet is simply the CharStrings dict.
    		"""
    		return self["CharStrings"]

    	def __getitem__(self, key):
    		if not hasattr(self, "font"):
    			self.parse()
    		return self.font[key]

    	def parse(self):
    		from fontTools.misc import psLib
    		from fontTools.misc import psCharStrings
    		self.font = psLib.suckfont(self.data, self.encoding)
    		charStrings = self.font["CharStrings"]
    		lenIV = self.font["Private"].get("lenIV", 4)
    		assert lenIV >= 0
    		subrs = self.font["Private"]["Subrs"]
    		for glyphName, charString in charStrings.items():
    			charString, R = eexec.decrypt(charString, 4330)
    			charStrings[glyphName] = psCharStrings.T1CharString(charString[lenIV:],
    					subrs=subrs)
    		for i in range(len(subrs)):
    			charString, R = eexec.decrypt(subrs[i], 4330)
    			subrs[i] = psCharStrings.T1CharString(charString[lenIV:], subrs=subrs)
    		del self.data

    	def createData(self):
    		sf = self.font

    		eexec_began = False
    		eexec_dict = {}
    		lines = []
    		lines.extend([self._tobytes(f"%!FontType1-1.1: {sf['FontName']}"),
    					  self._tobytes(f"%t1Font: ({fontTools.version})"),
    					  self._tobytes(f"%%BeginResource: font {sf['FontName']}")])
    		# follow t1write.c:writeRegNameKeyedFont
    		size = 3 # Headroom for new key addition
    		size += 1 # FontMatrix is always counted
    		size += 1 + 1 # Private, CharStings
    		for key in font_dictionary_keys:
    			size += int(key in sf)
    		lines.append(self._tobytes(f"{size} dict dup begin"))

    		for key, value in sf.items():
    			if eexec_began:
    				eexec_dict[key] = value
    				continue

    			if key == "FontInfo":
    				fi = sf["FontInfo"]
    				# follow t1write.c:writeFontInfoDict
    				size = 3 # Headroom for new key addition
    				for subkey in FontInfo_dictionary_keys:
    					size += int(subkey in fi)
    				lines.append(self._tobytes(f"/FontInfo {size} dict dup begin"))

    				for subkey, subvalue in fi.items():
    					lines.extend(self._make_lines(subkey, subvalue))
    				lines.append(b"end def")
    			elif key in _type1_post_eexec_order: # usually 'Private'
    				eexec_dict[key] = value
    				eexec_began = True
    			else:
    				lines.extend(self._make_lines(key, value))
    		lines.append(b"end")
    		eexec_portion = self.encode_eexec(eexec_dict)
    		lines.append(bytesjoin([b"currentfile eexec ", eexec_portion]))

    		for _ in range(8):
    			lines.append(self._tobytes("0"*64))
    		lines.extend([b"cleartomark",
    					  b"%%EndResource",
    					  b"%%EOF"])

    		data = bytesjoin(lines, "\n")
    		return data

    	def encode_eexec(self, eexec_dict):
    		lines = []

    		# '-|', '|-', '|'
    		RD_key, ND_key, NP_key = None, None, None

    		for key, value in eexec_dict.items():
    			if key == "Private":
    				pr = eexec_dict["Private"]
    				# follow t1write.c:writePrivateDict
    				size = 3 # for RD, ND, NP
    				for subkey in Private_dictionary_keys:
    					size += int(subkey in pr)
    				lines.append(b"dup /Private")
    				lines.append(self._tobytes(f"{size} dict dup begin"))
    				for subkey, subvalue in pr.items():
    					if not RD_key and subvalue == RD_value:
    						RD_key = subkey
    					elif not ND_key and subvalue == ND_value:
    						ND_key = subkey
    					elif not NP_key and subvalue == PD_value:
    						NP_key = subkey

    					if subkey == 'OtherSubrs':
    						# XXX: assert that no flex hint is used
    						lines.append(self._tobytes(hintothers))
    					elif subkey == "Subrs":
    						# XXX: standard Subrs only
    						lines.append(b"/Subrs 5 array")
    						for i, subr_bin in enumerate(std_subrs):
    							encrypted_subr, R = eexec.encrypt(bytesjoin([char_IV, subr_bin]), 4330)
    							lines.append(bytesjoin([self._tobytes(f"dup {i} {len(encrypted_subr)} {RD_key} "), encrypted_subr, self._tobytes(f" {NP_key}")]))
    						lines.append(b'def')

    						lines.append(b"put")
    					else:
    						lines.extend(self._make_lines(subkey, subvalue))
    			elif key == "CharStrings":
    				lines.append(b"dup /CharStrings")
    				lines.append(self._tobytes(f"{len(eexec_dict['CharStrings'])} dict dup begin"))
    				for glyph_name, char_bin in eexec_dict["CharStrings"].items():
    					char_bin.compile()
    					encrypted_char, R = eexec.encrypt(bytesjoin([char_IV, char_bin.bytecode]), 4330)
    					lines.append(bytesjoin([self._tobytes(f"/{glyph_name} {len(encrypted_char)} {RD_key} "), encrypted_char, self._tobytes(f" {ND_key}")]))
    				lines.append(b"end put")
    			else:
    				lines.extend(self._make_lines(key, value))

    		lines.extend([b"end",
    					  b"dup /FontName get exch definefont pop",
    					  b"mark",
    					  b"currentfile closefile\n"])

    		eexec_portion = bytesjoin(lines, "\n")
    		encrypted_eexec, R = eexec.encrypt(bytesjoin([eexec_IV, eexec_portion]), 55665)

    		return encrypted_eexec

    	def _make_lines(self, key, value):
    		if key == "FontName":
    			return [self._tobytes(f"/{key} /{value} def")]
    		if key in ["isFixedPitch", "ForceBold", "RndStemUp"]:
    			return [self._tobytes(f"/{key} {'true' if value else 'false'} def")]
    		elif key == "Encoding":
    			if value == StandardEncoding:
    				return [self._tobytes(f"/{key} StandardEncoding def")]
    			else:
    				# follow fontTools.misc.psOperators._type1_Encoding_repr
    				lines = []
    				lines.append(b"/Encoding 256 array")
    				lines.append(b"0 1 255 {1 index exch /.notdef put} for")
    				for i in range(256):
    					name = value[i]
    					if name != ".notdef":
    						lines.append(self._tobytes(f"dup {i} /{name} put"))
    				lines.append(b"def")
    				return lines
    		if isinstance(value, str):
    			return [self._tobytes(f"/{key} ({value}) def")]
    		elif isinstance(value, bool):
    			return [self._tobytes(f"/{key} {'true' if value else 'false'} def")]
    		elif isinstance(value, list):
    			return [self._tobytes(f"/{key} [{' '.join(str(v) for v in value)}] def")]
    		elif isinstance(value, tuple):
    			return [self._tobytes(f"/{key} {{{' '.join(str(v) for v in value)}}} def")]
    		else:
    			return [self._tobytes(f"/{key} {value} def")]

    	def _tobytes(self, s, errors="strict"):
    		return tobytes(s, self.encoding, errors)

    # low level T1 data read and write functions

    def read(path, onlyHeader=False):
    	"""reads any Type 1 font file, returns raw data"""
    	_, ext = os.path.splitext(path)
    	ext = ext.lower()
    	creator, typ = getMacCreatorAndType(path)
    	if typ == 'LWFN':
    		return readLWFN(path, onlyHeader), 'LWFN'
    	if ext == '.pfb':
    		return readPFB(path, onlyHeader), 'PFB'
    	else:
    		return readOther(path), 'OTHER'

    def write(path, data, kind='OTHER', dohex=False):
    	assertType1(data)
    	kind = kind.upper()
    	try:
    		os.remove(path)
    	except os.error:
    		pass
    	err = 1
    	try:
    		if kind == 'LWFN':
    			writeLWFN(path, data)
    		elif kind == 'PFB':
    			writePFB(path, data)
    		else:
    			writeOther(path, data, dohex)
    		err = 0
    	finally:
    		if err and not DEBUG:
    			try:
    				os.remove(path)
    			except os.error:
    				pass

    # -- internal --

    LWFNCHUNKSIZE = 2000
    HEXLINELENGTH = 80

    def readLWFN(path, onlyHeader=False):
    	"""reads an LWFN font file, returns raw data"""
    	from fontTools.misc.macRes import ResourceReader
    	reader = ResourceReader(path)
    	try:
    		data = []
    		for res in reader.get('POST', []):
    			code = byteord(res.data[0])
    			if byteord(res.data[1]) != 0:
    				raise T1Error('corrupt LWFN file')
    			if code in [1, 2]:
    				if onlyHeader and code == 2:
    					break
    				data.append(res.data[2:])
    			elif code in [3, 5]:
    				break
    			elif code == 4:
    				with open(path, "rb") as f:
    					data.append(f.read())
    			elif code == 0:
    				pass # comment, ignore
    			else:
    				raise T1Error('bad chunk code: ' + repr(code))
    	finally:
    		reader.close()
    	data = bytesjoin(data)
    	assertType1(data)
    	return data

    def readPFB(path, onlyHeader=False):
    	"""reads a PFB font file, returns raw data"""
    	data = []
    	with open(path, "rb") as f:
    		while True:
    			if f.read(1) != bytechr(128):
    				raise T1Error('corrupt PFB file')
    			code = byteord(f.read(1))
    			if code in [1, 2]:
    				chunklen = stringToLong(f.read(4))
    				chunk = f.read(chunklen)
    				assert len(chunk) == chunklen
    				data.append(chunk)
    			elif code == 3:
    				break
    			else:
    				raise T1Error('bad chunk code: ' + repr(code))
    			if onlyHeader:
    				break
    	data = bytesjoin(data)
    	assertType1(data)
    	return data

    def readOther(path):
    	"""reads any (font) file, returns raw data"""
    	with open(path, "rb") as f:
    		data = f.read()
    	assertType1(data)
    	chunks = findEncryptedChunks(data)
    	data = []
    	for isEncrypted, chunk in chunks:
    		if isEncrypted and isHex(chunk[:4]):
    			data.append(deHexString(chunk))
    		else:
    			data.append(chunk)
    	return bytesjoin(data)

    # file writing tools

    def writeLWFN(path, data):
    	# Res.FSpCreateResFile was deprecated in OS X 10.5
    	Res.FSpCreateResFile(path, "just", "LWFN", 0)
    	resRef = Res.FSOpenResFile(path, 2)  # write-only
    	try:
    		Res.UseResFile(resRef)
    		resID = 501
    		chunks = findEncryptedChunks(data)
    		for isEncrypted, chunk in chunks:
    			if isEncrypted:
    				code = 2
    			else:
    				code = 1
    			while chunk:
    				res = Res.Resource(bytechr(code) + '\0' + chunk[:LWFNCHUNKSIZE - 2])
    				res.AddResource('POST', resID, '')
    				chunk = chunk[LWFNCHUNKSIZE - 2:]
    				resID = resID + 1
    		res = Res.Resource(bytechr(5) + '\0')
    		res.AddResource('POST', resID, '')
    	finally:
    		Res.CloseResFile(resRef)

    def writePFB(path, data):
    	chunks = findEncryptedChunks(data)
    	with open(path, "wb") as f:
    		for isEncrypted, chunk in chunks:
    			if isEncrypted:
    				code = 2
    			else:
    				code = 1
    			f.write(bytechr(128) + bytechr(code))
    			f.write(longToString(len(chunk)))
    			f.write(chunk)
    		f.write(bytechr(128) + bytechr(3))

    def writeOther(path, data, dohex=False):
    	chunks = findEncryptedChunks(data)
    	with open(path, "wb") as f:
    		hexlinelen = HEXLINELENGTH // 2
    		for isEncrypted, chunk in chunks:
    			if isEncrypted:
    				code = 2
    			else:
    				code = 1
    			if code == 2 and dohex:
    				while chunk:
    					f.write(eexec.hexString(chunk[:hexlinelen]))
    					f.write(b'\r')
    					chunk = chunk[hexlinelen:]
    			else:
    				f.write(chunk)

    # decryption tools

    EEXECBEGIN = b"currentfile eexec"
    # The spec allows for 512 ASCII zeros interrupted by arbitrary whitespace to
    # follow eexec
    EEXECEND = re.compile(b'(0[ \t\r\n]*){512}', flags=re.M)
    EEXECINTERNALEND = b"currentfile closefile"
    EEXECBEGINMARKER = b"%-- eexec start\r"
    EEXECENDMARKER = b"%-- eexec end\r"

    _ishexRE = re.compile(b'[0-9A-Fa-f]*$')

    def isHex(text):
    	return _ishexRE.match(text) is not None

    def decryptType1(data):
    	chunks = findEncryptedChunks(data)
    	data = []
    	for isEncrypted, chunk in chunks:
    		if isEncrypted:
    			if isHex(chunk[:4]):
    				chunk = deHexString(chunk)
    			decrypted, R = eexec.decrypt(chunk, 55665)
    			decrypted = decrypted[4:]
    			if decrypted[-len(EEXECINTERNALEND)-1:-1] != EEXECINTERNALEND \
    					and decrypted[-len(EEXECINTERNALEND)-2:-2] != EEXECINTERNALEND:
    				raise T1Error("invalid end of eexec part")
    			decrypted = decrypted[:-len(EEXECINTERNALEND)-2] + b'\r'
    			data.append(EEXECBEGINMARKER + decrypted + EEXECENDMARKER)
    		else:
    			if chunk[-len(EEXECBEGIN)-1:-1] == EEXECBEGIN:
    				data.append(chunk[:-len(EEXECBEGIN)-1])
    			else:
    				data.append(chunk)
    	return bytesjoin(data)

    def findEncryptedChunks(data):
    	chunks = []
    	while True:
    		eBegin = data.find(EEXECBEGIN)
    		if eBegin < 0:
    			break
    		eBegin = eBegin + len(EEXECBEGIN) + 1
    		endMatch = EEXECEND.search(data, eBegin)
    		if endMatch is None:
    			raise T1Error("can't find end of eexec part")
    		eEnd = endMatch.start()
    		cypherText = data[eBegin:eEnd + 2]
    		if isHex(cypherText[:4]):
    			cypherText = deHexString(cypherText)
    		plainText, R = eexec.decrypt(cypherText, 55665)
    		eEndLocal = plainText.find(EEXECINTERNALEND)
    		if eEndLocal < 0:
    			raise T1Error("can't find end of eexec part")
    		chunks.append((0, data[:eBegin]))
    		chunks.append((1, cypherText[:eEndLocal + len(EEXECINTERNALEND) + 1]))
    		data = data[eEnd:]
    	chunks.append((0, data))
    	return chunks

    def deHexString(hexstring):
    	return eexec.deHexString(bytesjoin(hexstring.split()))

    # Type 1 assertion

    _fontType1RE = re.compile(br"/FontType\s+1\s+def")

    def assertType1(data):
    	for head in [b'%!PS-AdobeFont', b'%!FontType1']:
    		if data[:len(head)] == head:
    			break
    	else:
    		raise T1Error("not a PostScript font")
    	if not _fontType1RE.search(data):
    		raise T1Error("not a Type 1 font")
    	if data.find(b"currentfile eexec") < 0:
    		raise T1Error("not an encrypted Type 1 font")
    	# XXX what else?
    	return data

    # pfb helpers

    def longToString(long):
    	s = b""
    	for i in range(4):
    		s += bytechr((long & (0xff << (i * 8))) >> i * 8)
    	return s

    def stringToLong(s):
    	if len(s) != 4:
    		raise ValueError('string must be 4 bytes long')
    	l = 0
    	for i in range(4):
    		l += byteord(s[i]) << (i * 8)
    	return l

    # PS stream helpers

    font_dictionary_keys = list(_type1_pre_eexec_order)
    # t1write.c:writeRegNameKeyedFont
    # always counts following keys
    font_dictionary_keys.remove("FontMatrix")

    FontInfo_dictionary_keys = list(_type1_fontinfo_order)
    # extend because AFDKO tx may use following keys
    FontInfo_dictionary_keys.extend([
    	"FSType",
    	"Copyright",
    ])

    Private_dictionary_keys = [
    	# We don't know what names will be actually used.
    	# "RD",
    	# "ND",
    	# "NP",
    	"Subrs",
    	"OtherSubrs",
    	"UniqueID",
    	"BlueValues",
    	"OtherBlues",
    	"FamilyBlues",
    	"FamilyOtherBlues",
    	"BlueScale",
    	"BlueShift",
    	"BlueFuzz",
    	"StdHW",
    	"StdVW",
    	"StemSnapH",
    	"StemSnapV",
    	"ForceBold",
    	"LanguageGroup",
    	"password",
    	"lenIV",
    	"MinFeature",
    	"RndStemUp",
    ]

    # t1write_hintothers.h
    hintothers = """/OtherSubrs[{}{}{}{systemdict/internaldict known not{pop 3}{1183615869
    systemdict/internaldict get exec dup/startlock known{/startlock get exec}{dup
    /strtlck known{/strtlck get exec}{pop 3}ifelse}ifelse}ifelse}executeonly]def"""
    # t1write.c:saveStdSubrs
    std_subrs = [
    	# 3 0 callother pop pop setcurrentpoint return
    	b"\x8e\x8b\x0c\x10\x0c\x11\x0c\x11\x0c\x21\x0b",
    	# 0 1 callother return
    	b"\x8b\x8c\x0c\x10\x0b",
    	# 0 2 callother return
    	b"\x8b\x8d\x0c\x10\x0b",
    	# return
    	b"\x0b",
    	# 3 1 3 callother pop callsubr return
    	b"\x8e\x8c\x8e\x0c\x10\x0c\x11\x0a\x0b"
    ]
    # follow t1write.c:writeRegNameKeyedFont
    eexec_IV = b"cccc"
    char_IV = b"\x0c\x0c\x0c\x0c"
    RD_value = ("string", "currentfile", "exch", "readstring", "pop")
    ND_value = ("def",)
    PD_value = ("put",)