CFF/T2 <-> XML roundtripping has begun!


git-svn-id: svn://svn.code.sf.net/p/fonttools/code/trunk@254 4cde692c-a291-49d1-8350-778aa11640f8
diff --git a/Lib/fontTools/cffLib.py b/Lib/fontTools/cffLib.py
index 6ff78a7..fe4c376 100644
--- a/Lib/fontTools/cffLib.py
+++ b/Lib/fontTools/cffLib.py
@@ -1,13 +1,14 @@
 """cffLib.py -- read/write tools for Adobe CFF fonts."""
 
 #
-# $Id: cffLib.py,v 1.21 2002-05-23 21:50:36 jvr Exp $
+# $Id: cffLib.py,v 1.22 2002-05-24 09:58:03 jvr Exp $
 #
 
 import struct, sstruct
 import string
-from types import FloatType, ListType, TupleType
+from types import FloatType, ListType, StringType, TupleType
 from fontTools.misc import psCharStrings
+from fontTools.misc.textTools import safeEval
 
 
 DEBUG = 0
@@ -25,16 +26,16 @@
 	def __init__(self):
 		pass
 	
-	def decompile(self, file):
+	def decompile(self, file, otFont):
 		sstruct.unpack(cffHeaderFormat, file.read(4), self)
 		assert self.major == 1 and self.minor == 0, \
 				"unknown CFF format: %d.%d" % (self.major, self.minor)
 		
 		file.seek(self.hdrSize)
-		self.fontNames = list(Index(file, "fontNames"))
+		self.fontNames = list(Index(file))
 		self.topDictIndex = TopDictIndex(file)
 		self.strings = IndexedStrings(file)
-		self.GlobalSubrs = GlobalSubrsIndex(file, name="GlobalSubrsIndex")
+		self.GlobalSubrs = GlobalSubrsIndex(file)
 		self.topDictIndex.strings = self.strings
 		self.topDictIndex.GlobalSubrs = self.GlobalSubrs
 	
@@ -54,7 +55,7 @@
 			raise KeyError, name
 		return self.topDictIndex[index]
 	
-	def compile(self, file):
+	def compile(self, file, otFont):
 		strings = IndexedStrings()
 		writer = CFFWriter()
 		writer.add(sstruct.pack(cffHeaderFormat, self))
@@ -67,11 +68,14 @@
 		writer.add(strings.getCompiler())
 		writer.add(self.GlobalSubrs.getCompiler(strings, None))
 		
+		for topDict in self.topDictIndex:
+			if not hasattr(topDict, "charset") or topDict.charset is None:
+				charset = otFont.getGlyphOrder()
+				topDict.charset = charset
+		
 		for child in topCompiler.getChildren(strings):
 			writer.add(child)
 		
-		print writer.data
-		
 		writer.toFile(file)
 	
 	def toXML(self, xmlWriter, progress=None):
@@ -92,7 +96,33 @@
 		xmlWriter.newline()
 	
 	def fromXML(self, (name, attrs, content)):
-		xxx
+		if not hasattr(self, "GlobalSubrs"):
+			self.GlobalSubrs = GlobalSubrsIndex()
+			self.major = 1
+			self.minor = 0
+			self.hdrSize = 4
+			self.offSize = 4  # XXX ??
+		if name == "CFFFont":
+			if not hasattr(self, "fontNames"):
+				self.fontNames = []
+				self.topDictIndex = TopDictIndex()
+			fontName = attrs["name"]
+			topDict = TopDict(GlobalSubrs=self.GlobalSubrs)
+			topDict.charset = None  # gets filled in later
+			self.fontNames.append(fontName)
+			self.topDictIndex.append(topDict)
+			for element in content:
+				if isinstance(element, StringType):
+					continue
+				topDict.fromXML(element)
+		elif name == "GlobalSubrs":
+			for element in content:
+				if isinstance(element, StringType):
+					continue
+				name, attrs, content = element
+				subr = psCharStrings.T2CharString(None, subrs=None, globalSubrs=None)
+				subr.fromXML((name, attrs, content))
+				self.GlobalSubrs.append(subr)
 
 
 class CFFWriter:
