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