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