# Source code for fontTools.cffLib

    """cffLib: read/write Adobe CFF fonts

    OpenType fonts with PostScript outlines contain a completely independent
    font file, Adobe's *Compact Font Format*. So dealing with OpenType fonts
    requires also dealing with CFF. This module allows you to read and write
    fonts written in the CFF format.

    In 2016, OpenType 1.8 introduced the `CFF2 <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2>`_
    format which, along with other changes, extended the CFF format to deal with
    the demands of variable fonts. This module parses both original CFF and CFF2.

    """

    from fontTools.misc import sstruct
    from fontTools.misc import psCharStrings
    from fontTools.misc.arrayTools import unionRect, intRect
    from fontTools.misc.textTools import bytechr, byteord, bytesjoin, tobytes, tostr, safeEval
    from fontTools.ttLib import TTFont
    from fontTools.ttLib.tables.otBase import OTTableWriter
    from fontTools.ttLib.tables.otBase import OTTableReader
    from fontTools.ttLib.tables import otTables as ot
    from io import BytesIO
    import struct
    import logging
    import re

    # mute cffLib debug messages when running ttx in verbose mode
    DEBUG = logging.DEBUG - 1
    log = logging.getLogger(__name__)

    cffHeaderFormat = """
    	major:   B
    	minor:   B
    	hdrSize: B
    """

    maxStackLimit = 513
    # maxstack operator has been deprecated. max stack is now always 513.

    class StopHintCountEvent(Exception):
    	pass

    class _DesubroutinizingT2Decompiler(psCharStrings.SimpleT2Decompiler):
    	stop_hintcount_ops = ("op_hintmask", "op_cntrmask", "op_rmoveto", "op_hmoveto",
    							"op_vmoveto")

    	def __init__(self, localSubrs, globalSubrs, private=None):
    		psCharStrings.SimpleT2Decompiler.__init__(self, localSubrs, globalSubrs,
    												private)

    	def execute(self, charString):
    		self.need_hintcount = True  # until proven otherwise
    		for op_name in self.stop_hintcount_ops:
    			setattr(self, op_name, self.stop_hint_count)

    		if hasattr(charString, '_desubroutinized'):
    			# If a charstring has already been desubroutinized, we will still
    			# need to execute it if we need to count hints in order to
    			# compute the byte length for mask arguments, and haven't finished
    			# counting hints pairs.
    			if self.need_hintcount and self.callingStack:
    				try:
    					psCharStrings.SimpleT2Decompiler.execute(self, charString)
    				except StopHintCountEvent:
    					del self.callingStack[-1]
    			return

    		charString._patches = []
    		psCharStrings.SimpleT2Decompiler.execute(self, charString)
    		desubroutinized = charString.program[:]
    		for idx, expansion in reversed(charString._patches):
    			assert idx >= 2
    			assert desubroutinized[idx - 1] in ['callsubr', 'callgsubr'], desubroutinized[idx - 1]
    			assert type(desubroutinized[idx - 2]) == int
    			if expansion[-1] == 'return':
    				expansion = expansion[:-1]
    			desubroutinized[idx-2:idx] = expansion
    		if not self.private.in_cff2:
    			if 'endchar' in desubroutinized:
    				# Cut off after first endchar
    				desubroutinized = desubroutinized[:desubroutinized.index('endchar') + 1]
    			else:
    				if not len(desubroutinized) or desubroutinized[-1] != 'return':
    					desubroutinized.append('return')

    		charString._desubroutinized = desubroutinized
    		del charString._patches

    	def op_callsubr(self, index):
    		subr = self.localSubrs[self.operandStack[-1]+self.localBias]
    		psCharStrings.SimpleT2Decompiler.op_callsubr(self, index)
    		self.processSubr(index, subr)

    	def op_callgsubr(self, index):
    		subr = self.globalSubrs[self.operandStack[-1]+self.globalBias]
    		psCharStrings.SimpleT2Decompiler.op_callgsubr(self, index)
    		self.processSubr(index, subr)

    	def stop_hint_count(self, *args):
    		self.need_hintcount = False
    		for op_name in self.stop_hintcount_ops:
    			setattr(self, op_name, None)
    		cs = self.callingStack[-1]
    		if hasattr(cs, '_desubroutinized'):
    			raise StopHintCountEvent()

    	def op_hintmask(self, index):
    		psCharStrings.SimpleT2Decompiler.op_hintmask(self, index)
    		if self.need_hintcount:
    			self.stop_hint_count()

    	def processSubr(self, index, subr):
    		cs = self.callingStack[-1]
    		if not hasattr(cs, '_desubroutinized'):
    			cs._patches.append((index, subr._desubroutinized))

    class CFFFontSet(object):
    	"""A CFF font "file" can contain more than one font, although this is
    	extremely rare (and not allowed within OpenType fonts).

    	This class is the entry point for parsing a CFF table. To actually
    	manipulate the data inside the CFF font, you will want to access the
    	``CFFFontSet``'s :class:`TopDict` object. To do this, a ``CFFFontSet``
    	object can either be treated as a dictionary (with appropriate
    	``keys()`` and ``values()`` methods) mapping font names to :class:`TopDict`
    	objects, or as a list.

    	.. code:: python

    		from fontTools import ttLib
    		tt = ttLib.TTFont("Tests/cffLib/data/LinLibertine_RBI.otf")
    		tt["CFF "].cff
    		# <fontTools.cffLib.CFFFontSet object at 0x101e24c90>
    		tt["CFF "].cff[0] # Here's your actual font data
    		# <fontTools.cffLib.TopDict object at 0x1020f1fd0>

    	"""

    	def decompile(self, file, otFont, isCFF2=None):
    		"""Parse a binary CFF file into an internal representation. ``file``
    		should be a file handle object. ``otFont`` is the top-level
    		:py:class:`fontTools.ttLib.ttFont.TTFont` object containing this CFF file.

    		If ``isCFF2`` is passed and set to ``True`` or ``False``, then the
    		library makes an assertion that the CFF header is of the appropriate
    		version.
    		"""

    		self.otFont = otFont
    		sstruct.unpack(cffHeaderFormat, file.read(3), self)
    		if isCFF2 is not None:
    			# called from ttLib: assert 'major' as read from file matches the
    			# expected version
    			expected_major = (2 if isCFF2 else 1)
    			if self.major != expected_major:
    				raise ValueError(
    					"Invalid CFF 'major' version: expected %d, found %d" %
    					(expected_major, self.major))
    		else:
    			# use 'major' version from file to determine if isCFF2
    			assert self.major in (1, 2), "Unknown CFF format"
    			isCFF2 = self.major == 2
    		if not isCFF2:
    			self.offSize = struct.unpack("B", file.read(1))[0]
    			file.seek(self.hdrSize)
    			self.fontNames = list(tostr(s) for s in Index(file, isCFF2=isCFF2))
    			self.topDictIndex = TopDictIndex(file, isCFF2=isCFF2)
    			self.strings = IndexedStrings(file)
    		else:  # isCFF2
    			self.topDictSize = struct.unpack(">H", file.read(2))[0]
    			file.seek(self.hdrSize)
    			self.fontNames = ["CFF2Font"]
    			cff2GetGlyphOrder = otFont.getGlyphOrder
    			# in CFF2, offsetSize is the size of the TopDict data.
    			self.topDictIndex = TopDictIndex(
    				file, cff2GetGlyphOrder, self.topDictSize, isCFF2=isCFF2)
    			self.strings = None
    		self.GlobalSubrs = GlobalSubrsIndex(file, isCFF2=isCFF2)
    		self.topDictIndex.strings = self.strings
    		self.topDictIndex.GlobalSubrs = self.GlobalSubrs

    	def __len__(self):
    		return len(self.fontNames)

    	def keys(self):
    		return list(self.fontNames)

    	def values(self):
    		return self.topDictIndex

    	def __getitem__(self, nameOrIndex):
    		""" Return TopDict instance identified by name (str) or index (int
    		or any object that implements `__index__`).
    		"""
    		if hasattr(nameOrIndex, "__index__"):
    			index = nameOrIndex.__index__()
    		elif isinstance(nameOrIndex, str):
    			name = nameOrIndex
    			try:
    				index = self.fontNames.index(name)
    			except ValueError:
    				raise KeyError(nameOrIndex)
    		else:
    			raise TypeError(nameOrIndex)
    		return self.topDictIndex[index]

    	def compile(self, file, otFont, isCFF2=None):
    		"""Write the object back into binary representation onto the given file.
    		``file`` should be a file handle object. ``otFont`` is the top-level
    		:py:class:`fontTools.ttLib.ttFont.TTFont` object containing this CFF file.

    		If ``isCFF2`` is passed and set to ``True`` or ``False``, then the
    		library makes an assertion that the CFF header is of the appropriate
    		version.
    		"""
    		self.otFont = otFont
    		if isCFF2 is not None:
    			# called from ttLib: assert 'major' value matches expected version
    			expected_major = (2 if isCFF2 else 1)
    			if self.major != expected_major:
    				raise ValueError(
    					"Invalid CFF 'major' version: expected %d, found %d" %
    					(expected_major, self.major))
    		else:
    			# use current 'major' value to determine output format
    			assert self.major in (1, 2), "Unknown CFF format"
    			isCFF2 = self.major == 2

    		if otFont.recalcBBoxes and not isCFF2:
    			for topDict in self.topDictIndex:
    				topDict.recalcFontBBox()

    		if not isCFF2:
    			strings = IndexedStrings()
    		else:
    			strings = None
    		writer = CFFWriter(isCFF2)
    		topCompiler = self.topDictIndex.getCompiler(strings, self, isCFF2=isCFF2)
    		if isCFF2:
    			self.hdrSize = 5
    			writer.add(sstruct.pack(cffHeaderFormat, self))
    			# Note: topDictSize will most likely change in CFFWriter.toFile().
    			self.topDictSize = topCompiler.getDataLength()
    			writer.add(struct.pack(">H", self.topDictSize))
    		else:
    			self.hdrSize = 4
    			self.offSize = 4  # will most likely change in CFFWriter.toFile().
    			writer.add(sstruct.pack(cffHeaderFormat, self))
    			writer.add(struct.pack("B", self.offSize))
    		if not isCFF2:
    			fontNames = Index()
    			for name in self.fontNames:
    				fontNames.append(name)
    			writer.add(fontNames.getCompiler(strings, self, isCFF2=isCFF2))
    		writer.add(topCompiler)
    		if not isCFF2:
    			writer.add(strings.getCompiler())
    		writer.add(self.GlobalSubrs.getCompiler(strings, self, isCFF2=isCFF2))

    		for topDict in self.topDictIndex:
    			if not hasattr(topDict, "charset") or topDict.charset is None:
    				charset = otFont.getGlyphOrder()
    				topDict.charset = charset
    		children = topCompiler.getChildren(strings)
    		for child in children:
    			writer.add(child)

    		writer.toFile(file)

    	def toXML(self, xmlWriter):
    		"""Write the object into XML representation onto the given
    		:class:`fontTools.misc.xmlWriter.XMLWriter`.

    		.. code:: python

    			writer = xmlWriter.XMLWriter(sys.stdout)
    			tt["CFF "].cff.toXML(writer)

    		"""

    		xmlWriter.simpletag("major", value=self.major)
    		xmlWriter.newline()
    		xmlWriter.simpletag("minor", value=self.minor)
    		xmlWriter.newline()
    		for fontName in self.fontNames:
    			xmlWriter.begintag("CFFFont", name=tostr(fontName))
    			xmlWriter.newline()
    			font = self[fontName]
    			font.toXML(xmlWriter)
    			xmlWriter.endtag("CFFFont")
    			xmlWriter.newline()
    		xmlWriter.newline()
    		xmlWriter.begintag("GlobalSubrs")
    		xmlWriter.newline()
    		self.GlobalSubrs.toXML(xmlWriter)
    		xmlWriter.endtag("GlobalSubrs")
    		xmlWriter.newline()

    	def fromXML(self, name, attrs, content, otFont=None):
    		"""Reads data from the XML element into the ``CFFFontSet`` object."""
    		self.otFont = otFont

    		# set defaults. These will be replaced if there are entries for them
    		# in the XML file.
    		if not hasattr(self, "major"):
    			self.major = 1
    		if not hasattr(self, "minor"):
    			self.minor = 0

    		if name == "CFFFont":
    			if self.major == 1:
    				if not hasattr(self, "offSize"):
    					# this will be recalculated when the cff is compiled.
    					self.offSize = 4
    				if not hasattr(self, "hdrSize"):
    					self.hdrSize = 4
    				if not hasattr(self, "GlobalSubrs"):
    					self.GlobalSubrs = GlobalSubrsIndex()
    				if not hasattr(self, "fontNames"):
    					self.fontNames = []
    					self.topDictIndex = TopDictIndex()
    				fontName = attrs["name"]
    				self.fontNames.append(fontName)
    				topDict = TopDict(GlobalSubrs=self.GlobalSubrs)
    				topDict.charset = None  # gets filled in later
    			elif self.major == 2:
    				if not hasattr(self, "hdrSize"):
    					self.hdrSize = 5
    				if not hasattr(self, "GlobalSubrs"):
    					self.GlobalSubrs = GlobalSubrsIndex()
    				if not hasattr(self, "fontNames"):
    					self.fontNames = ["CFF2Font"]
    				cff2GetGlyphOrder = self.otFont.getGlyphOrder
    				topDict = TopDict(
    					GlobalSubrs=self.GlobalSubrs,
    					cff2GetGlyphOrder=cff2GetGlyphOrder)
    				self.topDictIndex = TopDictIndex(None, cff2GetGlyphOrder)
    			self.topDictIndex.append(topDict)
    			for element in content:
    				if isinstance(element, str):
    					continue
    				name, attrs, content = element
    				topDict.fromXML(name, attrs, content)

    			if hasattr(topDict, "VarStore") and topDict.FDArray[0].vstore is None:
    				fdArray = topDict.FDArray
    				for fontDict in fdArray:
    					if hasattr(fontDict, "Private"):
    						fontDict.Private.vstore = topDict.VarStore

    		elif name == "GlobalSubrs":
    			subrCharStringClass = psCharStrings.T2CharString
    			if not hasattr(self, "GlobalSubrs"):
    				self.GlobalSubrs = GlobalSubrsIndex()
    			for element in content:
    				if isinstance(element, str):
    					continue
    				name, attrs, content = element
    				subr = subrCharStringClass()
    				subr.fromXML(name, attrs, content)
    				self.GlobalSubrs.append(subr)
    		elif name == "major":
    			self.major = int(attrs['value'])
    		elif name == "minor":
    			self.minor = int(attrs['value'])

    	def convertCFFToCFF2(self, otFont):
    		"""Converts this object from CFF format to CFF2 format. This conversion
    		is done 'in-place'. The conversion cannot be reversed.

    		This assumes a decompiled CFF table. (i.e. that the object has been
    		filled via :meth:`decompile`.)"""
    		self.major = 2
    		cff2GetGlyphOrder = self.otFont.getGlyphOrder
    		topDictData = TopDictIndex(None, cff2GetGlyphOrder)
    		topDictData.items = self.topDictIndex.items
    		self.topDictIndex = topDictData
    		topDict = topDictData[0]
    		if hasattr(topDict, 'Private'):
    			privateDict = topDict.Private
    		else:
    			privateDict = None
    		opOrder = buildOrder(topDictOperators2)
    		topDict.order = opOrder
    		topDict.cff2GetGlyphOrder = cff2GetGlyphOrder
    		for entry in topDictOperators:
    			key = entry[1]
    			if key not in opOrder:
    				if key in topDict.rawDict:
    					del topDict.rawDict[key]
    				if hasattr(topDict, key):
    					delattr(topDict, key)

    		if not hasattr(topDict, "FDArray"):
    			fdArray = topDict.FDArray = FDArrayIndex()
    			fdArray.strings = None
    			fdArray.GlobalSubrs = topDict.GlobalSubrs
    			topDict.GlobalSubrs.fdArray = fdArray
    			charStrings = topDict.CharStrings
    			if charStrings.charStringsAreIndexed:
    				charStrings.charStringsIndex.fdArray = fdArray
    			else:
    				charStrings.fdArray = fdArray
    			fontDict = FontDict()
    			fontDict.setCFF2(True)
    			fdArray.append(fontDict)
    			fontDict.Private = privateDict
    			privateOpOrder = buildOrder(privateDictOperators2)
    			for entry in privateDictOperators:
    				key = entry[1]
    				if key not in privateOpOrder:
    					if key in privateDict.rawDict:
    						# print "Removing private dict", key
    						del privateDict.rawDict[key]
    					if hasattr(privateDict, key):
    						delattr(privateDict, key)
    						# print "Removing privateDict attr", key
    		else:
    			# clean up the PrivateDicts in the fdArray
    			fdArray = topDict.FDArray
    			privateOpOrder = buildOrder(privateDictOperators2)
    			for fontDict in fdArray:
    				fontDict.setCFF2(True)
    				for key in fontDict.rawDict.keys():
    					if key not in fontDict.order:
    						del fontDict.rawDict[key]
    						if hasattr(fontDict, key):
    							delattr(fontDict, key)

    				privateDict = fontDict.Private
    				for entry in privateDictOperators:
    					key = entry[1]
    					if key not in privateOpOrder:
    						if key in privateDict.rawDict:
    							# print "Removing private dict", key
    							del privateDict.rawDict[key]
    						if hasattr(privateDict, key):
    							delattr(privateDict, key)
    							# print "Removing privateDict attr", key
    		# At this point, the Subrs and Charstrings are all still T2Charstring class
    		# easiest to fix this by compiling, then decompiling again
    		file = BytesIO()
    		self.compile(file, otFont, isCFF2=True)
    		file.seek(0)
    		self.decompile(file, otFont, isCFF2=True)

    	def desubroutinize(self):
    		for fontName in self.fontNames:
    			font = self[fontName]
    			cs = font.CharStrings
    			for g in font.charset:
    				c, _ = cs.getItemAndSelector(g)
    				c.decompile()
    				subrs = getattr(c.private, "Subrs", [])
    				decompiler = _DesubroutinizingT2Decompiler(subrs, c.globalSubrs, c.private)
    				decompiler.execute(c)
    				c.program = c._desubroutinized
    				del c._desubroutinized
    			# Delete all the local subrs
    			if hasattr(font, 'FDArray'):
    				for fd in font.FDArray:
    					pd = fd.Private
    					if hasattr(pd, 'Subrs'):
    						del pd.Subrs
    					if 'Subrs' in pd.rawDict:
    						del pd.rawDict['Subrs']
    			else:
    				pd = font.Private
    				if hasattr(pd, 'Subrs'):
    					del pd.Subrs
    				if 'Subrs' in pd.rawDict:
    					del pd.rawDict['Subrs']
    		# as well as the global subrs
    		self.GlobalSubrs.clear()

    class CFFWriter(object):
    	"""Helper class for serializing CFF data to binary. Used by
    	:meth:`CFFFontSet.compile`."""
    	def __init__(self, isCFF2):
    		self.data = []
    		self.isCFF2 = isCFF2

    	def add(self, table):
    		self.data.append(table)

    	def toFile(self, file):
    		lastPosList = None
    		count = 1
    		while True:
    			log.log(DEBUG, "CFFWriter.toFile() iteration: %d", count)
    			count = count + 1
    			pos = 0
    			posList = [pos]
    			for item in self.data:
    				if hasattr(item, "getDataLength"):
    					endPos = pos + item.getDataLength()
    					if isinstance(item, TopDictIndexCompiler) and item.isCFF2:
    						self.topDictSize = item.getDataLength()
    				else:
    					endPos = pos + len(item)
    				if hasattr(item, "setPos"):
    					item.setPos(pos, endPos)
    				pos = endPos
    				posList.append(pos)
    			if posList == lastPosList:
    				break
    			lastPosList = posList
    		log.log(DEBUG, "CFFWriter.toFile() writing to file.")
    		begin = file.tell()
    		if self.isCFF2:
    			self.data[1] = struct.pack(">H", self.topDictSize)
    		else:
    			self.offSize = calcOffSize(lastPosList[-1])
    			self.data[1] = struct.pack("B", self.offSize)
    		posList = [0]
    		for item in self.data:
    			if hasattr(item, "toFile"):
    				item.toFile(file)
    			else:
    				file.write(item)
    			posList.append(file.tell() - begin)
    		assert posList == lastPosList

    def calcOffSize(largestOffset):
    	if largestOffset < 0x100:
    		offSize = 1
    	elif largestOffset < 0x10000:
    		offSize = 2
    	elif largestOffset < 0x1000000:
    		offSize = 3
    	else:
    		offSize = 4
    	return offSize

    class IndexCompiler(object):
    	"""Base class for writing CFF `INDEX data <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#5-index-data>`_
    	to binary."""

    	def __init__(self, items, strings, parent, isCFF2=None):
    		if isCFF2 is None and hasattr(parent, "isCFF2"):
    			isCFF2 = parent.isCFF2
    			assert isCFF2 is not None
    		self.isCFF2 = isCFF2
    		self.items = self.getItems(items, strings)
    		self.parent = parent

    	def getItems(self, items, strings):
    		return items

    	def getOffsets(self):
    		# An empty INDEX contains only the count field.
    		if self.items:
    			pos = 1
    			offsets = [pos]
    			for item in self.items:
    				if hasattr(item, "getDataLength"):
    					pos = pos + item.getDataLength()
    				else:
    					pos = pos + len(item)
    				offsets.append(pos)
    		else:
    			offsets = []
    		return offsets

    	def getDataLength(self):
    		if self.isCFF2:
    			countSize = 4
    		else:
    			countSize = 2

    		if self.items:
    			lastOffset = self.getOffsets()[-1]
    			offSize = calcOffSize(lastOffset)
    			dataLength = (
    				countSize +                        # count
    				1 +                                # offSize
    				(len(self.items) + 1) * offSize +  # the offsets
    				lastOffset - 1                     # size of object data
    			)
    		else:
    			# count. For empty INDEX tables, this is the only entry.
    			dataLength = countSize

    		return dataLength

    	def toFile(self, file):
    		offsets = self.getOffsets()
    		if self.isCFF2:
    			writeCard32(file, len(self.items))
    		else:
    			writeCard16(file, len(self.items))
    		# An empty INDEX contains only the count field.
    		if self.items:
    			offSize = calcOffSize(offsets[-1])
    			writeCard8(file, offSize)
    			offSize = -offSize
    			pack = struct.pack
    			for offset in offsets:
    				binOffset = pack(">l", offset)[offSize:]
    				assert len(binOffset) == -offSize
    				file.write(binOffset)
    			for item in self.items:
    				if hasattr(item, "toFile"):
    					item.toFile(file)
    				else:
    					data = tobytes(item, encoding="latin1")
    					file.write(data)

    class IndexedStringsCompiler(IndexCompiler):

    	def getItems(self, items, strings):
    		return items.strings

    class TopDictIndexCompiler(IndexCompiler):
    	"""Helper class for writing the TopDict to binary."""

    	def getItems(self, items, strings):
    		out = []
    		for item in items:
    			out.append(item.getCompiler(strings, self))
    		return out

    	def getChildren(self, strings):
    		children = []
    		for topDict in self.items:
    			children.extend(topDict.getChildren(strings))
    		return children

    	def getOffsets(self):
    		if self.isCFF2:
    			offsets = [0, self.items[0].getDataLength()]
    			return offsets
    		else:
    			return super(TopDictIndexCompiler, self).getOffsets()

    	def getDataLength(self):
    		if self.isCFF2:
    			dataLength = self.items[0].getDataLength()
    			return dataLength
    		else:
    			return super(TopDictIndexCompiler, self).getDataLength()

    	def toFile(self, file):
    		if self.isCFF2:
    			self.items[0].toFile(file)
    		else:
    			super(TopDictIndexCompiler, self).toFile(file)

    class FDArrayIndexCompiler(IndexCompiler):
    	"""Helper class for writing the
    	`Font DICT INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#10-font-dict-index-font-dicts-and-fdselect>`_
    	to binary."""

    	def getItems(self, items, strings):
    		out = []
    		for item in items:
    			out.append(item.getCompiler(strings, self))
    		return out

    	def getChildren(self, strings):
    		children = []
    		for fontDict in self.items:
    			children.extend(fontDict.getChildren(strings))
    		return children

    	def toFile(self, file):
    		offsets = self.getOffsets()
    		if self.isCFF2:
    			writeCard32(file, len(self.items))
    		else:
    			writeCard16(file, len(self.items))
    		offSize = calcOffSize(offsets[-1])
    		writeCard8(file, offSize)
    		offSize = -offSize
    		pack = struct.pack
    		for offset in offsets:
    			binOffset = pack(">l", offset)[offSize:]
    			assert len(binOffset) == -offSize
    			file.write(binOffset)
    		for item in self.items:
    			if hasattr(item, "toFile"):
    				item.toFile(file)
    			else:
    				file.write(item)

    	def setPos(self, pos, endPos):
    		self.parent.rawDict["FDArray"] = pos

    class GlobalSubrsCompiler(IndexCompiler):
    	"""Helper class for writing the `global subroutine INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#9-local-and-global-subr-indexes>`_
    	to binary."""

    	def getItems(self, items, strings):
    		out = []
    		for cs in items:
    			cs.compile(self.isCFF2)
    			out.append(cs.bytecode)
    		return out

    class SubrsCompiler(GlobalSubrsCompiler):
    	"""Helper class for writing the `local subroutine INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#9-local-and-global-subr-indexes>`_
    	to binary."""

    	def setPos(self, pos, endPos):
    		offset = pos - self.parent.pos
    		self.parent.rawDict["Subrs"] = offset

    class CharStringsCompiler(GlobalSubrsCompiler):
    	"""Helper class for writing the `CharStrings INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#9-local-and-global-subr-indexes>`_
    	to binary."""
    	def getItems(self, items, strings):
    		out = []
    		for cs in items:
    			cs.compile(self.isCFF2)
    			out.append(cs.bytecode)
    		return out

    	def setPos(self, pos, endPos):
    		self.parent.rawDict["CharStrings"] = pos

    class Index(object):
    	"""This class represents what the CFF spec calls an INDEX (an array of
    	variable-sized objects). `Index` items can be addressed and set using
    	Python list indexing."""

    	compilerClass = IndexCompiler

    	def __init__(self, file=None, isCFF2=None):
    		assert (isCFF2 is None) == (file is None)
    		self.items = []
    		name = self.__class__.__name__
    		if file is None:
    			return
    		self._isCFF2 = isCFF2
    		log.log(DEBUG, "loading %s at %s", name, file.tell())
    		self.file = file
    		if isCFF2:
    			count = readCard32(file)
    		else:
    			count = readCard16(file)
    		if count == 0:
    			return
    		self.items = [None] * count
    		offSize = readCard8(file)
    		log.log(DEBUG, "    index count: %s offSize: %s", count, offSize)
    		assert offSize <= 4, "offSize too large: %s" % offSize
    		self.offsets = offsets = []
    		pad = b'\0' * (4 - offSize)
    		for index in range(count + 1):
    			chunk = file.read(offSize)
    			chunk = pad + chunk
    			offset, = struct.unpack(">L", chunk)
    			offsets.append(int(offset))
    		self.offsetBase = file.tell() - 1
    		file.seek(self.offsetBase + offsets[-1])  # pretend we've read the whole lot
    		log.log(DEBUG, "    end of %s at %s", name, file.tell())

    	def __len__(self):
    		return len(self.items)

    	def __getitem__(self, index):
    		item = self.items[index]
    		if item is not None:
    			return item
    		offset = self.offsets[index] + self.offsetBase
    		size = self.offsets[index + 1] - self.offsets[index]
    		file = self.file
    		file.seek(offset)
    		data = file.read(size)
    		assert len(data) == size
    		item = self.produceItem(index, data, file, offset)
    		self.items[index] = item
    		return item

    	def __setitem__(self, index, item):
    		self.items[index] = item

    	def produceItem(self, index, data, file, offset):
    		return data

    	def append(self, item):
    		"""Add an item to an INDEX."""
    		self.items.append(item)

    	def getCompiler(self, strings, parent, isCFF2=None):
    		return self.compilerClass(self, strings, parent, isCFF2=isCFF2)

    	def clear(self):
    		"""Empty the INDEX."""
    		del self.items[:]

    class GlobalSubrsIndex(Index):
    	"""This index contains all the global subroutines in the font. A global
    	subroutine is a set of ``CharString`` data which is accessible to any
    	glyph in the font, and are used to store repeated instructions - for
    	example, components may be encoded as global subroutines, but so could
    	hinting instructions.

    	Remember that when interpreting a ``callgsubr`` instruction (or indeed
    	a ``callsubr`` instruction) that you will need to add the "subroutine
    	number bias" to number given:

    	.. code:: python

    		tt = ttLib.TTFont("Almendra-Bold.otf")
    		u = tt["CFF "].cff[0].CharStrings["udieresis"]
    		u.decompile()

    		u.toXML(XMLWriter(sys.stdout))
    		# <some stuff>
    		# -64 callgsubr <-- Subroutine which implements the dieresis mark
    		# <other stuff>

    		tt["CFF "].cff[0].GlobalSubrs[-64] # <-- WRONG
    		# <T2CharString (bytecode) at 103451d10>

    		tt["CFF "].cff[0].GlobalSubrs[-64 + 107] # <-- RIGHT
    		# <T2CharString (source) at 103451390>

    	("The bias applied depends on the number of subrs (gsubrs). If the number of
    	subrs (gsubrs) is less than 1240, the bias is 107. Otherwise if it is less
    	than 33900, it is 1131; otherwise it is 32768.",
    	`Subroutine Operators <https://docs.microsoft.com/en-us/typography/opentype/otspec180/cff2charstr#section4.4>`)
    	"""

    	compilerClass = GlobalSubrsCompiler
    	subrClass = psCharStrings.T2CharString
    	charStringClass = psCharStrings.T2CharString

    	def __init__(self, file=None, globalSubrs=None, private=None,
    			fdSelect=None, fdArray=None, isCFF2=None):
    		super(GlobalSubrsIndex, self).__init__(file, isCFF2=isCFF2)
    		self.globalSubrs = globalSubrs
    		self.private = private
    		if fdSelect:
    			self.fdSelect = fdSelect
    		if fdArray:
    			self.fdArray = fdArray

    	def produceItem(self, index, data, file, offset):
    		if self.private is not None:
    			private = self.private
    		elif hasattr(self, 'fdArray') and self.fdArray is not None:
    			if hasattr(self, 'fdSelect') and self.fdSelect is not None:
    				fdIndex = self.fdSelect[index]
    			else:
    				fdIndex = 0
    			private = self.fdArray[fdIndex].Private
    		else:
    			private = None
    		return self.subrClass(data, private=private, globalSubrs=self.globalSubrs)

    	def toXML(self, xmlWriter):
    		"""Write the subroutines index into XML representation onto the given
    		:class:`fontTools.misc.xmlWriter.XMLWriter`.

    		.. code:: python

    			writer = xmlWriter.XMLWriter(sys.stdout)
    			tt["CFF "].cff[0].GlobalSubrs.toXML(writer)

    		"""
    		xmlWriter.comment(
    			"The 'index' attribute is only for humans; "
    			"it is ignored when parsed.")
    		xmlWriter.newline()
    		for i in range(len(self)):
    			subr = self[i]
    			if subr.needsDecompilation():
    				xmlWriter.begintag("CharString", index=i, raw=1)
    			else:
    				xmlWriter.begintag("CharString", index=i)
    			xmlWriter.newline()
    			subr.toXML(xmlWriter)
    			xmlWriter.endtag("CharString")
    			xmlWriter.newline()

    	def fromXML(self, name, attrs, content):
    		if name != "CharString":
    			return
    		subr = self.subrClass()
    		subr.fromXML(name, attrs, content)
    		self.append(subr)

    	def getItemAndSelector(self, index):
    		sel = None
    		if hasattr(self, 'fdSelect'):
    			sel = self.fdSelect[index]
    		return self[index], sel

    class SubrsIndex(GlobalSubrsIndex):
    	"""This index contains a glyph's local subroutines. A local subroutine is a
    	private set of ``CharString`` data which is accessible only to the glyph to
    	which the index is attached."""

    	compilerClass = SubrsCompiler

    class TopDictIndex(Index):
    	"""This index represents the array of ``TopDict`` structures in the font
    	(again, usually only one entry is present). Hence the following calls are
    	equivalent:

    	.. code:: python

    		tt["CFF "].cff[0]
    		# <fontTools.cffLib.TopDict object at 0x102ed6e50>
    		tt["CFF "].cff.topDictIndex[0]
    		# <fontTools.cffLib.TopDict object at 0x102ed6e50>

    	"""

    	compilerClass = TopDictIndexCompiler

    	def __init__(self, file=None, cff2GetGlyphOrder=None, topSize=0,
    			isCFF2=None):
    		assert (isCFF2 is None) == (file is None)
    		self.cff2GetGlyphOrder = cff2GetGlyphOrder
    		if file is not None and isCFF2:
    			self._isCFF2 = isCFF2
    			self.items = []
    			name = self.__class__.__name__
    			log.log(DEBUG, "loading %s at %s", name, file.tell())
    			self.file = file
    			count = 1
    			self.items = [None] * count
    			self.offsets = [0, topSize]
    			self.offsetBase = file.tell()
    			# pretend we've read the whole lot
    			file.seek(self.offsetBase + topSize)
    			log.log(DEBUG, "    end of %s at %s", name, file.tell())
    		else:
    			super(TopDictIndex, self).__init__(file, isCFF2=isCFF2)

    	def produceItem(self, index, data, file, offset):
    		top = TopDict(
    			self.strings, file, offset, self.GlobalSubrs,
    			self.cff2GetGlyphOrder, isCFF2=self._isCFF2)
    		top.decompile(data)
    		return top

    	def toXML(self, xmlWriter):
    		for i in range(len(self)):
    			xmlWriter.begintag("FontDict", index=i)
    			xmlWriter.newline()
    			self[i].toXML(xmlWriter)
    			xmlWriter.endtag("FontDict")
    			xmlWriter.newline()

    class FDArrayIndex(Index):

    	compilerClass = FDArrayIndexCompiler

    	def toXML(self, xmlWriter):
    		for i in range(len(self)):
    			xmlWriter.begintag("FontDict", index=i)
    			xmlWriter.newline()
    			self[i].toXML(xmlWriter)
    			xmlWriter.endtag("FontDict")
    			xmlWriter.newline()

    	def produceItem(self, index, data, file, offset):
    		fontDict = FontDict(
    			self.strings, file, offset, self.GlobalSubrs, isCFF2=self._isCFF2,
    			vstore=self.vstore)
    		fontDict.decompile(data)
    		return fontDict

    	def fromXML(self, name, attrs, content):
    		if name != "FontDict":
    			return
    		fontDict = FontDict()
    		for element in content:
    			if isinstance(element, str):
    				continue
    			name, attrs, content = element
    			fontDict.fromXML(name, attrs, content)
    		self.append(fontDict)

    class VarStoreData(object):

    	def __init__(self, file=None, otVarStore=None):
    		self.file = file
    		self.data = None
    		self.otVarStore = otVarStore
    		self.font = TTFont()  # dummy font for the decompile function.

    	def decompile(self):
    		if self.file:
    			# read data in from file. Assume position is correct.
    			length = readCard16(self.file)
    			self.data = self.file.read(length)
    			globalState = {}
    			reader = OTTableReader(self.data, globalState)
    			self.otVarStore = ot.VarStore()
    			self.otVarStore.decompile(reader, self.font)
    		return self

    	def compile(self):
    		writer = OTTableWriter()
    		self.otVarStore.compile(writer, self.font)
    		# Note that this omits the initial Card16 length from the CFF2
    		# VarStore data block
    		self.data = writer.getAllData()

    	def writeXML(self, xmlWriter, name):
    		self.otVarStore.toXML(xmlWriter, self.font)

    	def xmlRead(self, name, attrs, content, parent):
    		self.otVarStore = ot.VarStore()
    		for element in content:
    			if isinstance(element, tuple):
    				name, attrs, content = element
    				self.otVarStore.fromXML(name, attrs, content, self.font)
    			else:
    				pass
    		return None

    	def __len__(self):
    		return len(self.data)

    	def getNumRegions(self, vsIndex):
    		if vsIndex is None:
    			vsIndex = 0
    		varData = self.otVarStore.VarData[vsIndex]
    		numRegions = varData.VarRegionCount
    		return numRegions

    class FDSelect(object):

    	def __init__(self, file=None, numGlyphs=None, format=None):
    		if file:
    			# read data in from file
    			self.format = readCard8(file)
    			if self.format == 0:
    				from array import array
    				self.gidArray = array("B", file.read(numGlyphs)).tolist()
    			elif self.format == 3:
    				gidArray = [None] * numGlyphs
    				nRanges = readCard16(file)
    				fd = None
    				prev = None
    				for i in range(nRanges):
    					first = readCard16(file)
    					if prev is not None:
    						for glyphID in range(prev, first):
    							gidArray[glyphID] = fd
    					prev = first
    					fd = readCard8(file)
    				if prev is not None:
    					first = readCard16(file)
    					for glyphID in range(prev, first):
    						gidArray[glyphID] = fd
    				self.gidArray = gidArray
    			elif self.format == 4:
    				gidArray = [None] * numGlyphs
    				nRanges = readCard32(file)
    				fd = None
    				prev = None
    				for i in range(nRanges):
    					first = readCard32(file)
    					if prev is not None:
    						for glyphID in range(prev, first):
    							gidArray[glyphID] = fd
    					prev = first
    					fd = readCard16(file)
    				if prev is not None:
    					first = readCard32(file)
    					for glyphID in range(prev, first):
    						gidArray[glyphID] = fd
    				self.gidArray = gidArray
    			else:
    				assert False, "unsupported FDSelect format: %s" % format
    		else:
    			# reading from XML. Make empty gidArray, and leave format as passed in.
    			# format is None will result in the smallest representation being used.
    			self.format = format
    			self.gidArray = []

    	def __len__(self):
    		return len(self.gidArray)

    	def __getitem__(self, index):
    		return self.gidArray[index]

    	def __setitem__(self, index, fdSelectValue):
    		self.gidArray[index] = fdSelectValue

    	def append(self, fdSelectValue):
    		self.gidArray.append(fdSelectValue)

    class CharStrings(object):
    	"""The ``CharStrings`` in the font represent the instructions for drawing
    	each glyph. This object presents a dictionary interface to the font's
    	CharStrings, indexed by glyph name:

    	.. code:: python

    		tt["CFF "].cff[0].CharStrings["a"]
    		# <T2CharString (bytecode) at 103451e90>

    	See :class:`fontTools.misc.psCharStrings.T1CharString` and
    	:class:`fontTools.misc.psCharStrings.T2CharString` for how to decompile,
    	compile and interpret the glyph drawing instructions in the returned objects.

    	"""

    	def __init__(self, file, charset, globalSubrs, private, fdSelect, fdArray,
    			isCFF2=None, varStore=None):
    		self.globalSubrs = globalSubrs
    		self.varStore = varStore
    		if file is not None:
    			self.charStringsIndex = SubrsIndex(
    				file, globalSubrs, private, fdSelect, fdArray, isCFF2=isCFF2)
    			self.charStrings = charStrings = {}
    			for i in range(len(charset)):
    				charStrings[charset[i]] = i
    			# read from OTF file: charStrings.values() are indices into
    			# charStringsIndex.
    			self.charStringsAreIndexed = 1
    		else:
    			self.charStrings = {}
    			# read from ttx file: charStrings.values() are actual charstrings
    			self.charStringsAreIndexed = 0
    			self.private = private
    			if fdSelect is not None:
    				self.fdSelect = fdSelect
    			if fdArray is not None:
    				self.fdArray = fdArray

    	def keys(self):
    		return list(self.charStrings.keys())

    	def values(self):
    		if self.charStringsAreIndexed:
    			return self.charStringsIndex
    		else:
    			return list(self.charStrings.values())

    	def has_key(self, name):
    		return name in self.charStrings

    	__contains__ = has_key

    	def __len__(self):
    		return len(self.charStrings)

    	def __getitem__(self, name):
    		charString = self.charStrings[name]
    		if self.charStringsAreIndexed:
    			charString = self.charStringsIndex[charString]
    		return charString

    	def __setitem__(self, name, charString):
    		if self.charStringsAreIndexed:
    			index = self.charStrings[name]
    			self.charStringsIndex[index] = charString
    		else:
    			self.charStrings[name] = charString

    	def getItemAndSelector(self, name):
    		if self.charStringsAreIndexed:
    			index = self.charStrings[name]
    			return self.charStringsIndex.getItemAndSelector(index)
    		else:
    			if hasattr(self, 'fdArray'):
    				if hasattr(self, 'fdSelect'):
    					sel = self.charStrings[name].fdSelectIndex
    				else:
    					sel = 0
    			else:
    				sel = None
    			return self.charStrings[name], sel

    	def toXML(self, xmlWriter):
    		names = sorted(self.keys())
    		for name in names:
    			charStr, fdSelectIndex = self.getItemAndSelector(name)
    			if charStr.needsDecompilation():
    				raw = [("raw", 1)]
    			else:
    				raw = []
    			if fdSelectIndex is None:
    				xmlWriter.begintag("CharString", [('name', name)] + raw)
    			else:
    				xmlWriter.begintag(
    					"CharString",
    					[('name', name), ('fdSelectIndex', fdSelectIndex)] + raw)
    			xmlWriter.newline()
    			charStr.toXML(xmlWriter)
    			xmlWriter.endtag("CharString")
    			xmlWriter.newline()

    	def fromXML(self, name, attrs, content):
    		for element in content:
    			if isinstance(element, str):
    				continue
    			name, attrs, content = element
    			if name != "CharString":
    				continue
    			fdID = -1
    			if hasattr(self, "fdArray"):
    				try:
    					fdID = safeEval(attrs["fdSelectIndex"])
    				except KeyError:
    					fdID = 0
    				private = self.fdArray[fdID].Private
    			else:
    				private = self.private

    			glyphName = attrs["name"]
    			charStringClass = psCharStrings.T2CharString
    			charString = charStringClass(
    					private=private,
    					globalSubrs=self.globalSubrs)
    			charString.fromXML(name, attrs, content)
    			if fdID >= 0:
    				charString.fdSelectIndex = fdID
    			self[glyphName] = charString

    def readCard8(file):
    	return byteord(file.read(1))

    def readCard16(file):
    	value, = struct.unpack(">H", file.read(2))
    	return value

    def readCard32(file):
    	value, = struct.unpack(">L", file.read(4))
    	return value

    def writeCard8(file, value):
    	file.write(bytechr(value))

    def writeCard16(file, value):
    	file.write(struct.pack(">H", value))

    def writeCard32(file, value):
    	file.write(struct.pack(">L", value))

    def packCard8(value):
    	return bytechr(value)

    def packCard16(value):
    	return struct.pack(">H", value)

    def packCard32(value):
    	return struct.pack(">L", value)

    def buildOperatorDict(table):
    	d = {}
    	for op, name, arg, default, conv in table:
    		d[op] = (name, arg)
    	return d

    def buildOpcodeDict(table):
    	d = {}
    	for op, name, arg, default, conv in table:
    		if isinstance(op, tuple):
    			op = bytechr(op[0]) + bytechr(op[1])
    		else:
    			op = bytechr(op)
    		d[name] = (op, arg)
    	return d

    def buildOrder(table):
    	l = []
    	for op, name, arg, default, conv in table:
    		l.append(name)
    	return l

    def buildDefaults(table):
    	d = {}
    	for op, name, arg, default, conv in table:
    		if default is not None:
    			d[name] = default
    	return d

    def buildConverters(table):
    	d = {}
    	for op, name, arg, default, conv in table:
    		d[name] = conv
    	return d

    class SimpleConverter(object):

    	def read(self, parent, value):
    		if not hasattr(parent, "file"):
    			return self._read(parent, value)
    		file = parent.file
    		pos = file.tell()
    		try:
    			return self._read(parent, value)
    		finally:
    			file.seek(pos)

    	def _read(self, parent, value):
    		return value

    	def write(self, parent, value):
    		return value

    	def xmlWrite(self, xmlWriter, name, value):
    		xmlWriter.simpletag(name, value=value)
    		xmlWriter.newline()

    	def xmlRead(self, name, attrs, content, parent):
    		return attrs["value"]

    class ASCIIConverter(SimpleConverter):

    	def _read(self, parent, value):
    		return tostr(value, encoding='ascii')

    	def write(self, parent, value):
    		return tobytes(value, encoding='ascii')

    	def xmlWrite(self, xmlWriter, name, value):
    		xmlWriter.simpletag(name, value=tostr(value, encoding="ascii"))
    		xmlWriter.newline()

    	def xmlRead(self, name, attrs, content, parent):
    		return tobytes(attrs["value"], encoding=("ascii"))

    class Latin1Converter(SimpleConverter):

    	def _read(self, parent, value):
    		return tostr(value, encoding='latin1')

    	def write(self, parent, value):
    		return tobytes(value, encoding='latin1')

    	def xmlWrite(self, xmlWriter, name, value):
    		value = tostr(value, encoding="latin1")
    		if name in ['Notice', 'Copyright']:
    			value = re.sub(r"[\r\n]\s+", " ", value)
    		xmlWriter.simpletag(name, value=value)
    		xmlWriter.newline()

    	def xmlRead(self, name, attrs, content, parent):
    		return tobytes(attrs["value"], encoding=("latin1"))

    def parseNum(s):
    	try:
    		value = int(s)
    	except:
    		value = float(s)
    	return value

    def parseBlendList(s):
    	valueList = []
    	for element in s:
    		if isinstance(element, str):
    			continue
    		name, attrs, content = element
    		blendList = attrs["value"].split()
    		blendList = [eval(val) for val in blendList]
    		valueList.append(blendList)
    	if len(valueList) == 1:
    		valueList = valueList[0]
    	return valueList

    class NumberConverter(SimpleConverter):
    	def xmlWrite(self, xmlWriter, name, value):
    		if isinstance(value, list):
    			xmlWriter.begintag(name)
    			xmlWriter.newline()
    			xmlWriter.indent()
    			blendValue = " ".join([str(val) for val in value])
    			xmlWriter.simpletag(kBlendDictOpName, value=blendValue)
    			xmlWriter.newline()
    			xmlWriter.dedent()
    			xmlWriter.endtag(name)
    			xmlWriter.newline()
    		else:
    			xmlWriter.simpletag(name, value=value)
    			xmlWriter.newline()

    	def xmlRead(self, name, attrs, content, parent):
    		valueString = attrs.get("value", None)
    		if valueString is None:
    			value = parseBlendList(content)
    		else:
    			value = parseNum(attrs["value"])
    		return value

    class ArrayConverter(SimpleConverter):
    	def xmlWrite(self, xmlWriter, name, value):
    		if value and isinstance(value[0], list):
    			xmlWriter.begintag(name)
    			xmlWriter.newline()
    			xmlWriter.indent()
    			for valueList in value:
    				blendValue = " ".join([str(val) for val in valueList])
    				xmlWriter.simpletag(kBlendDictOpName, value=blendValue)
    				xmlWriter.newline()
    			xmlWriter.dedent()
    			xmlWriter.endtag(name)
    			xmlWriter.newline()
    		else:
    			value = " ".join([str(val) for val in value])
    			xmlWriter.simpletag(name, value=value)
    			xmlWriter.newline()

    	def xmlRead(self, name, attrs, content, parent):
    		valueString = attrs.get("value", None)
    		if valueString is None:
    			valueList = parseBlendList(content)
    		else:
    			values = valueString.split()
    			valueList = [parseNum(value) for value in values]
    		return valueList

    class TableConverter(SimpleConverter):

    	def xmlWrite(self, xmlWriter, name, value):
    		xmlWriter.begintag(name)
    		xmlWriter.newline()
    		value.toXML(xmlWriter)
    		xmlWriter.endtag(name)
    		xmlWriter.newline()

    	def xmlRead(self, name, attrs, content, parent):
    		ob = self.getClass()()
    		for element in content:
    			if isinstance(element, str):
    				continue
    			name, attrs, content = element
    			ob.fromXML(name, attrs, content)
    		return ob

    class PrivateDictConverter(TableConverter):

    	def getClass(self):
    		return PrivateDict

    	def _read(self, parent, value):
    		size, offset = value
    		file = parent.file
    		isCFF2 = parent._isCFF2
    		try:
    			vstore = parent.vstore
    		except AttributeError:
    			vstore = None
    		priv = PrivateDict(
    			parent.strings, file, offset, isCFF2=isCFF2, vstore=vstore)
    		file.seek(offset)
    		data = file.read(size)
    		assert len(data) == size
    		priv.decompile(data)
    		return priv

    	def write(self, parent, value):
    		return (0, 0)  # dummy value

    class SubrsConverter(TableConverter):

    	def getClass(self):
    		return SubrsIndex

    	def _read(self, parent, value):
    		file = parent.file
    		isCFF2 = parent._isCFF2
    		file.seek(parent.offset + value)  # Offset(self)
    		return SubrsIndex(file, isCFF2=isCFF2)

    	def write(self, parent, value):
    		return 0  # dummy value

    class CharStringsConverter(TableConverter):

    	def _read(self, parent, value):
    		file = parent.file
    		isCFF2 = parent._isCFF2
    		charset = parent.charset
    		varStore = getattr(parent, "VarStore", None)
    		globalSubrs = parent.GlobalSubrs
    		if hasattr(parent, "FDArray"):
    			fdArray = parent.FDArray
    			if hasattr(parent, "FDSelect"):
    				fdSelect = parent.FDSelect
    			else:
    				fdSelect = None
    			private = None
    		else:
    			fdSelect, fdArray = None, None
    			private = parent.Private
    		file.seek(value)  # Offset(0)
    		charStrings = CharStrings(
    			file, charset, globalSubrs, private, fdSelect, fdArray, isCFF2=isCFF2, varStore=varStore)
    		return charStrings

    	def write(self, parent, value):
    		return 0  # dummy value

    	def xmlRead(self, name, attrs, content, parent):
    		if hasattr(parent, "FDArray"):
    			# if it is a CID-keyed font, then the private Dict is extracted from the
    			# parent.FDArray
    			fdArray = parent.FDArray
    			if hasattr(parent, "FDSelect"):
    				fdSelect = parent.FDSelect
    			else:
    				fdSelect = None
    			private = None
    		else:
    			# if it is a name-keyed font, then the private dict is in the top dict,
    			# and
    			# there is no fdArray.
    			private, fdSelect, fdArray = parent.Private, None, None
    		charStrings = CharStrings(
    			None, None, parent.GlobalSubrs, private, fdSelect, fdArray, varStore=getattr(parent, "VarStore", None))
    		charStrings.fromXML(name, attrs, content)
    		return charStrings

    class CharsetConverter(SimpleConverter):
    	def _read(self, parent, value):
    		isCID = hasattr(parent, "ROS")
    		if value > 2:
    			numGlyphs = parent.numGlyphs
    			file = parent.file
    			file.seek(value)
    			log.log(DEBUG, "loading charset at %s", value)
    			format = readCard8(file)
    			if format == 0:
    				charset = parseCharset0(numGlyphs, file, parent.strings, isCID)
    			elif format == 1 or format == 2:
    				charset = parseCharset(numGlyphs, file, parent.strings, isCID, format)
    			else:
    				raise NotImplementedError
    			assert len(charset) == numGlyphs
    			log.log(DEBUG, "    charset end at %s", file.tell())
    			# make sure glyph names are unique
    			allNames = {}
    			newCharset = []
    			for glyphName in charset:
    				if glyphName in allNames:
    					# make up a new glyphName that's unique
    					n = allNames[glyphName]
    					while (glyphName + "#" + str(n)) in allNames:
    						n += 1
    					allNames[glyphName] = n + 1
    					glyphName = glyphName + "#" + str(n)
    				allNames[glyphName] = 1
    				newCharset.append(glyphName)
    			charset = newCharset
    		else:  # offset == 0 -> no charset data.
    			if isCID or "CharStrings" not in parent.rawDict:
    				# We get here only when processing fontDicts from the FDArray of
    				# CFF-CID fonts. Only the real topDict references the chrset.
    				assert value == 0
    				charset = None
    			elif value == 0:
    				charset = cffISOAdobeStrings
    			elif value == 1:
    				charset = cffIExpertStrings
    			elif value == 2:
    				charset = cffExpertSubsetStrings
    		if charset and (len(charset) != parent.numGlyphs):
    			charset = charset[:parent.numGlyphs]
    		return charset

    	def write(self, parent, value):
    		return 0  # dummy value

    	def xmlWrite(self, xmlWriter, name, value):
    		# XXX only write charset when not in OT/TTX context, where we
    		# dump charset as a separate "GlyphOrder" table.
    		# # xmlWriter.simpletag("charset")
    		xmlWriter.comment("charset is dumped separately as the 'GlyphOrder' element")
    		xmlWriter.newline()

    	def xmlRead(self, name, attrs, content, parent):
    		pass

    class CharsetCompiler(object):

    	def __init__(self, strings, charset, parent):
    		assert charset[0] == '.notdef'
    		isCID = hasattr(parent.dictObj, "ROS")
    		data0 = packCharset0(charset, isCID, strings)
    		data = packCharset(charset, isCID, strings)
    		if len(data) < len(data0):
    			self.data = data
    		else:
    			self.data = data0
    		self.parent = parent

    	def setPos(self, pos, endPos):
    		self.parent.rawDict["charset"] = pos

    	def getDataLength(self):
    		return len(self.data)

    	def toFile(self, file):
    		file.write(self.data)

    def getStdCharSet(charset):
    	# check to see if we can use a predefined charset value.
    	predefinedCharSetVal = None
    	predefinedCharSets = [
    		(cffISOAdobeStringCount, cffISOAdobeStrings, 0),
    		(cffExpertStringCount, cffIExpertStrings, 1),
    		(cffExpertSubsetStringCount, cffExpertSubsetStrings, 2)]
    	lcs = len(charset)
    	for cnt, pcs, csv in predefinedCharSets:
    		if predefinedCharSetVal is not None:
    			break
    		if lcs > cnt:
    			continue
    		predefinedCharSetVal = csv
    		for i in range(lcs):
    			if charset[i] != pcs[i]:
    				predefinedCharSetVal = None
    				break
    	return predefinedCharSetVal

    def getCIDfromName(name, strings):
    	return int(name[3:])

    def getSIDfromName(name, strings):
    	return strings.getSID(name)

    def packCharset0(charset, isCID, strings):
    	fmt = 0
    	data = [packCard8(fmt)]
    	if isCID:
    		getNameID = getCIDfromName
    	else:
    		getNameID = getSIDfromName

    	for name in charset[1:]:
    		data.append(packCard16(getNameID(name, strings)))
    	return bytesjoin(data)

    def packCharset(charset, isCID, strings):
    	fmt = 1
    	ranges = []
    	first = None
    	end = 0
    	if isCID:
    		getNameID = getCIDfromName
    	else:
    		getNameID = getSIDfromName

    	for name in charset[1:]:
    		SID = getNameID(name, strings)
    		if first is None:
    			first = SID
    		elif end + 1 != SID:
    			nLeft = end - first
    			if nLeft > 255:
    				fmt = 2
    			ranges.append((first, nLeft))
    			first = SID
    		end = SID
    	if end:
    		nLeft = end - first
    		if nLeft > 255:
    			fmt = 2
    		ranges.append((first, nLeft))

    	data = [packCard8(fmt)]
    	if fmt == 1:
    		nLeftFunc = packCard8
    	else:
    		nLeftFunc = packCard16
    	for first, nLeft in ranges:
    		data.append(packCard16(first) + nLeftFunc(nLeft))
    	return bytesjoin(data)

    def parseCharset0(numGlyphs, file, strings, isCID):
    	charset = [".notdef"]
    	if isCID:
    		for i in range(numGlyphs - 1):
    			CID = readCard16(file)
    			charset.append("cid" + str(CID).zfill(5))
    	else:
    		for i in range(numGlyphs - 1):
    			SID = readCard16(file)
    			charset.append(strings[SID])
    	return charset

    def parseCharset(numGlyphs, file, strings, isCID, fmt):
    	charset = ['.notdef']
    	count = 1
    	if fmt == 1:
    		nLeftFunc = readCard8
    	else:
    		nLeftFunc = readCard16
    	while count < numGlyphs:
    		first = readCard16(file)
    		nLeft = nLeftFunc(file)
    		if isCID:
    			for CID in range(first, first + nLeft + 1):
    				charset.append("cid" + str(CID).zfill(5))
    		else:
    			for SID in range(first, first + nLeft + 1):
    				charset.append(strings[SID])
    		count = count + nLeft + 1
    	return charset

    class EncodingCompiler(object):

    	def __init__(self, strings, encoding, parent):
    		assert not isinstance(encoding, str)
    		data0 = packEncoding0(parent.dictObj.charset, encoding, parent.strings)
    		data1 = packEncoding1(parent.dictObj.charset, encoding, parent.strings)
    		if len(data0) < len(data1):
    			self.data = data0
    		else:
    			self.data = data1
    		self.parent = parent

    	def setPos(self, pos, endPos):
    		self.parent.rawDict["Encoding"] = pos

    	def getDataLength(self):
    		return len(self.data)

    	def toFile(self, file):
    		file.write(self.data)

    class EncodingConverter(SimpleConverter):

    	def _read(self, parent, value):
    		if value == 0:
    			return "StandardEncoding"
    		elif value == 1:
    			return "ExpertEncoding"
    		else:
    			assert value > 1
    			file = parent.file
    			file.seek(value)
    			log.log(DEBUG, "loading Encoding at %s", value)
    			fmt = readCard8(file)
    			haveSupplement = fmt & 0x80
    			if haveSupplement:
    				raise NotImplementedError("Encoding supplements are not yet supported")
    			fmt = fmt & 0x7f
    			if fmt == 0:
    				encoding = parseEncoding0(parent.charset, file, haveSupplement,
    						parent.strings)
    			elif fmt == 1:
    				encoding = parseEncoding1(parent.charset, file, haveSupplement,
    						parent.strings)
    			return encoding

    	def write(self, parent, value):
    		if value == "StandardEncoding":
    			return 0
    		elif value == "ExpertEncoding":
    			return 1
    		return 0  # dummy value

    	def xmlWrite(self, xmlWriter, name, value):
    		if value in ("StandardEncoding", "ExpertEncoding"):
    			xmlWriter.simpletag(name, name=value)
    			xmlWriter.newline()
    			return
    		xmlWriter.begintag(name)
    		xmlWriter.newline()
    		for code in range(len(value)):
    			glyphName = value[code]
    			if glyphName != ".notdef":
    				xmlWriter.simpletag("map", code=hex(code), name=glyphName)
    				xmlWriter.newline()
    		xmlWriter.endtag(name)
    		xmlWriter.newline()

    	def xmlRead(self, name, attrs, content, parent):
    		if "name" in attrs:
    			return attrs["name"]
    		encoding = [".notdef"] * 256
    		for element in content:
    			if isinstance(element, str):
    				continue
    			name, attrs, content = element
    			code = safeEval(attrs["code"])
    			glyphName = attrs["name"]
    			encoding[code] = glyphName
    		return encoding

    def parseEncoding0(charset, file, haveSupplement, strings):
    	nCodes = readCard8(file)
    	encoding = [".notdef"] * 256
    	for glyphID in range(1, nCodes + 1):
    		code = readCard8(file)
    		if code != 0:
    			encoding[code] = charset[glyphID]
    	return encoding

    def parseEncoding1(charset, file, haveSupplement, strings):
    	nRanges = readCard8(file)
    	encoding = [".notdef"] * 256
    	glyphID = 1
    	for i in range(nRanges):
    		code = readCard8(file)
    		nLeft = readCard8(file)
    		for glyphID in range(glyphID, glyphID + nLeft + 1):
    			encoding[code] = charset[glyphID]
    			code = code + 1
    		glyphID = glyphID + 1
    	return encoding

    def packEncoding0(charset, encoding, strings):
    	fmt = 0
    	m = {}
    	for code in range(len(encoding)):
    		name = encoding[code]
    		if name != ".notdef":
    			m[name] = code
    	codes = []
    	for name in charset[1:]:
    		code = m.get(name)
    		codes.append(code)

    	while codes and codes[-1] is None:
    		codes.pop()

    	data = [packCard8(fmt), packCard8(len(codes))]
    	for code in codes:
    		if code is None:
    			code = 0
    		data.append(packCard8(code))
    	return bytesjoin(data)

    def packEncoding1(charset, encoding, strings):
    	fmt = 1
    	m = {}
    	for code in range(len(encoding)):
    		name = encoding[code]
    		if name != ".notdef":
    			m[name] = code
    	ranges = []
    	first = None
    	end = 0
    	for name in charset[1:]:
    		code = m.get(name, -1)
    		if first is None:
    			first = code
    		elif end + 1 != code:
    			nLeft = end - first
    			ranges.append((first, nLeft))
    			first = code
    		end = code
    	nLeft = end - first
    	ranges.append((first, nLeft))

    	# remove unencoded glyphs at the end.
    	while ranges and ranges[-1][0] == -1:
    		ranges.pop()

    	data = [packCard8(fmt), packCard8(len(ranges))]
    	for first, nLeft in ranges:
    		if first == -1:  # unencoded
    			first = 0
    		data.append(packCard8(first) + packCard8(nLeft))
    	return bytesjoin(data)

    class FDArrayConverter(TableConverter):

    	def _read(self, parent, value):
    		try:
    			vstore = parent.VarStore
    		except AttributeError:
    			vstore = None
    		file = parent.file
    		isCFF2 = parent._isCFF2
    		file.seek(value)
    		fdArray = FDArrayIndex(file, isCFF2=isCFF2)
    		fdArray.vstore = vstore
    		fdArray.strings = parent.strings
    		fdArray.GlobalSubrs = parent.GlobalSubrs
    		return fdArray

    	def write(self, parent, value):
    		return 0  # dummy value

    	def xmlRead(self, name, attrs, content, parent):
    		fdArray = FDArrayIndex()
    		for element in content:
    			if isinstance(element, str):
    				continue
    			name, attrs, content = element
    			fdArray.fromXML(name, attrs, content)
    		return fdArray

    class FDSelectConverter(SimpleConverter):

    	def _read(self, parent, value):
    		file = parent.file
    		file.seek(value)
    		fdSelect = FDSelect(file, parent.numGlyphs)
    		return fdSelect

    	def write(self, parent, value):
    		return 0  # dummy value

    	# The FDSelect glyph data is written out to XML in the charstring keys,
    	# so we write out only the format selector
    	def xmlWrite(self, xmlWriter, name, value):
    		xmlWriter.simpletag(name, [('format', value.format)])
    		xmlWriter.newline()

    	def xmlRead(self, name, attrs, content, parent):
    		fmt = safeEval(attrs["format"])
    		file = None
    		numGlyphs = None
    		fdSelect = FDSelect(file, numGlyphs, fmt)
    		return fdSelect

    class VarStoreConverter(SimpleConverter):

    	def _read(self, parent, value):
    		file = parent.file
    		file.seek(value)
    		varStore = VarStoreData(file)
    		varStore.decompile()
    		return varStore

    	def write(self, parent, value):
    		return 0  # dummy value

    	def xmlWrite(self, xmlWriter, name, value):
    		value.writeXML(xmlWriter, name)

    	def xmlRead(self, name, attrs, content, parent):
    		varStore = VarStoreData()
    		varStore.xmlRead(name, attrs, content, parent)
    		return varStore

    def packFDSelect0(fdSelectArray):
    	fmt = 0
    	data = [packCard8(fmt)]
    	for index in fdSelectArray:
    		data.append(packCard8(index))
    	return bytesjoin(data)

    def packFDSelect3(fdSelectArray):
    	fmt = 3
    	fdRanges = []
    	lenArray = len(fdSelectArray)
    	lastFDIndex = -1
    	for i in range(lenArray):
    		fdIndex = fdSelectArray[i]
    		if lastFDIndex != fdIndex:
    			fdRanges.append([i, fdIndex])
    			lastFDIndex = fdIndex
    	sentinelGID = i + 1

    	data = [packCard8(fmt)]
    	data.append(packCard16(len(fdRanges)))
    	for fdRange in fdRanges:
    		data.append(packCard16(fdRange[0]))
    		data.append(packCard8(fdRange[1]))
    	data.append(packCard16(sentinelGID))
    	return bytesjoin(data)

    def packFDSelect4(fdSelectArray):
    	fmt = 4
    	fdRanges = []
    	lenArray = len(fdSelectArray)
    	lastFDIndex = -1
    	for i in range(lenArray):
    		fdIndex = fdSelectArray[i]
    		if lastFDIndex != fdIndex:
    			fdRanges.append([i, fdIndex])
    			lastFDIndex = fdIndex
    	sentinelGID = i + 1

    	data = [packCard8(fmt)]
    	data.append(packCard32(len(fdRanges)))
    	for fdRange in fdRanges:
    		data.append(packCard32(fdRange[0]))
    		data.append(packCard16(fdRange[1]))
    	data.append(packCard32(sentinelGID))
    	return bytesjoin(data)

    class FDSelectCompiler(object):

    	def __init__(self, fdSelect, parent):
    		fmt = fdSelect.format
    		fdSelectArray = fdSelect.gidArray
    		if fmt == 0:
    			self.data = packFDSelect0(fdSelectArray)
    		elif fmt == 3:
    			self.data = packFDSelect3(fdSelectArray)
    		elif fmt == 4:
    			self.data = packFDSelect4(fdSelectArray)
    		else:
    			# choose smaller of the two formats
    			data0 = packFDSelect0(fdSelectArray)
    			data3 = packFDSelect3(fdSelectArray)
    			if len(data0) < len(data3):
    				self.data = data0
    				fdSelect.format = 0
    			else:
    				self.data = data3
    				fdSelect.format = 3

    		self.parent = parent

    	def setPos(self, pos, endPos):
    		self.parent.rawDict["FDSelect"] = pos

    	def getDataLength(self):
    		return len(self.data)

    	def toFile(self, file):
    		file.write(self.data)

    class VarStoreCompiler(object):

    	def __init__(self, varStoreData, parent):
    		self.parent = parent
    		if not varStoreData.data:
    			varStoreData.compile()
    		data = [
    			packCard16(len(varStoreData.data)),
    			varStoreData.data
    		]
    		self.data = bytesjoin(data)

    	def setPos(self, pos, endPos):
    		self.parent.rawDict["VarStore"] = pos

    	def getDataLength(self):
    		return len(self.data)

    	def toFile(self, file):
    		file.write(self.data)

    class ROSConverter(SimpleConverter):

    	def xmlWrite(self, xmlWriter, name, value):
    		registry, order, supplement = value
    		xmlWriter.simpletag(
    			name,
    			[
    				('Registry', tostr(registry)),
    				('Order', tostr(order)),
    				('Supplement', supplement)
    			])
    		xmlWriter.newline()

    	def xmlRead(self, name, attrs, content, parent):
    		return (attrs['Registry'], attrs['Order'], safeEval(attrs['Supplement']))

    topDictOperators = [
    #	opcode		name			argument type	default	converter
    	(25,		'maxstack',		'number',	None,	None),
    	((12, 30),	'ROS',	('SID', 'SID', 'number'),	None,	ROSConverter()),
    	((12, 20),	'SyntheticBase',	'number',	None,	None),
    	(0,		'version',		'SID',		None,	None),
    	(1,		'Notice',		'SID',		None,	Latin1Converter()),
    	((12, 0),	'Copyright',		'SID',		None,	Latin1Converter()),
    	(2,		'FullName',		'SID',		None,	None),
    	((12, 38),	'FontName',		'SID',		None,	None),
    	(3,		'FamilyName',		'SID',		None,	None),
    	(4,		'Weight',		'SID',		None,	None),
    	((12, 1),	'isFixedPitch',		'number',	0,	None),
    	((12, 2),	'ItalicAngle',		'number',	0,	None),
    	((12, 3),	'UnderlinePosition',	'number',	-100,	None),
    	((12, 4),	'UnderlineThickness',	'number',	50,	None),
    	((12, 5),	'PaintType',		'number',	0,	None),
    	((12, 6),	'CharstringType',	'number',	2,	None),
    	((12, 7),	'FontMatrix',		'array',	[0.001, 0, 0, 0.001, 0, 0],	None),
    	(13,		'UniqueID',		'number',	None,	None),
    	(5,		'FontBBox',		'array',	[0, 0, 0, 0],	None),
    	((12, 8),	'StrokeWidth',		'number',	0,	None),
    	(14,		'XUID',			'array',	None,	None),
    	((12, 21),	'PostScript',		'SID',		None,	None),
    	((12, 22),	'BaseFontName',		'SID',		None,	None),
    	((12, 23),	'BaseFontBlend',	'delta',	None,	None),
    	((12, 31),	'CIDFontVersion',	'number',	0,	None),
    	((12, 32),	'CIDFontRevision',	'number',	0,	None),
    	((12, 33),	'CIDFontType',		'number',	0,	None),
    	((12, 34),	'CIDCount',		'number',	8720,	None),
    	(15,		'charset',		'number',	None,	CharsetConverter()),
    	((12, 35),	'UIDBase',		'number',	None,	None),
    	(16,		'Encoding',		'number',	0,	EncodingConverter()),
    	(18,		'Private',	('number', 'number'),	None,	PrivateDictConverter()),
    	((12, 37),	'FDSelect',		'number',	None,	FDSelectConverter()),
    	((12, 36),	'FDArray',		'number',	None,	FDArrayConverter()),
    	(17,		'CharStrings',		'number',	None,	CharStringsConverter()),
    	(24,		'VarStore',		'number',	None,	VarStoreConverter()),
    ]

    topDictOperators2 = [
    #	opcode		name			argument type	default	converter
    	(25,		'maxstack',		'number',	None,	None),
    	((12, 7),	'FontMatrix',		'array',	[0.001, 0, 0, 0.001, 0, 0],	None),
    	((12, 37),	'FDSelect',		'number',	None,	FDSelectConverter()),
    	((12, 36),	'FDArray',		'number',	None,	FDArrayConverter()),
    	(17,		'CharStrings',		'number',	None,	CharStringsConverter()),
    	(24,		'VarStore',		'number',	None,	VarStoreConverter()),
    ]

    # Note! FDSelect and FDArray must both preceed CharStrings in the output XML build order,
    # in order for the font to compile back from xml.

    kBlendDictOpName = "blend"
    blendOp = 23

    privateDictOperators = [
    #	opcode		name			argument type	default	converter
    	(22,	"vsindex",		'number',	None,	None),
    	(blendOp,	kBlendDictOpName,		'blendList',	None,	None), # This is for reading to/from XML: it not written to CFF.
    	(6,		'BlueValues',		'delta',	None,	None),
    	(7,		'OtherBlues',		'delta',	None,	None),
    	(8,		'FamilyBlues',		'delta',	None,	None),
    	(9,		'FamilyOtherBlues',	'delta',	None,	None),
    	((12, 9),	'BlueScale',		'number',	0.039625, None),
    	((12, 10),	'BlueShift',		'number',	7,	None),
    	((12, 11),	'BlueFuzz',		'number',	1,	None),
    	(10,		'StdHW',		'number',	None,	None),
    	(11,		'StdVW',		'number',	None,	None),
    	((12, 12),	'StemSnapH',		'delta',	None,	None),
    	((12, 13),	'StemSnapV',		'delta',	None,	None),
    	((12, 14),	'ForceBold',		'number',	0,	None),
    	((12, 15),	'ForceBoldThreshold',	'number',	None,	None), # deprecated
    	((12, 16),	'lenIV',		'number',	None,	None), # deprecated
    	((12, 17),	'LanguageGroup',	'number',	0,	None),
    	((12, 18),	'ExpansionFactor',	'number',	0.06,	None),
    	((12, 19),	'initialRandomSeed',	'number',	0,	None),
    	(20,		'defaultWidthX',	'number',	0,	None),
    	(21,		'nominalWidthX',	'number',	0,	None),
    	(19,		'Subrs',		'number',	None,	SubrsConverter()),
    ]

    privateDictOperators2 = [
    #	opcode		name			argument type	default	converter
    	(22,	"vsindex",		'number',	None,	None),
    	(blendOp,	kBlendDictOpName,		'blendList',	None,	None), # This is for reading to/from XML: it not written to CFF.
    	(6,		'BlueValues',		'delta',	None,	None),
    	(7,		'OtherBlues',		'delta',	None,	None),
    	(8,		'FamilyBlues',		'delta',	None,	None),
    	(9,		'FamilyOtherBlues',	'delta',	None,	None),
    	((12, 9),	'BlueScale',		'number',	0.039625, None),
    	((12, 10),	'BlueShift',		'number',	7,	None),
    	((12, 11),	'BlueFuzz',		'number',	1,	None),
    	(10,		'StdHW',		'number',	None,	None),
    	(11,		'StdVW',		'number',	None,	None),
    	((12, 12),	'StemSnapH',		'delta',	None,	None),
    	((12, 13),	'StemSnapV',		'delta',	None,	None),
    	((12, 17),	'LanguageGroup',	'number',	0,	None),
    	((12, 18),	'ExpansionFactor',	'number',	0.06,	None),
    	(19,		'Subrs',		'number',	None,	SubrsConverter()),
    ]

    def addConverters(table):
    	for i in range(len(table)):
    		op, name, arg, default, conv = table[i]
    		if conv is not None:
    			continue
    		if arg in ("delta", "array"):
    			conv = ArrayConverter()
    		elif arg == "number":
    			conv = NumberConverter()
    		elif arg == "SID":
    			conv = ASCIIConverter()
    		elif arg == 'blendList':
    			conv = None
    		else:
    			assert False
    		table[i] = op, name, arg, default, conv

    addConverters(privateDictOperators)
    addConverters(topDictOperators)

    class TopDictDecompiler(psCharStrings.DictDecompiler):
    	operators = buildOperatorDict(topDictOperators)

    class PrivateDictDecompiler(psCharStrings.DictDecompiler):
    	operators = buildOperatorDict(privateDictOperators)

    class DictCompiler(object):
    	maxBlendStack = 0

    	def __init__(self, dictObj, strings, parent, isCFF2=None):
    		if strings:
    			assert isinstance(strings, IndexedStrings)
    		if isCFF2 is None and hasattr(parent, "isCFF2"):
    			isCFF2 = parent.isCFF2
    			assert isCFF2 is not None
    		self.isCFF2 = isCFF2
    		self.dictObj = dictObj
    		self.strings = strings
    		self.parent = parent
    		rawDict = {}
    		for name in dictObj.order:
    			value = getattr(dictObj, name, None)
    			if value is None:
    				continue
    			conv = dictObj.converters[name]
    			value = conv.write(dictObj, value)
    			if value == dictObj.defaults.get(name):
    				continue
    			rawDict[name] = value
    		self.rawDict = rawDict

    	def setPos(self, pos, endPos):
    		pass

    	def getDataLength(self):
    		return len(self.compile("getDataLength"))

    	def compile(self, reason):
    		log.log(DEBUG, "-- compiling %s for %s", self.__class__.__name__, reason)
    		rawDict = self.rawDict
    		data = []
    		for name in self.dictObj.order:
    			value = rawDict.get(name)
    			if value is None:
    				continue
    			op, argType = self.opcodes[name]
    			if isinstance(argType, tuple):
    				l = len(argType)
    				assert len(value) == l, "value doesn't match arg type"
    				for i in range(l):
    					arg = argType[i]
    					v = value[i]
    					arghandler = getattr(self, "arg_" + arg)
    					data.append(arghandler(v))
    			else:
    				arghandler = getattr(self, "arg_" + argType)
    				data.append(arghandler(value))
    			data.append(op)
    		data = bytesjoin(data)
    		return data

    	def toFile(self, file):
    		data = self.compile("toFile")
    		file.write(data)

    	def arg_number(self, num):
    		if isinstance(num, list):
    			data = [encodeNumber(val) for val in num]
    			data.append(encodeNumber(1))
    			data.append(bytechr(blendOp))
    			datum = bytesjoin(data)
    		else:
    			datum = encodeNumber(num)
    		return datum

    	def arg_SID(self, s):
    		return psCharStrings.encodeIntCFF(self.strings.getSID(s))

    	def arg_array(self, value):
    		data = []
    		for num in value:
    			data.append(self.arg_number(num))
    		return bytesjoin(data)

    	def arg_delta(self, value):
    		if not value:
    			return b""
    		val0 = value[0]
    		if isinstance(val0, list):
    			data = self.arg_delta_blend(value)
    		else:
    			out = []
    			last = 0
    			for v in value:
    				out.append(v - last)
    				last = v
    			data = []
    			for num in out:
    				data.append(encodeNumber(num))
    		return bytesjoin(data)

    	def arg_delta_blend(self, value):
    		"""A delta list with blend lists has to be *all* blend lists.

    		The value is a list is arranged as follows::

    			[
    				[V0, d0..dn]
    				[V1, d0..dn]
    				...
    				[Vm, d0..dn]
    			]

    		``V`` is the absolute coordinate value from the default font, and ``d0-dn``
    		are the delta values from the *n* regions. Each ``V`` is an absolute
    		coordinate from the default font.

    		We want to return a list::

    			[
    				[v0, v1..vm]
    				[d0..dn]
    				...
    				[d0..dn]
    				numBlends
    				blendOp
    			]

    		where each ``v`` is relative to the previous default font value.
    		"""
    		numMasters = len(value[0])
    		numBlends = len(value)
    		numStack = (numBlends * numMasters) + 1
    		if numStack > self.maxBlendStack:
    			# Figure out the max number of value we can blend
    			# and divide this list up into chunks of that size.

    			numBlendValues = int((self.maxBlendStack - 1) / numMasters)
    			out = []
    			while True:
    				numVal = min(len(value), numBlendValues)
    				if numVal == 0:
    					break
    				valList = value[0:numVal]
    				out1 = self.arg_delta_blend(valList)
    				out.extend(out1)
    				value = value[numVal:]
    		else:
    			firstList = [0] * numBlends
    			deltaList = [None] * numBlends
    			i = 0
    			prevVal = 0
    			while i < numBlends:
    				# For PrivateDict BlueValues, the default font
    				# values are absolute, not relative.
    				# Must convert these back to relative coordinates
    				# befor writing to CFF2.
    				defaultValue = value[i][0]
    				firstList[i] = defaultValue - prevVal
    				prevVal = defaultValue
    				deltaList[i] = value[i][1:]
    				i += 1

    			relValueList = firstList
    			for blendList in deltaList:
    				relValueList.extend(blendList)
    			out = [encodeNumber(val) for val in relValueList]
    			out.append(encodeNumber(numBlends))
    			out.append(bytechr(blendOp))
    		return out

    def encodeNumber(num):
    	if isinstance(num, float):
    		return psCharStrings.encodeFloat(num)
    	else:
    		return psCharStrings.encodeIntCFF(num)

    class TopDictCompiler(DictCompiler):

    	opcodes = buildOpcodeDict(topDictOperators)

    	def getChildren(self, strings):
    		isCFF2 = self.isCFF2
    		children = []
    		if self.dictObj.cff2GetGlyphOrder is None:
    			if hasattr(self.dictObj, "charset") and self.dictObj.charset:
    				if hasattr(self.dictObj, "ROS"):  # aka isCID
    					charsetCode = None
    				else:
    					charsetCode = getStdCharSet(self.dictObj.charset)
    				if charsetCode is None:
    					children.append(CharsetCompiler(strings, self.dictObj.charset, self))
    				else:
    					self.rawDict["charset"] = charsetCode
    			if hasattr(self.dictObj, "Encoding") and self.dictObj.Encoding:
    				encoding = self.dictObj.Encoding
    				if not isinstance(encoding, str):
    					children.append(EncodingCompiler(strings, encoding, self))
    		else:
    			if hasattr(self.dictObj, "VarStore"):
    				varStoreData = self.dictObj.VarStore
    				varStoreComp = VarStoreCompiler(varStoreData, self)
    				children.append(varStoreComp)
    		if hasattr(self.dictObj, "FDSelect"):
    			# I have not yet supported merging a ttx CFF-CID font, as there are
    			# interesting issues about merging the FDArrays. Here I assume that
    			# either the font was read from XML, and the FDSelect indices are all
    			# in the charstring data, or the FDSelect array is already fully defined.
    			fdSelect = self.dictObj.FDSelect
    			# probably read in from XML; assume fdIndex in CharString data
    			if len(fdSelect) == 0:
    				charStrings = self.dictObj.CharStrings
    				for name in self.dictObj.charset:
    					fdSelect.append(charStrings[name].fdSelectIndex)
    			fdSelectComp = FDSelectCompiler(fdSelect, self)
    			children.append(fdSelectComp)
    		if hasattr(self.dictObj, "CharStrings"):
    			items = []
    			charStrings = self.dictObj.CharStrings
    			for name in self.dictObj.charset:
    				items.append(charStrings[name])
    			charStringsComp = CharStringsCompiler(
    				items, strings, self, isCFF2=isCFF2)
    			children.append(charStringsComp)
    		if hasattr(self.dictObj, "FDArray"):
    			# I have not yet supported merging a ttx CFF-CID font, as there are
    			# interesting issues about merging the FDArrays. Here I assume that the
    			# FDArray info is correct and complete.
    			fdArrayIndexComp = self.dictObj.FDArray.getCompiler(strings, self)
    			children.append(fdArrayIndexComp)
    			children.extend(fdArrayIndexComp.getChildren(strings))
    		if hasattr(self.dictObj, "Private"):
    			privComp = self.dictObj.Private.getCompiler(strings, self)
    			children.append(privComp)
    			children.extend(privComp.getChildren(strings))
    		return children

    class FontDictCompiler(DictCompiler):
    	opcodes = buildOpcodeDict(topDictOperators)

    	def __init__(self, dictObj, strings, parent, isCFF2=None):
    		super(FontDictCompiler, self).__init__(dictObj, strings, parent, isCFF2=isCFF2)
    		#
    		# We now take some effort to detect if there were any key/value pairs
    		# supplied that were ignored in the FontDict context, and issue a warning
    		# for those cases.
    		#
    		ignoredNames = []
    		dictObj = self.dictObj
    		for name in sorted(set(dictObj.converters) - set(dictObj.order)):
    			if name in dictObj.rawDict:
    				# The font was directly read from binary. In this
    				# case, we want to report *all* "useless" key/value
    				# pairs that are in the font, not just the ones that
    				# are different from the default.
    				ignoredNames.append(name)
    			else:
    				# The font was probably read from a TTX file. We only
    				# warn about keys whos value is not the default. The
    				# ones that have the default value will not be written
    				# to binary anyway.
    				default = dictObj.defaults.get(name)
    				if default is not None:
    					conv = dictObj.converters[name]
    					default = conv.read(dictObj, default)
    				if getattr(dictObj, name, None) != default:
    					ignoredNames.append(name)
    		if ignoredNames:
    			log.warning(
    				"Some CFF FDArray/FontDict keys were ignored upon compile: " +
    				" ".join(sorted(ignoredNames)))

    	def getChildren(self, strings):
    		children = []
    		if hasattr(self.dictObj, "Private"):
    			privComp = self.dictObj.Private.getCompiler(strings, self)
    			children.append(privComp)
    			children.extend(privComp.getChildren(strings))
    		return children

    class PrivateDictCompiler(DictCompiler):

    	maxBlendStack = maxStackLimit
    	opcodes = buildOpcodeDict(privateDictOperators)

    	def setPos(self, pos, endPos):
    		size = endPos - pos
    		self.parent.rawDict["Private"] = size, pos
    		self.pos = pos

    	def getChildren(self, strings):
    		children = []
    		if hasattr(self.dictObj, "Subrs"):
    			children.append(self.dictObj.Subrs.getCompiler(strings, self))
    		return children

    class BaseDict(object):

    	def __init__(self, strings=None, file=None, offset=None, isCFF2=None):
    		assert (isCFF2 is None) == (file is None)
    		self.rawDict = {}
    		self.skipNames = []
    		self.strings = strings
    		if file is None:
    			return
    		self._isCFF2 = isCFF2
    		self.file = file
    		if offset is not None:
    			log.log(DEBUG, "loading %s at %s", self.__class__.__name__, offset)
    			self.offset = offset

    	def decompile(self, data):
    		log.log(DEBUG, "    length %s is %d", self.__class__.__name__, len(data))
    		dec = self.decompilerClass(self.strings, self)
    		dec.decompile(data)
    		self.rawDict = dec.getDict()
    		self.postDecompile()

    	def postDecompile(self):
    		pass

    	def getCompiler(self, strings, parent, isCFF2=None):
    		return self.compilerClass(self, strings, parent, isCFF2=isCFF2)

    	def __getattr__(self, name):
    		if name[:2] == name[-2:] == "__":
    			# to make deepcopy() and pickle.load() work, we need to signal with
    			# AttributeError that dunder methods like '__deepcopy__' or '__getstate__'
    			# aren't implemented. For more details, see:
    			# https://github.com/fonttools/fonttools/pull/1488
    			raise AttributeError(name)
    		value = self.rawDict.get(name, None)
    		if value is None:
    			value = self.defaults.get(name)
    		if value is None:
    			raise AttributeError(name)
    		conv = self.converters[name]
    		value = conv.read(self, value)
    		setattr(self, name, value)
    		return value

    	def toXML(self, xmlWriter):
    		for name in self.order:
    			if name in self.skipNames:
    				continue
    			value = getattr(self, name, None)
    			# XXX For "charset" we never skip calling xmlWrite even if the
    			# value is None, so we always write the following XML comment:
    			#
    			# <!-- charset is dumped separately as the 'GlyphOrder' element -->
    			#
    			# Charset is None when 'CFF ' table is imported from XML into an
    			# empty TTFont(). By writing this comment all the time, we obtain
    			# the same XML output whether roundtripping XML-to-XML or
    			# dumping binary-to-XML
    			if value is None and name != "charset":
    				continue
    			conv = self.converters[name]
    			conv.xmlWrite(xmlWriter, name, value)
    		ignoredNames = set(self.rawDict) - set(self.order)
    		if ignoredNames:
    			xmlWriter.comment(
    				"some keys were ignored: %s" % " ".join(sorted(ignoredNames)))
    			xmlWriter.newline()

    	def fromXML(self, name, attrs, content):
    		conv = self.converters[name]
    		value = conv.xmlRead(name, attrs, content, self)
    		setattr(self, name, value)

    class TopDict(BaseDict):
    	"""The ``TopDict`` represents the top-level dictionary holding font
    	information. CFF2 tables contain a restricted set of top-level entries
    	as described `here <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#7-top-dict-data>`_,
    	but CFF tables may contain a wider range of information. This information
    	can be accessed through attributes or through the dictionary returned
    	through the ``rawDict`` property:

    	.. code:: python

    		font = tt["CFF "].cff[0]
    		font.FamilyName
    		# 'Linux Libertine O'
    		font.rawDict["FamilyName"]
    		# 'Linux Libertine O'

    	More information is available in the CFF file's private dictionary, accessed
    	via the ``Private`` property:

    	.. code:: python

    		tt["CFF "].cff[0].Private.BlueValues
    		# [-15, 0, 515, 515, 666, 666]

    	"""

    	defaults = buildDefaults(topDictOperators)
    	converters = buildConverters(topDictOperators)
    	compilerClass = TopDictCompiler
    	order = buildOrder(topDictOperators)
    	decompilerClass = TopDictDecompiler

    	def __init__(self, strings=None, file=None, offset=None,
    			GlobalSubrs=None, cff2GetGlyphOrder=None, isCFF2=None):
    		super(TopDict, self).__init__(strings, file, offset, isCFF2=isCFF2)
    		self.cff2GetGlyphOrder = cff2GetGlyphOrder
    		self.GlobalSubrs = GlobalSubrs
    		if isCFF2:
    			self.defaults = buildDefaults(topDictOperators2)
    			self.charset = cff2GetGlyphOrder()
    			self.order = buildOrder(topDictOperators2)
    		else:
    			self.defaults = buildDefaults(topDictOperators)
    			self.order = buildOrder(topDictOperators)

    	def getGlyphOrder(self):
    		"""Returns a list of glyph names in the CFF font."""
    		return self.charset

    	def postDecompile(self):
    		offset = self.rawDict.get("CharStrings")
    		if offset is None:
    			return
    		# get the number of glyphs beforehand.
    		self.file.seek(offset)
    		if self._isCFF2:
    			self.numGlyphs = readCard32(self.file)
    		else:
    			self.numGlyphs = readCard16(self.file)

    	def toXML(self, xmlWriter):
    		if hasattr(self, "CharStrings"):
    			self.decompileAllCharStrings()
    		if hasattr(self, "ROS"):
    			self.skipNames = ['Encoding']
    		if not hasattr(self, "ROS") or not hasattr(self, "CharStrings"):
    			# these values have default values, but I only want them to show up
    			# in CID fonts.
    			self.skipNames = [
    				'CIDFontVersion', 'CIDFontRevision', 'CIDFontType', 'CIDCount']
    		BaseDict.toXML(self, xmlWriter)

    	def decompileAllCharStrings(self):
    		# Make sure that all the Private Dicts have been instantiated.
    		for i, charString in enumerate(self.CharStrings.values()):
    			try:
    				charString.decompile()
    			except:
    				log.error("Error in charstring %s", i)
    				raise

    	def recalcFontBBox(self):
    		fontBBox = None
    		for charString in self.CharStrings.values():
    			bounds = charString.calcBounds(self.CharStrings)
    			if bounds is not None:
    				if fontBBox is not None:
    					fontBBox = unionRect(fontBBox, bounds)
    				else:
    					fontBBox = bounds

    		if fontBBox is None:
    			self.FontBBox = self.defaults['FontBBox'][:]
    		else:
    			self.FontBBox = list(intRect(fontBBox))

    class FontDict(BaseDict):
    	#
    	# Since fonttools used to pass a lot of fields that are not relevant in the FDArray
    	# FontDict, there are 'ttx' files in the wild that contain all these. These got in
    	# the ttx files because fonttools writes explicit values for all the TopDict default
    	# values. These are not actually illegal in the context of an FDArray FontDict - you
    	# can legally, per spec, put any arbitrary key/value pair in a FontDict - but are
    	# useless since current major company CFF interpreters ignore anything but the set
    	# listed in this file. So, we just silently skip them. An exception is Weight: this
    	# is not used by any interpreter, but some foundries have asked that this be
    	# supported in FDArray FontDicts just to preserve information about the design when
    	# the font is being inspected.
    	#
    	# On top of that, there are fonts out there that contain such useless FontDict values.
    	#
    	# By subclassing TopDict, we *allow* all key/values from TopDict, both when reading
    	# from binary or when reading from XML, but by overriding `order` with a limited
    	# list of names, we ensure that only the useful names ever get exported to XML and
    	# ever get compiled into the binary font.
    	#
    	# We override compilerClass so we can warn about "useless" key/value pairs, either
    	# from the original binary font or from TTX input.
    	#
    	# See:
    	# - https://github.com/fonttools/fonttools/issues/740
    	# - https://github.com/fonttools/fonttools/issues/601
    	# - https://github.com/adobe-type-tools/afdko/issues/137
    	#
    	defaults = {}
    	converters = buildConverters(topDictOperators)
    	compilerClass = FontDictCompiler
    	orderCFF = ['FontName', 'FontMatrix', 'Weight', 'Private']
    	orderCFF2 = ['Private']
    	decompilerClass = TopDictDecompiler

    	def __init__(self, strings=None, file=None, offset=None,
    			GlobalSubrs=None, isCFF2=None, vstore=None):
    		super(FontDict, self).__init__(strings, file, offset, isCFF2=isCFF2)
    		self.vstore = vstore
    		self.setCFF2(isCFF2)

    	def setCFF2(self, isCFF2):
    		# isCFF2 may be None.
    		if isCFF2:
    			self.order = self.orderCFF2
    			self._isCFF2 = True
    		else:
    			self.order = self.orderCFF
    			self._isCFF2 = False

    class PrivateDict(BaseDict):
    	defaults = buildDefaults(privateDictOperators)
    	converters = buildConverters(privateDictOperators)
    	order = buildOrder(privateDictOperators)
    	decompilerClass = PrivateDictDecompiler
    	compilerClass = PrivateDictCompiler

    	def __init__(self, strings=None, file=None, offset=None, isCFF2=None,
    			vstore=None):
    		super(PrivateDict, self).__init__(strings, file, offset, isCFF2=isCFF2)
    		self.vstore = vstore
    		if isCFF2:
    			self.defaults = buildDefaults(privateDictOperators2)
    			self.order = buildOrder(privateDictOperators2)
    			# Provide dummy values. This avoids needing to provide
    			# an isCFF2 state in a lot of places.
    			self.nominalWidthX = self.defaultWidthX = None
    		else:
    			self.defaults = buildDefaults(privateDictOperators)
    			self.order = buildOrder(privateDictOperators)

    	@property
    	def in_cff2(self):
    		return self._isCFF2

    	def getNumRegions(self, vi=None):  # called from misc/psCharStrings.py
    		# if getNumRegions is being called, we can assume that VarStore exists.
    		if vi is None:
    			if hasattr(self, 'vsindex'):
    				vi = self.vsindex
    			else:
    				vi = 0
    		numRegions = self.vstore.getNumRegions(vi)
    		return numRegions

    class IndexedStrings(object):

    	"""SID -> string mapping."""

    	def __init__(self, file=None):
    		if file is None:
    			strings = []
    		else:
    			strings = [
    				tostr(s, encoding="latin1")
    				for s in Index(file, isCFF2=False)
    			]
    		self.strings = strings

    	def getCompiler(self):
    		return IndexedStringsCompiler(self, None, self, isCFF2=False)

    	def __len__(self):
    		return len(self.strings)

    	def __getitem__(self, SID):
    		if SID < cffStandardStringCount:
    			return cffStandardStrings[SID]
    		else:
    			return self.strings[SID - cffStandardStringCount]

    	def getSID(self, s):
    		if not hasattr(self, "stringMapping"):
    			self.buildStringMapping()
    		s = tostr(s, encoding="latin1")
    		if s in cffStandardStringMapping:
    			SID = cffStandardStringMapping[s]
    		elif s in self.stringMapping:
    			SID = self.stringMapping[s]
    		else:
    			SID = len(self.strings) + cffStandardStringCount
    			self.strings.append(s)
    			self.stringMapping[s] = SID
    		return SID

    	def getStrings(self):
    		return self.strings

    	def buildStringMapping(self):
    		self.stringMapping = {}
    		for index in range(len(self.strings)):
    			self.stringMapping[self.strings[index]] = index + cffStandardStringCount

    # The 391 Standard Strings as used in the CFF format.
    # from Adobe Technical None #5176, version 1.0, 18 March 1998

    cffStandardStrings = ['.notdef', 'space', 'exclam', 'quotedbl', 'numbersign',
    		'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright',
    		'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one',
    		'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon',
    		'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C',
    		'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
    		'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash',
    		'bracketright', 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c',
    		'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
    		's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright',
    		'asciitilde', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin',
    		'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft',
    		'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger',
    		'daggerdbl', 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase',
    		'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand',
    		'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve',
    		'dotaccent', 'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron',
    		'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae',
    		'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior',
    		'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn',
    		'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters',
    		'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior',
    		'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring',
    		'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave',
    		'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute',
    		'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute',
    		'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron',
    		'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla',
    		'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex',
    		'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis',
    		'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave',
    		'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall',
    		'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall',
    		'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader',
    		'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle',
    		'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle',
    		'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior',
    		'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior',
    		'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior',
    		'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', 'parenleftinferior',
    		'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall',
    		'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall',
    		'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall',
    		'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall',
    		'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall',
    		'exclamdownsmall', 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall',
    		'Dieresissmall', 'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall',
    		'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall',
    		'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths',
    		'onethird', 'twothirds', 'zerosuperior', 'foursuperior', 'fivesuperior',
    		'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior',
    		'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior',
    		'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior',
    		'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall',
    		'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall',
    		'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall',
    		'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall',
    		'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall',
    		'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall',
    		'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall',
    		'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', '001.001', '001.002',
    		'001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman',
    		'Semibold'
    ]

    cffStandardStringCount = 391
    assert len(cffStandardStrings) == cffStandardStringCount
    # build reverse mapping
    cffStandardStringMapping = {}
    for _i in range(cffStandardStringCount):
    	cffStandardStringMapping[cffStandardStrings[_i]] = _i

    cffISOAdobeStrings = [".notdef", "space", "exclam", "quotedbl", "numbersign",
    "dollar", "percent", "ampersand", "quoteright", "parenleft", "parenright",
    "asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two",
    "three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon",
    "less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G",
    "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W",
    "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum",
    "underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j",
    "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
    "braceleft", "bar", "braceright", "asciitilde", "exclamdown", "cent",
    "sterling", "fraction", "yen", "florin", "section", "currency", "quotesingle",
    "quotedblleft", "guillemotleft", "guilsinglleft", "guilsinglright", "fi", "fl",
    "endash", "dagger", "daggerdbl", "periodcentered", "paragraph", "bullet",
    "quotesinglbase", "quotedblbase", "quotedblright", "guillemotright", "ellipsis",
    "perthousand", "questiondown", "grave", "acute", "circumflex", "tilde",
    "macron", "breve", "dotaccent", "dieresis", "ring", "cedilla", "hungarumlaut",
    "ogonek", "caron", "emdash", "AE", "ordfeminine", "Lslash", "Oslash", "OE",
    "ordmasculine", "ae", "dotlessi", "lslash", "oslash", "oe", "germandbls",
    "onesuperior", "logicalnot", "mu", "trademark", "Eth", "onehalf", "plusminus",
    "Thorn", "onequarter", "divide", "brokenbar", "degree", "thorn",
    "threequarters", "twosuperior", "registered", "minus", "eth", "multiply",
    "threesuperior", "copyright", "Aacute", "Acircumflex", "Adieresis", "Agrave",
    "Aring", "Atilde", "Ccedilla", "Eacute", "Ecircumflex", "Edieresis", "Egrave",
    "Iacute", "Icircumflex", "Idieresis", "Igrave", "Ntilde", "Oacute",
    "Ocircumflex", "Odieresis", "Ograve", "Otilde", "Scaron", "Uacute",
    "Ucircumflex", "Udieresis", "Ugrave", "Yacute", "Ydieresis", "Zcaron", "aacute",
    "acircumflex", "adieresis", "agrave", "aring", "atilde", "ccedilla", "eacute",
    "ecircumflex", "edieresis", "egrave", "iacute", "icircumflex", "idieresis",
    "igrave", "ntilde", "oacute", "ocircumflex", "odieresis", "ograve", "otilde",
    "scaron", "uacute", "ucircumflex", "udieresis", "ugrave", "yacute", "ydieresis",
    "zcaron"]

    cffISOAdobeStringCount = 229
    assert len(cffISOAdobeStrings) == cffISOAdobeStringCount

    cffIExpertStrings = [".notdef", "space", "exclamsmall", "Hungarumlautsmall",
    "dollaroldstyle", "dollarsuperior", "ampersandsmall", "Acutesmall",
    "parenleftsuperior", "parenrightsuperior", "twodotenleader", "onedotenleader",
    "comma", "hyphen", "period", "fraction", "zerooldstyle", "oneoldstyle",
    "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle",
    "sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", "semicolon",
    "commasuperior", "threequartersemdash", "periodsuperior", "questionsmall",
    "asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior",
    "lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior",
    "tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior",
    "parenrightinferior", "Circumflexsmall", "hyphensuperior", "Gravesmall",
    "Asmall", "Bsmall", "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall",
    "Ismall", "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall",
    "Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall", "Xsmall",
    "Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah", "Tildesmall",
    "exclamdownsmall", "centoldstyle", "Lslashsmall", "Scaronsmall", "Zcaronsmall",
    "Dieresissmall", "Brevesmall", "Caronsmall", "Dotaccentsmall", "Macronsmall",
    "figuredash", "hypheninferior", "Ogoneksmall", "Ringsmall", "Cedillasmall",
    "onequarter", "onehalf", "threequarters", "questiondownsmall", "oneeighth",
    "threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds",
    "zerosuperior", "onesuperior", "twosuperior", "threesuperior", "foursuperior",
    "fivesuperior", "sixsuperior", "sevensuperior", "eightsuperior", "ninesuperior",
    "zeroinferior", "oneinferior", "twoinferior", "threeinferior", "fourinferior",
    "fiveinferior", "sixinferior", "seveninferior", "eightinferior", "nineinferior",
    "centinferior", "dollarinferior", "periodinferior", "commainferior",
    "Agravesmall", "Aacutesmall", "Acircumflexsmall", "Atildesmall",
    "Adieresissmall", "Aringsmall", "AEsmall", "Ccedillasmall", "Egravesmall",
    "Eacutesmall", "Ecircumflexsmall", "Edieresissmall", "Igravesmall",
    "Iacutesmall", "Icircumflexsmall", "Idieresissmall", "Ethsmall", "Ntildesmall",
    "Ogravesmall", "Oacutesmall", "Ocircumflexsmall", "Otildesmall",
    "Odieresissmall", "OEsmall", "Oslashsmall", "Ugravesmall", "Uacutesmall",
    "Ucircumflexsmall", "Udieresissmall", "Yacutesmall", "Thornsmall",
    "Ydieresissmall"]

    cffExpertStringCount = 166
    assert len(cffIExpertStrings) == cffExpertStringCount

    cffExpertSubsetStrings = [".notdef", "space", "dollaroldstyle",
    "dollarsuperior", "parenleftsuperior", "parenrightsuperior", "twodotenleader",
    "onedotenleader", "comma", "hyphen", "period", "fraction", "zerooldstyle",
    "oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle",
    "sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon",
    "semicolon", "commasuperior", "threequartersemdash", "periodsuperior",
    "asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior",
    "lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior",
    "tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior",
    "parenrightinferior", "hyphensuperior", "colonmonetary", "onefitted", "rupiah",
    "centoldstyle", "figuredash", "hypheninferior", "onequarter", "onehalf",
    "threequarters", "oneeighth", "threeeighths", "fiveeighths", "seveneighths",
    "onethird", "twothirds", "zerosuperior", "onesuperior", "twosuperior",
    "threesuperior", "foursuperior", "fivesuperior", "sixsuperior", "sevensuperior",
    "eightsuperior", "ninesuperior", "zeroinferior", "oneinferior", "twoinferior",
    "threeinferior", "fourinferior", "fiveinferior", "sixinferior", "seveninferior",
    "eightinferior", "nineinferior", "centinferior", "dollarinferior",
    "periodinferior", "commainferior"]

    cffExpertSubsetStringCount = 87
    assert len(cffExpertSubsetStrings) == cffExpertSubsetStringCount