@@ -107,21 +137,25 @@
 		lastPosList = None
 		count = 1
 		while 1:
-			print "XXX iteration", count
-			count += 1
+			if DEBUG:
+				print "CFFWriter.toFile() iteration:", count
+			count = count + 1
 			pos = 0
 			posList = [pos]
 			for item in self.data:
-				if hasattr(item, "setPos"):
-					item.setPos(pos)
 				if hasattr(item, "getDataLength"):
-					pos = pos + item.getDataLength()
+					endPos = pos + item.getDataLength()
 				else:
-					pos = pos + len(item)
+					endPos = pos + len(item)
+				if hasattr(item, "setPos"):
+					item.setPos(pos, endPos)
+				pos = endPos
 				posList.append(pos)
 			if posList == lastPosList:
 				break
 			lastPosList = posList
+		if DEBUG:
+			print "CFFWriter.toFile() writing to file."
 		begin = file.tell()
 		posList = [0]
 		for item in self.data:
@@ -130,10 +164,6 @@
 			else:
 				file.write(item)
 			posList.append(file.tell() - begin)
-		if posList != lastPosList:
-			print "++++"
-			print posList
-			print lastPosList
 		assert posList == lastPosList
 
 
@@ -181,8 +211,6 @@
 		return dataLength
 	
 	def toFile(self, file):
-		size = self.getDataLength()
-		start = file.tell()
 		offsets = self.getOffsets()
 		writeCard16(file, len(self.items))
 		offSize = calcOffSize(offsets[-1])
@@ -198,7 +226,6 @@
 				item.toFile(file)
 			else:
 				file.write(item)
-		assert start + size == file.tell()
 
 
 class IndexedStringsCompiler(IndexCompiler):
@@ -231,12 +258,12 @@
 		return out
 
 class SubrsCompiler(GlobalSubrsCompiler):
-	def setPos(self, pos):
+	def setPos(self, pos, endPos):
 		offset = pos - self.parent.pos
 		self.parent.rawDict["Subrs"] = offset
 
 class CharStringsCompiler(GlobalSubrsCompiler):
-	def setPos(self, pos):
+	def setPos(self, pos, endPos):
 		self.parent.rawDict["CharStrings"] = pos
 
 
@@ -246,9 +273,8 @@
 	
 	compilerClass = IndexCompiler
 	
-	def __init__(self, file=None, name=None):
-		if name is None:
-			name = self.__class__.__name__
+	def __init__(self, file=None):
+		name = self.__class__.__name__
 		if file is None:
 			self.items = []
 			return
@@ -308,9 +334,8 @@
 	
 	compilerClass = GlobalSubrsCompiler
 	
-	def __init__(self, file=None, globalSubrs=None, private=None, fdSelect=None, fdArray=None,
-			name=None):
-		Index.__init__(self, file, name)
+	def __init__(self, file=None, globalSubrs=None, private=None, fdSelect=None, fdArray=None):
+		Index.__init__(self, file)
 		self.globalSubrs = globalSubrs
 		self.private = private
 		self.fdSelect = fdSelect
@@ -331,6 +356,9 @@
 	
 	def toXML(self, xmlWriter, progress):
 		fdSelect = self.fdSelect
+		xmlWriter.comment("The 'index' attribute is only for humans; "
+				"it is ignored when parsed.")
+		xmlWriter.newline()
 		for i in range(len(self)):
 			xmlWriter.begintag("CharString", index=i)
 			xmlWriter.newline()
@@ -338,6 +366,13 @@
 			xmlWriter.endtag("CharString")
 			xmlWriter.newline()
 	
+	def fromXML(self, (name, attrs, content)):
+		if name <> "CharString":
+			return
+		subr = psCharStrings.T2CharString(None, subrs=None, globalSubrs=None)
+		subr.fromXML((name, attrs, content))
+		self.append(subr)
+	
 	def getItemAndSelector(self, index):
 		fdSelect = self.fdSelect
 		if fdSelect is None:
@@ -371,30 +406,55 @@
 class CharStrings:
 	
 	def __init__(self, file, charset, globalSubrs, private, fdSelect, fdArray):
