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