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