cffLib.py revision 3a9fd301808f5a8991ca9ac44028d1ecb22d307f
1"""cffLib.py -- read/write tools for Adobe CFF fonts."""
2
3#
4# $Id: cffLib.py,v 1.34 2008-03-07 19:56:17 jvr Exp $
5#
6
7import struct
8from fontTools.misc import sstruct
9import string
10from fontTools.misc import psCharStrings
11from fontTools.misc.textTools import safeEval
12
13DEBUG = 0
14
15
16cffHeaderFormat = """
17	major:   B
18	minor:   B
19	hdrSize: B
20	offSize: B
21"""
22
23class CFFFontSet:
24
25	def __init__(self):
26		pass
27
28	def decompile(self, file, otFont):
29		sstruct.unpack(cffHeaderFormat, file.read(4), self)
30		assert self.major == 1 and self.minor == 0, \
31				"unknown CFF format: %d.%d" % (self.major, self.minor)
32
33		file.seek(self.hdrSize)
34		self.fontNames = list(Index(file))
35		self.topDictIndex = TopDictIndex(file)
36		self.strings = IndexedStrings(file)
37		self.GlobalSubrs = GlobalSubrsIndex(file)
38		self.topDictIndex.strings = self.strings
39		self.topDictIndex.GlobalSubrs = self.GlobalSubrs
40
41	def __len__(self):
42		return len(self.fontNames)
43
44	def keys(self):
45		return list(self.fontNames)
46
47	def values(self):
48		return self.topDictIndex
49
50	def __getitem__(self, name):
51		try:
52			index = self.fontNames.index(name)
53		except ValueError:
54			raise KeyError(name)
55		return self.topDictIndex[index]
56
57	def compile(self, file, otFont):
58		strings = IndexedStrings()
59		writer = CFFWriter()
60		writer.add(sstruct.pack(cffHeaderFormat, self))
61		fontNames = Index()
62		for name in self.fontNames:
63			fontNames.append(name)
64		writer.add(fontNames.getCompiler(strings, None))
65		topCompiler = self.topDictIndex.getCompiler(strings, None)
66		writer.add(topCompiler)
67		writer.add(strings.getCompiler())
68		writer.add(self.GlobalSubrs.getCompiler(strings, None))
69
70		for topDict in self.topDictIndex:
71			if not hasattr(topDict, "charset") or topDict.charset is None:
72				charset = otFont.getGlyphOrder()
73				topDict.charset = charset
74
75		for child in topCompiler.getChildren(strings):
76			writer.add(child)
77
78		writer.toFile(file)
79
80	def toXML(self, xmlWriter, progress=None):
81		xmlWriter.newline()
82		for fontName in self.fontNames:
83			xmlWriter.begintag("CFFFont", name=fontName)
84			xmlWriter.newline()
85			font = self[fontName]
86			font.toXML(xmlWriter, progress)
87			xmlWriter.endtag("CFFFont")
88			xmlWriter.newline()
89		xmlWriter.newline()
90		xmlWriter.begintag("GlobalSubrs")
91		xmlWriter.newline()
92		self.GlobalSubrs.toXML(xmlWriter, progress)
93		xmlWriter.endtag("GlobalSubrs")
94		xmlWriter.newline()
95		xmlWriter.newline()
96
97	def fromXML(self, name, attrs, content):
98		if not hasattr(self, "GlobalSubrs"):
99			self.GlobalSubrs = GlobalSubrsIndex()
100			self.major = 1
101			self.minor = 0
102			self.hdrSize = 4
103			self.offSize = 4  # XXX ??
104		if name == "CFFFont":
105			if not hasattr(self, "fontNames"):
106				self.fontNames = []
107				self.topDictIndex = TopDictIndex()
108			fontName = attrs["name"]
109			topDict = TopDict(GlobalSubrs=self.GlobalSubrs)
110			topDict.charset = None  # gets filled in later
111			self.fontNames.append(fontName)
112			self.topDictIndex.append(topDict)
113			for element in content:
114				if isinstance(element, basestring):
115					continue
116				name, attrs, content = element
117				topDict.fromXML(name, attrs, content)
118		elif name == "GlobalSubrs":
119			for element in content:
120				if isinstance(element, basestring):
121					continue
122				name, attrs, content = element
123				subr = psCharStrings.T2CharString()
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 FDArrayIndexCompiler(IndexCompiler):
253
254	def getItems(self, items, strings):
255		out = []
256		for item in items:
257			out.append(item.getCompiler(strings, self))
258		return out
259
260	def getChildren(self, strings):
261		children = []
262		for fontDict in self.items:
263			children.extend(fontDict.getChildren(strings))
264		return children
265
266	def toFile(self, file):
267		offsets = self.getOffsets()
268		writeCard16(file, len(self.items))
269		offSize = calcOffSize(offsets[-1])
270		writeCard8(file, offSize)
271		offSize = -offSize
272		pack = struct.pack
273		for offset in offsets:
274			binOffset = pack(">l", offset)[offSize:]
275			assert len(binOffset) == -offSize
276			file.write(binOffset)
277		for item in self.items:
278			if hasattr(item, "toFile"):
279				item.toFile(file)
280			else:
281				file.write(item)
282
283	def setPos(self, pos, endPos):
284		self.parent.rawDict["FDArray"] = pos
285
286
287class GlobalSubrsCompiler(IndexCompiler):
288	def getItems(self, items, strings):
289		out = []
290		for cs in items:
291			cs.compile()
292			out.append(cs.bytecode)
293		return out
294
295class SubrsCompiler(GlobalSubrsCompiler):
296	def setPos(self, pos, endPos):
297		offset = pos - self.parent.pos
298		self.parent.rawDict["Subrs"] = offset
299
300class CharStringsCompiler(GlobalSubrsCompiler):
301	def setPos(self, pos, endPos):
302		self.parent.rawDict["CharStrings"] = pos
303
304
305class Index:
306
307	"""This class represents what the CFF spec calls an INDEX."""
308
309	compilerClass = IndexCompiler
310
311	def __init__(self, file=None):
312		name = self.__class__.__name__
313		if file is None:
314			self.items = []
315			return
316		if DEBUG:
317			print "loading %s at %s" % (name, file.tell())
318		self.file = file
319		count = readCard16(file)
320		self.count = count
321		self.items = [None] * count
322		if count == 0:
323			self.items = []
324			return
325		offSize = readCard8(file)
326		if DEBUG:
327			print "    index count: %s offSize: %s" % (count, offSize)
328		assert offSize <= 4, "offSize too large: %s" % offSize
329		self.offsets = offsets = []
330		pad = '\0' * (4 - offSize)
331		for index in range(count+1):
332			chunk = file.read(offSize)
333			chunk = pad + chunk
334			offset, = struct.unpack(">L", chunk)
335			offsets.append(int(offset))
336		self.offsetBase = file.tell() - 1
337		file.seek(self.offsetBase + offsets[-1])  # pretend we've read the whole lot
338		if DEBUG:
339			print "    end of %s at %s" % (name, file.tell())
340
341	def __len__(self):
342		return len(self.items)
343
344	def __getitem__(self, index):
345		item = self.items[index]
346		if item is not None:
347			return item
348		offset = self.offsets[index] + self.offsetBase
349		size = self.offsets[index+1] - self.offsets[index]
350		file = self.file
351		file.seek(offset)
352		data = file.read(size)
353		assert len(data) == size
354		item = self.produceItem(index, data, file, offset, size)
355		self.items[index] = item
356		return item
357
358	def produceItem(self, index, data, file, offset, size):
359		return data
360
361	def append(self, item):
362		self.items.append(item)
363
364	def getCompiler(self, strings, parent):
365		return self.compilerClass(self, strings, parent)
366
367
368class GlobalSubrsIndex(Index):
369
370	compilerClass = GlobalSubrsCompiler
371
372	def __init__(self, file=None, globalSubrs=None, private=None, fdSelect=None, fdArray=None):
373		Index.__init__(self, file)
374		self.globalSubrs = globalSubrs
375		self.private = private
376		if fdSelect:
377			self.fdSelect = fdSelect
378		if fdArray:
379			self.fdArray = fdArray
380
381	def produceItem(self, index, data, file, offset, size):
382		if self.private is not None:
383			private = self.private
384		elif hasattr(self, 'fdArray') and self.fdArray is not None:
385			private = self.fdArray[self.fdSelect[index]].Private
386		else:
387			private = None
388		return psCharStrings.T2CharString(data, private=private, globalSubrs=self.globalSubrs)
389
390	def toXML(self, xmlWriter, progress):
391		xmlWriter.comment("The 'index' attribute is only for humans; it is ignored when parsed.")
392		xmlWriter.newline()
393		for i in range(len(self)):
394			subr = self[i]
395			if subr.needsDecompilation():
396				xmlWriter.begintag("CharString", index=i, raw=1)
397			else:
398				xmlWriter.begintag("CharString", index=i)
399			xmlWriter.newline()
400			subr.toXML(xmlWriter)
401			xmlWriter.endtag("CharString")
402			xmlWriter.newline()
403
404	def fromXML(self, name, attrs, content):
405		if name != "CharString":
406			return
407		subr = psCharStrings.T2CharString()
408		subr.fromXML(name, attrs, content)
409		self.append(subr)
410
411	def getItemAndSelector(self, index):
412		sel = None
413		if hasattr(self, 'fdSelect'):
414			sel = self.fdSelect[index]
415		return self[index], sel
416
417
418class SubrsIndex(GlobalSubrsIndex):
419	compilerClass = SubrsCompiler
420
421
422class TopDictIndex(Index):
423
424	compilerClass = TopDictIndexCompiler
425
426	def produceItem(self, index, data, file, offset, size):
427		top = TopDict(self.strings, file, offset, self.GlobalSubrs)
428		top.decompile(data)
429		return top
430
431	def toXML(self, xmlWriter, progress):
432		for i in range(len(self)):
433			xmlWriter.begintag("FontDict", index=i)
434			xmlWriter.newline()
435			self[i].toXML(xmlWriter, progress)
436			xmlWriter.endtag("FontDict")
437			xmlWriter.newline()
438
439
440class FDArrayIndex(TopDictIndex):
441
442	compilerClass = FDArrayIndexCompiler
443
444	def fromXML(self, name, attrs, content):
445		if name != "FontDict":
446			return
447		fontDict = FontDict()
448		for element in content:
449			if isinstance(element, basestring):
450				continue
451			name, attrs, content = element
452			fontDict.fromXML(name, attrs, content)
453		self.append(fontDict)
454
455
456class	FDSelect:
457	def __init__(self, file = None, numGlyphs = None, format=None):
458		if file:
459			# read data in from file
460			self.format = readCard8(file)
461			if self.format == 0:
462				from array import array
463				self.gidArray = array("B", file.read(numGlyphs)).tolist()
464			elif self.format == 3:
465				gidArray = [None] * numGlyphs
466				nRanges = readCard16(file)
467				prev = None
468				for i in range(nRanges):
469					first = readCard16(file)
470					if prev is not None:
471						for glyphID in range(prev, first):
472							gidArray[glyphID] = fd
473					prev = first
474					fd = readCard8(file)
475				if prev is not None:
476					first = readCard16(file)
477					for glyphID in range(prev, first):
478						gidArray[glyphID] = fd
479				self.gidArray = gidArray
480			else:
481				assert 0, "unsupported FDSelect format: %s" % format
482		else:
483			# reading from XML. Make empty gidArray,, and leave format as passed in.
484			# format == None will result in the smallest representation being used.
485			self.format = format
486			self.gidArray = []
487
488
489	def __len__(self):
490		return len(self.gidArray)
491
492	def __getitem__(self, index):
493		return self.gidArray[index]
494
495	def __setitem__(self, index, fdSelectValue):
496		self.gidArray[index] = fdSelectValue
497
498	def append(self, fdSelectValue):
499		self.gidArray.append(fdSelectValue)
500
501
502class CharStrings:
503
504	def __init__(self, file, charset, globalSubrs, private, fdSelect, fdArray):
505		if file is not None:
506			self.charStringsIndex = SubrsIndex(file, globalSubrs, private, fdSelect, fdArray)
507			self.charStrings = charStrings = {}
508			for i in range(len(charset)):
509				charStrings[charset[i]] = i
510			self.charStringsAreIndexed = 1
511		else:
512			self.charStrings = {}
513			self.charStringsAreIndexed = 0
514			self.globalSubrs = globalSubrs
515			self.private = private
516			if fdSelect != None:
517				self.fdSelect = fdSelect
518			if fdArray!= None:
519				self.fdArray = fdArray
520
521	def keys(self):
522		return self.charStrings.keys()
523
524	def values(self):
525		if self.charStringsAreIndexed:
526			return self.charStringsIndex
527		else:
528			return self.charStrings.values()
529
530	def has_key(self, name):
531		return name in self.charStrings
532
533	def __len__(self):
534		return len(self.charStrings)
535
536	def __getitem__(self, name):
537		charString = self.charStrings[name]
538		if self.charStringsAreIndexed:
539			charString = self.charStringsIndex[charString]
540		return charString
541
542	def __setitem__(self, name, charString):
543		if self.charStringsAreIndexed:
544			index = self.charStrings[name]
545			self.charStringsIndex[index] = charString
546		else:
547			self.charStrings[name] = charString
548
549	def getItemAndSelector(self, name):
550		if self.charStringsAreIndexed:
551			index = self.charStrings[name]
552			return self.charStringsIndex.getItemAndSelector(index)
553		else:
554			if hasattr(self, 'fdSelect'):
555				sel = self.fdSelect[index]  # index is not defined at this point. Read R. ?
556			else:
557				raise KeyError("fdSelect array not yet defined.")
558			return self.charStrings[name], sel
559
560	def toXML(self, xmlWriter, progress):
561		names = self.keys()
562		names.sort()
563		i = 0
564		step = 10
565		numGlyphs = len(names)
566		for name in names:
567			charStr, fdSelectIndex = self.getItemAndSelector(name)
568			if charStr.needsDecompilation():
569				raw = [("raw", 1)]
570			else:
571				raw = []
572			if fdSelectIndex is None:
573				xmlWriter.begintag("CharString", [('name', name)] + raw)
574			else:
575				xmlWriter.begintag("CharString",
576						[('name', name), ('fdSelectIndex', fdSelectIndex)] + raw)
577			xmlWriter.newline()
578			charStr.toXML(xmlWriter)
579			xmlWriter.endtag("CharString")
580			xmlWriter.newline()
581			if not i % step and progress is not None:
582				progress.setLabel("Dumping 'CFF ' table... (%s)" % name)
583				progress.increment(step / float(numGlyphs))
584			i = i + 1
585
586	def fromXML(self, name, attrs, content):
587		for element in content:
588			if isinstance(element, basestring):
589				continue
590			name, attrs, content = element
591			if name != "CharString":
592				continue
593			fdID = -1
594			if hasattr(self, "fdArray"):
595				fdID = safeEval(attrs["fdSelectIndex"])
596				private = self.fdArray[fdID].Private
597			else:
598				private = self.private
599
600			glyphName = attrs["name"]
601			charString = psCharStrings.T2CharString(
602					private=private,
603					globalSubrs=self.globalSubrs)
604			charString.fromXML(name, attrs, content)
605			if fdID >= 0:
606				charString.fdSelectIndex = fdID
607			self[glyphName] = charString
608
609
610def readCard8(file):
611	return ord(file.read(1))
612
613def readCard16(file):
614	value, = struct.unpack(">H", file.read(2))
615	return value
616
617def writeCard8(file, value):
618	file.write(chr(value))
619
620def writeCard16(file, value):
621	file.write(struct.pack(">H", value))
622
623def packCard8(value):
624	return chr(value)
625
626def packCard16(value):
627	return struct.pack(">H", value)
628
629def buildOperatorDict(table):
630	d = {}
631	for op, name, arg, default, conv in table:
632		d[op] = (name, arg)
633	return d
634
635def buildOpcodeDict(table):
636	d = {}
637	for op, name, arg, default, conv in table:
638		if isinstance(op, tuple):
639			op = chr(op[0]) + chr(op[1])
640		else:
641			op = chr(op)
642		d[name] = (op, arg)
643	return d
644
645def buildOrder(table):
646	l = []
647	for op, name, arg, default, conv in table:
648		l.append(name)
649	return l
650
651def buildDefaults(table):
652	d = {}
653	for op, name, arg, default, conv in table:
654		if default is not None:
655			d[name] = default
656	return d
657
658def buildConverters(table):
659	d = {}
660	for op, name, arg, default, conv in table:
661		d[name] = conv
662	return d
663
664
665class SimpleConverter:
666	def read(self, parent, value):
667		return value
668	def write(self, parent, value):
669		return value
670	def xmlWrite(self, xmlWriter, name, value, progress):
671		xmlWriter.simpletag(name, value=value)
672		xmlWriter.newline()
673	def xmlRead(self, name, attrs, content, parent):
674		return attrs["value"]
675
676class Latin1Converter(SimpleConverter):
677	def xmlWrite(self, xmlWriter, name, value, progress):
678		# Store as UTF-8
679		value = unicode(value, "latin-1").encode("utf-8")
680		xmlWriter.simpletag(name, value=value)
681		xmlWriter.newline()
682	def xmlRead(self, name, attrs, content, parent):
683		s = unicode(attrs["value"], "utf-8")
684		return s.encode("latin-1")
685
686
687def parseNum(s):
688	try:
689		value = int(s)
690	except:
691		value = float(s)
692	return value
693
694class NumberConverter(SimpleConverter):
695	def xmlRead(self, name, attrs, content, parent):
696		return parseNum(attrs["value"])
697
698class ArrayConverter(SimpleConverter):
699	def xmlWrite(self, xmlWriter, name, value, progress):
700		value = map(str, value)
701		xmlWriter.simpletag(name, value=" ".join(value))
702		xmlWriter.newline()
703	def xmlRead(self, name, attrs, content, parent):
704		values = attrs["value"].split()
705		return map(parseNum, values)
706
707class TableConverter(SimpleConverter):
708	def xmlWrite(self, xmlWriter, name, value, progress):
709		xmlWriter.begintag(name)
710		xmlWriter.newline()
711		value.toXML(xmlWriter, progress)
712		xmlWriter.endtag(name)
713		xmlWriter.newline()
714	def xmlRead(self, name, attrs, content, parent):
715		ob = self.getClass()()
716		for element in content:
717			if isinstance(element, basestring):
718				continue
719			name, attrs, content = element
720			ob.fromXML(name, attrs, content)
721		return ob
722
723class PrivateDictConverter(TableConverter):
724	def getClass(self):
725		return PrivateDict
726	def read(self, parent, value):
727		size, offset = value
728		file = parent.file
729		priv = PrivateDict(parent.strings, file, offset)
730		file.seek(offset)
731		data = file.read(size)
732		len(data) == size
733		priv.decompile(data)
734		return priv
735	def write(self, parent, value):
736		return (0, 0)  # dummy value
737
738class SubrsConverter(TableConverter):
739	def getClass(self):
740		return SubrsIndex
741	def read(self, parent, value):
742		file = parent.file
743		file.seek(parent.offset + value)  # Offset(self)
744		return SubrsIndex(file)
745	def write(self, parent, value):
746		return 0  # dummy value
747
748class CharStringsConverter(TableConverter):
749	def read(self, parent, value):
750		file = parent.file
751		charset = parent.charset
752		globalSubrs = parent.GlobalSubrs
753		if hasattr(parent, "ROS"):
754			fdSelect, fdArray = parent.FDSelect, parent.FDArray
755			private = None
756		else:
757			fdSelect, fdArray = None, None
758			private = parent.Private
759		file.seek(value)  # Offset(0)
760		return CharStrings(file, charset, globalSubrs, private, fdSelect, fdArray)
761	def write(self, parent, value):
762		return 0  # dummy value
763	def xmlRead(self, name, attrs, content, parent):
764		if hasattr(parent, "ROS"):
765			# if it is a CID-keyed font, then the private Dict is extracted from the parent.FDArray
766			private, fdSelect, fdArray = None, parent.FDSelect, parent.FDArray
767		else:
768			# if it is a name-keyed font, then the private dict is in the top dict, and there is no fdArray.
769			private, fdSelect, fdArray = parent.Private, None, None
770		charStrings = CharStrings(None, None, parent.GlobalSubrs, private, fdSelect, fdArray)
771		charStrings.fromXML(name, attrs, content)
772		return charStrings
773
774class CharsetConverter:
775	def read(self, parent, value):
776		isCID = hasattr(parent, "ROS")
777		if value > 2:
778			numGlyphs = parent.numGlyphs
779			file = parent.file
780			file.seek(value)
781			if DEBUG:
782				print "loading charset at %s" % value
783			format = readCard8(file)
784			if format == 0:
785				charset = parseCharset0(numGlyphs, file, parent.strings, isCID)
786			elif format == 1 or format == 2:
787				charset = parseCharset(numGlyphs, file, parent.strings, isCID, format)
788			else:
789				raise NotImplementedError
790			assert len(charset) == numGlyphs
791			if DEBUG:
792				print "    charset end at %s" % file.tell()
793		else: # offset == 0 -> no charset data.
794			if isCID or "CharStrings" not in parent.rawDict:
795				assert value == 0 # We get here only when processing fontDicts from the FDArray of CFF-CID fonts. Only the real topDict references the chrset.
796				charset = None
797			elif value == 0:
798				charset = cffISOAdobeStrings
799			elif value == 1:
800				charset = cffIExpertStrings
801			elif value == 2:
802				charset = cffExpertSubsetStrings
803		return charset
804
805	def write(self, parent, value):
806		return 0  # dummy value
807	def xmlWrite(self, xmlWriter, name, value, progress):
808		# XXX only write charset when not in OT/TTX context, where we
809		# dump charset as a separate "GlyphOrder" table.
810		##xmlWriter.simpletag("charset")
811		xmlWriter.comment("charset is dumped separately as the 'GlyphOrder' element")
812		xmlWriter.newline()
813	def xmlRead(self, name, attrs, content, parent):
814		if 0:
815			return safeEval(attrs["value"])
816
817
818class CharsetCompiler:
819
820	def __init__(self, strings, charset, parent):
821		assert charset[0] == '.notdef'
822		isCID = hasattr(parent.dictObj, "ROS")
823		data0 = packCharset0(charset, isCID, strings)
824		data = packCharset(charset, isCID, strings)
825		if len(data) < len(data0):
826			self.data = data
827		else:
828			self.data = data0
829		self.parent = parent
830
831	def setPos(self, pos, endPos):
832		self.parent.rawDict["charset"] = pos
833
834	def getDataLength(self):
835		return len(self.data)
836
837	def toFile(self, file):
838		file.write(self.data)
839
840
841def getCIDfromName(name, strings):
842	return int(name[3:])
843
844def getSIDfromName(name, strings):
845	return strings.getSID(name)
846
847def packCharset0(charset, isCID, strings):
848	format = 0
849	data = [packCard8(format)]
850	if isCID:
851		getNameID = getCIDfromName
852	else:
853		getNameID = getSIDfromName
854
855	for name in charset[1:]:
856		data.append(packCard16(getNameID(name,strings)))
857	return "".join(data)
858
859
860def packCharset(charset, isCID, strings):
861	format = 1
862	ranges = []
863	first = None
864	end = 0
865	if isCID:
866		getNameID = getCIDfromName
867	else:
868		getNameID = getSIDfromName
869
870	for name in charset[1:]:
871		SID = getNameID(name, strings)
872		if first is None:
873			first = SID
874		elif end + 1 != SID:
875			nLeft = end - first
876			if nLeft > 255:
877				format = 2
878			ranges.append((first, nLeft))
879			first = SID
880		end = SID
881	nLeft = end - first
882	if nLeft > 255:
883		format = 2
884	ranges.append((first, nLeft))
885
886	data = [packCard8(format)]
887	if format == 1:
888		nLeftFunc = packCard8
889	else:
890		nLeftFunc = packCard16
891	for first, nLeft in ranges:
892		data.append(packCard16(first) + nLeftFunc(nLeft))
893	return "".join(data)
894
895def parseCharset0(numGlyphs, file, strings, isCID):
896	charset = [".notdef"]
897	if isCID:
898		for i in range(numGlyphs - 1):
899			CID = readCard16(file)
900			charset.append("cid" + string.zfill(str(CID), 5) )
901	else:
902		for i in range(numGlyphs - 1):
903			SID = readCard16(file)
904			charset.append(strings[SID])
905	return charset
906
907def parseCharset(numGlyphs, file, strings, isCID, format):
908	charset = ['.notdef']
909	count = 1
910	if format == 1:
911		nLeftFunc = readCard8
912	else:
913		nLeftFunc = readCard16
914	while count < numGlyphs:
915		first = readCard16(file)
916		nLeft = nLeftFunc(file)
917		if isCID:
918			for CID in range(first, first+nLeft+1):
919				charset.append("cid" + string.zfill(str(CID), 5) )
920		else:
921			for SID in range(first, first+nLeft+1):
922				charset.append(strings[SID])
923		count = count + nLeft + 1
924	return charset
925
926
927class EncodingCompiler:
928
929	def __init__(self, strings, encoding, parent):
930		assert not isinstance(encoding, basestring)
931		data0 = packEncoding0(parent.dictObj.charset, encoding, parent.strings)
932		data1 = packEncoding1(parent.dictObj.charset, encoding, parent.strings)
933		if len(data0) < len(data1):
934			self.data = data0
935		else:
936			self.data = data1
937		self.parent = parent
938
939	def setPos(self, pos, endPos):
940		self.parent.rawDict["Encoding"] = pos
941
942	def getDataLength(self):
943		return len(self.data)
944
945	def toFile(self, file):
946		file.write(self.data)
947
948
949class EncodingConverter(SimpleConverter):
950
951	def read(self, parent, value):
952		if value == 0:
953			return "StandardEncoding"
954		elif value == 1:
955			return "ExpertEncoding"
956		else:
957			assert value > 1
958			file = parent.file
959			file.seek(value)
960			if DEBUG:
961				print "loading Encoding at %s" % value
962			format = readCard8(file)
963			haveSupplement = format & 0x80
964			if haveSupplement:
965				raise NotImplementedError("Encoding supplements are not yet supported")
966			format = format & 0x7f
967			if format == 0:
968				encoding = parseEncoding0(parent.charset, file, haveSupplement,
969						parent.strings)
970			elif format == 1:
971				encoding = parseEncoding1(parent.charset, file, haveSupplement,
972						parent.strings)
973			return encoding
974
975	def write(self, parent, value):
976		if value == "StandardEncoding":
977			return 0
978		elif value == "ExpertEncoding":
979			return 1
980		return 0  # dummy value
981
982	def xmlWrite(self, xmlWriter, name, value, progress):
983		if value in ("StandardEncoding", "ExpertEncoding"):
984			xmlWriter.simpletag(name, name=value)
985			xmlWriter.newline()
986			return
987		xmlWriter.begintag(name)
988		xmlWriter.newline()
989		for code in range(len(value)):
990			glyphName = value[code]
991			if glyphName != ".notdef":
992				xmlWriter.simpletag("map", code=hex(code), name=glyphName)
993				xmlWriter.newline()
994		xmlWriter.endtag(name)
995		xmlWriter.newline()
996
997	def xmlRead(self, name, attrs, content, parent):
998		if "name" in attrs:
999			return attrs["name"]
1000		encoding = [".notdef"] * 256
1001		for element in content:
1002			if isinstance(element, basestring):
1003				continue
1004			name, attrs, content = element
1005			code = safeEval(attrs["code"])
1006			glyphName = attrs["name"]
1007			encoding[code] = glyphName
1008		return encoding
1009
1010
1011def parseEncoding0(charset, file, haveSupplement, strings):
1012	nCodes = readCard8(file)
1013	encoding = [".notdef"] * 256
1014	for glyphID in range(1, nCodes + 1):
1015		code = readCard8(file)
1016		if code != 0:
1017			encoding[code] = charset[glyphID]
1018	return encoding
1019
1020def parseEncoding1(charset, file, haveSupplement, strings):
1021	nRanges = readCard8(file)
1022	encoding = [".notdef"] * 256
1023	glyphID = 1
1024	for i in range(nRanges):
1025		code = readCard8(file)
1026		nLeft = readCard8(file)
1027		for glyphID in range(glyphID, glyphID + nLeft + 1):
1028			encoding[code] = charset[glyphID]
1029			code = code + 1
1030		glyphID = glyphID + 1
1031	return encoding
1032
1033def packEncoding0(charset, encoding, strings):
1034	format = 0
1035	m = {}
1036	for code in range(len(encoding)):
1037		name = encoding[code]
1038		if name != ".notdef":
1039			m[name] = code
1040	codes = []
1041	for name in charset[1:]:
1042		code = m.get(name)
1043		codes.append(code)
1044
1045	while codes and codes[-1] is None:
1046		codes.pop()
1047
1048	data = [packCard8(format), packCard8(len(codes))]
1049	for code in codes:
1050		if code is None:
1051			code = 0
1052		data.append(packCard8(code))
1053	return "".join(data)
1054
1055def packEncoding1(charset, encoding, strings):
1056	format = 1
1057	m = {}
1058	for code in range(len(encoding)):
1059		name = encoding[code]
1060		if name != ".notdef":
1061			m[name] = code
1062	ranges = []
1063	first = None
1064	end = 0
1065	for name in charset[1:]:
1066		code = m.get(name, -1)
1067		if first is None:
1068			first = code
1069		elif end + 1 != code:
1070			nLeft = end - first
1071			ranges.append((first, nLeft))
1072			first = code
1073		end = code
1074	nLeft = end - first
1075	ranges.append((first, nLeft))
1076
1077	# remove unencoded glyphs at the end.
1078	while ranges and ranges[-1][0] == -1:
1079		ranges.pop()
1080
1081	data = [packCard8(format), packCard8(len(ranges))]
1082	for first, nLeft in ranges:
1083		if first == -1:  # unencoded
1084			first = 0
1085		data.append(packCard8(first) + packCard8(nLeft))
1086	return "".join(data)
1087
1088
1089class FDArrayConverter(TableConverter):
1090
1091	def read(self, parent, value):
1092		file = parent.file
1093		file.seek(value)
1094		fdArray = FDArrayIndex(file)
1095		fdArray.strings = parent.strings
1096		fdArray.GlobalSubrs = parent.GlobalSubrs
1097		return fdArray
1098
1099	def write(self, parent, value):
1100		return 0  # dummy value
1101
1102	def xmlRead(self, name, attrs, content, parent):
1103		fdArray = FDArrayIndex()
1104		for element in content:
1105			if isinstance(element, basestring):
1106				continue
1107			name, attrs, content = element
1108			fdArray.fromXML(name, attrs, content)
1109		return fdArray
1110
1111
1112class FDSelectConverter:
1113
1114	def read(self, parent, value):
1115		file = parent.file
1116		file.seek(value)
1117		fdSelect = FDSelect(file, parent.numGlyphs)
1118		return 	fdSelect
1119
1120	def write(self, parent, value):
1121		return 0  # dummy value
1122
1123	# The FDSelect glyph data is written out to XML in the charstring keys,
1124	# so we write out only the format selector
1125	def xmlWrite(self, xmlWriter, name, value, progress):
1126		xmlWriter.simpletag(name, [('format', value.format)])
1127		xmlWriter.newline()
1128
1129	def xmlRead(self, name, attrs, content, parent):
1130		format = safeEval(attrs["format"])
1131		file = None
1132		numGlyphs = None
1133		fdSelect = FDSelect(file, numGlyphs, format)
1134		return fdSelect
1135
1136
1137def packFDSelect0(fdSelectArray):
1138	format = 0
1139	data = [packCard8(format)]
1140	for index in fdSelectArray:
1141		data.append(packCard8(index))
1142	return "".join(data)
1143
1144
1145def packFDSelect3(fdSelectArray):
1146	format = 3
1147	fdRanges = []
1148	first = None
1149	end = 0
1150	lenArray = len(fdSelectArray)
1151	lastFDIndex = -1
1152	for i in range(lenArray):
1153		fdIndex = fdSelectArray[i]
1154		if lastFDIndex != fdIndex:
1155			fdRanges.append([i, fdIndex])
1156			lastFDIndex = fdIndex
1157	sentinelGID = i + 1
1158
1159	data = [packCard8(format)]
1160	data.append(packCard16( len(fdRanges) ))
1161	for fdRange in fdRanges:
1162		data.append(packCard16(fdRange[0]))
1163		data.append(packCard8(fdRange[1]))
1164	data.append(packCard16(sentinelGID))
1165	return "".join(data)
1166
1167
1168class FDSelectCompiler:
1169
1170	def __init__(self, fdSelect, parent):
1171		format = fdSelect.format
1172		fdSelectArray = fdSelect.gidArray
1173		if format == 0:
1174			self.data = packFDSelect0(fdSelectArray)
1175		elif format == 3:
1176			self.data = packFDSelect3(fdSelectArray)
1177		else:
1178			# choose smaller of the two formats
1179			data0 = packFDSelect0(fdSelectArray)
1180			data3 = packFDSelect3(fdSelectArray)
1181			if len(data0) < len(data3):
1182				self.data = data0
1183				fdSelect.format = 0
1184			else:
1185				self.data = data3
1186				fdSelect.format = 3
1187
1188		self.parent = parent
1189
1190	def setPos(self, pos, endPos):
1191		self.parent.rawDict["FDSelect"] = pos
1192
1193	def getDataLength(self):
1194		return len(self.data)
1195
1196	def toFile(self, file):
1197		file.write(self.data)
1198
1199
1200class ROSConverter(SimpleConverter):
1201
1202	def xmlWrite(self, xmlWriter, name, value, progress):
1203		registry, order, supplement = value
1204		xmlWriter.simpletag(name, [('Registry', registry), ('Order', order),
1205			('Supplement', supplement)])
1206		xmlWriter.newline()
1207
1208	def xmlRead(self, name, attrs, content, parent):
1209		return (attrs['Registry'], attrs['Order'], safeEval(attrs['Supplement']))
1210
1211
1212
1213topDictOperators = [
1214#	opcode     name                  argument type   default    converter
1215	((12, 30), 'ROS',        ('SID','SID','number'), None,      ROSConverter()),
1216	((12, 20), 'SyntheticBase',      'number',       None,      None),
1217	(0,        'version',            'SID',          None,      None),
1218	(1,        'Notice',             'SID',          None,      Latin1Converter()),
1219	((12, 0),  'Copyright',          'SID',          None,      Latin1Converter()),
1220	(2,        'FullName',           'SID',          None,      None),
1221	((12, 38), 'FontName',           'SID',          None,      None),
1222	(3,        'FamilyName',         'SID',          None,      None),
1223	(4,        'Weight',             'SID',          None,      None),
1224	((12, 1),  'isFixedPitch',       'number',       0,         None),
1225	((12, 2),  'ItalicAngle',        'number',       0,         None),
1226	((12, 3),  'UnderlinePosition',  'number',       None,      None),
1227	((12, 4),  'UnderlineThickness', 'number',       50,        None),
1228	((12, 5),  'PaintType',          'number',       0,         None),
1229	((12, 6),  'CharstringType',     'number',       2,         None),
1230	((12, 7),  'FontMatrix',         'array',  [0.001,0,0,0.001,0,0],  None),
1231	(13,       'UniqueID',           'number',       None,      None),
1232	(5,        'FontBBox',           'array',  [0,0,0,0],       None),
1233	((12, 8),  'StrokeWidth',        'number',       0,         None),
1234	(14,       'XUID',               'array',        None,      None),
1235	((12, 21), 'PostScript',         'SID',          None,      None),
1236	((12, 22), 'BaseFontName',       'SID',          None,      None),
1237	((12, 23), 'BaseFontBlend',      'delta',        None,      None),
1238	((12, 31), 'CIDFontVersion',     'number',       0,         None),
1239	((12, 32), 'CIDFontRevision',    'number',       0,         None),
1240	((12, 33), 'CIDFontType',        'number',       0,         None),
1241	((12, 34), 'CIDCount',           'number',       8720,      None),
1242	(15,       'charset',            'number',       0,         CharsetConverter()),
1243	((12, 35), 'UIDBase',            'number',       None,      None),
1244	(16,       'Encoding',           'number',       0,         EncodingConverter()),
1245	(18,       'Private',       ('number','number'), None,      PrivateDictConverter()),
1246	((12, 37), 'FDSelect',           'number',       None,      FDSelectConverter()),
1247	((12, 36), 'FDArray',            'number',       None,      FDArrayConverter()),
1248	(17,       'CharStrings',        'number',       None,      CharStringsConverter()),
1249]
1250
1251# Note! FDSelect and FDArray must both preceed CharStrings in the output XML build order,
1252# in order for the font to compile back from xml.
1253
1254
1255privateDictOperators = [
1256#	opcode     name                  argument type   default    converter
1257	(6,        'BlueValues',         'delta',        None,      None),
1258	(7,        'OtherBlues',         'delta',        None,      None),
1259	(8,        'FamilyBlues',        'delta',        None,      None),
1260	(9,        'FamilyOtherBlues',   'delta',        None,      None),
1261	((12, 9),  'BlueScale',          'number',       0.039625,  None),
1262	((12, 10), 'BlueShift',          'number',       7,         None),
1263	((12, 11), 'BlueFuzz',           'number',       1,         None),
1264	(10,       'StdHW',              'number',       None,      None),
1265	(11,       'StdVW',              'number',       None,      None),
1266	((12, 12), 'StemSnapH',          'delta',        None,      None),
1267	((12, 13), 'StemSnapV',          'delta',        None,      None),
1268	((12, 14), 'ForceBold',          'number',       0,         None),
1269	((12, 15), 'ForceBoldThreshold', 'number',       None,      None),  # deprecated
1270	((12, 16), 'lenIV',              'number',       None,      None),  # deprecated
1271	((12, 17), 'LanguageGroup',      'number',       0,         None),
1272	((12, 18), 'ExpansionFactor',    'number',       0.06,      None),
1273	((12, 19), 'initialRandomSeed',  'number',       0,         None),
1274	(20,       'defaultWidthX',      'number',       0,         None),
1275	(21,       'nominalWidthX',      'number',       0,         None),
1276	(19,       'Subrs',              'number',       None,      SubrsConverter()),
1277]
1278
1279def addConverters(table):
1280	for i in range(len(table)):
1281		op, name, arg, default, conv = table[i]
1282		if conv is not None:
1283			continue
1284		if arg in ("delta", "array"):
1285			conv = ArrayConverter()
1286		elif arg == "number":
1287			conv = NumberConverter()
1288		elif arg == "SID":
1289			conv = SimpleConverter()
1290		else:
1291			assert 0
1292		table[i] = op, name, arg, default, conv
1293
1294addConverters(privateDictOperators)
1295addConverters(topDictOperators)
1296
1297
1298class TopDictDecompiler(psCharStrings.DictDecompiler):
1299	operators = buildOperatorDict(topDictOperators)
1300
1301
1302class PrivateDictDecompiler(psCharStrings.DictDecompiler):
1303	operators = buildOperatorDict(privateDictOperators)
1304
1305
1306class DictCompiler:
1307
1308	def __init__(self, dictObj, strings, parent):
1309		assert isinstance(strings, IndexedStrings)
1310		self.dictObj = dictObj
1311		self.strings = strings
1312		self.parent = parent
1313		rawDict = {}
1314		for name in dictObj.order:
1315			value = getattr(dictObj, name, None)
1316			if value is None:
1317				continue
1318			conv = dictObj.converters[name]
1319			value = conv.write(dictObj, value)
1320			if value == dictObj.defaults.get(name):
1321				continue
1322			rawDict[name] = value
1323		self.rawDict = rawDict
1324
1325	def setPos(self, pos, endPos):
1326		pass
1327
1328	def getDataLength(self):
1329		return len(self.compile("getDataLength"))
1330
1331	def compile(self, reason):
1332		if DEBUG:
1333			print "-- compiling %s for %s" % (self.__class__.__name__, reason)
1334			print "in baseDict: ", self
1335		rawDict = self.rawDict
1336		data = []
1337		for name in self.dictObj.order:
1338			value = rawDict.get(name)
1339			if value is None:
1340				continue
1341			op, argType = self.opcodes[name]
1342			if isinstance(argType, tuple):
1343				l = len(argType)
1344				assert len(value) == l, "value doesn't match arg type"
1345				for i in range(l):
1346					arg = argType[i]
1347					v = value[i]
1348					arghandler = getattr(self, "arg_" + arg)
1349					data.append(arghandler(v))
1350			else:
1351				arghandler = getattr(self, "arg_" + argType)
1352				data.append(arghandler(value))
1353			data.append(op)
1354		return "".join(data)
1355
1356	def toFile(self, file):
1357		file.write(self.compile("toFile"))
1358
1359	def arg_number(self, num):
1360		return encodeNumber(num)
1361	def arg_SID(self, s):
1362		return psCharStrings.encodeIntCFF(self.strings.getSID(s))
1363	def arg_array(self, value):
1364		data = []
1365		for num in value:
1366			data.append(encodeNumber(num))
1367		return "".join(data)
1368	def arg_delta(self, value):
1369		out = []
1370		last = 0
1371		for v in value:
1372			out.append(v - last)
1373			last = v
1374		data = []
1375		for num in out:
1376			data.append(encodeNumber(num))
1377		return "".join(data)
1378
1379
1380def encodeNumber(num):
1381	if isinstance(num, float):
1382		return psCharStrings.encodeFloat(num)
1383	else:
1384		return psCharStrings.encodeIntCFF(num)
1385
1386
1387class TopDictCompiler(DictCompiler):
1388
1389	opcodes = buildOpcodeDict(topDictOperators)
1390
1391	def getChildren(self, strings):
1392		children = []
1393		if hasattr(self.dictObj, "charset") and self.dictObj.charset:
1394			children.append(CharsetCompiler(strings, self.dictObj.charset, self))
1395		if hasattr(self.dictObj, "Encoding"):
1396			encoding = self.dictObj.Encoding
1397			if not isinstance(encoding, basestring):
1398				children.append(EncodingCompiler(strings, encoding, self))
1399		if hasattr(self.dictObj, "FDSelect"):
1400			# I have not yet supported merging a ttx CFF-CID font, as there are interesting
1401			# issues about merging the FDArrays. Here I assume that
1402			# either the font was read from XML, and teh FDSelect indices are all
1403			# in the charstring data, or the FDSelect array is already fully defined.
1404			fdSelect = self.dictObj.FDSelect
1405			if len(fdSelect) == 0: # probably read in from XML; assume fdIndex in CharString data
1406				charStrings = self.dictObj.CharStrings
1407				for name in self.dictObj.charset:
1408					charstring = charStrings[name]
1409					fdSelect.append(charStrings[name].fdSelectIndex)
1410			fdSelectComp = FDSelectCompiler(fdSelect, self)
1411			children.append(fdSelectComp)
1412		if hasattr(self.dictObj, "CharStrings"):
1413			items = []
1414			charStrings = self.dictObj.CharStrings
1415			for name in self.dictObj.charset:
1416				items.append(charStrings[name])
1417			charStringsComp = CharStringsCompiler(items, strings, self)
1418			children.append(charStringsComp)
1419		if hasattr(self.dictObj, "FDArray"):
1420			# I have not yet supported merging a ttx CFF-CID font, as there are interesting
1421			# issues about merging the FDArrays. Here I assume that the FDArray info is correct
1422			# and complete.
1423			fdArrayIndexComp = self.dictObj.FDArray.getCompiler(strings, self)
1424			children.append(fdArrayIndexComp)
1425			children.extend(fdArrayIndexComp.getChildren(strings))
1426		if hasattr(self.dictObj, "Private"):
1427			privComp = self.dictObj.Private.getCompiler(strings, self)
1428			children.append(privComp)
1429			children.extend(privComp.getChildren(strings))
1430		return children
1431
1432
1433class FontDictCompiler(DictCompiler):
1434
1435	opcodes = buildOpcodeDict(topDictOperators)
1436
1437	def getChildren(self, strings):
1438		children = []
1439		if hasattr(self.dictObj, "Private"):
1440			privComp = self.dictObj.Private.getCompiler(strings, self)
1441			children.append(privComp)
1442			children.extend(privComp.getChildren(strings))
1443		return children
1444
1445
1446class PrivateDictCompiler(DictCompiler):
1447
1448	opcodes = buildOpcodeDict(privateDictOperators)
1449
1450	def setPos(self, pos, endPos):
1451		size = endPos - pos
1452		self.parent.rawDict["Private"] = size, pos
1453		self.pos = pos
1454
1455	def getChildren(self, strings):
1456		children = []
1457		if hasattr(self.dictObj, "Subrs"):
1458			children.append(self.dictObj.Subrs.getCompiler(strings, self))
1459		return children
1460
1461
1462class BaseDict:
1463
1464	def __init__(self, strings=None, file=None, offset=None):
1465		self.rawDict = {}
1466		if DEBUG:
1467			print "loading %s at %s" % (self.__class__.__name__, offset)
1468		self.file = file
1469		self.offset = offset
1470		self.strings = strings
1471		self.skipNames = []
1472
1473	def decompile(self, data):
1474		if DEBUG:
1475			print "    length %s is %s" % (self.__class__.__name__, len(data))
1476		dec = self.decompilerClass(self.strings)
1477		dec.decompile(data)
1478		self.rawDict = dec.getDict()
1479		self.postDecompile()
1480
1481	def postDecompile(self):
1482		pass
1483
1484	def getCompiler(self, strings, parent):
1485		return self.compilerClass(self, strings, parent)
1486
1487	def __getattr__(self, name):
1488		value = self.rawDict.get(name)
1489		if value is None:
1490			value = self.defaults.get(name)
1491		if value is None:
1492			raise AttributeError(name)
1493		conv = self.converters[name]
1494		value = conv.read(self, value)
1495		setattr(self, name, value)
1496		return value
1497
1498	def toXML(self, xmlWriter, progress):
1499		for name in self.order:
1500			if name in self.skipNames:
1501				continue
1502			value = getattr(self, name, None)
1503			if value is None:
1504				continue
1505			conv = self.converters[name]
1506			conv.xmlWrite(xmlWriter, name, value, progress)
1507
1508	def fromXML(self, name, attrs, content):
1509		conv = self.converters[name]
1510		value = conv.xmlRead(name, attrs, content, self)
1511		setattr(self, name, value)
1512
1513
1514class TopDict(BaseDict):
1515
1516	defaults = buildDefaults(topDictOperators)
1517	converters = buildConverters(topDictOperators)
1518	order = buildOrder(topDictOperators)
1519	decompilerClass = TopDictDecompiler
1520	compilerClass = TopDictCompiler
1521
1522	def __init__(self, strings=None, file=None, offset=None, GlobalSubrs=None):
1523		BaseDict.__init__(self, strings, file, offset)
1524		self.GlobalSubrs = GlobalSubrs
1525
1526	def getGlyphOrder(self):
1527		return self.charset
1528
1529	def postDecompile(self):
1530		offset = self.rawDict.get("CharStrings")
1531		if offset is None:
1532			return
1533		# get the number of glyphs beforehand.
1534		self.file.seek(offset)
1535		self.numGlyphs = readCard16(self.file)
1536
1537	def toXML(self, xmlWriter, progress):
1538		if hasattr(self, "CharStrings"):
1539			self.decompileAllCharStrings(progress)
1540		if hasattr(self, "ROS"):
1541			self.skipNames = ['Encoding']
1542		if not hasattr(self, "ROS") or not hasattr(self, "CharStrings"):
1543			# these values have default values, but I only want them to show up
1544			# in CID fonts.
1545			self.skipNames = ['CIDFontVersion', 'CIDFontRevision', 'CIDFontType',
1546					'CIDCount']
1547		BaseDict.toXML(self, xmlWriter, progress)
1548
1549	def decompileAllCharStrings(self, progress):
1550		# XXX only when doing ttdump -i?
1551		i = 0
1552		for charString in self.CharStrings.values():
1553			try:
1554				charString.decompile()
1555			except:
1556				print "Error in charstring ", i
1557				import sys
1558				type, value = sys. exc_info()[0:2]
1559				raise type(value)
1560			if not i % 30 and progress:
1561				progress.increment(0)  # update
1562			i = i + 1
1563
1564
1565class FontDict(BaseDict):
1566
1567	defaults = buildDefaults(topDictOperators)
1568	converters = buildConverters(topDictOperators)
1569	order = buildOrder(topDictOperators)
1570	decompilerClass = None
1571	compilerClass = FontDictCompiler
1572
1573	def __init__(self, strings=None, file=None, offset=None, GlobalSubrs=None):
1574		BaseDict.__init__(self, strings, file, offset)
1575		self.GlobalSubrs = GlobalSubrs
1576
1577	def getGlyphOrder(self):
1578		return self.charset
1579
1580	def toXML(self, xmlWriter, progress):
1581		self.skipNames = ['Encoding']
1582		BaseDict.toXML(self, xmlWriter, progress)
1583
1584
1585
1586class PrivateDict(BaseDict):
1587	defaults = buildDefaults(privateDictOperators)
1588	converters = buildConverters(privateDictOperators)
1589	order = buildOrder(privateDictOperators)
1590	decompilerClass = PrivateDictDecompiler
1591	compilerClass = PrivateDictCompiler
1592
1593
1594class IndexedStrings:
1595
1596	"""SID -> string mapping."""
1597
1598	def __init__(self, file=None):
1599		if file is None:
1600			strings = []
1601		else:
1602			strings = list(Index(file))
1603		self.strings = strings
1604
1605	def getCompiler(self):
1606		return IndexedStringsCompiler(self, None, None)
1607
1608	def __len__(self):
1609		return len(self.strings)
1610
1611	def __getitem__(self, SID):
1612		if SID < cffStandardStringCount:
1613			return cffStandardStrings[SID]
1614		else:
1615			return self.strings[SID - cffStandardStringCount]
1616
1617	def getSID(self, s):
1618		if not hasattr(self, "stringMapping"):
1619			self.buildStringMapping()
1620		if s in cffStandardStringMapping:
1621			SID = cffStandardStringMapping[s]
1622		elif s in self.stringMapping:
1623			SID = self.stringMapping[s]
1624		else:
1625			SID = len(self.strings) + cffStandardStringCount
1626			self.strings.append(s)
1627			self.stringMapping[s] = SID
1628		return SID
1629
1630	def getStrings(self):
1631		return self.strings
1632
1633	def buildStringMapping(self):
1634		self.stringMapping = {}
1635		for index in range(len(self.strings)):
1636			self.stringMapping[self.strings[index]] = index + cffStandardStringCount
1637
1638
1639# The 391 Standard Strings as used in the CFF format.
1640# from Adobe Technical None #5176, version 1.0, 18 March 1998
1641
1642cffStandardStrings = ['.notdef', 'space', 'exclam', 'quotedbl', 'numbersign',
1643		'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright',
1644		'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one',
1645		'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon',
1646		'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C',
1647		'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
1648		'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash',
1649		'bracketright', 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c',
1650		'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
1651		's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright',
1652		'asciitilde', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin',
1653		'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft',
1654		'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger',
1655		'daggerdbl', 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase',
1656		'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand',
1657		'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve',
1658		'dotaccent', 'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron',
1659		'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae',
1660		'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior',
1661		'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn',
1662		'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters',
1663		'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior',
1664		'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring',
1665		'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave',
1666		'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute',
1667		'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute',
1668		'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron',
1669		'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla',
1670		'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex',
1671		'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis',
1672		'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave',
1673		'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall',
1674		'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall',
1675		'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader',
1676		'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle',
1677		'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle',
1678		'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior',
1679		'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior',
1680		'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior',
1681		'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', 'parenleftinferior',
1682		'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall',
1683		'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall',
1684		'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall',
1685		'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall',
1686		'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall',
1687		'exclamdownsmall', 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall',
1688		'Dieresissmall', 'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall',
1689		'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall',
1690		'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths',
1691		'onethird', 'twothirds', 'zerosuperior', 'foursuperior', 'fivesuperior',
1692		'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior',
1693		'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior',
1694		'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior',
1695		'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall',
1696		'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall',
1697		'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall',
1698		'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall',
1699		'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall',
1700		'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall',
1701		'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall',
1702		'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', '001.001', '001.002',
1703		'001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman',
1704		'Semibold'
1705]
1706
1707cffStandardStringCount = 391
1708assert len(cffStandardStrings) == cffStandardStringCount
1709# build reverse mapping
1710cffStandardStringMapping = {}
1711for _i in range(cffStandardStringCount):
1712	cffStandardStringMapping[cffStandardStrings[_i]] = _i
1713
1714cffISOAdobeStrings = [".notdef", "space", "exclam", "quotedbl", "numbersign",
1715"dollar", "percent", "ampersand", "quoteright", "parenleft", "parenright",
1716"asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two",
1717"three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon",
1718"less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G",
1719"H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W",
1720"X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum",
1721"underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j",
1722"k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
1723"braceleft", "bar", "braceright", "asciitilde", "exclamdown", "cent",
1724"sterling", "fraction", "yen", "florin", "section", "currency", "quotesingle",
1725"quotedblleft", "guillemotleft", "guilsinglleft", "guilsinglright", "fi", "fl",
1726"endash", "dagger", "daggerdbl", "periodcentered", "paragraph", "bullet",
1727"quotesinglbase", "quotedblbase", "quotedblright", "guillemotright", "ellipsis",
1728"perthousand", "questiondown", "grave", "acute", "circumflex", "tilde",
1729"macron", "breve", "dotaccent", "dieresis", "ring", "cedilla", "hungarumlaut",
1730"ogonek", "caron", "emdash", "AE", "ordfeminine", "Lslash", "Oslash", "OE",
1731"ordmasculine", "ae", "dotlessi", "lslash", "oslash", "oe", "germandbls",
1732"onesuperior", "logicalnot", "mu", "trademark", "Eth", "onehalf", "plusminus",
1733"Thorn", "onequarter", "divide", "brokenbar", "degree", "thorn",
1734"threequarters", "twosuperior", "registered", "minus", "eth", "multiply",
1735"threesuperior", "copyright", "Aacute", "Acircumflex", "Adieresis", "Agrave",
1736"Aring", "Atilde", "Ccedilla", "Eacute", "Ecircumflex", "Edieresis", "Egrave",
1737"Iacute", "Icircumflex", "Idieresis", "Igrave", "Ntilde", "Oacute",
1738"Ocircumflex", "Odieresis", "Ograve", "Otilde", "Scaron", "Uacute",
1739"Ucircumflex", "Udieresis", "Ugrave", "Yacute", "Ydieresis", "Zcaron", "aacute",
1740"acircumflex", "adieresis", "agrave", "aring", "atilde", "ccedilla", "eacute",
1741"ecircumflex", "edieresis", "egrave", "iacute", "icircumflex", "idieresis",
1742"igrave", "ntilde", "oacute", "ocircumflex", "odieresis", "ograve", "otilde",
1743"scaron", "uacute", "ucircumflex", "udieresis", "ugrave", "yacute", "ydieresis",
1744"zcaron"]
1745
1746cffISOAdobeStringCount = 229
1747assert len(cffISOAdobeStrings) == cffISOAdobeStringCount
1748
1749cffIExpertStrings = [".notdef", "space", "exclamsmall", "Hungarumlautsmall",
1750"dollaroldstyle", "dollarsuperior", "ampersandsmall", "Acutesmall",
1751"parenleftsuperior", "parenrightsuperior", "twodotenleader", "onedotenleader",
1752"comma", "hyphen", "period", "fraction", "zerooldstyle", "oneoldstyle",
1753"twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle",
1754"sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", "semicolon",
1755"commasuperior", "threequartersemdash", "periodsuperior", "questionsmall",
1756"asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior",
1757"lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior",
1758"tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior",
1759"parenrightinferior", "Circumflexsmall", "hyphensuperior", "Gravesmall",
1760"Asmall", "Bsmall", "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall",
1761"Ismall", "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall",
1762"Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall", "Xsmall",
1763"Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah", "Tildesmall",
1764"exclamdownsmall", "centoldstyle", "Lslashsmall", "Scaronsmall", "Zcaronsmall",
1765"Dieresissmall", "Brevesmall", "Caronsmall", "Dotaccentsmall", "Macronsmall",
1766"figuredash", "hypheninferior", "Ogoneksmall", "Ringsmall", "Cedillasmall",
1767"onequarter", "onehalf", "threequarters", "questiondownsmall", "oneeighth",
1768"threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds",
1769"zerosuperior", "onesuperior", "twosuperior", "threesuperior", "foursuperior",
1770"fivesuperior", "sixsuperior", "sevensuperior", "eightsuperior", "ninesuperior",
1771"zeroinferior", "oneinferior", "twoinferior", "threeinferior", "fourinferior",
1772"fiveinferior", "sixinferior", "seveninferior", "eightinferior", "nineinferior",
1773"centinferior", "dollarinferior", "periodinferior", "commainferior",
1774"Agravesmall", "Aacutesmall", "Acircumflexsmall", "Atildesmall",
1775"Adieresissmall", "Aringsmall", "AEsmall", "Ccedillasmall", "Egravesmall",
1776"Eacutesmall", "Ecircumflexsmall", "Edieresissmall", "Igravesmall",
1777"Iacutesmall", "Icircumflexsmall", "Idieresissmall", "Ethsmall", "Ntildesmall",
1778"Ogravesmall", "Oacutesmall", "Ocircumflexsmall", "Otildesmall",
1779"Odieresissmall", "OEsmall", "Oslashsmall", "Ugravesmall", "Uacutesmall",
1780"Ucircumflexsmall", "Udieresissmall", "Yacutesmall", "Thornsmall",
1781"Ydieresissmall"]
1782
1783cffExpertStringCount = 166
1784assert len(cffIExpertStrings) == cffExpertStringCount
1785
1786cffExpertSubsetStrings = [".notdef", "space", "dollaroldstyle",
1787"dollarsuperior", "parenleftsuperior", "parenrightsuperior", "twodotenleader",
1788"onedotenleader", "comma", "hyphen", "period", "fraction", "zerooldstyle",
1789"oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle",
1790"sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon",
1791"semicolon", "commasuperior", "threequartersemdash", "periodsuperior",
1792"asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior",
1793"lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior",
1794"tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior",
1795"parenrightinferior", "hyphensuperior", "colonmonetary", "onefitted", "rupiah",
1796"centoldstyle", "figuredash", "hypheninferior", "onequarter", "onehalf",
1797"threequarters", "oneeighth", "threeeighths", "fiveeighths", "seveneighths",
1798"onethird", "twothirds", "zerosuperior", "onesuperior", "twosuperior",
1799"threesuperior", "foursuperior", "fivesuperior", "sixsuperior", "sevensuperior",
1800"eightsuperior", "ninesuperior", "zeroinferior", "oneinferior", "twoinferior",
1801"threeinferior", "fourinferior", "fiveinferior", "sixinferior", "seveninferior",
1802"eightinferior", "nineinferior", "centinferior", "dollarinferior",
1803"periodinferior", "commainferior"]
1804
1805cffExpertSubsetStringCount = 87
1806assert len(cffExpertSubsetStrings) == cffExpertSubsetStringCount
1807