cffLib.py revision b58176e5ac4d1e0b0e6a6c71c3020f5e85bd4dfe
1"""cffLib.py -- read/write tools for Adobe CFF fonts."""
2
3#
4# $Id: cffLib.py,v 1.25 2002-05-24 11:55:37 jvr Exp $
5#
6
7import struct, sstruct
8import string
9from types import FloatType, ListType, StringType, TupleType
10from fontTools.misc import psCharStrings
11from fontTools.misc.textTools import safeEval
12
13
14DEBUG = 0
15
16
17cffHeaderFormat = """
18	major:   B
19	minor:   B
20	hdrSize: B
21	offSize: B
22"""
23
24class CFFFontSet:
25
26	def __init__(self):
27		pass
28
29	def decompile(self, file, otFont):
30		sstruct.unpack(cffHeaderFormat, file.read(4), self)
31		assert self.major == 1 and self.minor == 0, \
32				"unknown CFF format: %d.%d" % (self.major, self.minor)
33
34		file.seek(self.hdrSize)
35		self.fontNames = list(Index(file))
36		self.topDictIndex = TopDictIndex(file)
37		self.strings = IndexedStrings(file)
38		self.GlobalSubrs = GlobalSubrsIndex(file)
39		self.topDictIndex.strings = self.strings
40		self.topDictIndex.GlobalSubrs = self.GlobalSubrs
41
42	def __len__(self):
43		return len(self.fontNames)
44
45	def keys(self):
46		return self.fontNames[:]
47
48	def values(self):
49		return self.topDictIndex
50
51	def __getitem__(self, name):
52		try:
53			index = self.fontNames.index(name)
54		except ValueError:
55			raise KeyError, name
56		return self.topDictIndex[index]
57
58	def compile(self, file, otFont):
59		strings = IndexedStrings()
60		writer = CFFWriter()
61		writer.add(sstruct.pack(cffHeaderFormat, self))
62		fontNames = Index()
63		for name in self.fontNames:
64			fontNames.append(name)
65		writer.add(fontNames.getCompiler(strings, None))
66		topCompiler = self.topDictIndex.getCompiler(strings, None)
67		writer.add(topCompiler)
68		writer.add(strings.getCompiler())
69		writer.add(self.GlobalSubrs.getCompiler(strings, None))
70
71		for topDict in self.topDictIndex:
72			if not hasattr(topDict, "charset") or topDict.charset is None:
73				charset = otFont.getGlyphOrder()
74				topDict.charset = charset
75
76		for child in topCompiler.getChildren(strings):
77			writer.add(child)
78
79		writer.toFile(file)
80
81	def toXML(self, xmlWriter, progress=None):
82		xmlWriter.newline()
83		for fontName in self.fontNames:
84			xmlWriter.begintag("CFFFont", name=fontName)
85			xmlWriter.newline()
86			font = self[fontName]
87			font.toXML(xmlWriter, progress)
88			xmlWriter.endtag("CFFFont")
89			xmlWriter.newline()
90		xmlWriter.newline()
91		xmlWriter.begintag("GlobalSubrs")
92		xmlWriter.newline()
93		self.GlobalSubrs.toXML(xmlWriter, progress)
94		xmlWriter.endtag("GlobalSubrs")
95		xmlWriter.newline()
96		xmlWriter.newline()
97
98	def fromXML(self, (name, attrs, content)):
99		if not hasattr(self, "GlobalSubrs"):
100			self.GlobalSubrs = GlobalSubrsIndex()
101			self.major = 1
102			self.minor = 0
103			self.hdrSize = 4
104			self.offSize = 4  # XXX ??
105		if name == "CFFFont":
106			if not hasattr(self, "fontNames"):
107				self.fontNames = []
108				self.topDictIndex = TopDictIndex()
109			fontName = attrs["name"]
110			topDict = TopDict(GlobalSubrs=self.GlobalSubrs)
111			topDict.charset = None  # gets filled in later
112			self.fontNames.append(fontName)
113			self.topDictIndex.append(topDict)
114			for element in content:
115				if isinstance(element, StringType):
116					continue
117				topDict.fromXML(element)
118		elif name == "GlobalSubrs":
119			for element in content:
120				if isinstance(element, StringType):
121					continue
122				name, attrs, content = element
123				subr = psCharStrings.T2CharString(None, subrs=None, globalSubrs=None)
124				subr.fromXML((name, attrs, content))
125				self.GlobalSubrs.append(subr)
126
127
128class CFFWriter:
129
130	def __init__(self):
131		self.data = []
132
133	def add(self, table):
134		self.data.append(table)
135
136	def toFile(self, file):
137		lastPosList = None
138		count = 1
139		while 1:
140			if DEBUG:
141				print "CFFWriter.toFile() iteration:", count
142			count = count + 1
143			pos = 0
144			posList = [pos]
145			for item in self.data:
146				if hasattr(item, "getDataLength"):
147					endPos = pos + item.getDataLength()
148				else:
149					endPos = pos + len(item)
150				if hasattr(item, "setPos"):
151					item.setPos(pos, endPos)
152				pos = endPos
153				posList.append(pos)
154			if posList == lastPosList:
155				break
156			lastPosList = posList
157		if DEBUG:
158			print "CFFWriter.toFile() writing to file."
159		begin = file.tell()
160		posList = [0]
161		for item in self.data:
162			if hasattr(item, "toFile"):
163				item.toFile(file)
164			else:
165				file.write(item)
166			posList.append(file.tell() - begin)
167		assert posList == lastPosList
168
169
170def calcOffSize(largestOffset):
171	if largestOffset < 0x100:
172		offSize = 1
173	elif largestOffset < 0x10000:
174		offSize = 2
175	elif largestOffset < 0x1000000:
176		offSize = 3
177	else:
178		offSize = 4
179	return offSize
180
181
182class IndexCompiler:
183
184	def __init__(self, items, strings, parent):
185		self.items = self.getItems(items, strings)
186		self.parent = parent
187
188	def getItems(self, items, strings):
189		return items
190
191	def getOffsets(self):
192		pos = 1
193		offsets = [pos]
194		for item in self.items:
195			if hasattr(item, "getDataLength"):
196				pos = pos + item.getDataLength()
197			else:
198				pos = pos + len(item)
199			offsets.append(pos)
200		return offsets
201
202	def getDataLength(self):
203		lastOffset = self.getOffsets()[-1]
204		offSize = calcOffSize(lastOffset)
205		dataLength = (
206			2 +                                # count
207			1 +                                # offSize
208			(len(self.items) + 1) * offSize +  # the offsets
209			lastOffset - 1                     # size of object data
210		)
211		return dataLength
212
213	def toFile(self, file):
214		offsets = self.getOffsets()
215		writeCard16(file, len(self.items))
216		offSize = calcOffSize(offsets[-1])
217		writeCard8(file, offSize)
218		offSize = -offSize
219		pack = struct.pack
220		for offset in offsets:
221			binOffset = pack(">l", offset)[offSize:]
222			assert len(binOffset) == -offSize
223			file.write(binOffset)
224		for item in self.items:
225			if hasattr(item, "toFile"):
226				item.toFile(file)
227			else:
228				file.write(item)
229
230
231class IndexedStringsCompiler(IndexCompiler):
232
233	def getItems(self, items, strings):
234		return items.strings
235
236
237class TopDictIndexCompiler(IndexCompiler):
238
239	def getItems(self, items, strings):
240		out = []
241		for item in items:
242			out.append(item.getCompiler(strings, self))
243		return out
244
245	def getChildren(self, strings):
246		children = []
247		for topDict in self.items:
248			children.extend(topDict.getChildren(strings))
249		return children
250
251
252class GlobalSubrsCompiler(IndexCompiler):
253	def getItems(self, items, strings):
254		out = []
255		for cs in items:
256			cs.compile()
257			out.append(cs.bytecode)
258		return out
259
260class SubrsCompiler(GlobalSubrsCompiler):
261	def setPos(self, pos, endPos):
262		offset = pos - self.parent.pos
263		self.parent.rawDict["Subrs"] = offset
264
265class CharStringsCompiler(GlobalSubrsCompiler):
266	def setPos(self, pos, endPos):
267		self.parent.rawDict["CharStrings"] = pos
268
269
270class Index:
271
272	"""This class represents what the CFF spec calls an INDEX."""
273
274	compilerClass = IndexCompiler
275
276	def __init__(self, file=None):
277		name = self.__class__.__name__
278		if file is None:
279			self.items = []
280			return
281		if DEBUG:
282			print "loading %s at %s" % (name, file.tell())
283		self.file = file
284		count = readCard16(file)
285		self.count = count
286		self.items = [None] * count
287		if count == 0:
288			self.items = []
289			return
290		offSize = readCard8(file)
291		if DEBUG:
292			print "    index count: %s offSize: %s" % (count, offSize)
293		assert offSize <= 4, "offSize too large: %s" % offSize
294		self.offsets = offsets = []
295		pad = '\0' * (4 - offSize)
296		for index in range(count+1):
297			chunk = file.read(offSize)
298			chunk = pad + chunk
299			offset, = struct.unpack(">L", chunk)
300			offsets.append(int(offset))
301		self.offsetBase = file.tell() - 1
302		file.seek(self.offsetBase + offsets[-1])  # pretend we've read the whole lot
303		if DEBUG:
304			print "    end of %s at %s" % (name, file.tell())
305
306	def __len__(self):
307		return len(self.items)
308
309	def __getitem__(self, index):
310		item = self.items[index]
311		if item is not None:
312			return item
313		offset = self.offsets[index] + self.offsetBase
314		size = self.offsets[index+1] - self.offsets[index]
315		file = self.file
316		file.seek(offset)
317		data = file.read(size)
318		assert len(data) == size
319		item = self.produceItem(index, data, file, offset, size)
320		self.items[index] = item
321		return item
322
323	def produceItem(self, index, data, file, offset, size):
324		return data
325
326	def append(self, item):
327		self.items.append(item)
328
329	def getCompiler(self, strings, parent):
330		return self.compilerClass(self, strings, parent)
331
332
333class GlobalSubrsIndex(Index):
334
335	compilerClass = GlobalSubrsCompiler
336
337	def __init__(self, file=None, globalSubrs=None, private=None, fdSelect=None, fdArray=None):
338		Index.__init__(self, file)
339		self.globalSubrs = globalSubrs
340		self.private = private
341		self.fdSelect = fdSelect
342		self.fdArray = fdArray
343
344	def produceItem(self, index, data, file, offset, size):
345		if self.private is not None:
346			private = self.private
347		elif self.fdArray is not None:
348			private = self.fdArray[self.fdSelect[index]].Private
349		else:
350			private = None
351		if hasattr(private, "Subrs"):
352			subrs = private.Subrs
353		else:
354			subrs = []
355		return psCharStrings.T2CharString(data, subrs=subrs, globalSubrs=self.globalSubrs)
356
357	def toXML(self, xmlWriter, progress):
358		fdSelect = self.fdSelect
359		xmlWriter.comment("The 'index' attribute is only for humans; "
360				"it is ignored when parsed.")
361		xmlWriter.newline()
362		for i in range(len(self)):
363			subr = self[i]
364			if subr.needsDecompilation():
365				xmlWriter.begintag("CharString", index=i, raw=1)
366			else:
367				xmlWriter.begintag("CharString", index=i)
368			xmlWriter.newline()
369			subr.toXML(xmlWriter)
370			xmlWriter.endtag("CharString")
371			xmlWriter.newline()
372
373	def fromXML(self, (name, attrs, content)):
374		if name <> "CharString":
375			return
376		subr = psCharStrings.T2CharString(None, subrs=None, globalSubrs=None)
377		subr.fromXML((name, attrs, content))
378		self.append(subr)
379
380	def getItemAndSelector(self, index):
381		fdSelect = self.fdSelect
382		if fdSelect is None:
383			sel = None
384		else:
385			sel = fdSelect[index]
386		return self[index], sel
387
388class SubrsIndex(GlobalSubrsIndex):
389	compilerClass = SubrsCompiler
390
391
392class TopDictIndex(Index):
393
394	compilerClass = TopDictIndexCompiler
395
396	def produceItem(self, index, data, file, offset, size):
397		top = TopDict(self.strings, file, offset, self.GlobalSubrs)
398		top.decompile(data)
399		return top
400
401	def toXML(self, xmlWriter, progress):
402		for i in range(len(self)):
403			xmlWriter.begintag("FontDict", index=i)
404			xmlWriter.newline()
405			self[i].toXML(xmlWriter, progress)
406			xmlWriter.endtag("FontDict")
407			xmlWriter.newline()
408
409
410class CharStrings:
411
412	def __init__(self, file, charset, globalSubrs, private, fdSelect, fdArray):
413		if file is not None:
414			self.charStringsIndex = SubrsIndex(file, globalSubrs, private, fdSelect, fdArray)
415			self.charStrings = charStrings = {}
416			for i in range(len(charset)):
417				charStrings[charset[i]] = i
418			self.charStringsAreIndexed = 1
419		else:
420			self.charStrings = {}
421			self.charStringsAreIndexed = 0
422			self.globalSubrs = globalSubrs
423			self.private = private
424			self.fdSelect = fdSelect
425			self.fdArray = fdArray
426
427	def keys(self):
428		return self.charStrings.keys()
429
430	def values(self):
431		if self.charStringsAreIndexed:
432			return self.charStringsIndex
433		else:
434			return self.charStrings.values()
435
436	def has_key(self, name):
437		return self.charStrings.has_key(name)
438
439	def __len__(self):
440		return len(self.charStrings)
441
442	def __getitem__(self, name):
443		charString = self.charStrings[name]
444		if self.charStringsAreIndexed:
445			charString = self.charStringsIndex[charString]
446		return charString
447
448	def __setitem__(self, name, charString):
449		if self.charStringsAreIndexed:
450			index = self.charStrings[name]
451			self.charStringsIndex[index] = charString
452		else:
453			self.charStrings[name] = charString
454
455	def getItemAndSelector(self, name):
456		if self.charStringsAreIndexed:
457			index = self.charStrings[name]
458			return self.charStringsIndex.getItemAndSelector(index)
459		else:
460			# XXX needs work for CID fonts
461			return self.charStrings[name], None
462
463	def toXML(self, xmlWriter, progress):
464		names = self.keys()
465		names.sort()
466		for name in names:
467			charStr, fdSelect = self.getItemAndSelector(name)
468			if charStr.needsDecompilation():
469				raw = [("raw", 1)]
470			else:
471				raw = []
472			if fdSelect is None:
473				xmlWriter.begintag("CharString", [('name', name)] + raw)
474			else:
475				xmlWriter.begintag("CharString",
476						[('name', name), ('fdSelect', fdSelect)] + raw)
477			xmlWriter.newline()
478			charStr.toXML(xmlWriter)
479			xmlWriter.endtag("CharString")
480			xmlWriter.newline()
481
482	def fromXML(self, (name, attrs, content)):
483		for element in content:
484			if isinstance(element, StringType):
485				continue
486			name, attrs, content = element
487			if name <> "CharString":
488				continue
489			glyphName = attrs["name"]
490			if hasattr(self.private, "Subrs"):
491				subrs = self.private.Subrs
492			else:
493				subrs = []
494			globalSubrs = self.globalSubrs
495			charString = psCharStrings.T2CharString(None, subrs=subrs, globalSubrs=globalSubrs)
496			charString.fromXML((name, attrs, content))
497			self[glyphName] = charString
498
499
500def readCard8(file):
501	return ord(file.read(1))
502
503def readCard16(file):
504	value, = struct.unpack(">H", file.read(2))
505	return value
506
507def writeCard8(file, value):
508	file.write(chr(value))
509
510def writeCard16(file, value):
511	file.write(struct.pack(">H", value))
512
513def packCard8(value):
514	return chr(value)
515
516def packCard16(value):
517	return struct.pack(">H", value)
518
519def buildOperatorDict(table):
520	d = {}
521	for op, name, arg, default, conv in table:
522		d[op] = (name, arg)
523	return d
524
525def buildOpcodeDict(table):
526	d = {}
527	for op, name, arg, default, conv in table:
528		if type(op) == TupleType:
529			op = chr(op[0]) + chr(op[1])
530		else:
531			op = chr(op)
532		d[name] = (op, arg)
533	return d
534
535def buildOrder(table):
536	l = []
537	for op, name, arg, default, conv in table:
538		l.append(name)
539	return l
540
541def buildDefaults(table):
542	d = {}
543	for op, name, arg, default, conv in table:
544		if default is not None:
545			d[name] = default
546	return d
547
548def buildConverters(table):
549	d = {}
550	for op, name, arg, default, conv in table:
551		d[name] = conv
552	return d
553
554
555class SimpleConverter:
556	def read(self, parent, value):
557		return value
558	def write(self, parent, value):
559		return value
560	def xmlWrite(self, xmlWriter, name, value):
561		xmlWriter.simpletag(name, value=value)
562		xmlWriter.newline()
563	def xmlRead(self, (name, attrs, content), parent):
564		return attrs["value"]
565
566def parseNum(s):
567	try:
568		value = int(s)
569	except:
570		value = float(s)
571	return value
572
573class NumberConverter(SimpleConverter):
574	def xmlRead(self, (name, attrs, content), parent):
575		return parseNum(attrs["value"])
576
577class ArrayConverter(SimpleConverter):
578	def xmlWrite(self, xmlWriter, name, value):
579		value = map(str, value)
580		xmlWriter.simpletag(name, value=" ".join(value))
581		xmlWriter.newline()
582	def xmlRead(self, (name, attrs, content), parent):
583		values = attrs["value"].split()
584		return map(parseNum, values)
585
586class TableConverter(SimpleConverter):
587	def xmlWrite(self, xmlWriter, name, value):
588		xmlWriter.begintag(name)
589		xmlWriter.newline()
590		value.toXML(xmlWriter, None)
591		xmlWriter.endtag(name)
592		xmlWriter.newline()
593	def xmlRead(self, (name, attrs, content), parent):
594		ob = self.getClass()()
595		for element in content:
596			if isinstance(element, StringType):
597				continue
598			ob.fromXML(element)
599		return ob
600
601class PrivateDictConverter(TableConverter):
602	def getClass(self):
603		return PrivateDict
604	def read(self, parent, value):
605		size, offset = value
606		file = parent.file
607		priv = PrivateDict(parent.strings, file, offset)
608		file.seek(offset)
609		data = file.read(size)
610		len(data) == size
611		priv.decompile(data)
612		return priv
613	def write(self, parent, value):
614		return (0, 0)  # dummy value
615
616class SubrsConverter(TableConverter):
617	def getClass(self):
618		return SubrsIndex
619	def read(self, parent, value):
620		file = parent.file
621		file.seek(parent.offset + value)  # Offset(self)
622		return SubrsIndex(file)
623	def write(self, parent, value):
624		return 0  # dummy value
625
626class CharStringsConverter(TableConverter):
627	def read(self, parent, value):
628		file = parent.file
629		charset = parent.charset
630		globalSubrs = parent.GlobalSubrs
631		if hasattr(parent, "ROS"):
632			fdSelect, fdArray = parent.FDSelect, parent.FDArray
633			private = None
634		else:
635			fdSelect, fdArray = None, None
636			private = parent.Private
637		file.seek(value)  # Offset(0)
638		return CharStrings(file, charset, globalSubrs, private, fdSelect, fdArray)
639	def write(self, parent, value):
640		return 0  # dummy value
641	def xmlRead(self, (name, attrs, content), parent):
642		# XXX needs work for CID fonts
643		fdSelect, fdArray = None, None
644		charStrings = CharStrings(None, None, parent.GlobalSubrs, parent.Private, fdSelect, fdArray)
645		charStrings.fromXML((name, attrs, content))
646		return charStrings
647
648class CharsetConverter:
649	def read(self, parent, value):
650		isCID = hasattr(parent, "ROS")
651		if value > 2:
652			numGlyphs = parent.numGlyphs
653			file = parent.file
654			file.seek(value)
655			if DEBUG:
656				print "loading charset at %s" % value
657			format = readCard8(file)
658			if format == 0:
659				charset =parseCharset0(numGlyphs, file, parent.strings)
660			elif format == 1 or format == 2:
661				charset = parseCharset(numGlyphs, file, parent.strings, isCID, format)
662			else:
663				raise NotImplementedError
664			assert len(charset) == numGlyphs
665			if DEBUG:
666				print "    charset end at %s" % file.tell()
667		else:
668			if isCID or not hasattr(parent, "CharStrings"):
669				assert value == 0
670				charset = None
671			elif value == 0:
672				charset = ISOAdobe
673			elif value == 1:
674				charset = Expert
675			elif value == 2:
676				charset = ExpertSubset
677			# self.charset:
678			#   0: ISOAdobe (or CID font!)
679			#   1: Expert
680			#   2: ExpertSubset
681			charset = None  #
682		return charset
683	def write(self, parent, value):
684		return 0  # dummy value
685	def xmlWrite(self, xmlWriter, name, value):
686		# XXX only write charset when not in OT/TTX context, where we
687		# dump charset as a separate "GlyphOrder" table.
688		##xmlWriter.simpletag("charset")
689		xmlWriter.comment("charset is dumped separately as the 'GlyphOrder' element")
690		xmlWriter.newline()
691	def xmlRead(self, (name, attrs, content), parent):
692		if 0:
693			return safeEval(attrs["value"])
694
695
696class CharsetCompiler:
697
698	def __init__(self, strings, charset, parent):
699		assert charset[0] == '.notdef'
700		data0 = packCharset0(charset, strings)
701		data = packCharset(charset, strings)
702		if len(data) < len(data0):
703			self.data = data
704		else:
705			self.data = data0
706		self.parent = parent
707
708	def setPos(self, pos, endPos):
709		self.parent.rawDict["charset"] = pos
710
711	def getDataLength(self):
712		return len(self.data)
713
714	def toFile(self, file):
715		file.write(self.data)
716
717
718def packCharset0(charset, strings):
719	format = 0
720	data = [packCard8(format)]
721	for name in charset[1:]:
722		data.append(packCard16(strings.getSID(name)))
723	return "".join(data)
724
725def packCharset(charset, strings):
726	format = 1
727	ranges = []
728	first = None
729	end = 0
730	for name in charset[1:]:
731		SID = strings.getSID(name)
732		if first is None:
733			first = SID
734		elif end + 1 <> SID:
735			nLeft = end - first
736			if nLeft > 255:
737				format = 2
738			ranges.append((first, nLeft))
739			first = SID
740		end = SID
741	nLeft = end - first
742	if nLeft > 255:
743		format = 2
744	ranges.append((first, nLeft))
745
746	data = [packCard8(format)]
747	if format == 1:
748		nLeftFunc = packCard8
749	else:
750		nLeftFunc = packCard16
751	for first, nLeft in ranges:
752		data.append(packCard16(first) + nLeftFunc(nLeft))
753	return "".join(data)
754
755def parseCharset0(numGlyphs, file, strings):
756	charset = [".notdef"]
757	for i in range(numGlyphs - 1):
758		SID = readCard16(file)
759		charset.append(strings[SID])
760	return charset
761
762def parseCharset(numGlyphs, file, strings, isCID, format):
763	charset = ['.notdef']
764	count = 1
765	if format == 1:
766		nLeftFunc = readCard8
767	else:
768		nLeftFunc = readCard16
769	while count < numGlyphs:
770		first = readCard16(file)
771		nLeft = nLeftFunc(file)
772		if isCID:
773			for CID in range(first, first+nLeft+1):
774				charset.append(CID)
775		else:
776			for SID in range(first, first+nLeft+1):
777				charset.append(strings[SID])
778		count = count + nLeft + 1
779	return charset
780
781
782class FDArrayConverter(TableConverter):
783	def read(self, parent, value):
784		file = parent.file
785		file.seek(value)
786		fdArray = TopDictIndex(file)
787		fdArray.strings = parent.strings
788		fdArray.GlobalSubrs = parent.GlobalSubrs
789		return fdArray
790
791
792class FDSelectConverter:
793	def read(self, parent, value):
794		file = parent.file
795		file.seek(value)
796		format = readCard8(file)
797		numGlyphs = parent.numGlyphs
798		if format == 0:
799			from array import array
800			fdSelect = array("B", file.read(numGlyphs)).tolist()
801		elif format == 3:
802			fdSelect = [None] * numGlyphs
803			nRanges = readCard16(file)
804			prev = None
805			for i in range(nRanges):
806				first = readCard16(file)
807				if prev is not None:
808					for glyphID in range(prev, first):
809						fdSelect[glyphID] = fd
810				prev = first
811				fd = readCard8(file)
812			if prev is not None:
813				first = readCard16(file)
814				for glyphID in range(prev, first):
815					fdSelect[glyphID] = fd
816		else:
817			assert 0, "unsupported FDSelect format: %s" % format
818		return fdSelect
819	def xmlWrite(self, xmlWriter, name, value):
820		pass
821
822
823class ROSConverter(SimpleConverter):
824	def xmlWrite(self, xmlWriter, name, value):
825		registry, order, supplement = value
826		xmlWriter.simpletag(name, [('Registry', registry), ('Order', order),
827			('Supplement', supplement)])
828		xmlWriter.newline()
829
830
831topDictOperators = [
832#	opcode     name                  argument type   default    converter
833	((12, 30), 'ROS',        ('SID','SID','number'), None,      ROSConverter()),
834	((12, 20), 'SyntheticBase',      'number',       None,      None),
835	(0,        'version',            'SID',          None,      None),
836	(1,        'Notice',             'SID',          None,      None),
837	((12, 0),  'Copyright',          'SID',          None,      None),
838	(2,        'FullName',           'SID',          None,      None),
839	((12, 38), 'FontName',           'SID',          None,      None),
840	(3,        'FamilyName',         'SID',          None,      None),
841	(4,        'Weight',             'SID',          None,      None),
842	((12, 1),  'isFixedPitch',       'number',       0,         None),
843	((12, 2),  'ItalicAngle',        'number',       0,         None),
844	((12, 3),  'UnderlinePosition',  'number',       None,      None),
845	((12, 4),  'UnderlineThickness', 'number',       50,        None),
846	((12, 5),  'PaintType',          'number',       0,         None),
847	((12, 6),  'CharstringType',     'number',       2,         None),
848	((12, 7),  'FontMatrix',         'array',  [0.001,0,0,0.001,0,0],  None),
849	(13,       'UniqueID',           'number',       None,      None),
850	(5,        'FontBBox',           'array',  [0,0,0,0],       None),
851	((12, 8),  'StrokeWidth',        'number',       0,         None),
852	(14,       'XUID',               'array',        None,      None),
853	(15,       'charset',            'number',       0,         CharsetConverter()),
854	((12, 21), 'PostScript',         'SID',          None,      None),
855	((12, 22), 'BaseFontName',       'SID',          None,      None),
856	((12, 23), 'BaseFontBlend',      'delta',        None,      None),
857	((12, 31), 'CIDFontVersion',     'number',       0,         None),
858	((12, 32), 'CIDFontRevision',    'number',       0,         None),
859	((12, 33), 'CIDFontType',        'number',       0,         None),
860	((12, 34), 'CIDCount',           'number',       8720,      None),
861	((12, 35), 'UIDBase',            'number',       None,      None),
862	(16,       'Encoding',           'number',       0,         None), # XXX
863	((12, 36), 'FDArray',            'number',       None,      FDArrayConverter()),
864	((12, 37), 'FDSelect',           'number',       None,      FDSelectConverter()),
865	(18,       'Private',       ('number','number'), None,      PrivateDictConverter()),
866	(17,       'CharStrings',        'number',       None,      CharStringsConverter()),
867]
868
869privateDictOperators = [
870#	opcode     name                  argument type   default    converter
871	(6,        'BlueValues',         'delta',        None,      None),
872	(7,        'OtherBlues',         'delta',        None,      None),
873	(8,        'FamilyBlues',        'delta',        None,      None),
874	(9,        'FamilyOtherBlues',   'delta',        None,      None),
875	((12, 9),  'BlueScale',          'number',       0.039625,  None),
876	((12, 10), 'BlueShift',          'number',       7,         None),
877	((12, 11), 'BlueFuzz',           'number',       1,         None),
878	(10,       'StdHW',              'number',       None,      None),
879	(11,       'StdVW',              'number',       None,      None),
880	((12, 12), 'StemSnapH',          'delta',        None,      None),
881	((12, 13), 'StemSnapV',          'delta',        None,      None),
882	((12, 14), 'ForceBold',          'number',       0,         None),
883	((12, 15), 'ForceBoldThreshold', 'number',       None,      None),  # deprecated
884	((12, 16), 'lenIV',              'number',       None,      None),  # deprecated
885	((12, 17), 'LanguageGroup',      'number',       0,         None),
886	((12, 18), 'ExpansionFactor',    'number',       0.06,      None),
887	((12, 19), 'initialRandomSeed',  'number',       0,         None),
888	(20,       'defaultWidthX',      'number',       0,         None),
889	(21,       'nominalWidthX',      'number',       0,         None),
890	(19,       'Subrs',              'number',       None,      SubrsConverter()),
891]
892
893def addConverters(table):
894	for i in range(len(table)):
895		op, name, arg, default, conv = table[i]
896		if conv is not None:
897			continue
898		if arg in ("delta", "array"):
899			conv = ArrayConverter()
900		elif arg == "number":
901			conv = NumberConverter()
902		elif arg == "SID":
903			conv = SimpleConverter()
904		else:
905			assert 0
906		table[i] = op, name, arg, default, conv
907
908addConverters(privateDictOperators)
909addConverters(topDictOperators)
910
911
912class TopDictDecompiler(psCharStrings.DictDecompiler):
913	operators = buildOperatorDict(topDictOperators)
914
915
916class PrivateDictDecompiler(psCharStrings.DictDecompiler):
917	operators = buildOperatorDict(privateDictOperators)
918
919
920class DictCompiler:
921
922	def __init__(self, dictObj, strings, parent):
923		assert isinstance(strings, IndexedStrings)
924		self.dictObj = dictObj
925		self.strings = strings
926		self.parent = parent
927		rawDict = {}
928		for name in dictObj.order:
929			value = getattr(dictObj, name, None)
930			if value is None:
931				continue
932			conv = dictObj.converters[name]
933			value = conv.write(dictObj, value)
934			if value == dictObj.defaults.get(name):
935				continue
936			rawDict[name] = value
937		self.rawDict = rawDict
938
939	def setPos(self, pos, endPos):
940		pass
941
942	def getDataLength(self):
943		return len(self.compile("getDataLength"))
944
945	def compile(self, reason):
946		if DEBUG:
947			print "-- compiling %s for %s" % (self.__class__.__name__, reason)
948		rawDict = self.rawDict
949		data = []
950		for name in self.dictObj.order:
951			value = rawDict.get(name)
952			if value is None:
953				continue
954			op, argType = self.opcodes[name]
955			if type(argType) == TupleType:
956				l = len(argType)
957				assert len(value) == l, "value doesn't match arg type"
958				for i in range(l):
959					arg = argType[l - i - 1]
960					v = value[i]
961					arghandler = getattr(self, "arg_" + arg)
962					data.append(arghandler(v))
963			else:
964				arghandler = getattr(self, "arg_" + argType)
965				data.append(arghandler(value))
966			data.append(op)
967		return "".join(data)
968
969	def toFile(self, file):
970		file.write(self.compile("toFile"))
971
972	def arg_number(self, num):
973		return encodeNumber(num)
974	def arg_SID(self, s):
975		return psCharStrings.encodeIntCFF(self.strings.getSID(s))
976	def arg_array(self, value):
977		data = []
978		for num in value:
979			data.append(encodeNumber(num))
980		return "".join(data)
981	def arg_delta(self, value):
982		out = []
983		last = 0
984		for v in value:
985			out.append(v - last)
986			last = v
987		data = []
988		for num in out:
989			data.append(encodeNumber(num))
990		return "".join(data)
991
992
993def encodeNumber(num):
994	if type(num) == FloatType:
995		return psCharStrings.encodeFloat(num)
996	else:
997		return psCharStrings.encodeIntCFF(num)
998
999
1000class TopDictCompiler(DictCompiler):
1001
1002	opcodes = buildOpcodeDict(topDictOperators)
1003
1004	def getChildren(self, strings):
1005		children = []
1006		if hasattr(self.dictObj, "charset"):
1007			children.append(CharsetCompiler(strings, self.dictObj.charset, self))
1008		if hasattr(self.dictObj, "CharStrings"):
1009			items = []
1010			charStrings = self.dictObj.CharStrings
1011			for name in self.dictObj.charset:
1012				items.append(charStrings[name])
1013			charStringsComp = CharStringsCompiler(items, strings, self)
1014			children.append(charStringsComp)
1015		if hasattr(self.dictObj, "Private"):
1016			privComp = self.dictObj.Private.getCompiler(strings, self)
1017			children.append(privComp)
1018			children.extend(privComp.getChildren(strings))
1019		return children
1020
1021
1022class PrivateDictCompiler(DictCompiler):
1023
1024	opcodes = buildOpcodeDict(privateDictOperators)
1025
1026	def setPos(self, pos, endPos):
1027		size = endPos - pos
1028		self.parent.rawDict["Private"] = size, pos
1029		self.pos = pos
1030
1031	def getChildren(self, strings):
1032		children = []
1033		if hasattr(self.dictObj, "Subrs"):
1034			children.append(self.dictObj.Subrs.getCompiler(strings, self))
1035		return children
1036
1037
1038class BaseDict:
1039
1040	def __init__(self, strings=None, file=None, offset=None):
1041		self.rawDict = {}
1042		if DEBUG:
1043			print "loading %s at %s" % (self.__class__.__name__, offset)
1044		self.file = file
1045		self.offset = offset
1046		self.strings = strings
1047		self.skipNames = []
1048
1049	def decompile(self, data):
1050		if DEBUG:
1051			print "    length %s is %s" % (self.__class__.__name__, len(data))
1052		dec = self.decompilerClass(self.strings)
1053		dec.decompile(data)
1054		self.rawDict = dec.getDict()
1055		self.postDecompile()
1056
1057	def postDecompile(self):
1058		pass
1059
1060	def getCompiler(self, strings, parent):
1061		return self.compilerClass(self, strings, parent)
1062
1063	def __getattr__(self, name):
1064		value = self.rawDict.get(name)
1065		if value is None:
1066			value = self.defaults.get(name)
1067		if value is None:
1068			raise AttributeError, name
1069		conv = self.converters[name]
1070		value = conv.read(self, value)
1071		setattr(self, name, value)
1072		return value
1073
1074	def toXML(self, xmlWriter, progress):
1075		for name in self.order:
1076			if name in self.skipNames:
1077				continue
1078			value = getattr(self, name, None)
1079			if value is None:
1080				continue
1081			conv = self.converters[name]
1082			conv.xmlWrite(xmlWriter, name, value)
1083
1084	def fromXML(self, (name, attrs, content)):
1085		conv = self.converters[name]
1086		value = conv.xmlRead((name, attrs, content), self)
1087		setattr(self, name, value)
1088
1089
1090class TopDict(BaseDict):
1091
1092	defaults = buildDefaults(topDictOperators)
1093	converters = buildConverters(topDictOperators)
1094	order = buildOrder(topDictOperators)
1095	decompilerClass = TopDictDecompiler
1096	compilerClass = TopDictCompiler
1097
1098	def __init__(self, strings=None, file=None, offset=None, GlobalSubrs=None):
1099		BaseDict.__init__(self, strings, file, offset)
1100		self.GlobalSubrs = GlobalSubrs
1101
1102	def getGlyphOrder(self):
1103		return self.charset
1104
1105	def postDecompile(self):
1106		offset = self.rawDict.get("CharStrings")
1107		if offset is None:
1108			return
1109		# get the number of glyphs beforehand.
1110		self.file.seek(offset)
1111		self.numGlyphs = readCard16(self.file)
1112
1113	def toXML(self, xmlWriter, progress):
1114		if hasattr(self, "CharStrings"):
1115			self.decompileAllCharStrings()
1116		if not hasattr(self, "ROS") or not hasattr(self, "CharStrings"):
1117			# these values have default values, but I only want them to show up
1118			# in CID fonts.
1119			self.skipNames = ['CIDFontVersion', 'CIDFontRevision', 'CIDFontType',
1120					'CIDCount']
1121		BaseDict.toXML(self, xmlWriter, progress)
1122
1123	def decompileAllCharStrings(self):
1124		# XXX only when doing ttdump -i?
1125		for charString in self.CharStrings.values():
1126			charString.decompile()
1127
1128
1129class PrivateDict(BaseDict):
1130	defaults = buildDefaults(privateDictOperators)
1131	converters = buildConverters(privateDictOperators)
1132	order = buildOrder(privateDictOperators)
1133	decompilerClass = PrivateDictDecompiler
1134	compilerClass = PrivateDictCompiler
1135
1136
1137class IndexedStrings:
1138
1139	"""SID -> string mapping."""
1140
1141	def __init__(self, file=None):
1142		if file is None:
1143			strings = []
1144		else:
1145			strings = list(Index(file))
1146		self.strings = strings
1147
1148	def getCompiler(self):
1149		return IndexedStringsCompiler(self, None, None)
1150
1151	def __len__(self):
1152		return len(self.strings)
1153
1154	def __getitem__(self, SID):
1155		if SID < cffStandardStringCount:
1156			return cffStandardStrings[SID]
1157		else:
1158			return self.strings[SID - cffStandardStringCount]
1159
1160	def getSID(self, s):
1161		if not hasattr(self, "stringMapping"):
1162			self.buildStringMapping()
1163		if cffStandardStringMapping.has_key(s):
1164			SID = cffStandardStringMapping[s]
1165		elif self.stringMapping.has_key(s):
1166			SID = self.stringMapping[s]
1167		else:
1168			SID = len(self.strings) + cffStandardStringCount
1169			self.strings.append(s)
1170			self.stringMapping[s] = SID
1171		return SID
1172
1173	def getStrings(self):
1174		return self.strings
1175
1176	def buildStringMapping(self):
1177		self.stringMapping = {}
1178		for index in range(len(self.strings)):
1179			self.stringMapping[self.strings[index]] = index + cffStandardStringCount
1180
1181
1182# The 391 Standard Strings as used in the CFF format.
1183# from Adobe Technical None #5176, version 1.0, 18 March 1998
1184
1185cffStandardStrings = ['.notdef', 'space', 'exclam', 'quotedbl', 'numbersign',
1186		'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright',
1187		'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one',
1188		'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon',
1189		'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C',
1190		'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
1191		'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash',
1192		'bracketright', 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c',
1193		'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
1194		's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright',
1195		'asciitilde', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin',
1196		'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft',
1197		'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger',
1198		'daggerdbl', 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase',
1199		'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand',
1200		'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve',
1201		'dotaccent', 'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron',
1202		'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae',
1203		'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior',
1204		'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn',
1205		'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters',
1206		'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior',
1207		'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring',
1208		'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave',
1209		'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute',
1210		'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute',
1211		'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron',
1212		'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla',
1213		'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex',
1214		'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis',
1215		'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave',
1216		'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall',
1217		'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall',
1218		'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader',
1219		'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle',
1220		'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle',
1221		'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior',
1222		'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior',
1223		'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior',
1224		'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', 'parenleftinferior',
1225		'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall',
1226		'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall',
1227		'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall',
1228		'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall',
1229		'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall',
1230		'exclamdownsmall', 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall',
1231		'Dieresissmall', 'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall',
1232		'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall',
1233		'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths',
1234		'onethird', 'twothirds', 'zerosuperior', 'foursuperior', 'fivesuperior',
1235		'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior',
1236		'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior',
1237		'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior',
1238		'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall',
1239		'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall',
1240		'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall',
1241		'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall',
1242		'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall',
1243		'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall',
1244		'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall',
1245		'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', '001.001', '001.002',
1246		'001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman',
1247		'Semibold'
1248]
1249
1250cffStandardStringCount = 391
1251assert len(cffStandardStrings) == cffStandardStringCount
1252# build reverse mapping
1253cffStandardStringMapping = {}
1254for _i in range(cffStandardStringCount):
1255	cffStandardStringMapping[cffStandardStrings[_i]] = _i
1256
1257