cffLib.py revision 617ec41d4c2db2011e62cadf619e2a8b07aec93e
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 False, "unsupported FDSelect format: %s" % format
483		else:
484			# reading from XML. Make empty gidArray,, and leave format as passed in.
485			# format is 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 is not None:
518				self.fdSelect = fdSelect
519			if fdArray is not 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		assert 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	fmt = 0
861	data = [packCard8(fmt)]
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	fmt = 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				fmt = 2
890			ranges.append((first, nLeft))
891			first = SID
892		end = SID
893	nLeft = end - first
894	if nLeft > 255:
895		fmt = 2
896	ranges.append((first, nLeft))
897
898	data = [packCard8(fmt)]
899	if fmt == 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, fmt):
920	charset = ['.notdef']
921	count = 1
922	if fmt == 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			fmt = readCard8(file)
975			haveSupplement = fmt & 0x80
976			if haveSupplement:
977				raise NotImplementedError("Encoding supplements are not yet supported")
978			fmt = fmt & 0x7f
979			if fmt == 0:
980				encoding = parseEncoding0(parent.charset, file, haveSupplement,
981						parent.strings)
982			elif fmt == 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	fmt = 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(fmt), 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	fmt = 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(fmt), 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		fmt = safeEval(attrs["format"])
1143		file = None
1144		numGlyphs = None
1145		fdSelect = FDSelect(file, numGlyphs, fmt)
1146		return fdSelect
1147
1148
1149def packFDSelect0(fdSelectArray):
1150	fmt = 0
1151	data = [packCard8(fmt)]
1152	for index in fdSelectArray:
1153		data.append(packCard8(index))
1154	return bytesjoin(data)
1155
1156
1157def packFDSelect3(fdSelectArray):
1158	fmt = 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(fmt)]
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		fmt = fdSelect.format
1184		fdSelectArray = fdSelect.gidArray
1185		if fmt == 0:
1186			self.data = packFDSelect0(fdSelectArray)
1187		elif fmt == 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 False
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					fdSelect.append(charStrings[name].fdSelectIndex)
1421			fdSelectComp = FDSelectCompiler(fdSelect, self)
1422			children.append(fdSelectComp)
1423		if hasattr(self.dictObj, "CharStrings"):
1424			items = []
1425			charStrings = self.dictObj.CharStrings
1426			for name in self.dictObj.charset:
1427				items.append(charStrings[name])
1428			charStringsComp = CharStringsCompiler(items, strings, self)
1429			children.append(charStringsComp)
1430		if hasattr(self.dictObj, "FDArray"):
1431			# I have not yet supported merging a ttx CFF-CID font, as there are interesting
1432			# issues about merging the FDArrays. Here I assume that the FDArray info is correct
1433			# and complete.
1434			fdArrayIndexComp = self.dictObj.FDArray.getCompiler(strings, self)
1435			children.append(fdArrayIndexComp)
1436			children.extend(fdArrayIndexComp.getChildren(strings))
1437		if hasattr(self.dictObj, "Private"):
1438			privComp = self.dictObj.Private.getCompiler(strings, self)
1439			children.append(privComp)
1440			children.extend(privComp.getChildren(strings))
1441		return children
1442
1443
1444class FontDictCompiler(DictCompiler):
1445
1446	opcodes = buildOpcodeDict(topDictOperators)
1447
1448	def getChildren(self, strings):
1449		children = []
1450		if hasattr(self.dictObj, "Private"):
1451			privComp = self.dictObj.Private.getCompiler(strings, self)
1452			children.append(privComp)
1453			children.extend(privComp.getChildren(strings))
1454		return children
1455
1456
1457class PrivateDictCompiler(DictCompiler):
1458
1459	opcodes = buildOpcodeDict(privateDictOperators)
1460
1461	def setPos(self, pos, endPos):
1462		size = endPos - pos
1463		self.parent.rawDict["Private"] = size, pos
1464		self.pos = pos
1465
1466	def getChildren(self, strings):
1467		children = []
1468		if hasattr(self.dictObj, "Subrs"):
1469			children.append(self.dictObj.Subrs.getCompiler(strings, self))
1470		return children
1471
1472
1473class BaseDict(object):
1474
1475	def __init__(self, strings=None, file=None, offset=None):
1476		self.rawDict = {}
1477		if DEBUG:
1478			print("loading %s at %s" % (self.__class__.__name__, offset))
1479		self.file = file
1480		self.offset = offset
1481		self.strings = strings
1482		self.skipNames = []
1483
1484	def decompile(self, data):
1485		if DEBUG:
1486			print("    length %s is %s" % (self.__class__.__name__, len(data)))
1487		dec = self.decompilerClass(self.strings)
1488		dec.decompile(data)
1489		self.rawDict = dec.getDict()
1490		self.postDecompile()
1491
1492	def postDecompile(self):
1493		pass
1494
1495	def getCompiler(self, strings, parent):
1496		return self.compilerClass(self, strings, parent)
1497
1498	def __getattr__(self, name):
1499		value = self.rawDict.get(name)
1500		if value is None:
1501			value = self.defaults.get(name)
1502		if value is None:
1503			raise AttributeError(name)
1504		conv = self.converters[name]
1505		value = conv.read(self, value)
1506		setattr(self, name, value)
1507		return value
1508
1509	def toXML(self, xmlWriter, progress):
1510		for name in self.order:
1511			if name in self.skipNames:
1512				continue
1513			value = getattr(self, name, None)
1514			if value is None:
1515				continue
1516			conv = self.converters[name]
1517			conv.xmlWrite(xmlWriter, name, value, progress)
1518
1519	def fromXML(self, name, attrs, content):
1520		conv = self.converters[name]
1521		value = conv.xmlRead(name, attrs, content, self)
1522		setattr(self, name, value)
1523
1524
1525class TopDict(BaseDict):
1526
1527	defaults = buildDefaults(topDictOperators)
1528	converters = buildConverters(topDictOperators)
1529	order = buildOrder(topDictOperators)
1530	decompilerClass = TopDictDecompiler
1531	compilerClass = TopDictCompiler
1532
1533	def __init__(self, strings=None, file=None, offset=None, GlobalSubrs=None):
1534		BaseDict.__init__(self, strings, file, offset)
1535		self.GlobalSubrs = GlobalSubrs
1536
1537	def getGlyphOrder(self):
1538		return self.charset
1539
1540	def postDecompile(self):
1541		offset = self.rawDict.get("CharStrings")
1542		if offset is None:
1543			return
1544		# get the number of glyphs beforehand.
1545		self.file.seek(offset)
1546		self.numGlyphs = readCard16(self.file)
1547
1548	def toXML(self, xmlWriter, progress):
1549		if hasattr(self, "CharStrings"):
1550			self.decompileAllCharStrings(progress)
1551		if hasattr(self, "ROS"):
1552			self.skipNames = ['Encoding']
1553		if not hasattr(self, "ROS") or not hasattr(self, "CharStrings"):
1554			# these values have default values, but I only want them to show up
1555			# in CID fonts.
1556			self.skipNames = ['CIDFontVersion', 'CIDFontRevision', 'CIDFontType',
1557					'CIDCount']
1558		BaseDict.toXML(self, xmlWriter, progress)
1559
1560	def decompileAllCharStrings(self, progress):
1561		# XXX only when doing ttdump -i?
1562		i = 0
1563		for charString in self.CharStrings.values():
1564			try:
1565				charString.decompile()
1566			except:
1567				print("Error in charstring ", i)
1568				import sys
1569				typ, value = sys.exc_info()[0:2]
1570				raise typ(value)
1571			if not i % 30 and progress:
1572				progress.increment(0)  # update
1573			i = i + 1
1574
1575
1576class FontDict(BaseDict):
1577
1578	defaults = buildDefaults(topDictOperators)
1579	converters = buildConverters(topDictOperators)
1580	order = buildOrder(topDictOperators)
1581	decompilerClass = None
1582	compilerClass = FontDictCompiler
1583
1584	def __init__(self, strings=None, file=None, offset=None, GlobalSubrs=None):
1585		BaseDict.__init__(self, strings, file, offset)
1586		self.GlobalSubrs = GlobalSubrs
1587
1588	def getGlyphOrder(self):
1589		return self.charset
1590
1591	def toXML(self, xmlWriter, progress):
1592		self.skipNames = ['Encoding']
1593		BaseDict.toXML(self, xmlWriter, progress)
1594
1595
1596
1597class PrivateDict(BaseDict):
1598	defaults = buildDefaults(privateDictOperators)
1599	converters = buildConverters(privateDictOperators)
1600	order = buildOrder(privateDictOperators)
1601	decompilerClass = PrivateDictDecompiler
1602	compilerClass = PrivateDictCompiler
1603
1604
1605class IndexedStrings(object):
1606
1607	"""SID -> string mapping."""
1608
1609	def __init__(self, file=None):
1610		if file is None:
1611			strings = []
1612		else:
1613			strings = [tostr(s, encoding="latin1") for s in Index(file)]
1614		self.strings = strings
1615
1616	def getCompiler(self):
1617		return IndexedStringsCompiler(self, None, None)
1618
1619	def __len__(self):
1620		return len(self.strings)
1621
1622	def __getitem__(self, SID):
1623		if SID < cffStandardStringCount:
1624			return cffStandardStrings[SID]
1625		else:
1626			return self.strings[SID - cffStandardStringCount]
1627
1628	def getSID(self, s):
1629		if not hasattr(self, "stringMapping"):
1630			self.buildStringMapping()
1631		if s in cffStandardStringMapping:
1632			SID = cffStandardStringMapping[s]
1633		elif s in self.stringMapping:
1634			SID = self.stringMapping[s]
1635		else:
1636			SID = len(self.strings) + cffStandardStringCount
1637			self.strings.append(s)
1638			self.stringMapping[s] = SID
1639		return SID
1640
1641	def getStrings(self):
1642		return self.strings
1643
1644	def buildStringMapping(self):
1645		self.stringMapping = {}
1646		for index in range(len(self.strings)):
1647			self.stringMapping[self.strings[index]] = index + cffStandardStringCount
1648
1649
1650# The 391 Standard Strings as used in the CFF format.
1651# from Adobe Technical None #5176, version 1.0, 18 March 1998
1652
1653cffStandardStrings = ['.notdef', 'space', 'exclam', 'quotedbl', 'numbersign',
1654		'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright',
1655		'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one',
1656		'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon',
1657		'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C',
1658		'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
1659		'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash',
1660		'bracketright', 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c',
1661		'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
1662		's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright',
1663		'asciitilde', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin',
1664		'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft',
1665		'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger',
1666		'daggerdbl', 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase',
1667		'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand',
1668		'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve',
1669		'dotaccent', 'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron',
1670		'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae',
1671		'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior',
1672		'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn',
1673		'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters',
1674		'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior',
1675		'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring',
1676		'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave',
1677		'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute',
1678		'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute',
1679		'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron',
1680		'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla',
1681		'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex',
1682		'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis',
1683		'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave',
1684		'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall',
1685		'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall',
1686		'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader',
1687		'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle',
1688		'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle',
1689		'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior',
1690		'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior',
1691		'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior',
1692		'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', 'parenleftinferior',
1693		'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall',
1694		'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall',
1695		'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall',
1696		'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall',
1697		'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall',
1698		'exclamdownsmall', 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall',
1699		'Dieresissmall', 'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall',
1700		'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall',
1701		'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths',
1702		'onethird', 'twothirds', 'zerosuperior', 'foursuperior', 'fivesuperior',
1703		'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior',
1704		'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior',
1705		'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior',
1706		'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall',
1707		'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall',
1708		'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall',
1709		'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall',
1710		'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall',
1711		'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall',
1712		'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall',
1713		'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', '001.001', '001.002',
1714		'001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman',
1715		'Semibold'
1716]
1717
1718cffStandardStringCount = 391
1719assert len(cffStandardStrings) == cffStandardStringCount
1720# build reverse mapping
1721cffStandardStringMapping = {}
1722for _i in range(cffStandardStringCount):
1723	cffStandardStringMapping[cffStandardStrings[_i]] = _i
1724
1725cffISOAdobeStrings = [".notdef", "space", "exclam", "quotedbl", "numbersign",
1726"dollar", "percent", "ampersand", "quoteright", "parenleft", "parenright",
1727"asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two",
1728"three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon",
1729"less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G",
1730"H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W",
1731"X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum",
1732"underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j",
1733"k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
1734"braceleft", "bar", "braceright", "asciitilde", "exclamdown", "cent",
1735"sterling", "fraction", "yen", "florin", "section", "currency", "quotesingle",
1736"quotedblleft", "guillemotleft", "guilsinglleft", "guilsinglright", "fi", "fl",
1737"endash", "dagger", "daggerdbl", "periodcentered", "paragraph", "bullet",
1738"quotesinglbase", "quotedblbase", "quotedblright", "guillemotright", "ellipsis",
1739"perthousand", "questiondown", "grave", "acute", "circumflex", "tilde",
1740"macron", "breve", "dotaccent", "dieresis", "ring", "cedilla", "hungarumlaut",
1741"ogonek", "caron", "emdash", "AE", "ordfeminine", "Lslash", "Oslash", "OE",
1742"ordmasculine", "ae", "dotlessi", "lslash", "oslash", "oe", "germandbls",
1743"onesuperior", "logicalnot", "mu", "trademark", "Eth", "onehalf", "plusminus",
1744"Thorn", "onequarter", "divide", "brokenbar", "degree", "thorn",
1745"threequarters", "twosuperior", "registered", "minus", "eth", "multiply",
1746"threesuperior", "copyright", "Aacute", "Acircumflex", "Adieresis", "Agrave",
1747"Aring", "Atilde", "Ccedilla", "Eacute", "Ecircumflex", "Edieresis", "Egrave",
1748"Iacute", "Icircumflex", "Idieresis", "Igrave", "Ntilde", "Oacute",
1749"Ocircumflex", "Odieresis", "Ograve", "Otilde", "Scaron", "Uacute",
1750"Ucircumflex", "Udieresis", "Ugrave", "Yacute", "Ydieresis", "Zcaron", "aacute",
1751"acircumflex", "adieresis", "agrave", "aring", "atilde", "ccedilla", "eacute",
1752"ecircumflex", "edieresis", "egrave", "iacute", "icircumflex", "idieresis",
1753"igrave", "ntilde", "oacute", "ocircumflex", "odieresis", "ograve", "otilde",
1754"scaron", "uacute", "ucircumflex", "udieresis", "ugrave", "yacute", "ydieresis",
1755"zcaron"]
1756
1757cffISOAdobeStringCount = 229
1758assert len(cffISOAdobeStrings) == cffISOAdobeStringCount
1759
1760cffIExpertStrings = [".notdef", "space", "exclamsmall", "Hungarumlautsmall",
1761"dollaroldstyle", "dollarsuperior", "ampersandsmall", "Acutesmall",
1762"parenleftsuperior", "parenrightsuperior", "twodotenleader", "onedotenleader",
1763"comma", "hyphen", "period", "fraction", "zerooldstyle", "oneoldstyle",
1764"twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle",
1765"sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", "semicolon",
1766"commasuperior", "threequartersemdash", "periodsuperior", "questionsmall",
1767"asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior",
1768"lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior",
1769"tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior",
1770"parenrightinferior", "Circumflexsmall", "hyphensuperior", "Gravesmall",
1771"Asmall", "Bsmall", "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall",
1772"Ismall", "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall",
1773"Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall", "Xsmall",
1774"Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah", "Tildesmall",
1775"exclamdownsmall", "centoldstyle", "Lslashsmall", "Scaronsmall", "Zcaronsmall",
1776"Dieresissmall", "Brevesmall", "Caronsmall", "Dotaccentsmall", "Macronsmall",
1777"figuredash", "hypheninferior", "Ogoneksmall", "Ringsmall", "Cedillasmall",
1778"onequarter", "onehalf", "threequarters", "questiondownsmall", "oneeighth",
1779"threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds",
1780"zerosuperior", "onesuperior", "twosuperior", "threesuperior", "foursuperior",
1781"fivesuperior", "sixsuperior", "sevensuperior", "eightsuperior", "ninesuperior",
1782"zeroinferior", "oneinferior", "twoinferior", "threeinferior", "fourinferior",
1783"fiveinferior", "sixinferior", "seveninferior", "eightinferior", "nineinferior",
1784"centinferior", "dollarinferior", "periodinferior", "commainferior",
1785"Agravesmall", "Aacutesmall", "Acircumflexsmall", "Atildesmall",
1786"Adieresissmall", "Aringsmall", "AEsmall", "Ccedillasmall", "Egravesmall",
1787"Eacutesmall", "Ecircumflexsmall", "Edieresissmall", "Igravesmall",
1788"Iacutesmall", "Icircumflexsmall", "Idieresissmall", "Ethsmall", "Ntildesmall",
1789"Ogravesmall", "Oacutesmall", "Ocircumflexsmall", "Otildesmall",
1790"Odieresissmall", "OEsmall", "Oslashsmall", "Ugravesmall", "Uacutesmall",
1791"Ucircumflexsmall", "Udieresissmall", "Yacutesmall", "Thornsmall",
1792"Ydieresissmall"]
1793
1794cffExpertStringCount = 166
1795assert len(cffIExpertStrings) == cffExpertStringCount
1796
1797cffExpertSubsetStrings = [".notdef", "space", "dollaroldstyle",
1798"dollarsuperior", "parenleftsuperior", "parenrightsuperior", "twodotenleader",
1799"onedotenleader", "comma", "hyphen", "period", "fraction", "zerooldstyle",
1800"oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle",
1801"sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon",
1802"semicolon", "commasuperior", "threequartersemdash", "periodsuperior",
1803"asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior",
1804"lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior",
1805"tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior",
1806"parenrightinferior", "hyphensuperior", "colonmonetary", "onefitted", "rupiah",
1807"centoldstyle", "figuredash", "hypheninferior", "onequarter", "onehalf",
1808"threequarters", "oneeighth", "threeeighths", "fiveeighths", "seveneighths",
1809"onethird", "twothirds", "zerosuperior", "onesuperior", "twosuperior",
1810"threesuperior", "foursuperior", "fivesuperior", "sixsuperior", "sevensuperior",
1811"eightsuperior", "ninesuperior", "zeroinferior", "oneinferior", "twoinferior",
1812"threeinferior", "fourinferior", "fiveinferior", "sixinferior", "seveninferior",
1813"eightinferior", "nineinferior", "centinferior", "dollarinferior",
1814"periodinferior", "commainferior"]
1815
1816cffExpertSubsetStringCount = 87
1817assert len(cffExpertSubsetStrings) == cffExpertSubsetStringCount
1818