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