• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""cffLib.py -- read/write tools for Adobe CFF fonts."""
2
3from __future__ import print_function, division, absolute_import
4from fontTools.misc.py23 import *
5from fontTools.misc import sstruct
6from fontTools.misc import psCharStrings
7from fontTools.misc.arrayTools import unionRect, intRect
8from fontTools.misc.textTools import safeEval
9from fontTools.ttLib import TTFont
10from fontTools.ttLib.tables.otBase import OTTableWriter
11from fontTools.ttLib.tables.otBase import OTTableReader
12from fontTools.ttLib.tables import otTables as ot
13import struct
14import logging
15import re
16
17# mute cffLib debug messages when running ttx in verbose mode
18DEBUG = logging.DEBUG - 1
19log = logging.getLogger(__name__)
20
21cffHeaderFormat = """
22	major:   B
23	minor:   B
24	hdrSize: B
25"""
26
27maxStackLimit = 513
28# maxstack operator has been deprecated. max stack is now always 513.
29
30
31class CFFFontSet(object):
32
33	def decompile(self, file, otFont, isCFF2=None):
34		self.otFont = otFont
35		sstruct.unpack(cffHeaderFormat, file.read(3), self)
36		if isCFF2 is not None:
37			# called from ttLib: assert 'major' as read from file matches the
38			# expected version
39			expected_major = (2 if isCFF2 else 1)
40			if self.major != expected_major:
41				raise ValueError(
42					"Invalid CFF 'major' version: expected %d, found %d" %
43					(expected_major, self.major))
44		else:
45			# use 'major' version from file to determine if isCFF2
46			assert self.major in (1, 2), "Unknown CFF format"
47			isCFF2 = self.major == 2
48		if not isCFF2:
49			self.offSize = struct.unpack("B", file.read(1))[0]
50			file.seek(self.hdrSize)
51			self.fontNames = list(tostr(s) for s in Index(file, isCFF2=isCFF2))
52			self.topDictIndex = TopDictIndex(file, isCFF2=isCFF2)
53			self.strings = IndexedStrings(file)
54		else:  # isCFF2
55			self.topDictSize = struct.unpack(">H", file.read(2))[0]
56			file.seek(self.hdrSize)
57			self.fontNames = ["CFF2Font"]
58			cff2GetGlyphOrder = otFont.getGlyphOrder
59			# in CFF2, offsetSize is the size of the TopDict data.
60			self.topDictIndex = TopDictIndex(
61				file, cff2GetGlyphOrder, self.topDictSize, isCFF2=isCFF2)
62			self.strings = None
63		self.GlobalSubrs = GlobalSubrsIndex(file, isCFF2=isCFF2)
64		self.topDictIndex.strings = self.strings
65		self.topDictIndex.GlobalSubrs = self.GlobalSubrs
66
67	def __len__(self):
68		return len(self.fontNames)
69
70	def keys(self):
71		return list(self.fontNames)
72
73	def values(self):
74		return self.topDictIndex
75
76	def __getitem__(self, nameOrIndex):
77		""" Return TopDict instance identified by name (str) or index (int
78		or any object that implements `__index__`).
79		"""
80		if hasattr(nameOrIndex, "__index__"):
81			index = nameOrIndex.__index__()
82		elif isinstance(nameOrIndex, basestring):
83			name = nameOrIndex
84			try:
85				index = self.fontNames.index(name)
86			except ValueError:
87				raise KeyError(nameOrIndex)
88		else:
89			raise TypeError(nameOrIndex)
90		return self.topDictIndex[index]
91
92	def compile(self, file, otFont, isCFF2=None):
93		self.otFont = otFont
94		if isCFF2 is not None:
95			# called from ttLib: assert 'major' value matches expected version
96			expected_major = (2 if isCFF2 else 1)
97			if self.major != expected_major:
98				raise ValueError(
99					"Invalid CFF 'major' version: expected %d, found %d" %
100					(expected_major, self.major))
101		else:
102			# use current 'major' value to determine output format
103			assert self.major in (1, 2), "Unknown CFF format"
104			isCFF2 = self.major == 2
105
106		if otFont.recalcBBoxes and not isCFF2:
107			for topDict in self.topDictIndex:
108				topDict.recalcFontBBox()
109
110		if not isCFF2:
111			strings = IndexedStrings()
112		else:
113			strings = None
114		writer = CFFWriter(isCFF2)
115		topCompiler = self.topDictIndex.getCompiler(strings, self, isCFF2=isCFF2)
116		if isCFF2:
117			self.hdrSize = 5
118			writer.add(sstruct.pack(cffHeaderFormat, self))
119			# Note: topDictSize will most likely change in CFFWriter.toFile().
120			self.topDictSize = topCompiler.getDataLength()
121			writer.add(struct.pack(">H", self.topDictSize))
122		else:
123			self.hdrSize = 4
124			self.offSize = 4  # will most likely change in CFFWriter.toFile().
125			writer.add(sstruct.pack(cffHeaderFormat, self))
126			writer.add(struct.pack("B", self.offSize))
127		if not isCFF2:
128			fontNames = Index()
129			for name in self.fontNames:
130				fontNames.append(name)
131			writer.add(fontNames.getCompiler(strings, self, isCFF2=isCFF2))
132		writer.add(topCompiler)
133		if not isCFF2:
134			writer.add(strings.getCompiler())
135		writer.add(self.GlobalSubrs.getCompiler(strings, self, isCFF2=isCFF2))
136
137		for topDict in self.topDictIndex:
138			if not hasattr(topDict, "charset") or topDict.charset is None:
139				charset = otFont.getGlyphOrder()
140				topDict.charset = charset
141		children = topCompiler.getChildren(strings)
142		for child in children:
143			writer.add(child)
144
145		writer.toFile(file)
146
147	def toXML(self, xmlWriter):
148		xmlWriter.simpletag("major", value=self.major)
149		xmlWriter.newline()
150		xmlWriter.simpletag("minor", value=self.minor)
151		xmlWriter.newline()
152		for fontName in self.fontNames:
153			xmlWriter.begintag("CFFFont", name=tostr(fontName))
154			xmlWriter.newline()
155			font = self[fontName]
156			font.toXML(xmlWriter)
157			xmlWriter.endtag("CFFFont")
158			xmlWriter.newline()
159		xmlWriter.newline()
160		xmlWriter.begintag("GlobalSubrs")
161		xmlWriter.newline()
162		self.GlobalSubrs.toXML(xmlWriter)
163		xmlWriter.endtag("GlobalSubrs")
164		xmlWriter.newline()
165
166	def fromXML(self, name, attrs, content, otFont=None):
167		self.otFont = otFont
168
169		# set defaults. These will be replaced if there are entries for them
170		# in the XML file.
171		if not hasattr(self, "major"):
172			self.major = 1
173		if not hasattr(self, "minor"):
174			self.minor = 0
175
176		if name == "CFFFont":
177			if self.major == 1:
178				if not hasattr(self, "offSize"):
179					# this will be recalculated when the cff is compiled.
180					self.offSize = 4
181				if not hasattr(self, "hdrSize"):
182					self.hdrSize = 4
183				if not hasattr(self, "GlobalSubrs"):
184					self.GlobalSubrs = GlobalSubrsIndex()
185				if not hasattr(self, "fontNames"):
186					self.fontNames = []
187					self.topDictIndex = TopDictIndex()
188				fontName = attrs["name"]
189				self.fontNames.append(fontName)
190				topDict = TopDict(GlobalSubrs=self.GlobalSubrs)
191				topDict.charset = None  # gets filled in later
192			elif self.major == 2:
193				if not hasattr(self, "hdrSize"):
194					self.hdrSize = 5
195				if not hasattr(self, "GlobalSubrs"):
196					self.GlobalSubrs = GlobalSubrsIndex()
197				if not hasattr(self, "fontNames"):
198					self.fontNames = ["CFF2Font"]
199				cff2GetGlyphOrder = self.otFont.getGlyphOrder
200				topDict = TopDict(
201					GlobalSubrs=self.GlobalSubrs,
202					cff2GetGlyphOrder=cff2GetGlyphOrder)
203				self.topDictIndex = TopDictIndex(None, cff2GetGlyphOrder, None)
204			self.topDictIndex.append(topDict)
205			for element in content:
206				if isinstance(element, basestring):
207					continue
208				name, attrs, content = element
209				topDict.fromXML(name, attrs, content)
210		elif name == "GlobalSubrs":
211			subrCharStringClass = psCharStrings.T2CharString
212			if not hasattr(self, "GlobalSubrs"):
213				self.GlobalSubrs = GlobalSubrsIndex()
214			for element in content:
215				if isinstance(element, basestring):
216					continue
217				name, attrs, content = element
218				subr = subrCharStringClass()
219				subr.fromXML(name, attrs, content)
220				self.GlobalSubrs.append(subr)
221		elif name == "major":
222			self.major = int(attrs['value'])
223		elif name == "minor":
224			self.minor = int(attrs['value'])
225
226	def convertCFFToCFF2(self, otFont):
227		# This assumes a decompiled CFF table.
228		self.major = 2
229		cff2GetGlyphOrder = self.otFont.getGlyphOrder
230		topDictData = TopDictIndex(None, cff2GetGlyphOrder, None)
231		topDictData.items = self.topDictIndex.items
232		self.topDictIndex = topDictData
233		topDict = topDictData[0]
234		if hasattr(topDict, 'Private'):
235			privateDict = topDict.Private
236		else:
237			privateDict = None
238		opOrder = buildOrder(topDictOperators2)
239		topDict.order = opOrder
240		topDict.cff2GetGlyphOrder = cff2GetGlyphOrder
241		for entry in topDictOperators:
242			key = entry[1]
243			if key not in opOrder:
244				if key in topDict.rawDict:
245					del topDict.rawDict[key]
246				if hasattr(topDict, key):
247					delattr(topDict, key)
248
249		if not hasattr(topDict, "FDArray"):
250			fdArray = topDict.FDArray = FDArrayIndex()
251			fdArray.strings = None
252			fdArray.GlobalSubrs = topDict.GlobalSubrs
253			topDict.GlobalSubrs.fdArray = fdArray
254			charStrings = topDict.CharStrings
255			if charStrings.charStringsAreIndexed:
256				charStrings.charStringsIndex.fdArray = fdArray
257			else:
258				charStrings.fdArray = fdArray
259			fontDict = FontDict()
260			fontDict.setCFF2(True)
261			fdArray.append(fontDict)
262			fontDict.Private = privateDict
263			privateOpOrder = buildOrder(privateDictOperators2)
264			for entry in privateDictOperators:
265				key = entry[1]
266				if key not in privateOpOrder:
267					if key in privateDict.rawDict:
268						# print "Removing private dict", key
269						del privateDict.rawDict[key]
270					if hasattr(privateDict, key):
271						delattr(privateDict, key)
272						# print "Removing privateDict attr", key
273		else:
274			# clean up the PrivateDicts in the fdArray
275			fdArray = topDict.FDArray
276			privateOpOrder = buildOrder(privateDictOperators2)
277			for fontDict in fdArray:
278				fontDict.setCFF2(True)
279				for key in fontDict.rawDict.keys():
280					if key not in fontDict.order:
281						del fontDict.rawDict[key]
282						if hasattr(fontDict, key):
283							delattr(fontDict, key)
284
285				privateDict = fontDict.Private
286				for entry in privateDictOperators:
287					key = entry[1]
288					if key not in privateOpOrder:
289						if key in privateDict.rawDict:
290							# print "Removing private dict", key
291							del privateDict.rawDict[key]
292						if hasattr(privateDict, key):
293							delattr(privateDict, key)
294							# print "Removing privateDict attr", key
295		# At this point, the Subrs and Charstrings are all still T2Charstring class
296		# easiest to fix this by compiling, then decompiling again
297		file = BytesIO()
298		self.compile(file, otFont, isCFF2=True)
299		file.seek(0)
300		self.decompile(file, otFont, isCFF2=True)
301
302
303class CFFWriter(object):
304
305	def __init__(self, isCFF2):
306		self.data = []
307		self.isCFF2 = isCFF2
308
309	def add(self, table):
310		self.data.append(table)
311
312	def toFile(self, file):
313		lastPosList = None
314		count = 1
315		while True:
316			log.log(DEBUG, "CFFWriter.toFile() iteration: %d", count)
317			count = count + 1
318			pos = 0
319			posList = [pos]
320			for item in self.data:
321				if hasattr(item, "getDataLength"):
322					endPos = pos + item.getDataLength()
323					if isinstance(item, TopDictIndexCompiler) and item.isCFF2:
324						self.topDictSize = item.getDataLength()
325				else:
326					endPos = pos + len(item)
327				if hasattr(item, "setPos"):
328					item.setPos(pos, endPos)
329				pos = endPos
330				posList.append(pos)
331			if posList == lastPosList:
332				break
333			lastPosList = posList
334		log.log(DEBUG, "CFFWriter.toFile() writing to file.")
335		begin = file.tell()
336		if self.isCFF2:
337			self.data[1] = struct.pack(">H", self.topDictSize)
338		else:
339			self.offSize = calcOffSize(lastPosList[-1])
340			self.data[1] = struct.pack("B", self.offSize)
341		posList = [0]
342		for item in self.data:
343			if hasattr(item, "toFile"):
344				item.toFile(file)
345			else:
346				file.write(item)
347			posList.append(file.tell() - begin)
348		assert posList == lastPosList
349
350
351def calcOffSize(largestOffset):
352	if largestOffset < 0x100:
353		offSize = 1
354	elif largestOffset < 0x10000:
355		offSize = 2
356	elif largestOffset < 0x1000000:
357		offSize = 3
358	else:
359		offSize = 4
360	return offSize
361
362
363class IndexCompiler(object):
364
365	def __init__(self, items, strings, parent, isCFF2=None):
366		if isCFF2 is None and hasattr(parent, "isCFF2"):
367			isCFF2 = parent.isCFF2
368			assert isCFF2 is not None
369		self.isCFF2 = isCFF2
370		self.items = self.getItems(items, strings)
371		self.parent = parent
372
373	def getItems(self, items, strings):
374		return items
375
376	def getOffsets(self):
377		# An empty INDEX contains only the count field.
378		if self.items:
379			pos = 1
380			offsets = [pos]
381			for item in self.items:
382				if hasattr(item, "getDataLength"):
383					pos = pos + item.getDataLength()
384				else:
385					pos = pos + len(item)
386				offsets.append(pos)
387		else:
388			offsets = []
389		return offsets
390
391	def getDataLength(self):
392		if self.isCFF2:
393			countSize = 4
394		else:
395			countSize = 2
396
397		if self.items:
398			lastOffset = self.getOffsets()[-1]
399			offSize = calcOffSize(lastOffset)
400			dataLength = (
401				countSize +                        # count
402				1 +                                # offSize
403				(len(self.items) + 1) * offSize +  # the offsets
404				lastOffset - 1                     # size of object data
405			)
406		else:
407			# count. For empty INDEX tables, this is the only entry.
408			dataLength = countSize
409
410		return dataLength
411
412	def toFile(self, file):
413		offsets = self.getOffsets()
414		if self.isCFF2:
415			writeCard32(file, len(self.items))
416		else:
417			writeCard16(file, len(self.items))
418		# An empty INDEX contains only the count field.
419		if self.items:
420			offSize = calcOffSize(offsets[-1])
421			writeCard8(file, offSize)
422			offSize = -offSize
423			pack = struct.pack
424			for offset in offsets:
425				binOffset = pack(">l", offset)[offSize:]
426				assert len(binOffset) == -offSize
427				file.write(binOffset)
428			for item in self.items:
429				if hasattr(item, "toFile"):
430					item.toFile(file)
431				else:
432					data = tobytes(item, encoding="latin1")
433					file.write(data)
434
435
436class IndexedStringsCompiler(IndexCompiler):
437
438	def getItems(self, items, strings):
439		return items.strings
440
441
442class TopDictIndexCompiler(IndexCompiler):
443
444	def getItems(self, items, strings):
445		out = []
446		for item in items:
447			out.append(item.getCompiler(strings, self))
448		return out
449
450	def getChildren(self, strings):
451		children = []
452		for topDict in self.items:
453			children.extend(topDict.getChildren(strings))
454		return children
455
456	def getOffsets(self):
457		if self.isCFF2:
458			offsets = [0, self.items[0].getDataLength()]
459			return offsets
460		else:
461			return super(TopDictIndexCompiler, self).getOffsets()
462
463	def getDataLength(self):
464		if self.isCFF2:
465			dataLength = self.items[0].getDataLength()
466			return dataLength
467		else:
468			return super(TopDictIndexCompiler, self).getDataLength()
469
470	def toFile(self, file):
471		if self.isCFF2:
472			self.items[0].toFile(file)
473		else:
474			super(TopDictIndexCompiler, self).toFile(file)
475
476
477class FDArrayIndexCompiler(IndexCompiler):
478
479	def getItems(self, items, strings):
480		out = []
481		for item in items:
482			out.append(item.getCompiler(strings, self))
483		return out
484
485	def getChildren(self, strings):
486		children = []
487		for fontDict in self.items:
488			children.extend(fontDict.getChildren(strings))
489		return children
490
491	def toFile(self, file):
492		offsets = self.getOffsets()
493		if self.isCFF2:
494			writeCard32(file, len(self.items))
495		else:
496			writeCard16(file, len(self.items))
497		offSize = calcOffSize(offsets[-1])
498		writeCard8(file, offSize)
499		offSize = -offSize
500		pack = struct.pack
501		for offset in offsets:
502			binOffset = pack(">l", offset)[offSize:]
503			assert len(binOffset) == -offSize
504			file.write(binOffset)
505		for item in self.items:
506			if hasattr(item, "toFile"):
507				item.toFile(file)
508			else:
509				file.write(item)
510
511	def setPos(self, pos, endPos):
512		self.parent.rawDict["FDArray"] = pos
513
514
515class GlobalSubrsCompiler(IndexCompiler):
516
517	def getItems(self, items, strings):
518		out = []
519		for cs in items:
520			cs.compile(self.isCFF2)
521			out.append(cs.bytecode)
522		return out
523
524
525class SubrsCompiler(GlobalSubrsCompiler):
526
527	def setPos(self, pos, endPos):
528		offset = pos - self.parent.pos
529		self.parent.rawDict["Subrs"] = offset
530
531
532class CharStringsCompiler(GlobalSubrsCompiler):
533
534	def getItems(self, items, strings):
535		out = []
536		for cs in items:
537			cs.compile(self.isCFF2)
538			out.append(cs.bytecode)
539		return out
540
541	def setPos(self, pos, endPos):
542		self.parent.rawDict["CharStrings"] = pos
543
544
545class Index(object):
546
547	"""This class represents what the CFF spec calls an INDEX."""
548
549	compilerClass = IndexCompiler
550
551	def __init__(self, file=None, isCFF2=None):
552		assert (isCFF2 is None) == (file is None)
553		self.items = []
554		name = self.__class__.__name__
555		if file is None:
556			return
557		self._isCFF2 = isCFF2
558		log.log(DEBUG, "loading %s at %s", name, file.tell())
559		self.file = file
560		if isCFF2:
561			count = readCard32(file)
562		else:
563			count = readCard16(file)
564		if count == 0:
565			return
566		self.items = [None] * count
567		offSize = readCard8(file)
568		log.log(DEBUG, "    index count: %s offSize: %s", count, offSize)
569		assert offSize <= 4, "offSize too large: %s" % offSize
570		self.offsets = offsets = []
571		pad = b'\0' * (4 - offSize)
572		for index in range(count + 1):
573			chunk = file.read(offSize)
574			chunk = pad + chunk
575			offset, = struct.unpack(">L", chunk)
576			offsets.append(int(offset))
577		self.offsetBase = file.tell() - 1
578		file.seek(self.offsetBase + offsets[-1])  # pretend we've read the whole lot
579		log.log(DEBUG, "    end of %s at %s", name, file.tell())
580
581	def __len__(self):
582		return len(self.items)
583
584	def __getitem__(self, index):
585		item = self.items[index]
586		if item is not None:
587			return item
588		offset = self.offsets[index] + self.offsetBase
589		size = self.offsets[index + 1] - self.offsets[index]
590		file = self.file
591		file.seek(offset)
592		data = file.read(size)
593		assert len(data) == size
594		item = self.produceItem(index, data, file, offset)
595		self.items[index] = item
596		return item
597
598	def __setitem__(self, index, item):
599		self.items[index] = item
600
601	def produceItem(self, index, data, file, offset):
602		return data
603
604	def append(self, item):
605		self.items.append(item)
606
607	def getCompiler(self, strings, parent, isCFF2=None):
608		return self.compilerClass(self, strings, parent, isCFF2=isCFF2)
609
610	def clear(self):
611		del self.items[:]
612
613
614class GlobalSubrsIndex(Index):
615
616	compilerClass = GlobalSubrsCompiler
617	subrClass = psCharStrings.T2CharString
618	charStringClass = psCharStrings.T2CharString
619
620	def __init__(self, file=None, globalSubrs=None, private=None,
621			fdSelect=None, fdArray=None, isCFF2=None):
622		super(GlobalSubrsIndex, self).__init__(file, isCFF2=isCFF2)
623		self.globalSubrs = globalSubrs
624		self.private = private
625		if fdSelect:
626			self.fdSelect = fdSelect
627		if fdArray:
628			self.fdArray = fdArray
629
630	def produceItem(self, index, data, file, offset):
631		if self.private is not None:
632			private = self.private
633		elif hasattr(self, 'fdArray') and self.fdArray is not None:
634			if hasattr(self, 'fdSelect') and self.fdSelect is not None:
635				fdIndex = self.fdSelect[index]
636			else:
637				fdIndex = 0
638			private = self.fdArray[fdIndex].Private
639		else:
640			private = None
641		return self.subrClass(data, private=private, globalSubrs=self.globalSubrs)
642
643	def toXML(self, xmlWriter):
644		xmlWriter.comment(
645			"The 'index' attribute is only for humans; "
646			"it is ignored when parsed.")
647		xmlWriter.newline()
648		for i in range(len(self)):
649			subr = self[i]
650			if subr.needsDecompilation():
651				xmlWriter.begintag("CharString", index=i, raw=1)
652			else:
653				xmlWriter.begintag("CharString", index=i)
654			xmlWriter.newline()
655			subr.toXML(xmlWriter)
656			xmlWriter.endtag("CharString")
657			xmlWriter.newline()
658
659	def fromXML(self, name, attrs, content):
660		if name != "CharString":
661			return
662		subr = self.subrClass()
663		subr.fromXML(name, attrs, content)
664		self.append(subr)
665
666	def getItemAndSelector(self, index):
667		sel = None
668		if hasattr(self, 'fdSelect'):
669			sel = self.fdSelect[index]
670		return self[index], sel
671
672
673class SubrsIndex(GlobalSubrsIndex):
674	compilerClass = SubrsCompiler
675
676
677class TopDictIndex(Index):
678
679	compilerClass = TopDictIndexCompiler
680
681	def __init__(self, file=None, cff2GetGlyphOrder=None, topSize=0,
682			isCFF2=None):
683		assert (isCFF2 is None) == (file is None)
684		self.cff2GetGlyphOrder = cff2GetGlyphOrder
685		if file is not None and isCFF2:
686			self._isCFF2 = isCFF2
687			self.items = []
688			name = self.__class__.__name__
689			log.log(DEBUG, "loading %s at %s", name, file.tell())
690			self.file = file
691			count = 1
692			self.items = [None] * count
693			self.offsets = [0, topSize]
694			self.offsetBase = file.tell()
695			# pretend we've read the whole lot
696			file.seek(self.offsetBase + topSize)
697			log.log(DEBUG, "    end of %s at %s", name, file.tell())
698		else:
699			super(TopDictIndex, self).__init__(file, isCFF2=isCFF2)
700
701	def produceItem(self, index, data, file, offset):
702		top = TopDict(
703			self.strings, file, offset, self.GlobalSubrs,
704			self.cff2GetGlyphOrder, isCFF2=self._isCFF2)
705		top.decompile(data)
706		return top
707
708	def toXML(self, xmlWriter):
709		for i in range(len(self)):
710			xmlWriter.begintag("FontDict", index=i)
711			xmlWriter.newline()
712			self[i].toXML(xmlWriter)
713			xmlWriter.endtag("FontDict")
714			xmlWriter.newline()
715
716
717class FDArrayIndex(Index):
718
719	compilerClass = FDArrayIndexCompiler
720
721	def toXML(self, xmlWriter):
722		for i in range(len(self)):
723			xmlWriter.begintag("FontDict", index=i)
724			xmlWriter.newline()
725			self[i].toXML(xmlWriter)
726			xmlWriter.endtag("FontDict")
727			xmlWriter.newline()
728
729	def produceItem(self, index, data, file, offset):
730		fontDict = FontDict(
731			self.strings, file, offset, self.GlobalSubrs, isCFF2=self._isCFF2,
732			vstore=self.vstore)
733		fontDict.decompile(data)
734		return fontDict
735
736	def fromXML(self, name, attrs, content):
737		if name != "FontDict":
738			return
739		fontDict = FontDict()
740		for element in content:
741			if isinstance(element, basestring):
742				continue
743			name, attrs, content = element
744			fontDict.fromXML(name, attrs, content)
745		self.append(fontDict)
746
747
748class VarStoreData(object):
749
750	def __init__(self, file=None, otVarStore=None):
751		self.file = file
752		self.data = None
753		self.otVarStore = otVarStore
754		self.font = TTFont()  # dummy font for the decompile function.
755
756	def decompile(self):
757		if self.file:
758			class GlobalState(object):
759				def __init__(self, tableType, cachingStats):
760					self.tableType = tableType
761					self.cachingStats = cachingStats
762			globalState = GlobalState(tableType="VarStore", cachingStats={})
763			# read data in from file. Assume position is correct.
764			length = readCard16(self.file)
765			self.data = self.file.read(length)
766			globalState = {}
767			reader = OTTableReader(self.data, globalState)
768			self.otVarStore = ot.VarStore()
769			self.otVarStore.decompile(reader, self.font)
770		return self
771
772	def compile(self):
773		writer = OTTableWriter()
774		self.otVarStore.compile(writer, self.font)
775		# Note that this omits the initial Card16 length from the CFF2
776		# VarStore data block
777		self.data = writer.getAllData()
778
779	def writeXML(self, xmlWriter, name):
780		self.otVarStore.toXML(xmlWriter, self.font)
781
782	def xmlRead(self, name, attrs, content, parent):
783		self.otVarStore = ot.VarStore()
784		for element in content:
785			if isinstance(element, tuple):
786				name, attrs, content = element
787				self.otVarStore.fromXML(name, attrs, content, self.font)
788			else:
789				pass
790		return None
791
792	def __len__(self):
793		return len(self.data)
794
795	def getNumRegions(self, vsIndex):
796		varData = self.otVarStore.VarData[vsIndex]
797		numRegions = varData.VarRegionCount
798		return numRegions
799
800
801class FDSelect(object):
802
803	def __init__(self, file=None, numGlyphs=None, format=None):
804		if file:
805			# read data in from file
806			self.format = readCard8(file)
807			if self.format == 0:
808				from array import array
809				self.gidArray = array("B", file.read(numGlyphs)).tolist()
810			elif self.format == 3:
811				gidArray = [None] * numGlyphs
812				nRanges = readCard16(file)
813				fd = None
814				prev = None
815				for i in range(nRanges):
816					first = readCard16(file)
817					if prev is not None:
818						for glyphID in range(prev, first):
819							gidArray[glyphID] = fd
820					prev = first
821					fd = readCard8(file)
822				if prev is not None:
823					first = readCard16(file)
824					for glyphID in range(prev, first):
825						gidArray[glyphID] = fd
826				self.gidArray = gidArray
827			else:
828				assert False, "unsupported FDSelect format: %s" % format
829		else:
830			# reading from XML. Make empty gidArray, and leave format as passed in.
831			# format is None will result in the smallest representation being used.
832			self.format = format
833			self.gidArray = []
834
835	def __len__(self):
836		return len(self.gidArray)
837
838	def __getitem__(self, index):
839		return self.gidArray[index]
840
841	def __setitem__(self, index, fdSelectValue):
842		self.gidArray[index] = fdSelectValue
843
844	def append(self, fdSelectValue):
845		self.gidArray.append(fdSelectValue)
846
847
848class CharStrings(object):
849
850	def __init__(self, file, charset, globalSubrs, private, fdSelect, fdArray,
851			isCFF2=None):
852		self.globalSubrs = globalSubrs
853		if file is not None:
854			self.charStringsIndex = SubrsIndex(
855				file, globalSubrs, private, fdSelect, fdArray, isCFF2=isCFF2)
856			self.charStrings = charStrings = {}
857			for i in range(len(charset)):
858				charStrings[charset[i]] = i
859			# read from OTF file: charStrings.values() are indices into
860			# charStringsIndex.
861			self.charStringsAreIndexed = 1
862		else:
863			self.charStrings = {}
864			# read from ttx file: charStrings.values() are actual charstrings
865			self.charStringsAreIndexed = 0
866			self.private = private
867			if fdSelect is not None:
868				self.fdSelect = fdSelect
869			if fdArray is not None:
870				self.fdArray = fdArray
871
872	def keys(self):
873		return list(self.charStrings.keys())
874
875	def values(self):
876		if self.charStringsAreIndexed:
877			return self.charStringsIndex
878		else:
879			return list(self.charStrings.values())
880
881	def has_key(self, name):
882		return name in self.charStrings
883
884	__contains__ = has_key
885
886	def __len__(self):
887		return len(self.charStrings)
888
889	def __getitem__(self, name):
890		charString = self.charStrings[name]
891		if self.charStringsAreIndexed:
892			charString = self.charStringsIndex[charString]
893		return charString
894
895	def __setitem__(self, name, charString):
896		if self.charStringsAreIndexed:
897			index = self.charStrings[name]
898			self.charStringsIndex[index] = charString
899		else:
900			self.charStrings[name] = charString
901
902	def getItemAndSelector(self, name):
903		if self.charStringsAreIndexed:
904			index = self.charStrings[name]
905			return self.charStringsIndex.getItemAndSelector(index)
906		else:
907			if hasattr(self, 'fdArray'):
908				if hasattr(self, 'fdSelect'):
909					sel = self.charStrings[name].fdSelectIndex
910				else:
911					sel = 0
912			else:
913				sel = None
914			return self.charStrings[name], sel
915
916	def toXML(self, xmlWriter):
917		names = sorted(self.keys())
918		for name in names:
919			charStr, fdSelectIndex = self.getItemAndSelector(name)
920			if charStr.needsDecompilation():
921				raw = [("raw", 1)]
922			else:
923				raw = []
924			if fdSelectIndex is None:
925				xmlWriter.begintag("CharString", [('name', name)] + raw)
926			else:
927				xmlWriter.begintag(
928					"CharString",
929					[('name', name), ('fdSelectIndex', fdSelectIndex)] + raw)
930			xmlWriter.newline()
931			charStr.toXML(xmlWriter)
932			xmlWriter.endtag("CharString")
933			xmlWriter.newline()
934
935	def fromXML(self, name, attrs, content):
936		for element in content:
937			if isinstance(element, basestring):
938				continue
939			name, attrs, content = element
940			if name != "CharString":
941				continue
942			fdID = -1
943			if hasattr(self, "fdArray"):
944				try:
945					fdID = safeEval(attrs["fdSelectIndex"])
946				except KeyError:
947					fdID = 0
948				private = self.fdArray[fdID].Private
949			else:
950				private = self.private
951
952			glyphName = attrs["name"]
953			charStringClass = psCharStrings.T2CharString
954			charString = charStringClass(
955					private=private,
956					globalSubrs=self.globalSubrs)
957			charString.fromXML(name, attrs, content)
958			if fdID >= 0:
959				charString.fdSelectIndex = fdID
960			self[glyphName] = charString
961
962
963def readCard8(file):
964	return byteord(file.read(1))
965
966
967def readCard16(file):
968	value, = struct.unpack(">H", file.read(2))
969	return value
970
971
972def readCard32(file):
973	value, = struct.unpack(">L", file.read(4))
974	return value
975
976
977def writeCard8(file, value):
978	file.write(bytechr(value))
979
980
981def writeCard16(file, value):
982	file.write(struct.pack(">H", value))
983
984
985def writeCard32(file, value):
986	file.write(struct.pack(">L", value))
987
988
989def packCard8(value):
990	return bytechr(value)
991
992
993def packCard16(value):
994	return struct.pack(">H", value)
995
996
997def buildOperatorDict(table):
998	d = {}
999	for op, name, arg, default, conv in table:
1000		d[op] = (name, arg)
1001	return d
1002
1003
1004def buildOpcodeDict(table):
1005	d = {}
1006	for op, name, arg, default, conv in table:
1007		if isinstance(op, tuple):
1008			op = bytechr(op[0]) + bytechr(op[1])
1009		else:
1010			op = bytechr(op)
1011		d[name] = (op, arg)
1012	return d
1013
1014
1015def buildOrder(table):
1016	l = []
1017	for op, name, arg, default, conv in table:
1018		l.append(name)
1019	return l
1020
1021
1022def buildDefaults(table):
1023	d = {}
1024	for op, name, arg, default, conv in table:
1025		if default is not None:
1026			d[name] = default
1027	return d
1028
1029
1030def buildConverters(table):
1031	d = {}
1032	for op, name, arg, default, conv in table:
1033		d[name] = conv
1034	return d
1035
1036
1037class SimpleConverter(object):
1038
1039	def read(self, parent, value):
1040		if not hasattr(parent, "file"):
1041			return self._read(parent, value)
1042		file = parent.file
1043		pos = file.tell()
1044		try:
1045			return self._read(parent, value)
1046		finally:
1047			file.seek(pos)
1048
1049	def _read(self, parent, value):
1050		return value
1051
1052	def write(self, parent, value):
1053		return value
1054
1055	def xmlWrite(self, xmlWriter, name, value):
1056		xmlWriter.simpletag(name, value=value)
1057		xmlWriter.newline()
1058
1059	def xmlRead(self, name, attrs, content, parent):
1060		return attrs["value"]
1061
1062
1063class ASCIIConverter(SimpleConverter):
1064
1065	def _read(self, parent, value):
1066		return tostr(value, encoding='ascii')
1067
1068	def write(self, parent, value):
1069		return tobytes(value, encoding='ascii')
1070
1071	def xmlWrite(self, xmlWriter, name, value):
1072		xmlWriter.simpletag(name, value=tounicode(value, encoding="ascii"))
1073		xmlWriter.newline()
1074
1075	def xmlRead(self, name, attrs, content, parent):
1076		return tobytes(attrs["value"], encoding=("ascii"))
1077
1078
1079class Latin1Converter(SimpleConverter):
1080
1081	def _read(self, parent, value):
1082		return tostr(value, encoding='latin1')
1083
1084	def write(self, parent, value):
1085		return tobytes(value, encoding='latin1')
1086
1087	def xmlWrite(self, xmlWriter, name, value):
1088		value = tounicode(value, encoding="latin1")
1089		if name in ['Notice', 'Copyright']:
1090			value = re.sub(r"[\r\n]\s+", " ", value)
1091		xmlWriter.simpletag(name, value=value)
1092		xmlWriter.newline()
1093
1094	def xmlRead(self, name, attrs, content, parent):
1095		return tobytes(attrs["value"], encoding=("latin1"))
1096
1097
1098def parseNum(s):
1099	try:
1100		value = int(s)
1101	except:
1102		value = float(s)
1103	return value
1104
1105
1106def parseBlendList(s):
1107	valueList = []
1108	for element in s:
1109		if isinstance(element, basestring):
1110			continue
1111		name, attrs, content = element
1112		blendList = attrs["value"].split()
1113		blendList = [eval(val) for val in blendList]
1114		valueList.append(blendList)
1115	if len(valueList) == 1:
1116		valueList = valueList[0]
1117	return valueList
1118
1119
1120class NumberConverter(SimpleConverter):
1121	def xmlWrite(self, xmlWriter, name, value):
1122		if isinstance(value, list):
1123			xmlWriter.begintag(name)
1124			xmlWriter.newline()
1125			xmlWriter.indent()
1126			blendValue = " ".join([str(val) for val in value])
1127			xmlWriter.simpletag(kBlendDictOpName, value=blendValue)
1128			xmlWriter.newline()
1129			xmlWriter.dedent()
1130			xmlWriter.endtag(name)
1131			xmlWriter.newline()
1132		else:
1133			xmlWriter.simpletag(name, value=value)
1134			xmlWriter.newline()
1135
1136	def xmlRead(self, name, attrs, content, parent):
1137		valueString = attrs.get("value", None)
1138		if valueString is None:
1139			value = parseBlendList(content)
1140		else:
1141			value = parseNum(attrs["value"])
1142		return value
1143
1144
1145class ArrayConverter(SimpleConverter):
1146	def xmlWrite(self, xmlWriter, name, value):
1147		if value and isinstance(value[0], list):
1148			xmlWriter.begintag(name)
1149			xmlWriter.newline()
1150			xmlWriter.indent()
1151			for valueList in value:
1152				blendValue = " ".join([str(val) for val in valueList])
1153				xmlWriter.simpletag(kBlendDictOpName, value=blendValue)
1154				xmlWriter.newline()
1155			xmlWriter.dedent()
1156			xmlWriter.endtag(name)
1157			xmlWriter.newline()
1158		else:
1159			value = " ".join([str(val) for val in value])
1160			xmlWriter.simpletag(name, value=value)
1161			xmlWriter.newline()
1162
1163	def xmlRead(self, name, attrs, content, parent):
1164		valueString = attrs.get("value", None)
1165		if valueString is None:
1166			valueList = parseBlendList(content)
1167		else:
1168			values = valueString.split()
1169			valueList = [parseNum(value) for value in values]
1170		return valueList
1171
1172
1173class TableConverter(SimpleConverter):
1174
1175	def xmlWrite(self, xmlWriter, name, value):
1176		xmlWriter.begintag(name)
1177		xmlWriter.newline()
1178		value.toXML(xmlWriter)
1179		xmlWriter.endtag(name)
1180		xmlWriter.newline()
1181
1182	def xmlRead(self, name, attrs, content, parent):
1183		ob = self.getClass()()
1184		for element in content:
1185			if isinstance(element, basestring):
1186				continue
1187			name, attrs, content = element
1188			ob.fromXML(name, attrs, content)
1189		return ob
1190
1191
1192class PrivateDictConverter(TableConverter):
1193
1194	def getClass(self):
1195		return PrivateDict
1196
1197	def _read(self, parent, value):
1198		size, offset = value
1199		file = parent.file
1200		isCFF2 = parent._isCFF2
1201		try:
1202			vstore = parent.vstore
1203		except AttributeError:
1204			vstore = None
1205		priv = PrivateDict(
1206			parent.strings, file, offset, isCFF2=isCFF2, vstore=vstore)
1207		file.seek(offset)
1208		data = file.read(size)
1209		assert len(data) == size
1210		priv.decompile(data)
1211		return priv
1212
1213	def write(self, parent, value):
1214		return (0, 0)  # dummy value
1215
1216
1217class SubrsConverter(TableConverter):
1218
1219	def getClass(self):
1220		return SubrsIndex
1221
1222	def _read(self, parent, value):
1223		file = parent.file
1224		isCFF2 = parent._isCFF2
1225		file.seek(parent.offset + value)  # Offset(self)
1226		return SubrsIndex(file, isCFF2=isCFF2)
1227
1228	def write(self, parent, value):
1229		return 0  # dummy value
1230
1231
1232class CharStringsConverter(TableConverter):
1233
1234	def _read(self, parent, value):
1235		file = parent.file
1236		isCFF2 = parent._isCFF2
1237		charset = parent.charset
1238		globalSubrs = parent.GlobalSubrs
1239		if hasattr(parent, "FDArray"):
1240			fdArray = parent.FDArray
1241			if hasattr(parent, "FDSelect"):
1242				fdSelect = parent.FDSelect
1243			else:
1244				fdSelect = None
1245			private = None
1246		else:
1247			fdSelect, fdArray = None, None
1248			private = parent.Private
1249		file.seek(value)  # Offset(0)
1250		charStrings = CharStrings(
1251			file, charset, globalSubrs, private, fdSelect, fdArray, isCFF2=isCFF2)
1252		return charStrings
1253
1254	def write(self, parent, value):
1255		return 0  # dummy value
1256
1257	def xmlRead(self, name, attrs, content, parent):
1258		if hasattr(parent, "FDArray"):
1259			# if it is a CID-keyed font, then the private Dict is extracted from the
1260			# parent.FDArray
1261			fdArray = parent.FDArray
1262			if hasattr(parent, "FDSelect"):
1263				fdSelect = parent.FDSelect
1264			else:
1265				fdSelect = None
1266			private = None
1267		else:
1268			# if it is a name-keyed font, then the private dict is in the top dict,
1269			# and
1270			# there is no fdArray.
1271			private, fdSelect, fdArray = parent.Private, None, None
1272		charStrings = CharStrings(
1273			None, None, parent.GlobalSubrs, private, fdSelect, fdArray)
1274		charStrings.fromXML(name, attrs, content)
1275		return charStrings
1276
1277
1278class CharsetConverter(SimpleConverter):
1279	def _read(self, parent, value):
1280		isCID = hasattr(parent, "ROS")
1281		if value > 2:
1282			numGlyphs = parent.numGlyphs
1283			file = parent.file
1284			file.seek(value)
1285			log.log(DEBUG, "loading charset at %s", value)
1286			format = readCard8(file)
1287			if format == 0:
1288				charset = parseCharset0(numGlyphs, file, parent.strings, isCID)
1289			elif format == 1 or format == 2:
1290				charset = parseCharset(numGlyphs, file, parent.strings, isCID, format)
1291			else:
1292				raise NotImplementedError
1293			assert len(charset) == numGlyphs
1294			log.log(DEBUG, "    charset end at %s", file.tell())
1295		else:  # offset == 0 -> no charset data.
1296			if isCID or "CharStrings" not in parent.rawDict:
1297				# We get here only when processing fontDicts from the FDArray of
1298				# CFF-CID fonts. Only the real topDict references the chrset.
1299				assert value == 0
1300				charset = None
1301			elif value == 0:
1302				charset = cffISOAdobeStrings
1303			elif value == 1:
1304				charset = cffIExpertStrings
1305			elif value == 2:
1306				charset = cffExpertSubsetStrings
1307		if charset and (len(charset) != parent.numGlyphs):
1308			charset = charset[:parent.numGlyphs]
1309		return charset
1310
1311	def write(self, parent, value):
1312		return 0  # dummy value
1313
1314	def xmlWrite(self, xmlWriter, name, value):
1315		# XXX only write charset when not in OT/TTX context, where we
1316		# dump charset as a separate "GlyphOrder" table.
1317		# # xmlWriter.simpletag("charset")
1318		xmlWriter.comment("charset is dumped separately as the 'GlyphOrder' element")
1319		xmlWriter.newline()
1320
1321	def xmlRead(self, name, attrs, content, parent):
1322		pass
1323
1324
1325class CharsetCompiler(object):
1326
1327	def __init__(self, strings, charset, parent):
1328		assert charset[0] == '.notdef'
1329		isCID = hasattr(parent.dictObj, "ROS")
1330		data0 = packCharset0(charset, isCID, strings)
1331		data = packCharset(charset, isCID, strings)
1332		if len(data) < len(data0):
1333			self.data = data
1334		else:
1335			self.data = data0
1336		self.parent = parent
1337
1338	def setPos(self, pos, endPos):
1339		self.parent.rawDict["charset"] = pos
1340
1341	def getDataLength(self):
1342		return len(self.data)
1343
1344	def toFile(self, file):
1345		file.write(self.data)
1346
1347
1348def getStdCharSet(charset):
1349	# check to see if we can use a predefined charset value.
1350	predefinedCharSetVal = None
1351	predefinedCharSets = [
1352		(cffISOAdobeStringCount, cffISOAdobeStrings, 0),
1353		(cffExpertStringCount, cffIExpertStrings, 1),
1354		(cffExpertSubsetStringCount, cffExpertSubsetStrings, 2)]
1355	lcs = len(charset)
1356	for cnt, pcs, csv in predefinedCharSets:
1357		if predefinedCharSetVal is not None:
1358			break
1359		if lcs > cnt:
1360			continue
1361		predefinedCharSetVal = csv
1362		for i in range(lcs):
1363			if charset[i] != pcs[i]:
1364				predefinedCharSetVal = None
1365				break
1366	return predefinedCharSetVal
1367
1368
1369def getCIDfromName(name, strings):
1370	return int(name[3:])
1371
1372
1373def getSIDfromName(name, strings):
1374	return strings.getSID(name)
1375
1376
1377def packCharset0(charset, isCID, strings):
1378	fmt = 0
1379	data = [packCard8(fmt)]
1380	if isCID:
1381		getNameID = getCIDfromName
1382	else:
1383		getNameID = getSIDfromName
1384
1385	for name in charset[1:]:
1386		data.append(packCard16(getNameID(name, strings)))
1387	return bytesjoin(data)
1388
1389
1390def packCharset(charset, isCID, strings):
1391	fmt = 1
1392	ranges = []
1393	first = None
1394	end = 0
1395	if isCID:
1396		getNameID = getCIDfromName
1397	else:
1398		getNameID = getSIDfromName
1399
1400	for name in charset[1:]:
1401		SID = getNameID(name, strings)
1402		if first is None:
1403			first = SID
1404		elif end + 1 != SID:
1405			nLeft = end - first
1406			if nLeft > 255:
1407				fmt = 2
1408			ranges.append((first, nLeft))
1409			first = SID
1410		end = SID
1411	if end:
1412		nLeft = end - first
1413		if nLeft > 255:
1414			fmt = 2
1415		ranges.append((first, nLeft))
1416
1417	data = [packCard8(fmt)]
1418	if fmt == 1:
1419		nLeftFunc = packCard8
1420	else:
1421		nLeftFunc = packCard16
1422	for first, nLeft in ranges:
1423		data.append(packCard16(first) + nLeftFunc(nLeft))
1424	return bytesjoin(data)
1425
1426
1427def parseCharset0(numGlyphs, file, strings, isCID):
1428	charset = [".notdef"]
1429	if isCID:
1430		for i in range(numGlyphs - 1):
1431			CID = readCard16(file)
1432			charset.append("cid" + str(CID).zfill(5))
1433	else:
1434		for i in range(numGlyphs - 1):
1435			SID = readCard16(file)
1436			charset.append(strings[SID])
1437	return charset
1438
1439
1440def parseCharset(numGlyphs, file, strings, isCID, fmt):
1441	charset = ['.notdef']
1442	count = 1
1443	if fmt == 1:
1444		nLeftFunc = readCard8
1445	else:
1446		nLeftFunc = readCard16
1447	while count < numGlyphs:
1448		first = readCard16(file)
1449		nLeft = nLeftFunc(file)
1450		if isCID:
1451			for CID in range(first, first + nLeft + 1):
1452				charset.append("cid" + str(CID).zfill(5))
1453		else:
1454			for SID in range(first, first + nLeft + 1):
1455				charset.append(strings[SID])
1456		count = count + nLeft + 1
1457	return charset
1458
1459
1460class EncodingCompiler(object):
1461
1462	def __init__(self, strings, encoding, parent):
1463		assert not isinstance(encoding, basestring)
1464		data0 = packEncoding0(parent.dictObj.charset, encoding, parent.strings)
1465		data1 = packEncoding1(parent.dictObj.charset, encoding, parent.strings)
1466		if len(data0) < len(data1):
1467			self.data = data0
1468		else:
1469			self.data = data1
1470		self.parent = parent
1471
1472	def setPos(self, pos, endPos):
1473		self.parent.rawDict["Encoding"] = pos
1474
1475	def getDataLength(self):
1476		return len(self.data)
1477
1478	def toFile(self, file):
1479		file.write(self.data)
1480
1481
1482class EncodingConverter(SimpleConverter):
1483
1484	def _read(self, parent, value):
1485		if value == 0:
1486			return "StandardEncoding"
1487		elif value == 1:
1488			return "ExpertEncoding"
1489		else:
1490			assert value > 1
1491			file = parent.file
1492			file.seek(value)
1493			log.log(DEBUG, "loading Encoding at %s", value)
1494			fmt = readCard8(file)
1495			haveSupplement = fmt & 0x80
1496			if haveSupplement:
1497				raise NotImplementedError("Encoding supplements are not yet supported")
1498			fmt = fmt & 0x7f
1499			if fmt == 0:
1500				encoding = parseEncoding0(parent.charset, file, haveSupplement,
1501						parent.strings)
1502			elif fmt == 1:
1503				encoding = parseEncoding1(parent.charset, file, haveSupplement,
1504						parent.strings)
1505			return encoding
1506
1507	def write(self, parent, value):
1508		if value == "StandardEncoding":
1509			return 0
1510		elif value == "ExpertEncoding":
1511			return 1
1512		return 0  # dummy value
1513
1514	def xmlWrite(self, xmlWriter, name, value):
1515		if value in ("StandardEncoding", "ExpertEncoding"):
1516			xmlWriter.simpletag(name, name=value)
1517			xmlWriter.newline()
1518			return
1519		xmlWriter.begintag(name)
1520		xmlWriter.newline()
1521		for code in range(len(value)):
1522			glyphName = value[code]
1523			if glyphName != ".notdef":
1524				xmlWriter.simpletag("map", code=hex(code), name=glyphName)
1525				xmlWriter.newline()
1526		xmlWriter.endtag(name)
1527		xmlWriter.newline()
1528
1529	def xmlRead(self, name, attrs, content, parent):
1530		if "name" in attrs:
1531			return attrs["name"]
1532		encoding = [".notdef"] * 256
1533		for element in content:
1534			if isinstance(element, basestring):
1535				continue
1536			name, attrs, content = element
1537			code = safeEval(attrs["code"])
1538			glyphName = attrs["name"]
1539			encoding[code] = glyphName
1540		return encoding
1541
1542
1543def parseEncoding0(charset, file, haveSupplement, strings):
1544	nCodes = readCard8(file)
1545	encoding = [".notdef"] * 256
1546	for glyphID in range(1, nCodes + 1):
1547		code = readCard8(file)
1548		if code != 0:
1549			encoding[code] = charset[glyphID]
1550	return encoding
1551
1552
1553def parseEncoding1(charset, file, haveSupplement, strings):
1554	nRanges = readCard8(file)
1555	encoding = [".notdef"] * 256
1556	glyphID = 1
1557	for i in range(nRanges):
1558		code = readCard8(file)
1559		nLeft = readCard8(file)
1560		for glyphID in range(glyphID, glyphID + nLeft + 1):
1561			encoding[code] = charset[glyphID]
1562			code = code + 1
1563		glyphID = glyphID + 1
1564	return encoding
1565
1566
1567def packEncoding0(charset, encoding, strings):
1568	fmt = 0
1569	m = {}
1570	for code in range(len(encoding)):
1571		name = encoding[code]
1572		if name != ".notdef":
1573			m[name] = code
1574	codes = []
1575	for name in charset[1:]:
1576		code = m.get(name)
1577		codes.append(code)
1578
1579	while codes and codes[-1] is None:
1580		codes.pop()
1581
1582	data = [packCard8(fmt), packCard8(len(codes))]
1583	for code in codes:
1584		if code is None:
1585			code = 0
1586		data.append(packCard8(code))
1587	return bytesjoin(data)
1588
1589
1590def packEncoding1(charset, encoding, strings):
1591	fmt = 1
1592	m = {}
1593	for code in range(len(encoding)):
1594		name = encoding[code]
1595		if name != ".notdef":
1596			m[name] = code
1597	ranges = []
1598	first = None
1599	end = 0
1600	for name in charset[1:]:
1601		code = m.get(name, -1)
1602		if first is None:
1603			first = code
1604		elif end + 1 != code:
1605			nLeft = end - first
1606			ranges.append((first, nLeft))
1607			first = code
1608		end = code
1609	nLeft = end - first
1610	ranges.append((first, nLeft))
1611
1612	# remove unencoded glyphs at the end.
1613	while ranges and ranges[-1][0] == -1:
1614		ranges.pop()
1615
1616	data = [packCard8(fmt), packCard8(len(ranges))]
1617	for first, nLeft in ranges:
1618		if first == -1:  # unencoded
1619			first = 0
1620		data.append(packCard8(first) + packCard8(nLeft))
1621	return bytesjoin(data)
1622
1623
1624class FDArrayConverter(TableConverter):
1625
1626	def _read(self, parent, value):
1627		try:
1628			vstore = parent.VarStore
1629		except AttributeError:
1630			vstore = None
1631		file = parent.file
1632		isCFF2 = parent._isCFF2
1633		file.seek(value)
1634		fdArray = FDArrayIndex(file, isCFF2=isCFF2)
1635		fdArray.vstore = vstore
1636		fdArray.strings = parent.strings
1637		fdArray.GlobalSubrs = parent.GlobalSubrs
1638		return fdArray
1639
1640	def write(self, parent, value):
1641		return 0  # dummy value
1642
1643	def xmlRead(self, name, attrs, content, parent):
1644		fdArray = FDArrayIndex()
1645		for element in content:
1646			if isinstance(element, basestring):
1647				continue
1648			name, attrs, content = element
1649			fdArray.fromXML(name, attrs, content)
1650		return fdArray
1651
1652
1653class FDSelectConverter(SimpleConverter):
1654
1655	def _read(self, parent, value):
1656		file = parent.file
1657		file.seek(value)
1658		fdSelect = FDSelect(file, parent.numGlyphs)
1659		return fdSelect
1660
1661	def write(self, parent, value):
1662		return 0  # dummy value
1663
1664	# The FDSelect glyph data is written out to XML in the charstring keys,
1665	# so we write out only the format selector
1666	def xmlWrite(self, xmlWriter, name, value):
1667		xmlWriter.simpletag(name, [('format', value.format)])
1668		xmlWriter.newline()
1669
1670	def xmlRead(self, name, attrs, content, parent):
1671		fmt = safeEval(attrs["format"])
1672		file = None
1673		numGlyphs = None
1674		fdSelect = FDSelect(file, numGlyphs, fmt)
1675		return fdSelect
1676
1677
1678class VarStoreConverter(SimpleConverter):
1679
1680	def _read(self, parent, value):
1681		file = parent.file
1682		file.seek(value)
1683		varStore = VarStoreData(file)
1684		varStore.decompile()
1685		return varStore
1686
1687	def write(self, parent, value):
1688		return 0  # dummy value
1689
1690	def xmlWrite(self, xmlWriter, name, value):
1691		value.writeXML(xmlWriter, name)
1692
1693	def xmlRead(self, name, attrs, content, parent):
1694		varStore = VarStoreData()
1695		varStore.xmlRead(name, attrs, content, parent)
1696		return varStore
1697
1698
1699def packFDSelect0(fdSelectArray):
1700	fmt = 0
1701	data = [packCard8(fmt)]
1702	for index in fdSelectArray:
1703		data.append(packCard8(index))
1704	return bytesjoin(data)
1705
1706
1707def packFDSelect3(fdSelectArray):
1708	fmt = 3
1709	fdRanges = []
1710	lenArray = len(fdSelectArray)
1711	lastFDIndex = -1
1712	for i in range(lenArray):
1713		fdIndex = fdSelectArray[i]
1714		if lastFDIndex != fdIndex:
1715			fdRanges.append([i, fdIndex])
1716			lastFDIndex = fdIndex
1717	sentinelGID = i + 1
1718
1719	data = [packCard8(fmt)]
1720	data.append(packCard16(len(fdRanges)))
1721	for fdRange in fdRanges:
1722		data.append(packCard16(fdRange[0]))
1723		data.append(packCard8(fdRange[1]))
1724	data.append(packCard16(sentinelGID))
1725	return bytesjoin(data)
1726
1727
1728class FDSelectCompiler(object):
1729
1730	def __init__(self, fdSelect, parent):
1731		fmt = fdSelect.format
1732		fdSelectArray = fdSelect.gidArray
1733		if fmt == 0:
1734			self.data = packFDSelect0(fdSelectArray)
1735		elif fmt == 3:
1736			self.data = packFDSelect3(fdSelectArray)
1737		else:
1738			# choose smaller of the two formats
1739			data0 = packFDSelect0(fdSelectArray)
1740			data3 = packFDSelect3(fdSelectArray)
1741			if len(data0) < len(data3):
1742				self.data = data0
1743				fdSelect.format = 0
1744			else:
1745				self.data = data3
1746				fdSelect.format = 3
1747
1748		self.parent = parent
1749
1750	def setPos(self, pos, endPos):
1751		self.parent.rawDict["FDSelect"] = pos
1752
1753	def getDataLength(self):
1754		return len(self.data)
1755
1756	def toFile(self, file):
1757		file.write(self.data)
1758
1759
1760class VarStoreCompiler(object):
1761
1762	def __init__(self, varStoreData, parent):
1763		self.parent = parent
1764		if not varStoreData.data:
1765			varStoreData.compile()
1766		data = [
1767			packCard16(len(varStoreData.data)),
1768			varStoreData.data
1769		]
1770		self.data = bytesjoin(data)
1771
1772	def setPos(self, pos, endPos):
1773		self.parent.rawDict["VarStore"] = pos
1774
1775	def getDataLength(self):
1776		return len(self.data)
1777
1778	def toFile(self, file):
1779		file.write(self.data)
1780
1781
1782class ROSConverter(SimpleConverter):
1783
1784	def xmlWrite(self, xmlWriter, name, value):
1785		registry, order, supplement = value
1786		xmlWriter.simpletag(
1787			name,
1788			[
1789				('Registry', tostr(registry)),
1790				('Order', tostr(order)),
1791				('Supplement', supplement)
1792			])
1793		xmlWriter.newline()
1794
1795	def xmlRead(self, name, attrs, content, parent):
1796		return (attrs['Registry'], attrs['Order'], safeEval(attrs['Supplement']))
1797
1798topDictOperators = [
1799#	opcode		name			argument type	default	converter
1800	(25,		'maxstack',		'number',	None,	None),
1801	((12, 30),	'ROS',	('SID', 'SID', 'number'),	None,	ROSConverter()),
1802	((12, 20),	'SyntheticBase',	'number',	None,	None),
1803	(0,		'version',		'SID',		None,	None),
1804	(1,		'Notice',		'SID',		None,	Latin1Converter()),
1805	((12, 0),	'Copyright',		'SID',		None,	Latin1Converter()),
1806	(2,		'FullName',		'SID',		None,	None),
1807	((12, 38),	'FontName',		'SID',		None,	None),
1808	(3,		'FamilyName',		'SID',		None,	None),
1809	(4,		'Weight',		'SID',		None,	None),
1810	((12, 1),	'isFixedPitch',		'number',	0,	None),
1811	((12, 2),	'ItalicAngle',		'number',	0,	None),
1812	((12, 3),	'UnderlinePosition',	'number',	-100,	None),
1813	((12, 4),	'UnderlineThickness',	'number',	50,	None),
1814	((12, 5),	'PaintType',		'number',	0,	None),
1815	((12, 6),	'CharstringType',	'number',	2,	None),
1816	((12, 7),	'FontMatrix',		'array',	[0.001, 0, 0, 0.001, 0, 0],	None),
1817	(13,		'UniqueID',		'number',	None,	None),
1818	(5,		'FontBBox',		'array',	[0, 0, 0, 0],	None),
1819	((12, 8),	'StrokeWidth',		'number',	0,	None),
1820	(14,		'XUID',			'array',	None,	None),
1821	((12, 21),	'PostScript',		'SID',		None,	None),
1822	((12, 22),	'BaseFontName',		'SID',		None,	None),
1823	((12, 23),	'BaseFontBlend',	'delta',	None,	None),
1824	((12, 31),	'CIDFontVersion',	'number',	0,	None),
1825	((12, 32),	'CIDFontRevision',	'number',	0,	None),
1826	((12, 33),	'CIDFontType',		'number',	0,	None),
1827	((12, 34),	'CIDCount',		'number',	8720,	None),
1828	(15,		'charset',		'number',	None,	CharsetConverter()),
1829	((12, 35),	'UIDBase',		'number',	None,	None),
1830	(16,		'Encoding',		'number',	0,	EncodingConverter()),
1831	(18,		'Private',	('number', 'number'),	None,	PrivateDictConverter()),
1832	((12, 37),	'FDSelect',		'number',	None,	FDSelectConverter()),
1833	((12, 36),	'FDArray',		'number',	None,	FDArrayConverter()),
1834	(17,		'CharStrings',		'number',	None,	CharStringsConverter()),
1835	(24,		'VarStore',		'number',	None,	VarStoreConverter()),
1836]
1837
1838topDictOperators2 = [
1839#	opcode		name			argument type	default	converter
1840	(25,		'maxstack',		'number',	None,	None),
1841	((12, 7),	'FontMatrix',		'array',	[0.001, 0, 0, 0.001, 0, 0],	None),
1842	((12, 37),	'FDSelect',		'number',	None,	FDSelectConverter()),
1843	((12, 36),	'FDArray',		'number',	None,	FDArrayConverter()),
1844	(17,		'CharStrings',		'number',	None,	CharStringsConverter()),
1845	(24,		'VarStore',		'number',	None,	VarStoreConverter()),
1846]
1847
1848# Note! FDSelect and FDArray must both preceed CharStrings in the output XML build order,
1849# in order for the font to compile back from xml.
1850
1851kBlendDictOpName = "blend"
1852blendOp = 23
1853
1854privateDictOperators = [
1855#	opcode		name			argument type	default	converter
1856	(22,	"vsindex",		'number',	None,	None),
1857	(blendOp,	kBlendDictOpName,		'blendList',	None,	None), # This is for reading to/from XML: it not written to CFF.
1858	(6,		'BlueValues',		'delta',	None,	None),
1859	(7,		'OtherBlues',		'delta',	None,	None),
1860	(8,		'FamilyBlues',		'delta',	None,	None),
1861	(9,		'FamilyOtherBlues',	'delta',	None,	None),
1862	((12, 9),	'BlueScale',		'number',	0.039625, None),
1863	((12, 10),	'BlueShift',		'number',	7,	None),
1864	((12, 11),	'BlueFuzz',		'number',	1,	None),
1865	(10,		'StdHW',		'number',	None,	None),
1866	(11,		'StdVW',		'number',	None,	None),
1867	((12, 12),	'StemSnapH',		'delta',	None,	None),
1868	((12, 13),	'StemSnapV',		'delta',	None,	None),
1869	((12, 14),	'ForceBold',		'number',	0,	None),
1870	((12, 15),	'ForceBoldThreshold',	'number',	None,	None), # deprecated
1871	((12, 16),	'lenIV',		'number',	None,	None), # deprecated
1872	((12, 17),	'LanguageGroup',	'number',	0,	None),
1873	((12, 18),	'ExpansionFactor',	'number',	0.06,	None),
1874	((12, 19),	'initialRandomSeed',	'number',	0,	None),
1875	(20,		'defaultWidthX',	'number',	0,	None),
1876	(21,		'nominalWidthX',	'number',	0,	None),
1877	(19,		'Subrs',		'number',	None,	SubrsConverter()),
1878]
1879
1880privateDictOperators2 = [
1881#	opcode		name			argument type	default	converter
1882	(22,	"vsindex",		'number',	None,	None),
1883	(blendOp,	kBlendDictOpName,		'blendList',	None,	None), # This is for reading to/from XML: it not written to CFF.
1884	(6,		'BlueValues',		'delta',	None,	None),
1885	(7,		'OtherBlues',		'delta',	None,	None),
1886	(8,		'FamilyBlues',		'delta',	None,	None),
1887	(9,		'FamilyOtherBlues',	'delta',	None,	None),
1888	((12, 9),	'BlueScale',		'number',	0.039625, None),
1889	((12, 10),	'BlueShift',		'number',	7,	None),
1890	((12, 11),	'BlueFuzz',		'number',	1,	None),
1891	(10,		'StdHW',		'number',	None,	None),
1892	(11,		'StdVW',		'number',	None,	None),
1893	((12, 12),	'StemSnapH',		'delta',	None,	None),
1894	((12, 13),	'StemSnapV',		'delta',	None,	None),
1895	(19,		'Subrs',		'number',	None,	SubrsConverter()),
1896]
1897
1898
1899def addConverters(table):
1900	for i in range(len(table)):
1901		op, name, arg, default, conv = table[i]
1902		if conv is not None:
1903			continue
1904		if arg in ("delta", "array"):
1905			conv = ArrayConverter()
1906		elif arg == "number":
1907			conv = NumberConverter()
1908		elif arg == "SID":
1909			conv = ASCIIConverter()
1910		elif arg == 'blendList':
1911			conv = None
1912		else:
1913			assert False
1914		table[i] = op, name, arg, default, conv
1915
1916
1917addConverters(privateDictOperators)
1918addConverters(topDictOperators)
1919
1920
1921class TopDictDecompiler(psCharStrings.DictDecompiler):
1922	operators = buildOperatorDict(topDictOperators)
1923
1924
1925class PrivateDictDecompiler(psCharStrings.DictDecompiler):
1926	operators = buildOperatorDict(privateDictOperators)
1927
1928
1929class DictCompiler(object):
1930	maxBlendStack = 0
1931
1932	def __init__(self, dictObj, strings, parent, isCFF2=None):
1933		if strings:
1934			assert isinstance(strings, IndexedStrings)
1935		if isCFF2 is None and hasattr(parent, "isCFF2"):
1936			isCFF2 = parent.isCFF2
1937			assert isCFF2 is not None
1938		self.isCFF2 = isCFF2
1939		self.dictObj = dictObj
1940		self.strings = strings
1941		self.parent = parent
1942		rawDict = {}
1943		for name in dictObj.order:
1944			value = getattr(dictObj, name, None)
1945			if value is None:
1946				continue
1947			conv = dictObj.converters[name]
1948			value = conv.write(dictObj, value)
1949			if value == dictObj.defaults.get(name):
1950				continue
1951			rawDict[name] = value
1952		self.rawDict = rawDict
1953
1954	def setPos(self, pos, endPos):
1955		pass
1956
1957	def getDataLength(self):
1958		return len(self.compile("getDataLength"))
1959
1960	def compile(self, reason):
1961		log.log(DEBUG, "-- compiling %s for %s", self.__class__.__name__, reason)
1962		rawDict = self.rawDict
1963		data = []
1964		for name in self.dictObj.order:
1965			value = rawDict.get(name)
1966			if value is None:
1967				continue
1968			op, argType = self.opcodes[name]
1969			if isinstance(argType, tuple):
1970				l = len(argType)
1971				assert len(value) == l, "value doesn't match arg type"
1972				for i in range(l):
1973					arg = argType[i]
1974					v = value[i]
1975					arghandler = getattr(self, "arg_" + arg)
1976					data.append(arghandler(v))
1977			else:
1978				arghandler = getattr(self, "arg_" + argType)
1979				data.append(arghandler(value))
1980			data.append(op)
1981		data = bytesjoin(data)
1982		return data
1983
1984	def toFile(self, file):
1985		data = self.compile("toFile")
1986		file.write(data)
1987
1988	def arg_number(self, num):
1989		if isinstance(num, list):
1990			data = [encodeNumber(val) for val in num]
1991			data.append(encodeNumber(1))
1992			data.append(bytechr(blendOp))
1993			datum = bytesjoin(data)
1994		else:
1995			datum = encodeNumber(num)
1996		return datum
1997
1998	def arg_SID(self, s):
1999		return psCharStrings.encodeIntCFF(self.strings.getSID(s))
2000
2001	def arg_array(self, value):
2002		data = []
2003		for num in value:
2004			data.append(self.arg_number(num))
2005		return bytesjoin(data)
2006
2007	def arg_delta(self, value):
2008		if not value:
2009			return b""
2010		val0 = value[0]
2011		if isinstance(val0, list):
2012			data = self.arg_delta_blend(value)
2013		else:
2014			out = []
2015			last = 0
2016			for v in value:
2017				out.append(v - last)
2018				last = v
2019			data = []
2020			for num in out:
2021				data.append(encodeNumber(num))
2022		return bytesjoin(data)
2023
2024
2025	def arg_delta_blend(self, value):
2026		""" A delta list with blend lists has to be *all* blend lists.
2027		The value is a list is arranged as follows.
2028		[
2029		   [V0, d0..dn]
2030		   [V1, d0..dn]
2031		   ...
2032		   [Vm, d0..dn]
2033		]
2034		V is the absolute coordinate value from the default font, and d0-dn are
2035		the delta values from the n regions. Each V is an absolute coordinate
2036		from the default font.
2037		We want to return a list:
2038		[
2039		   [v0, v1..vm]
2040		   [d0..dn]
2041		   ...
2042		   [d0..dn]
2043		   numBlends
2044		   blendOp
2045		]
2046		where each v is relative to the previous default font value.
2047		"""
2048		numMasters = len(value[0])
2049		numBlends = len(value)
2050		numStack = (numBlends * numMasters) + 1
2051		if numStack > self.maxBlendStack:
2052			# Figure out the max number of value we can blend
2053			# and divide this list up into chunks of that size.
2054
2055			numBlendValues = int((self.maxBlendStack - 1) / numMasters)
2056			out = []
2057			while True:
2058				numVal = min(len(value), numBlendValues)
2059				if numVal == 0:
2060					break
2061				valList = value[0:numVal]
2062				out1 = self.arg_delta_blend(valList)
2063				out.extend(out1)
2064				value = value[numVal:]
2065		else:
2066			firstList = [0] * numBlends
2067			deltaList = [None] * numBlends
2068			i = 0
2069			prevVal = 0
2070			while i < numBlends:
2071				# For PrivateDict BlueValues, the default font
2072				# values are absolute, not relative.
2073				# Must convert these back to relative coordinates
2074				# befor writing to CFF2.
2075				defaultValue = value[i][0]
2076				firstList[i] = defaultValue - prevVal
2077				prevVal = defaultValue
2078				deltaList[i] = value[i][1:]
2079				i += 1
2080
2081			relValueList = firstList
2082			for blendList in deltaList:
2083				relValueList.extend(blendList)
2084			out = [encodeNumber(val) for val in relValueList]
2085			out.append(encodeNumber(numBlends))
2086			out.append(bytechr(blendOp))
2087		return out
2088
2089
2090def encodeNumber(num):
2091	if isinstance(num, float):
2092		return psCharStrings.encodeFloat(num)
2093	else:
2094		return psCharStrings.encodeIntCFF(num)
2095
2096
2097class TopDictCompiler(DictCompiler):
2098
2099	opcodes = buildOpcodeDict(topDictOperators)
2100
2101	def getChildren(self, strings):
2102		isCFF2 = self.isCFF2
2103		children = []
2104		if self.dictObj.cff2GetGlyphOrder is None:
2105			if hasattr(self.dictObj, "charset") and self.dictObj.charset:
2106				if hasattr(self.dictObj, "ROS"):  # aka isCID
2107					charsetCode = None
2108				else:
2109					charsetCode = getStdCharSet(self.dictObj.charset)
2110				if charsetCode is None:
2111					children.append(CharsetCompiler(strings, self.dictObj.charset, self))
2112				else:
2113					self.rawDict["charset"] = charsetCode
2114			if hasattr(self.dictObj, "Encoding") and self.dictObj.Encoding:
2115				encoding = self.dictObj.Encoding
2116				if not isinstance(encoding, basestring):
2117					children.append(EncodingCompiler(strings, encoding, self))
2118		else:
2119			if hasattr(self.dictObj, "VarStore"):
2120				varStoreData = self.dictObj.VarStore
2121				varStoreComp = VarStoreCompiler(varStoreData, self)
2122				children.append(varStoreComp)
2123		if hasattr(self.dictObj, "FDSelect"):
2124			# I have not yet supported merging a ttx CFF-CID font, as there are
2125			# interesting issues about merging the FDArrays. Here I assume that
2126			# either the font was read from XML, and the FDSelect indices are all
2127			# in the charstring data, or the FDSelect array is already fully defined.
2128			fdSelect = self.dictObj.FDSelect
2129			# probably read in from XML; assume fdIndex in CharString data
2130			if len(fdSelect) == 0:
2131				charStrings = self.dictObj.CharStrings
2132				for name in self.dictObj.charset:
2133					fdSelect.append(charStrings[name].fdSelectIndex)
2134			fdSelectComp = FDSelectCompiler(fdSelect, self)
2135			children.append(fdSelectComp)
2136		if hasattr(self.dictObj, "CharStrings"):
2137			items = []
2138			charStrings = self.dictObj.CharStrings
2139			for name in self.dictObj.charset:
2140				items.append(charStrings[name])
2141			charStringsComp = CharStringsCompiler(
2142				items, strings, self, isCFF2=isCFF2)
2143			children.append(charStringsComp)
2144		if hasattr(self.dictObj, "FDArray"):
2145			# I have not yet supported merging a ttx CFF-CID font, as there are
2146			# interesting issues about merging the FDArrays. Here I assume that the
2147			# FDArray info is correct and complete.
2148			fdArrayIndexComp = self.dictObj.FDArray.getCompiler(strings, self)
2149			children.append(fdArrayIndexComp)
2150			children.extend(fdArrayIndexComp.getChildren(strings))
2151		if hasattr(self.dictObj, "Private"):
2152			privComp = self.dictObj.Private.getCompiler(strings, self)
2153			children.append(privComp)
2154			children.extend(privComp.getChildren(strings))
2155		return children
2156
2157
2158class FontDictCompiler(DictCompiler):
2159	opcodes = buildOpcodeDict(topDictOperators)
2160
2161	def __init__(self, dictObj, strings, parent, isCFF2=None):
2162		super(FontDictCompiler, self).__init__(dictObj, strings, parent, isCFF2=isCFF2)
2163		#
2164		# We now take some effort to detect if there were any key/value pairs
2165		# supplied that were ignored in the FontDict context, and issue a warning
2166		# for those cases.
2167		#
2168		ignoredNames = []
2169		dictObj = self.dictObj
2170		for name in sorted(set(dictObj.converters) - set(dictObj.order)):
2171			if name in dictObj.rawDict:
2172				# The font was directly read from binary. In this
2173				# case, we want to report *all* "useless" key/value
2174				# pairs that are in the font, not just the ones that
2175				# are different from the default.
2176				ignoredNames.append(name)
2177			else:
2178				# The font was probably read from a TTX file. We only
2179				# warn about keys whos value is not the default. The
2180				# ones that have the default value will not be written
2181				# to binary anyway.
2182				default = dictObj.defaults.get(name)
2183				if default is not None:
2184					conv = dictObj.converters[name]
2185					default = conv.read(dictObj, default)
2186				if getattr(dictObj, name, None) != default:
2187					ignoredNames.append(name)
2188		if ignoredNames:
2189			log.warning(
2190				"Some CFF FDArray/FontDict keys were ignored upon compile: " +
2191				" ".join(sorted(ignoredNames)))
2192
2193	def getChildren(self, strings):
2194		children = []
2195		if hasattr(self.dictObj, "Private"):
2196			privComp = self.dictObj.Private.getCompiler(strings, self)
2197			children.append(privComp)
2198			children.extend(privComp.getChildren(strings))
2199		return children
2200
2201
2202class PrivateDictCompiler(DictCompiler):
2203
2204	maxBlendStack = maxStackLimit
2205	opcodes = buildOpcodeDict(privateDictOperators)
2206
2207	def setPos(self, pos, endPos):
2208		size = endPos - pos
2209		self.parent.rawDict["Private"] = size, pos
2210		self.pos = pos
2211
2212	def getChildren(self, strings):
2213		children = []
2214		if hasattr(self.dictObj, "Subrs"):
2215			children.append(self.dictObj.Subrs.getCompiler(strings, self))
2216		return children
2217
2218
2219class BaseDict(object):
2220
2221	def __init__(self, strings=None, file=None, offset=None, isCFF2=None):
2222		assert (isCFF2 is None) == (file is None)
2223		self.rawDict = {}
2224		self.skipNames = []
2225		self.strings = strings
2226		if file is None:
2227			return
2228		self._isCFF2 = isCFF2
2229		self.file = file
2230		if offset is not None:
2231			log.log(DEBUG, "loading %s at %s", self.__class__.__name__, offset)
2232			self.offset = offset
2233
2234	def decompile(self, data):
2235		log.log(DEBUG, "    length %s is %d", self.__class__.__name__, len(data))
2236		dec = self.decompilerClass(self.strings, self)
2237		dec.decompile(data)
2238		self.rawDict = dec.getDict()
2239		self.postDecompile()
2240
2241	def postDecompile(self):
2242		pass
2243
2244	def getCompiler(self, strings, parent, isCFF2=None):
2245		return self.compilerClass(self, strings, parent, isCFF2=isCFF2)
2246
2247	def __getattr__(self, name):
2248		if name[:2] == name[-2:] == "__":
2249			# to make deepcopy() and pickle.load() work, we need to signal with
2250			# AttributeError that dunder methods like '__deepcopy__' or '__getstate__'
2251			# aren't implemented. For more details, see:
2252			# https://github.com/fonttools/fonttools/pull/1488
2253			raise AttributeError(name)
2254		value = self.rawDict.get(name, None)
2255		if value is None:
2256			value = self.defaults.get(name)
2257		if value is None:
2258			raise AttributeError(name)
2259		conv = self.converters[name]
2260		value = conv.read(self, value)
2261		setattr(self, name, value)
2262		return value
2263
2264	def toXML(self, xmlWriter):
2265		for name in self.order:
2266			if name in self.skipNames:
2267				continue
2268			value = getattr(self, name, None)
2269			# XXX For "charset" we never skip calling xmlWrite even if the
2270			# value is None, so we always write the following XML comment:
2271			#
2272			# <!-- charset is dumped separately as the 'GlyphOrder' element -->
2273			#
2274			# Charset is None when 'CFF ' table is imported from XML into an
2275			# empty TTFont(). By writing this comment all the time, we obtain
2276			# the same XML output whether roundtripping XML-to-XML or
2277			# dumping binary-to-XML
2278			if value is None and name != "charset":
2279				continue
2280			conv = self.converters[name]
2281			conv.xmlWrite(xmlWriter, name, value)
2282		ignoredNames = set(self.rawDict) - set(self.order)
2283		if ignoredNames:
2284			xmlWriter.comment(
2285				"some keys were ignored: %s" % " ".join(sorted(ignoredNames)))
2286			xmlWriter.newline()
2287
2288	def fromXML(self, name, attrs, content):
2289		conv = self.converters[name]
2290		value = conv.xmlRead(name, attrs, content, self)
2291		setattr(self, name, value)
2292
2293
2294class TopDict(BaseDict):
2295
2296	defaults = buildDefaults(topDictOperators)
2297	converters = buildConverters(topDictOperators)
2298	compilerClass = TopDictCompiler
2299	order = buildOrder(topDictOperators)
2300	decompilerClass = TopDictDecompiler
2301
2302	def __init__(self, strings=None, file=None, offset=None,
2303			GlobalSubrs=None, cff2GetGlyphOrder=None, isCFF2=None):
2304		super(TopDict, self).__init__(strings, file, offset, isCFF2=isCFF2)
2305		self.cff2GetGlyphOrder = cff2GetGlyphOrder
2306		self.GlobalSubrs = GlobalSubrs
2307		if isCFF2:
2308			self.defaults = buildDefaults(topDictOperators2)
2309			self.charset = cff2GetGlyphOrder()
2310			self.order = buildOrder(topDictOperators2)
2311		else:
2312			self.defaults = buildDefaults(topDictOperators)
2313			self.order = buildOrder(topDictOperators)
2314
2315	def getGlyphOrder(self):
2316		return self.charset
2317
2318	def postDecompile(self):
2319		offset = self.rawDict.get("CharStrings")
2320		if offset is None:
2321			return
2322		# get the number of glyphs beforehand.
2323		self.file.seek(offset)
2324		if self._isCFF2:
2325			self.numGlyphs = readCard32(self.file)
2326		else:
2327			self.numGlyphs = readCard16(self.file)
2328
2329	def toXML(self, xmlWriter):
2330		if hasattr(self, "CharStrings"):
2331			self.decompileAllCharStrings()
2332		if hasattr(self, "ROS"):
2333			self.skipNames = ['Encoding']
2334		if not hasattr(self, "ROS") or not hasattr(self, "CharStrings"):
2335			# these values have default values, but I only want them to show up
2336			# in CID fonts.
2337			self.skipNames = [
2338				'CIDFontVersion', 'CIDFontRevision', 'CIDFontType', 'CIDCount']
2339		BaseDict.toXML(self, xmlWriter)
2340
2341	def decompileAllCharStrings(self):
2342		# Make sure that all the Private Dicts have been instantiated.
2343		for i, charString in enumerate(self.CharStrings.values()):
2344			try:
2345				charString.decompile()
2346			except:
2347				log.error("Error in charstring %s", i)
2348				raise
2349
2350	def recalcFontBBox(self):
2351		fontBBox = None
2352		for charString in self.CharStrings.values():
2353			bounds = charString.calcBounds(self.CharStrings)
2354			if bounds is not None:
2355				if fontBBox is not None:
2356					fontBBox = unionRect(fontBBox, bounds)
2357				else:
2358					fontBBox = bounds
2359
2360		if fontBBox is None:
2361			self.FontBBox = self.defaults['FontBBox'][:]
2362		else:
2363			self.FontBBox = list(intRect(fontBBox))
2364
2365
2366class FontDict(BaseDict):
2367	#
2368	# Since fonttools used to pass a lot of fields that are not relevant in the FDArray
2369	# FontDict, there are 'ttx' files in the wild that contain all these. These got in
2370	# the ttx files because fonttools writes explicit values for all the TopDict default
2371	# values. These are not actually illegal in the context of an FDArray FontDict - you
2372	# can legally, per spec, put any arbitrary key/value pair in a FontDict - but are
2373	# useless since current major company CFF interpreters ignore anything but the set
2374	# listed in this file. So, we just silently skip them. An exception is Weight: this
2375	# is not used by any interpreter, but some foundries have asked that this be
2376	# supported in FDArray FontDicts just to preserve information about the design when
2377	# the font is being inspected.
2378	#
2379	# On top of that, there are fonts out there that contain such useless FontDict values.
2380	#
2381	# By subclassing TopDict, we *allow* all key/values from TopDict, both when reading
2382	# from binary or when reading from XML, but by overriding `order` with a limited
2383	# list of names, we ensure that only the useful names ever get exported to XML and
2384	# ever get compiled into the binary font.
2385	#
2386	# We override compilerClass so we can warn about "useless" key/value pairs, either
2387	# from the original binary font or from TTX input.
2388	#
2389	# See:
2390	# - https://github.com/fonttools/fonttools/issues/740
2391	# - https://github.com/fonttools/fonttools/issues/601
2392	# - https://github.com/adobe-type-tools/afdko/issues/137
2393	#
2394	defaults = {}
2395	converters = buildConverters(topDictOperators)
2396	compilerClass = FontDictCompiler
2397	orderCFF = ['FontName', 'FontMatrix', 'Weight', 'Private']
2398	orderCFF2 = ['Private']
2399	decompilerClass = TopDictDecompiler
2400
2401	def __init__(self, strings=None, file=None, offset=None,
2402			GlobalSubrs=None, isCFF2=None, vstore=None):
2403		super(FontDict, self).__init__(strings, file, offset, isCFF2=isCFF2)
2404		self.vstore = vstore
2405		self.setCFF2(isCFF2)
2406
2407	def setCFF2(self, isCFF2):
2408		# isCFF2 may be None.
2409		if isCFF2:
2410			self.order = self.orderCFF2
2411			self._isCFF2 = True
2412		else:
2413			self.order = self.orderCFF
2414			self._isCFF2 = False
2415
2416
2417class PrivateDict(BaseDict):
2418	defaults = buildDefaults(privateDictOperators)
2419	converters = buildConverters(privateDictOperators)
2420	order = buildOrder(privateDictOperators)
2421	decompilerClass = PrivateDictDecompiler
2422	compilerClass = PrivateDictCompiler
2423
2424	def __init__(self, strings=None, file=None, offset=None, isCFF2=None,
2425			vstore=None):
2426		super(PrivateDict, self).__init__(strings, file, offset, isCFF2=isCFF2)
2427		self.vstore = vstore
2428		if isCFF2:
2429			self.defaults = buildDefaults(privateDictOperators2)
2430			self.order = buildOrder(privateDictOperators2)
2431			# Provide dummy values. This avoids needing to provide
2432			# an isCFF2 state in a lot of places.
2433			self.nominalWidthX = self.defaultWidthX = None
2434		else:
2435			self.defaults = buildDefaults(privateDictOperators)
2436			self.order = buildOrder(privateDictOperators)
2437
2438	@property
2439	def in_cff2(self):
2440		return self._isCFF2
2441
2442	def getNumRegions(self, vi=None):  # called from misc/psCharStrings.py
2443		# if getNumRegions is being called, we can assume that VarStore exists.
2444		if vi is None:
2445			if hasattr(self, 'vsindex'):
2446				vi = self.vsindex
2447			else:
2448				vi = 0
2449		numRegions = self.vstore.getNumRegions(vi)
2450		return numRegions
2451
2452
2453class IndexedStrings(object):
2454
2455	"""SID -> string mapping."""
2456
2457	def __init__(self, file=None):
2458		if file is None:
2459			strings = []
2460		else:
2461			strings = [
2462				tostr(s, encoding="latin1")
2463				for s in Index(file, isCFF2=False)
2464			]
2465		self.strings = strings
2466
2467	def getCompiler(self):
2468		return IndexedStringsCompiler(self, None, self, isCFF2=False)
2469
2470	def __len__(self):
2471		return len(self.strings)
2472
2473	def __getitem__(self, SID):
2474		if SID < cffStandardStringCount:
2475			return cffStandardStrings[SID]
2476		else:
2477			return self.strings[SID - cffStandardStringCount]
2478
2479	def getSID(self, s):
2480		if not hasattr(self, "stringMapping"):
2481			self.buildStringMapping()
2482		s = tostr(s, encoding="latin1")
2483		if s in cffStandardStringMapping:
2484			SID = cffStandardStringMapping[s]
2485		elif s in self.stringMapping:
2486			SID = self.stringMapping[s]
2487		else:
2488			SID = len(self.strings) + cffStandardStringCount
2489			self.strings.append(s)
2490			self.stringMapping[s] = SID
2491		return SID
2492
2493	def getStrings(self):
2494		return self.strings
2495
2496	def buildStringMapping(self):
2497		self.stringMapping = {}
2498		for index in range(len(self.strings)):
2499			self.stringMapping[self.strings[index]] = index + cffStandardStringCount
2500
2501
2502# The 391 Standard Strings as used in the CFF format.
2503# from Adobe Technical None #5176, version 1.0, 18 March 1998
2504
2505cffStandardStrings = ['.notdef', 'space', 'exclam', 'quotedbl', 'numbersign',
2506		'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright',
2507		'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one',
2508		'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon',
2509		'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C',
2510		'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
2511		'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash',
2512		'bracketright', 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c',
2513		'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
2514		's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright',
2515		'asciitilde', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin',
2516		'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft',
2517		'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger',
2518		'daggerdbl', 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase',
2519		'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand',
2520		'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve',
2521		'dotaccent', 'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron',
2522		'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae',
2523		'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior',
2524		'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn',
2525		'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters',
2526		'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior',
2527		'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring',
2528		'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave',
2529		'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute',
2530		'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute',
2531		'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron',
2532		'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla',
2533		'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex',
2534		'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis',
2535		'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave',
2536		'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall',
2537		'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall',
2538		'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader',
2539		'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle',
2540		'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle',
2541		'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior',
2542		'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior',
2543		'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior',
2544		'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', 'parenleftinferior',
2545		'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall',
2546		'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall',
2547		'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall',
2548		'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall',
2549		'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall',
2550		'exclamdownsmall', 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall',
2551		'Dieresissmall', 'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall',
2552		'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall',
2553		'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths',
2554		'onethird', 'twothirds', 'zerosuperior', 'foursuperior', 'fivesuperior',
2555		'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior',
2556		'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior',
2557		'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior',
2558		'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall',
2559		'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall',
2560		'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall',
2561		'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall',
2562		'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall',
2563		'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall',
2564		'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall',
2565		'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', '001.001', '001.002',
2566		'001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman',
2567		'Semibold'
2568]
2569
2570cffStandardStringCount = 391
2571assert len(cffStandardStrings) == cffStandardStringCount
2572# build reverse mapping
2573cffStandardStringMapping = {}
2574for _i in range(cffStandardStringCount):
2575	cffStandardStringMapping[cffStandardStrings[_i]] = _i
2576
2577cffISOAdobeStrings = [".notdef", "space", "exclam", "quotedbl", "numbersign",
2578"dollar", "percent", "ampersand", "quoteright", "parenleft", "parenright",
2579"asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two",
2580"three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon",
2581"less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G",
2582"H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W",
2583"X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum",
2584"underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j",
2585"k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
2586"braceleft", "bar", "braceright", "asciitilde", "exclamdown", "cent",
2587"sterling", "fraction", "yen", "florin", "section", "currency", "quotesingle",
2588"quotedblleft", "guillemotleft", "guilsinglleft", "guilsinglright", "fi", "fl",
2589"endash", "dagger", "daggerdbl", "periodcentered", "paragraph", "bullet",
2590"quotesinglbase", "quotedblbase", "quotedblright", "guillemotright", "ellipsis",
2591"perthousand", "questiondown", "grave", "acute", "circumflex", "tilde",
2592"macron", "breve", "dotaccent", "dieresis", "ring", "cedilla", "hungarumlaut",
2593"ogonek", "caron", "emdash", "AE", "ordfeminine", "Lslash", "Oslash", "OE",
2594"ordmasculine", "ae", "dotlessi", "lslash", "oslash", "oe", "germandbls",
2595"onesuperior", "logicalnot", "mu", "trademark", "Eth", "onehalf", "plusminus",
2596"Thorn", "onequarter", "divide", "brokenbar", "degree", "thorn",
2597"threequarters", "twosuperior", "registered", "minus", "eth", "multiply",
2598"threesuperior", "copyright", "Aacute", "Acircumflex", "Adieresis", "Agrave",
2599"Aring", "Atilde", "Ccedilla", "Eacute", "Ecircumflex", "Edieresis", "Egrave",
2600"Iacute", "Icircumflex", "Idieresis", "Igrave", "Ntilde", "Oacute",
2601"Ocircumflex", "Odieresis", "Ograve", "Otilde", "Scaron", "Uacute",
2602"Ucircumflex", "Udieresis", "Ugrave", "Yacute", "Ydieresis", "Zcaron", "aacute",
2603"acircumflex", "adieresis", "agrave", "aring", "atilde", "ccedilla", "eacute",
2604"ecircumflex", "edieresis", "egrave", "iacute", "icircumflex", "idieresis",
2605"igrave", "ntilde", "oacute", "ocircumflex", "odieresis", "ograve", "otilde",
2606"scaron", "uacute", "ucircumflex", "udieresis", "ugrave", "yacute", "ydieresis",
2607"zcaron"]
2608
2609cffISOAdobeStringCount = 229
2610assert len(cffISOAdobeStrings) == cffISOAdobeStringCount
2611
2612cffIExpertStrings = [".notdef", "space", "exclamsmall", "Hungarumlautsmall",
2613"dollaroldstyle", "dollarsuperior", "ampersandsmall", "Acutesmall",
2614"parenleftsuperior", "parenrightsuperior", "twodotenleader", "onedotenleader",
2615"comma", "hyphen", "period", "fraction", "zerooldstyle", "oneoldstyle",
2616"twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle",
2617"sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", "semicolon",
2618"commasuperior", "threequartersemdash", "periodsuperior", "questionsmall",
2619"asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior",
2620"lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior",
2621"tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior",
2622"parenrightinferior", "Circumflexsmall", "hyphensuperior", "Gravesmall",
2623"Asmall", "Bsmall", "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall",
2624"Ismall", "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall",
2625"Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall", "Xsmall",
2626"Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah", "Tildesmall",
2627"exclamdownsmall", "centoldstyle", "Lslashsmall", "Scaronsmall", "Zcaronsmall",
2628"Dieresissmall", "Brevesmall", "Caronsmall", "Dotaccentsmall", "Macronsmall",
2629"figuredash", "hypheninferior", "Ogoneksmall", "Ringsmall", "Cedillasmall",
2630"onequarter", "onehalf", "threequarters", "questiondownsmall", "oneeighth",
2631"threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds",
2632"zerosuperior", "onesuperior", "twosuperior", "threesuperior", "foursuperior",
2633"fivesuperior", "sixsuperior", "sevensuperior", "eightsuperior", "ninesuperior",
2634"zeroinferior", "oneinferior", "twoinferior", "threeinferior", "fourinferior",
2635"fiveinferior", "sixinferior", "seveninferior", "eightinferior", "nineinferior",
2636"centinferior", "dollarinferior", "periodinferior", "commainferior",
2637"Agravesmall", "Aacutesmall", "Acircumflexsmall", "Atildesmall",
2638"Adieresissmall", "Aringsmall", "AEsmall", "Ccedillasmall", "Egravesmall",
2639"Eacutesmall", "Ecircumflexsmall", "Edieresissmall", "Igravesmall",
2640"Iacutesmall", "Icircumflexsmall", "Idieresissmall", "Ethsmall", "Ntildesmall",
2641"Ogravesmall", "Oacutesmall", "Ocircumflexsmall", "Otildesmall",
2642"Odieresissmall", "OEsmall", "Oslashsmall", "Ugravesmall", "Uacutesmall",
2643"Ucircumflexsmall", "Udieresissmall", "Yacutesmall", "Thornsmall",
2644"Ydieresissmall"]
2645
2646cffExpertStringCount = 166
2647assert len(cffIExpertStrings) == cffExpertStringCount
2648
2649cffExpertSubsetStrings = [".notdef", "space", "dollaroldstyle",
2650"dollarsuperior", "parenleftsuperior", "parenrightsuperior", "twodotenleader",
2651"onedotenleader", "comma", "hyphen", "period", "fraction", "zerooldstyle",
2652"oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle",
2653"sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon",
2654"semicolon", "commasuperior", "threequartersemdash", "periodsuperior",
2655"asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior",
2656"lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior",
2657"tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior",
2658"parenrightinferior", "hyphensuperior", "colonmonetary", "onefitted", "rupiah",
2659"centoldstyle", "figuredash", "hypheninferior", "onequarter", "onehalf",
2660"threequarters", "oneeighth", "threeeighths", "fiveeighths", "seveneighths",
2661"onethird", "twothirds", "zerosuperior", "onesuperior", "twosuperior",
2662"threesuperior", "foursuperior", "fivesuperior", "sixsuperior", "sevensuperior",
2663"eightsuperior", "ninesuperior", "zeroinferior", "oneinferior", "twoinferior",
2664"threeinferior", "fourinferior", "fiveinferior", "sixinferior", "seveninferior",
2665"eightinferior", "nineinferior", "centinferior", "dollarinferior",
2666"periodinferior", "commainferior"]
2667
2668cffExpertSubsetStringCount = 87
2669assert len(cffExpertSubsetStrings) == cffExpertSubsetStringCount
2670