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