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