cffLib.py revision e327558aa54f182912a26b8313f3af5a07bc2e77
1ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com"""cffLib.py -- read/write tools for Adobe CFF fonts."""
2ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com
3ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com#
4ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com# $Id: cffLib.py,v 1.9 2002-05-14 12:22:03 jvr Exp $
5ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com#
6ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.com
7ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.comimport struct, sstruct
8ec3ed6a5ebf6f2c406d7bcf94b6bc34fcaeb976eepoger@google.comimport string
98a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.comimport types
108a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.comfrom fontTools.misc import psCharStrings
118a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
128a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
138a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.comcffHeaderFormat = """
148a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com	major:   B
158a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com	minor:   B
168a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com	hdrSize: B
178a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com	offSize: B
188a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com"""
198a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
208a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.comclass CFFFontSet:
218a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
228a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com	def __init__(self):
238a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		self.fonts = {}
248a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
258a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com	def decompile(self, file):
268a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		sstruct.unpack(cffHeaderFormat, file.read(4), self)
278a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		assert self.major == 1 and self.minor == 0, \
288a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com				"unknown CFF format: %d.%d" % (self.major, self.minor)
298a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
308a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		self.fontNames = readINDEX(file)
318a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		topDicts = readINDEX(file)
328a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		strings = IndexedStrings(readINDEX(file))
338a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		globalSubrs = readINDEX(file)
348a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		self.GlobalSubrs = map(psCharStrings.T2CharString, globalSubrs)
358a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
368a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		for i in range(len(topDicts)):
378a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			font = self.fonts[self.fontNames[i]] = CFFFont()
388a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			font.GlobalSubrs = self.GlobalSubrs  # Hmm.
398a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			file.seek(0, 0)
408a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			font.decompile(file, topDicts[i], strings, self)  # maybe only 'on demand'?
418a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
428a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com	def compile(self):
438a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		strings = IndexedStrings()
448a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		XXXX
458a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
468a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com	def toXML(self, xmlWriter, progress=None):
478a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		xmlWriter.newline()
488a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		for fontName in self.fontNames:
498a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			xmlWriter.begintag("CFFFont", name=fontName)
508a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			xmlWriter.newline()
518a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			font = self.fonts[fontName]
528a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			font.toXML(xmlWriter, progress)
538a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			xmlWriter.endtag("CFFFont")
548a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			xmlWriter.newline()
558a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		xmlWriter.newline()
568a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		xmlWriter.begintag("GlobalSubrs")
578a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		xmlWriter.newline()
588a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		for i in range(len(self.GlobalSubrs)):
598a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			xmlWriter.newline()
608a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			xmlWriter.begintag("CharString", id=i)
618a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			xmlWriter.newline()
628a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			self.GlobalSubrs[i].toXML(xmlWriter)
638a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			xmlWriter.endtag("CharString")
648a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			xmlWriter.newline()
658a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		xmlWriter.newline()
668a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		xmlWriter.endtag("GlobalSubrs")
678a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		xmlWriter.newline()
688a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		xmlWriter.newline()
698a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
708a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com	def fromXML(self, (name, attrs, content)):
718a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		xxx
728a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
738a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
748a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.comclass CFFFont:
758a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
768a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com	defaults = topDictDefaults
778a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
788a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com	def __init__(self):
798a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		pass
808a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
818a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com	def __getattr__(self, attr):
828a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		if not self.defaults.has_key(attr):
838a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			raise AttributeError, attr
848a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		return self.defaults[attr]
858a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
868a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com	def fromDict(self, dict):
878a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		self.__dict__.update(dict)
888a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
898a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com	def decompileCID(self, data, strings):
908a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		offset = self.FDArray
918a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		fontDicts, restdata = readINDEX(data[offset:])
928a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		subFonts = []
938a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		for topDictData in fontDicts:
948a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			subFont = CFFFont()
958a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			subFonts.append(subFont)
968a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			subFont.decompile(data, topDictData, strings, None)
978a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
988a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		raise NotImplementedError
998a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
1008a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com	def decompile(self, file, topDictData, strings, fontSet):
1018a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		top = TopDictDecompiler(strings)
102d6176b0dcacb124539e0cfd051e6d93a9782f020rmistry@google.com		top.decompile(topDictData)
1038a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		self.fromDict(top.getDict())
1048a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
1058a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		if hasattr(self, "ROS"):
1068a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			isCID = 1
1078a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			# XXX CID subFonts
1088a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		else:
10964cc579efa7e416c7298ed159d76b074b283c0f9senorblanco@chromium.org			isCID = 0
1108a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			size, offset = self.Private
1118a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			file.seek(offset, 0)
1128a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			privateData = file.read(size)
1138a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			file.seek(offset, 0)
1148a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			assert len(privateData) == size
1158a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			self.Private = PrivateDict()
1168a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			self.Private.decompile(file, privateData, strings)
1178a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
1188a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		file.seek(self.CharStrings)
1198a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		rawCharStrings = readINDEX(file)
1208a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		nGlyphs = len(rawCharStrings)
1218a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
1228a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		# get charset (or rather: get glyphNames)
1238a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		if self.charset == 0:
1248a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			xxx  # standard charset
1258a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		else:
1268a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			file.seek(self.charset)
12764cc579efa7e416c7298ed159d76b074b283c0f9senorblanco@chromium.org			format = ord(file.read(1))
1288a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			if format == 0:
12964cc579efa7e416c7298ed159d76b074b283c0f9senorblanco@chromium.org				xxx
1308a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			elif format == 1:
1318a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com				charset = parseCharsetFormat1(nGlyphs, file, strings, isCID)
1328a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			elif format == 2:
1338a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com				charset = parseCharsetFormat2(nGlyphs, file, strings, isCID)
1348a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			elif format == 3:
1358a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com				xxx
1368a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			else:
1378a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com				xxx
138d6176b0dcacb124539e0cfd051e6d93a9782f020rmistry@google.com		self.charset = charset
1398a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
1408a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		assert len(charset) == nGlyphs
1418a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		self.CharStrings = charStrings = {}
1428a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		if self.CharstringType == 2:
1438a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			# Type 2 CharStrings
1448a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			charStringClass = psCharStrings.T2CharString
1458a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		else:
1468a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			# Type 1 CharStrings
1478a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			charStringClass = psCharStrings.T1CharString
1488a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		for i in range(nGlyphs):
1498a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			charStrings[charset[i]] = charStringClass(rawCharStrings[i])
1508a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		assert len(charStrings) == nGlyphs
1518a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
1528a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		# XXX Encoding!
1538a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		encoding = self.Encoding
1548a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		if encoding not in (0, 1):
1558a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			# encoding is an _offset_ from the beginning of 'data' to an encoding subtable
1568a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			XXX
1578a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			self.Encoding = encoding
1588a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
1598a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com	def getGlyphOrder(self):
1608a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		return self.charset
1618a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
1628a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com	def setGlyphOrder(self, glyphOrder):
1638a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		self.charset = glyphOrder
1648a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
1658a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com	def decompileAllCharStrings(self):
1668a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		if self.CharstringType == 2:
1678a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			# Type 2 CharStrings
1688a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			decompiler = psCharStrings.SimpleT2Decompiler(self.Private.Subrs, self.GlobalSubrs)
1698a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			for charString in self.CharStrings.values():
1708a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com				if charString.needsDecompilation():
1718a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com					decompiler.reset()
1728a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com					decompiler.execute(charString)
1738a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		else:
1748a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			# Type 1 CharStrings
17564cc579efa7e416c7298ed159d76b074b283c0f9senorblanco@chromium.org			for charString in self.CharStrings.values():
1768a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com				charString.decompile()
1778a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
1788a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com	def toXML(self, xmlWriter, progress=None):
1798a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		xmlWriter.newline()
1808a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		# first dump the simple values
1818a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		self.toXMLSimpleValues(xmlWriter)
1828a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
1838a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		# dump charset
1848a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		# XXX
1858a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
1868a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		# decompile all charstrings
1878a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		if progress:
1888a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			progress.setlabel("Decompiling CharStrings...")
1898a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		self.decompileAllCharStrings()
1908a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
1918a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		# dump private dict
1928a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		xmlWriter.begintag("Private")
1938a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		xmlWriter.newline()
1948a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		self.Private.toXML(xmlWriter)
1958a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		xmlWriter.endtag("Private")
1968a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		xmlWriter.newline()
1978a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
1988a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		self.toXMLCharStrings(xmlWriter, progress)
1998a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com
2008a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com	def toXMLSimpleValues(self, xmlWriter):
2018a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		keys = self.__dict__.keys()
20264cc579efa7e416c7298ed159d76b074b283c0f9senorblanco@chromium.org		keys.remove("CharStrings")
20364cc579efa7e416c7298ed159d76b074b283c0f9senorblanco@chromium.org		keys.remove("Private")
2048a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		keys.remove("charset")
2058a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		keys.remove("GlobalSubrs")
2068a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		keys.sort()
2078a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com		for key in keys:
2088a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			value = getattr(self, key)
2098a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com			if key == "Encoding":
2108a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com				if value == 0:
2118a1c16ff38322f0210116fa7293eb8817c7e477ereed@android.com					# encoding is (Adobe) Standard Encoding
212					value = "StandardEncoding"
213				elif value == 1:
214					# encoding is Expert Encoding
215					value = "ExpertEncoding"
216			if type(value) == types.ListType:
217				value = string.join(map(str, value), " ")
218			else:
219				value = str(value)
220			xmlWriter.begintag(key)
221			if hasattr(value, "toXML"):
222				xmlWriter.newline()
223				value.toXML(xmlWriter)
224				xmlWriter.newline()
225			else:
226				xmlWriter.write(value)
227			xmlWriter.endtag(key)
228			xmlWriter.newline()
229		xmlWriter.newline()
230
231	def toXMLCharStrings(self, xmlWriter, progress=None):
232		charStrings = self.CharStrings
233		xmlWriter.newline()
234		xmlWriter.begintag("CharStrings")
235		xmlWriter.newline()
236		glyphNames = charStrings.keys()
237		glyphNames.sort()
238		for glyphName in glyphNames:
239			if progress:
240				progress.setlabel("Dumping 'CFF ' table... (%s)" % glyphName)
241				progress.increment()
242			xmlWriter.newline()
243			charString = charStrings[glyphName]
244			xmlWriter.begintag("CharString", name=glyphName)
245			xmlWriter.newline()
246			charString.toXML(xmlWriter)
247			xmlWriter.endtag("CharString")
248			xmlWriter.newline()
249		xmlWriter.newline()
250		xmlWriter.endtag("CharStrings")
251		xmlWriter.newline()
252
253
254class PrivateDict:
255
256	defaults = privateDictDefaults
257
258	def __init__(self):
259		pass
260
261	def decompile(self, data, privateData, strings):
262		p = PrivateDictDecompiler(strings)
263		p.decompile(privateData)
264		self.fromDict(p.getDict())
265
266		# get local subrs
267		#print "YYY Private.Subrs:", self.Subrs
268		if hasattr(self, "Subrs"):
269			chunk = data[self.Subrs:]
270			localSubrs, restdata = readINDEX(chunk)
271			self.Subrs = map(psCharStrings.T2CharString, localSubrs)
272		else:
273			self.Subrs = []
274
275	def toXML(self, xmlWriter):
276		xmlWriter.newline()
277		keys = self.__dict__.keys()
278		keys.remove("Subrs")
279		for key in keys:
280			value = getattr(self, key)
281			if type(value) == types.ListType:
282				value = string.join(map(str, value), " ")
283			else:
284				value = str(value)
285			xmlWriter.begintag(key)
286			xmlWriter.write(value)
287			xmlWriter.endtag(key)
288			xmlWriter.newline()
289		# write subroutines
290		xmlWriter.newline()
291		xmlWriter.begintag("Subrs")
292		xmlWriter.newline()
293		for i in range(len(self.Subrs)):
294			xmlWriter.newline()
295			xmlWriter.begintag("CharString", id=i)
296			xmlWriter.newline()
297			self.Subrs[i].toXML(xmlWriter)
298			xmlWriter.endtag("CharString")
299			xmlWriter.newline()
300		xmlWriter.newline()
301		xmlWriter.endtag("Subrs")
302		xmlWriter.newline()
303		xmlWriter.newline()
304
305	def __getattr__(self, attr):
306		if not self.defaults.has_key(attr):
307			raise AttributeError, attr
308		return self.defaults[attr]
309
310	def fromDict(self, dict):
311		self.__dict__.update(dict)
312
313
314def readINDEX(file):
315	count, = struct.unpack(">H", file.read(2))
316	offSize = ord(file.read(1))
317	offsets = []
318	for index in range(count+1):
319		chunk = file.read(offSize)
320		chunk = '\0' * (4 - offSize) + chunk
321		offset, = struct.unpack(">L", chunk)
322		offset = int(offset)
323		offsets.append(offset)
324	prev = offsets[0]
325	stuff = []
326	next = offsets[0]
327	for next in offsets[1:]:
328		chunk = file.read(next - prev)
329		assert len(chunk) == next - prev
330		stuff.append(chunk)
331		prev = next
332	return stuff
333
334
335def parseCharsetFormat1(nGlyphs, file, strings, isCID):
336	charset = ['.notdef']
337	count = 1
338	while count < nGlyphs:
339		first, = struct.unpack(">H", file.read(2))
340		nLeft = ord(file.read(1))
341		if isCID:
342			for CID in range(first, first+nLeft+1):
343				charset.append(CID)
344		else:
345			for SID in range(first, first+nLeft+1):
346				charset.append(strings[SID])
347		count = count + nLeft + 1
348	return charset
349
350
351def parseCharsetFormat2(nGlyphs, file, strings, isCID):
352	charset = ['.notdef']
353	count = 1
354	while count < nGlyphs:
355		first, = struct.unpack(">H", file.read(2))
356		nLeft, = struct.unpack(">H", file.read(2))
357		if isCID:
358			for CID in range(first, first+nLeft+1):
359				charset.append(CID)
360		else:
361			for SID in range(first, first+nLeft+1):
362				charset.append(strings[SID])
363		count = count + nLeft + 1
364	return charset
365
366
367topDictOperators = [
368#   opcode     name                  argument type
369	(0,        'version',            'SID'),
370	(1,        'Notice',             'SID'),
371	(2,        'FullName',           'SID'),
372	(3,        'FamilyName',         'SID'),
373	(4,        'Weight',             'SID'),
374	(5,        'FontBBox',           'array'),
375	(13,       'UniqueID',           'number'),
376	(14,       'XUID',               'array'),
377	(15,       'charset',            'number'),
378	(16,       'Encoding',           'number'),
379	(17,       'CharStrings',        'number'),
380	(18,       'Private',            ('number', 'number')),
381	((12, 0),  'Copyright',          'SID'),
382	((12, 1),  'isFixedPitch',       'number'),
383	((12, 2),  'ItalicAngle',        'number'),
384	((12, 3),  'UnderlinePosition',  'number'),
385	((12, 4),  'UnderlineThickness', 'number'),
386	((12, 5),  'PaintType',          'number'),
387	((12, 6),  'CharstringType',     'number'),
388	((12, 7),  'FontMatrix',         'array'),
389	((12, 8),  'StrokeWidth',        'number'),
390	((12, 20), 'SyntheticBase',      'number'),
391	((12, 21), 'PostScript',         'SID'),
392	((12, 22), 'BaseFontName',       'SID'),
393	# CID additions
394	((12, 30), 'ROS',                ('SID', 'SID', 'number')),
395	((12, 31), 'CIDFontVersion',     'number'),
396	((12, 32), 'CIDFontRevision',    'number'),
397	((12, 33), 'CIDFontType',        'number'),
398	((12, 34), 'CIDCount',           'number'),
399	((12, 35), 'UIDBase',            'number'),
400	((12, 36), 'FDArray',            'number'),
401	((12, 37), 'FDSelect',           'number'),
402	((12, 38), 'FontName',           'SID'),
403]
404
405topDictDefaults = {
406	'isFixedPitch':        0,
407	'ItalicAngle':         0,
408	'UnderlineThickness':  50,
409	'PaintType':           0,
410	'CharstringType':      2,
411	'FontMatrix':          [0.001, 0, 0, 0.001, 0, 0],
412	'FontBBox':            [0, 0, 0, 0],
413	'StrokeWidth':         0,
414	'charset':             0,
415	'Encoding':            0,
416	# CID defaults
417	'CIDFontVersion':      0,
418	'CIDFontRevision':     0,
419	'CIDFontType':         0,
420	'CIDCount':            8720,
421}
422
423class TopDictDecompiler(psCharStrings.DictDecompiler):
424
425	operators = psCharStrings.buildOperatorDict(topDictOperators)
426	dictDefaults = topDictDefaults
427
428
429privateDictOperators = [
430#   opcode     name                  argument type
431	(6,        'BlueValues',         'array'),
432	(7,        'OtherBlues',         'array'),
433	(8,        'FamilyBlues',        'array'),
434	(9,        'FamilyOtherBlues',   'array'),
435	(10,       'StdHW',              'number'),
436	(11,       'StdVW',              'number'),
437	(19,       'Subrs',              'number'),
438	(20,       'defaultWidthX',      'number'),
439	(21,       'nominalWidthX',      'number'),
440	((12, 9),  'BlueScale',          'number'),
441	((12, 10), 'BlueShift',          'number'),
442	((12, 11), 'BlueFuzz',           'number'),
443	((12, 12), 'StemSnapH',          'array'),
444	((12, 13), 'StemSnapV',          'array'),
445	((12, 14), 'ForceBold',          'number'),
446	((12, 17), 'LanguageGroup',      'number'),
447	((12, 18), 'ExpansionFactor',    'number'),
448	((12, 19), 'initialRandomSeed',  'number'),
449]
450
451privateDictDefaults = {
452	'defaultWidthX':       0,
453	'nominalWidthX':       0,
454	'BlueScale':           0.039625,
455	'BlueShift':           7,
456	'BlueFuzz':            1,
457	'ForceBold':           0,
458	'LanguageGroup':       0,
459	'ExpansionFactor':     0.06,
460	'initialRandomSeed':   0,
461}
462
463class PrivateDictDecompiler(psCharStrings.DictDecompiler):
464
465	operators = psCharStrings.buildOperatorDict(privateDictOperators)
466	dictDefaults = privateDictDefaults
467
468
469class IndexedStrings:
470
471	def __init__(self, strings=None):
472		if strings is None:
473			strings = []
474		self.strings = strings
475
476	def __getitem__(self, SID):
477		if SID < cffStandardStringCount:
478			return cffStandardStrings[SID]
479		else:
480			return self.strings[SID - cffStandardStringCount]
481
482	def getSID(self, s):
483		if not hasattr(self, "stringMapping"):
484			self.buildStringMapping()
485		if cffStandardStringMapping.has_key(s):
486			SID = cffStandardStringMapping[s]
487		if self.stringMapping.has_key(s):
488			SID = self.stringMapping[s]
489		else:
490			SID = len(self.strings) + cffStandardStringCount
491			self.strings.append(s)
492			self.stringMapping[s] = SID
493		return SID
494
495	def getStrings(self):
496		return self.strings
497
498	def buildStringMapping(self):
499		self.stringMapping = {}
500		for index in range(len(self.strings)):
501			self.stringMapping[self.strings[index]] = index + cffStandardStringCount
502
503
504# The 391 Standard Strings as used in the CFF format.
505# from Adobe Technical None #5176, version 1.0, 18 March 1998
506
507cffStandardStrings = ['.notdef', 'space', 'exclam', 'quotedbl', 'numbersign',
508		'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright',
509		'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one',
510		'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon',
511		'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C',
512		'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
513		'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash',
514		'bracketright', 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c',
515		'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
516		's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright',
517		'asciitilde', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin',
518		'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft',
519		'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger',
520		'daggerdbl', 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase',
521		'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand',
522		'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve',
523		'dotaccent', 'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron',
524		'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae',
525		'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior',
526		'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn',
527		'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters',
528		'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior',
529		'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring',
530		'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave',
531		'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute',
532		'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute',
533		'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron',
534		'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla',
535		'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex',
536		'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis',
537		'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave',
538		'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall',
539		'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall',
540		'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader',
541		'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle',
542		'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle',
543		'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior',
544		'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior',
545		'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior',
546		'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', 'parenleftinferior',
547		'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall',
548		'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall',
549		'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall',
550		'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall',
551		'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall',
552		'exclamdownsmall', 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall',
553		'Dieresissmall', 'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall',
554		'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall',
555		'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths',
556		'onethird', 'twothirds', 'zerosuperior', 'foursuperior', 'fivesuperior',
557		'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior',
558		'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior',
559		'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior',
560		'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall',
561		'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall',
562		'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall',
563		'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall',
564		'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall',
565		'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall',
566		'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall',
567		'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', '001.001', '001.002',
568		'001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman',
569		'Semibold'
570]
571
572cffStandardStringCount = 391
573assert len(cffStandardStrings) == cffStandardStringCount
574# build reverse mapping
575cffStandardStringMapping = {}
576for _i in range(cffStandardStringCount):
577	cffStandardStringMapping[cffStandardStrings[_i]] = _i
578
579
580