cffLib.py revision 14fb031125b773f0a15eb19be4f02ed8540b2db6
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
12DEBUG = 0
13
14
15cffHeaderFormat = """
16	major:   B
17	minor:   B
18	hdrSize: B
19	offSize: B
20"""
21
22class CFFFontSet:
23
24	def __init__(self):
25		pass
26
27	def decompile(self, file, otFont):
28		sstruct.unpack(cffHeaderFormat, file.read(4), self)
29		assert self.major == 1 and self.minor == 0, \
30				"unknown CFF format: %d.%d" % (self.major, self.minor)
31
32		file.seek(self.hdrSize)
33		self.fontNames = list(Index(file))
34		self.topDictIndex = TopDictIndex(file)
35		self.strings = IndexedStrings(file)
36		self.GlobalSubrs = GlobalSubrsIndex(file)
37		self.topDictIndex.strings = self.strings
38		self.topDictIndex.GlobalSubrs = self.GlobalSubrs
39
40	def __len__(self):
41		return len(self.fontNames)
42
43	def keys(self):
44		return list(self.fontNames)
45
46	def values(self):
47		return self.topDictIndex
48
49	def __getitem__(self, name):
50		try:
51			index = self.fontNames.index(name)
52		except ValueError:
53			raise KeyError(name)
54		return self.topDictIndex[index]
55
56	def compile(self, file, otFont):
57		strings = IndexedStrings()
58		writer = CFFWriter()
59		writer.add(sstruct.pack(cffHeaderFormat, self))
60		fontNames = Index()
61		for name in self.fontNames:
62			fontNames.append(name)
63		writer.add(fontNames.getCompiler(strings, None))
64		topCompiler = self.topDictIndex.getCompiler(strings, None)
65		writer.add(topCompiler)
66		writer.add(strings.getCompiler())
67		writer.add(self.GlobalSubrs.getCompiler(strings, None))
68
69		for topDict in self.topDictIndex:
70			if not hasattr(topDict, "charset") or topDict.charset is None:
71				charset = otFont.getGlyphOrder()
72				topDict.charset = charset
73
74		for child in topCompiler.getChildren(strings):
75			writer.add(child)
76
77		writer.toFile(file)
78
79	def toXML(self, xmlWriter, progress=None):
80		xmlWriter.newline()
81		for fontName in self.fontNames:
82			xmlWriter.begintag("CFFFont", name=fontName)
83			xmlWriter.newline()
84			font = self[fontName]
85			font.toXML(xmlWriter, progress)
86			xmlWriter.endtag("CFFFont")
87			xmlWriter.newline()
88		xmlWriter.newline()
89		xmlWriter.begintag("GlobalSubrs")
90		xmlWriter.newline()
91		self.GlobalSubrs.toXML(xmlWriter, progress)
92		xmlWriter.endtag("GlobalSubrs")
93		xmlWriter.newline()
94		xmlWriter.newline()
95
96	def fromXML(self, name, attrs, content):
97		if not hasattr(self, "GlobalSubrs"):
98			self.GlobalSubrs = GlobalSubrsIndex()
99			self.major = 1
100			self.minor = 0
101			self.hdrSize = 4
102			self.offSize = 4  # XXX ??
103		if name == "CFFFont":
104			if not hasattr(self, "fontNames"):
105				self.fontNames = []
106				self.topDictIndex = TopDictIndex()
107			fontName = attrs["name"]
108			topDict = TopDict(GlobalSubrs=self.GlobalSubrs)
109			topDict.charset = None  # gets filled in later
110			self.fontNames.append(fontName)
111			self.topDictIndex.append(topDict)
112			for element in content:
113				if isinstance(element, basestring):
114					continue
115				name, attrs, content = element
116				topDict.fromXML(name, attrs, content)
117		elif name == "GlobalSubrs":
118			for element in content:
119				if isinstance(element, basestring):
120					continue
121				name, attrs, content = element
122				subr = psCharStrings.T2CharString()
123				subr.fromXML(name, attrs, content)
124				self.GlobalSubrs.append(subr)
125
126
127class CFFWriter:
128
129	def __init__(self):
130		self.data = []
131
132	def add(self, table):
133		self.data.append(table)
134
135	def toFile(self, file):
136		lastPosList = None
137		count = 1
138		while True:
139			if DEBUG:
140				print("CFFWriter.toFile() iteration:", count)
141			count = count + 1
142			pos = 0
143			posList = [pos]
144			for item in self.data:
145				if hasattr(item, "getDataLength"):
146					endPos = pos + item.getDataLength()
147				else:
148					endPos = pos + len(item)
149				if hasattr(item, "setPos"):
150					item.setPos(pos, endPos)
151				pos = endPos
152				posList.append(pos)
153			if posList == lastPosList:
154				break
155			lastPosList = posList
156		if DEBUG:
157			print("CFFWriter.toFile() writing to file.")
158		begin = file.tell()
159		posList = [0]
160		for item in self.data:
161			if hasattr(item, "toFile"):
162				item.toFile(file)
163			else:
164				file.write(item)
165			posList.append(file.tell() - begin)
166		assert posList == lastPosList
167
168
169def calcOffSize(largestOffset):
170	if largestOffset < 0x100:
171		offSize = 1
172	elif largestOffset < 0x10000:
173		offSize = 2
174	elif largestOffset < 0x1000000:
175		offSize = 3
176	else:
177		offSize = 4
178	return offSize
179
180
181class IndexCompiler:
182
183	def __init__(self, items, strings, parent):
184		self.items = self.getItems(items, strings)
185		self.parent = parent
186
187	def getItems(self, items, strings):
188		return items
189
190	def getOffsets(self):
191		pos = 1
192		offsets = [pos]
193		for item in self.items:
194			if hasattr(item, "getDataLength"):
195				pos = pos + item.getDataLength()
196			else:
197				pos = pos + len(item)
198			offsets.append(pos)
199		return offsets
200
201	def getDataLength(self):
202		lastOffset = self.getOffsets()[-1]
203		offSize = calcOffSize(lastOffset)
204		dataLength = (
205			2 +                                # count
206			1 +                                # offSize
207			(len(self.items) + 1) * offSize +  # the offsets
208			lastOffset - 1                     # size of object data
209		)
210		return dataLength
211
212	def toFile(self, file):
213		offsets = self.getOffsets()
214		writeCard16(file, len(self.items))
215		offSize = calcOffSize(offsets[-1])
216		writeCard8(file, offSize)
217		offSize = -offSize
218		pack = struct.pack
219		for offset in offsets:
220			binOffset = pack(">l", offset)[offSize:]
221			assert len(binOffset) == -offSize
222			file.write(binOffset)
223		for item in self.items:
224			if hasattr(item, "toFile"):
225				item.toFile(file)
226			else:
227				file.write(item)
228
229
230class IndexedStringsCompiler(IndexCompiler):
231
232	def getItems(self, items, strings):
233		return items.strings
234
235
236class TopDictIndexCompiler(IndexCompiler):
237
238	def getItems(self, items, strings):
239		out = []
240		for item in items:
241			out.append(item.getCompiler(strings, self))
242		return out
243
244	def getChildren(self, strings):
245		children = []
246		for topDict in self.items:
247			children.extend(topDict.getChildren(strings))
248		return children
249
250
251class FDArrayIndexCompiler(IndexCompiler):
252
253	def getItems(self, items, strings):
254		out = []
255		for item in items:
256			out.append(item.getCompiler(strings, self))
257		return out
258
259	def getChildren(self, strings):
260		children = []
261		for fontDict in self.items:
262			children.extend(fontDict.getChildren(strings))
263		return children
264
265	def toFile(self, file):
266		offsets = self.getOffsets()
267		writeCard16(file, len(self.items))
268		offSize = calcOffSize(offsets[-1])
269		writeCard8(file, offSize)
270		offSize = -offSize
271		pack = struct.pack
272		for offset in offsets:
273			binOffset = pack(">l", offset)[offSize:]
274			assert len(binOffset) == -offSize
275			file.write(binOffset)
276		for item in self.items:
277			if hasattr(item, "toFile"):
278				item.toFile(file)
279			else:
280				file.write(item)
281
282	def setPos(self, pos, endPos):
283		self.parent.rawDict["FDArray"] = pos
284
285
286class GlobalSubrsCompiler(IndexCompiler):
287	def getItems(self, items, strings):
288		out = []
289		for cs in items:
290			cs.compile()
291			out.append(cs.bytecode)
292		return out
293
294class SubrsCompiler(GlobalSubrsCompiler):
295	def setPos(self, pos, endPos):
296		offset = pos - self.parent.pos
297		self.parent.rawDict["Subrs"] = offset
298
299class CharStringsCompiler(GlobalSubrsCompiler):
300	def setPos(self, pos, endPos):
301		self.parent.rawDict["CharStrings"] = pos
302
303
304class Index:
305
306	"""This class represents what the CFF spec calls an INDEX."""
307
308	compilerClass = IndexCompiler
309
310	def __init__(self, file=None):
311		name = self.__class__.__name__
312		if file is None:
313			self.items = []
314			return
315		if DEBUG:
316			print("loading %s at %s" % (name, file.tell()))
317		self.file = file
318		count = readCard16(file)
319		self.count = count
320		self.items = [None] * count
321		if count == 0:
322			self.items = []
323			return
324		offSize = readCard8(file)
325		if DEBUG:
326			print("    index count: %s offSize: %s" % (count, offSize))
327		assert offSize <= 4, "offSize too large: %s" % offSize
328		self.offsets = offsets = []
329		pad = '\0' * (4 - offSize)
330		for index in range(count+1):
331			chunk = file.read(offSize)
332			chunk = pad + chunk
333			offset, = struct.unpack(">L", chunk)
334			offsets.append(int(offset))
335		self.offsetBase = file.tell() - 1
336		file.seek(self.offsetBase + offsets[-1])  # pretend we've read the whole lot
337		if DEBUG:
338			print("    end of %s at %s" % (name, file.tell()))
339
340	def __len__(self):
341		return len(self.items)
342
343	def __getitem__(self, index):
344		item = self.items[index]
345		if item is not None:
346			return item
347		offset = self.offsets[index] + self.offsetBase
348		size = self.offsets[index+1] - self.offsets[index]
349		file = self.file
350		file.seek(offset)
351		data = file.read(size)
352		assert len(data) == size
353		item = self.produceItem(index, data, file, offset, size)
354		self.items[index] = item
355		return item
356
357	def produceItem(self, index, data, file, offset, size):
358		return data
359
360	def append(self, item):
361		self.items.append(item)
362
363	def getCompiler(self, strings, parent):
364		return self.compilerClass(self, strings, parent)
365
366
367class GlobalSubrsIndex(Index):
368
369	compilerClass = GlobalSubrsCompiler
370
371	def __init__(self, file=None, globalSubrs=None, private=None, fdSelect=None, fdArray=None):
372		Index.__init__(self, file)
373		self.globalSubrs = globalSubrs
374		self.private = private
375		if fdSelect:
376			self.fdSelect = fdSelect
377		if fdArray:
378			self.fdArray = fdArray
379
380	def produceItem(self, index, data, file, offset, size):
381		if self.private is not None:
382			private = self.private
383		elif hasattr(self, 'fdArray') and self.fdArray is not None:
384			private = self.fdArray[self.fdSelect[index]].Private
385		else:
386			private = None
387		return psCharStrings.T2CharString(data, private=private, globalSubrs=self.globalSubrs)
388
389	def toXML(self, xmlWriter, progress):
390		xmlWriter.comment("The 'index' attribute is only for humans; it is ignored when parsed.")
391		xmlWriter.newline()
392		for i in range(len(self)):
393			subr = self[i]
394			if subr.needsDecompilation():
395				xmlWriter.begintag("CharString", index=i, raw=1)
396			else:
397				xmlWriter.begintag("CharString", index=i)
398			xmlWriter.newline()
399			subr.toXML(xmlWriter)
400			xmlWriter.endtag("CharString")
401			xmlWriter.newline()
402
403	def fromXML(self, name, attrs, content):
404		if name != "CharString":
405			return
406		subr = psCharStrings.T2CharString()
407		subr.fromXML(name, attrs, content)
408		self.append(subr)
409
410	def getItemAndSelector(self, index):
411		sel = None
412		if hasattr(self, 'fdSelect'):
413			sel = self.fdSelect[index]
414		return self[index], sel
415
416
417class SubrsIndex(GlobalSubrsIndex):
418	compilerClass = SubrsCompiler
419
420
421class TopDictIndex(Index):
422
423	compilerClass = TopDictIndexCompiler
424
425	def produceItem(self, index, data, file, offset, size):
426		top = TopDict(self.strings, file, offset, self.GlobalSubrs)
427		top.decompile(data)
428		return top
429
430	def toXML(self, xmlWriter, progress):
431		for i in range(len(self)):
432			xmlWriter.begintag("FontDict", index=i)
433			xmlWriter.newline()
434			self[i].toXML(xmlWriter, progress)
435			xmlWriter.endtag("FontDict")
436			xmlWriter.newline()
437
438
439class FDArrayIndex(TopDictIndex):
440
441	compilerClass = FDArrayIndexCompiler
442
443	def fromXML(self, name, attrs, content):
444		if name != "FontDict":
445			return
446		fontDict = FontDict()
447		for element in content:
448			if isinstance(element, basestring):
449				continue
450			name, attrs, content = element
451			fontDict.fromXML(name, attrs, content)
452		self.append(fontDict)
453
454
455class	FDSelect:
456	def __init__(self, file = None, numGlyphs = None, format=None):
457		if file:
458			# read data in from file
459			self.format = readCard8(file)
460			if self.format == 0:
461				from array import array
462				self.gidArray = array("B", file.read(numGlyphs)).tolist()
463			elif self.format == 3:
464				gidArray = [None] * numGlyphs
465				nRanges = readCard16(file)
466				prev = None
467				for i in range(nRanges):
468					first = readCard16(file)
469					if prev is not None:
470						for glyphID in range(prev, first):
471							gidArray[glyphID] = fd
472					prev = first
473					fd = readCard8(file)
474				if prev is not None:
475					first = readCard16(file)
476					for glyphID in range(prev, first):
477						gidArray[glyphID] = fd
478				self.gidArray = gidArray
479			else:
480				assert 0, "unsupported FDSelect format: %s" % format
481		else:
482			# reading from XML. Make empty gidArray,, and leave format as passed in.
483			# format == None will result in the smallest representation being used.
484			self.format = format
485			self.gidArray = []
486
487
488	def __len__(self):
489		return len(self.gidArray)
490
491	def __getitem__(self, index):
492		return self.gidArray[index]
493
494	def __setitem__(self, index, fdSelectValue):
495		self.gidArray[index] = fdSelectValue
496
497	def append(self, fdSelectValue):
498		self.gidArray.append(fdSelectValue)
499
500
501class CharStrings:
502
503	def __init__(self, file, charset, globalSubrs, private, fdSelect, fdArray):
504		if file is not None:
505			self.charStringsIndex = SubrsIndex(file, globalSubrs, private, fdSelect, fdArray)
506			self.charStrings = charStrings = {}
507			for i in range(len(charset)):
508				charStrings[charset[i]] = i
509			self.charStringsAreIndexed = 1
510		else:
511			self.charStrings = {}
512			self.charStringsAreIndexed = 0
513			self.globalSubrs = globalSubrs
514			self.private = private
515			if fdSelect != None:
516				self.fdSelect = fdSelect
517			if fdArray!= None:
518				self.fdArray = fdArray
519
520	def keys(self):
521		return self.charStrings.keys()
522
523	def values(self):
524		if self.charStringsAreIndexed:
525			return self.charStringsIndex
526		else:
527			return self.charStrings.values()
528
529	def has_key(self, name):
530		return name in self.charStrings
531
532	def __len__(self):
533		return len(self.charStrings)
534
535	def __getitem__(self, name):
536		charString = self.charStrings[name]
537		if self.charStringsAreIndexed:
538			charString = self.charStringsIndex[charString]
539		return charString
540
541	def __setitem__(self, name, charString):
542		if self.charStringsAreIndexed:
543			index = self.charStrings[name]
544			self.charStringsIndex[index] = charString
545		else:
546			self.charStrings[name] = charString
547
548	def getItemAndSelector(self, name):
549		if self.charStringsAreIndexed:
550			index = self.charStrings[name]
551			return self.charStringsIndex.getItemAndSelector(index)
552		else:
553			if hasattr(self, 'fdSelect'):
554				sel = self.fdSelect[index]  # index is not defined at this point. Read R. ?
555			else:
556				raise KeyError("fdSelect array not yet defined.")
557			return self.charStrings[name], sel
558
559	def toXML(self, xmlWriter, progress):
560		names = sorted(self.keys())
561		i = 0
562		step = 10
563		numGlyphs = len(names)
564		for name in names:
565			charStr, fdSelectIndex = self.getItemAndSelector(name)
566			if charStr.needsDecompilation():
567				raw = [("raw", 1)]
568			else:
569				raw = []
570			if fdSelectIndex is None:
571				xmlWriter.begintag("CharString", [('name', name)] + raw)
572			else:
573				xmlWriter.begintag("CharString",
574						[('name', name), ('fdSelectIndex', fdSelectIndex)] + raw)
575			xmlWriter.newline()
576			charStr.toXML(xmlWriter)
577			xmlWriter.endtag("CharString")
578			xmlWriter.newline()
579			if not i % step and progress is not None:
580				progress.setLabel("Dumping 'CFF ' table... (%s)" % name)
581				progress.increment(step / float(numGlyphs))
582			i = i + 1
583
584	def fromXML(self, name, attrs, content):
585		for element in content:
586			if isinstance(element, basestring):
587				continue
588			name, attrs, content = element
589			if name != "CharString":
590				continue
591			fdID = -1
592			if hasattr(self, "fdArray"):
593				fdID = safeEval(attrs["fdSelectIndex"])
594				private = self.fdArray[fdID].Private
595			else:
596				private = self.private
597
598			glyphName = attrs["name"]
599			charString = psCharStrings.T2CharString(
600					private=private,
601					globalSubrs=self.globalSubrs)
602			charString.fromXML(name, attrs, content)
603			if fdID >= 0:
604				charString.fdSelectIndex = fdID
605			self[glyphName] = charString
606
607
608def readCard8(file):
609	return ord(file.read(1))
610
611def readCard16(file):
612	value, = struct.unpack(">H", file.read(2))
613	return value
614
615def writeCard8(file, value):
616	file.write(chr(value))
617
618def writeCard16(file, value):
619	file.write(struct.pack(">H", value))
620
621def packCard8(value):
622	return chr(value)
623
624def packCard16(value):
625	return struct.pack(">H", value)
626
627def buildOperatorDict(table):
628	d = {}
629	for op, name, arg, default, conv in table:
630		d[op] = (name, arg)
631	return d
632
633def buildOpcodeDict(table):
634	d = {}
635	for op, name, arg, default, conv in table:
636		if isinstance(op, tuple):
637			op = chr(op[0]) + chr(op[1])
638		else:
639			op = chr(op)
640		d[name] = (op, arg)
641	return d
642
643def buildOrder(table):
644	l = []
645	for op, name, arg, default, conv in table:
646		l.append(name)
647	return l
648
649def buildDefaults(table):
650	d = {}
651	for op, name, arg, default, conv in table:
652		if default is not None:
653			d[name] = default
654	return d
655
656def buildConverters(table):
657	d = {}
658	for op, name, arg, default, conv in table:
659		d[name] = conv
660	return d
661
662
663class SimpleConverter:
664	def read(self, parent, value):
665		return value
666	def write(self, parent, value):
667		return value
668	def xmlWrite(self, xmlWriter, name, value, progress):
669		xmlWriter.simpletag(name, value=value)
670		xmlWriter.newline()
671	def xmlRead(self, name, attrs, content, parent):
672		return attrs["value"]
673
674class Latin1Converter(SimpleConverter):
675	def xmlWrite(self, xmlWriter, name, value, progress):
676		# Store as UTF-8
677		value = unicode(value, "latin-1").encode("utf-8")
678		xmlWriter.simpletag(name, value=value)
679		xmlWriter.newline()
680	def xmlRead(self, name, attrs, content, parent):
681		s = unicode(attrs["value"], "utf-8")
682		return s.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