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