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