-		self.charStringsIndex = SubrsIndex(file, globalSubrs, private, fdSelect, fdArray)
-		self.nameToIndex = nameToIndex = {}
-		for i in range(len(charset)):
-			nameToIndex[charset[i]] = i
+		if file is not None:
+			self.charStringsIndex = SubrsIndex(file, globalSubrs, private, fdSelect, fdArray)
+			self.charStrings = charStrings = {}
+			for i in range(len(charset)):
+				charStrings[charset[i]] = i
+			self.charStringsAreIndexed = 1
+		else:
+			self.charStrings = {}
+			self.charStringsAreIndexed = 0
+			self.globalSubrs = globalSubrs
+			self.private = private
+			self.fdSelect = fdSelect
+			self.fdArray = fdArray
 	
 	def keys(self):
-		return self.nameToIndex.keys()
+		return self.charStrings.keys()
 	
 	def values(self):
-		return self.charStringsIndex
+		if self.charStringsAreIndexed:
+			return self.charStringsIndex
+		else:
+			return self.charStrings.values()
 	
 	def has_key(self, name):
-		return self.nameToIndex.has_key(name)
+		return self.charStrings.has_key(name)
 	
 	def __len__(self):
-		return len(self.charStringsIndex)
+		return len(self.charStrings)
 	
 	def __getitem__(self, name):
-		index = self.nameToIndex[name]
-		return self.charStringsIndex[index]
+		charString = self.charStrings[name]
+		if self.charStringsAreIndexed:
+			charString = self.charStringsIndex[charString]
+		return charString
+	
+	def __setitem__(self, name, charString):
+		if self.charStringsAreIndexed:
+			index = self.charStrings[name]
+			self.charStringsIndex[index] = charString
+		else:
+			self.charStrings[name] = charString
 	
 	def getItemAndSelector(self, name):
-		index = self.nameToIndex[name]
-		return self.charStringsIndex.getItemAndSelector(index)
+		if self.charStringsAreIndexed:
+			index = self.charStrings[name]
+			return self.charStringsIndex.getItemAndSelector(index)
+		else:
+			# XXX needs work for CID fonts
+			return self.charStrings[name], None
 	
 	def toXML(self, xmlWriter, progress):
 		names = self.keys()
@@ -410,6 +470,23 @@
 			self[name].toXML(xmlWriter)
 			xmlWriter.endtag("CharString")
 			xmlWriter.newline()
+	
+	def fromXML(self, (name, attrs, content)):
+		for element in content:
+			if isinstance(element, StringType):
+				continue
+			name, attrs, content = element
+			if name <> "CharString":
+				continue
+			glyphName = attrs["name"]
+			if hasattr(self.private, "Subrs"):
+				subrs = self.private.Subrs
+			else:
+				subrs = []
+			globalSubrs = self.globalSubrs
+			charString = psCharStrings.T2CharString(None, subrs=subrs, globalSubrs=globalSubrs)
+			charString.fromXML((name, attrs, content))
+			self[glyphName] = charString
 
 
 def readCard8(file):
@@ -467,40 +544,78 @@
 	return d
 
 
-class BaseConverter:
+class SimpleConverter:
 	def read(self, parent, value):
 		return value
 	def write(self, parent, value):
 		return value
 	def xmlWrite(self, xmlWriter, name, value):
+		xmlWriter.simpletag(name, value=value)
+		xmlWriter.newline()
+	def xmlRead(self, (name, attrs, content), parent):
+		return attrs["value"]
+
+def parseNum(s):
+	try:
+		value = int(s)
+	except:
+		value = float(s)
+	return value
+
+class NumberConverter(SimpleConverter):
+	def xmlRead(self, (name, attrs, content), parent):
+		return parseNum(attrs["value"])
+
+class ArrayConverter(SimpleConverter):
+	def xmlWrite(self, xmlWriter, name, value):
+		value = map(str, value)
+		xmlWriter.simpletag(name, value=" ".join(value))
+		xmlWriter.newline()
+	def xmlRead(self, (name, attrs, content), parent):
+		values = attrs["value"].split()
+		return map(parseNum, values)
+
+class TableConverter(SimpleConverter):
+	def xmlWrite(self, xmlWriter, name, value):
 		xmlWriter.begintag(name)
 		xmlWriter.newline()
 		value.toXML(xmlWriter, None)
 		xmlWriter.endtag(name)
 		xmlWriter.newline()
