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