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