+	def xmlRead(self, (name, attrs, content), parent):
+		ob = self.getClass()()
+		for element in content:
+			if isinstance(element, StringType):
+				continue
+			ob.fromXML(element)
+		return ob
 
-class PrivateDictConverter(BaseConverter):
+class PrivateDictConverter(TableConverter):
+	def getClass(self):
+		return PrivateDict
 	def read(self, parent, value):
 		size, offset = value
 		file = parent.file
-		pr = PrivateDict(parent.strings, file, offset)
+		priv = PrivateDict(parent.strings, file, offset)
 		file.seek(offset)
 		data = file.read(size)
 		len(data) == size
-		pr.decompile(data)
-		return pr
+		priv.decompile(data)
+		return priv
 	def write(self, parent, value):
 		return (0, 0)  # dummy value
 
-class SubrsConverter(BaseConverter):
+class SubrsConverter(TableConverter):
+	def getClass(self):
+		return SubrsIndex
 	def read(self, parent, value):
 		file = parent.file
 		file.seek(parent.offset + value)  # Offset(self)
-		return SubrsIndex(file, name="SubrsIndex")
+		return SubrsIndex(file)
 	def write(self, parent, value):
 		return 0  # dummy value
 
-class CharStringsConverter(BaseConverter):
+class CharStringsConverter(TableConverter):
 	def read(self, parent, value):
 		file = parent.file
 		charset = parent.charset
@@ -515,6 +630,12 @@
 		return CharStrings(file, charset, globalSubrs, private, fdSelect, fdArray)
 	def write(self, parent, value):
 		return 0  # dummy value
+	def xmlRead(self, (name, attrs, content), parent):
+		# XXX needs work for CID fonts
+		fdSelect, fdArray = None, None
+		charStrings = CharStrings(None, None, parent.GlobalSubrs, parent.Private, fdSelect, fdArray)
+		charStrings.fromXML((name, attrs, content))
+		return charStrings
 
 class CharsetConverter:
 	def read(self, parent, value):
@@ -554,9 +675,14 @@
 	def write(self, parent, value):
 		return 0  # dummy value
 	def xmlWrite(self, xmlWriter, name, value):
-		# XXX GlyphOrder needs to be stored *somewhere*, but not here...
-		xmlWriter.simpletag("charset", value=value)
+		# XXX only write charset when not in OT/TTX context, where we
+		# dump charset as a separate "GlyphOrder" table.
+		##xmlWriter.simpletag("charset")
+		xmlWriter.comment("charset is dumped separately as the 'GlyphOrder' element")
 		xmlWriter.newline()
+	def xmlRead(self, (name, attrs, content), parent):
+		if 0:
+			return safeEval(attrs["value"])
 
 
 class CharsetCompiler:
@@ -570,7 +696,7 @@
 		self.data = "".join(data)
 		self.parent = parent
 	
-	def setPos(self, pos):
+	def setPos(self, pos, endPos):
 		self.parent.rawDict["charset"] = pos
 	
 	def getDataLength(self):
@@ -607,7 +733,7 @@
 	return charset
 
 
-class FDArrayConverter(BaseConverter):
+class FDArrayConverter(TableConverter):
 	def read(self, parent, value):
 		file = parent.file
 		file.seek(value)
@@ -648,7 +774,7 @@
 		pass
 
 
-class ROSConverter(BaseConverter):
+class ROSConverter(SimpleConverter):
 	def xmlWrite(self, xmlWriter, name, value):
 		registry, order, supplement = value
 		xmlWriter.simpletag(name, [('Registry', registry), ('Order', order),
@@ -718,6 +844,24 @@
 	(19,       'Subrs',              'number',       None,      SubrsConverter()),
 ]
 
+def addConverters(table):
+	for i in range(len(table)):
+		op, name, arg, default, conv = table[i]
+		if conv is not None:
+			continue
+		if arg in ("delta", "array"):
+			conv = ArrayConverter()
+		elif arg == "number":
+			conv = NumberConverter()
+		elif arg == "SID":
+			conv = SimpleConverter()
+		else:
+			assert 0
+		table[i] = op, name, arg, default, conv
+
+addConverters(privateDictOperators)
+addConverters(topDictOperators)
+
 
 class TopDictDecompiler(psCharStrings.DictDecompiler):
 	operators = buildOperatorDict(topDictOperators)
@@ -740,20 +884,21 @@
 			if value is None:
 				continue
 			conv = dictObj.converters[name]
-			if conv:
-				value = conv.write(dictObj, value)
+			value = conv.write(dictObj, value)
 			if value == dictObj.defaults.get(name):
 				continue
 			rawDict[name] = value
 		self.rawDict = rawDict
 	
-	def setPos(self, pos):
+	def setPos(self, pos, endPos):
 		pass
 	
 	def getDataLength(self):
-		return len(self.compile())
+		return len(self.compile("getDataLength"))
 	
-	def compile(self):
+	def compile(self, reason):
+		if DEBUG:
+			print "-- compiling %s for %s" % (self.__class__.__name__, reason)
 		rawDict = self.rawDict
 		data = []
 		for name in self.dictObj.order:
@@ -776,7 +921,7 @@
 		return "".join(data)
 	
 	def toFile(self, file):
-		file.write(self.compile())
+		file.write(self.compile("toFile"))
 	
 	def arg_number(self, num):
 		return encodeNumber(num)
@@ -832,8 +977,8 @@
 	
 	opcodes = buildOpcodeDict(privateDictOperators)
 	
-	def setPos(self, pos):
-		size = len(self.compile())
+	def setPos(self, pos, endPos):
+		size = endPos - pos
 		self.parent.rawDict["Private"] = size, pos
 		self.pos = pos
 	
@@ -846,7 +991,7 @@
 
 class BaseDict:
 	
-	def __init__(self, strings, file, offset):
+	def __init__(self, strings=None, file=None, offset=None):
 		self.rawDict = {}
 		if DEBUG:
 			print "loading %s at %s" % (self.__class__.__name__, offset)
@@ -876,8 +1021,7 @@
 		if value is None:
 			raise AttributeError, name
 		conv = self.converters[name]
-		if conv is not None:
-			value = conv.read(self, value)
+		value = conv.read(self, value)
 		setattr(self, name, value)
 		return value
 	
@@ -888,14 +1032,13 @@
 			value = getattr(self, name, None)
 			if value is None:
 				continue
-			conv = self.converters.get(name)
-			if conv is not None:
-				conv.xmlWrite(xmlWriter, name, value)
-			else:
-				if isinstance(value, ListType):
-					value = " ".join(map(str, value))
-				xmlWriter.simpletag(name, value=value)
-				xmlWriter.newline()
+			conv = self.converters[name]
+			conv.xmlWrite(xmlWriter, name, value)
+	
+	def fromXML(self, (name, attrs, content)):
+		conv = self.converters[name]
+		value = conv.xmlRead((name, attrs, content), self)
+		setattr(self, name, value)
 
 
 class TopDict(BaseDict):
@@ -906,7 +1049,7 @@
 	decompilerClass = TopDictDecompiler
 	compilerClass = TopDictCompiler
 	
-	def __init__(self, strings, file, offset, GlobalSubrs):
+	def __init__(self, strings=None, file=None, offset=None, GlobalSubrs=None):
 		BaseDict.__init__(self, strings, file, offset)
 		self.GlobalSubrs = GlobalSubrs
 	
@@ -932,6 +1075,7 @@
 		BaseDict.toXML(self, xmlWriter, progress)
 	
 	def decompileAllCharStrings(self):
+		# XXX only when doing ttdump -i?
 		for charString in self.CharStrings.values():
 			charString.decompile()
 
@@ -952,7 +1096,7 @@
 		if file is None:
 			strings = []
 		else:
-			strings = list(Index(file, "IndexedStrings"))
+			strings = list(Index(file))
 		self.strings = strings
 	
 	def getCompiler(self):