Home | History | Annotate | Download | only in fontTools
      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.textTools import safeEval
      8 import struct
      9 
     10 DEBUG = 0
     11 
     12 
     13 cffHeaderFormat = """
     14 	major:   B
     15 	minor:   B
     16 	hdrSize: B
     17 	offSize: B
     18 """
     19 
     20 class CFFFontSet(object):
     21 	
     22 	def __init__(self):
     23 		pass
     24 	
     25 	def decompile(self, file, otFont):
     26 		sstruct.unpack(cffHeaderFormat, file.read(4), self)
     27 		assert self.major == 1 and self.minor == 0, \
     28 				"unknown CFF format: %d.%d" % (self.major, self.minor)
     29 		
     30 		file.seek(self.hdrSize)
     31 		self.fontNames = list(Index(file))
     32 		self.topDictIndex = TopDictIndex(file)
     33 		self.strings = IndexedStrings(file)
     34 		self.GlobalSubrs = GlobalSubrsIndex(file)
     35 		self.topDictIndex.strings = self.strings
     36 		self.topDictIndex.GlobalSubrs = self.GlobalSubrs
     37 	
     38 	def __len__(self):
     39 		return len(self.fontNames)
     40 	
     41 	def keys(self):
     42 		return list(self.fontNames)
     43 	
     44 	def values(self):
     45 		return self.topDictIndex
     46 	
     47 	def __getitem__(self, name):
     48 		try:
     49 			index = self.fontNames.index(name)
     50 		except ValueError:
     51 			raise KeyError(name)
     52 		return self.topDictIndex[index]
     53 	
     54 	def compile(self, file, otFont):
     55 		strings = IndexedStrings()
     56 		writer = CFFWriter()
     57 		writer.add(sstruct.pack(cffHeaderFormat, self))
     58 		fontNames = Index()
     59 		for name in self.fontNames:
     60 			fontNames.append(name)
     61 		writer.add(fontNames.getCompiler(strings, None))
     62 		topCompiler = self.topDictIndex.getCompiler(strings, None)
     63 		writer.add(topCompiler)
     64 		writer.add(strings.getCompiler())
     65 		writer.add(self.GlobalSubrs.getCompiler(strings, None))
     66 		
     67 		for topDict in self.topDictIndex:
     68 			if not hasattr(topDict, "charset") or topDict.charset is None:
     69 				charset = otFont.getGlyphOrder()
     70 				topDict.charset = charset
     71 		
     72 		for child in topCompiler.getChildren(strings):
     73 			writer.add(child)
     74 		
     75 		writer.toFile(file)
     76 	
     77 	def toXML(self, xmlWriter, progress=None):
     78 		for fontName in self.fontNames:
     79 			xmlWriter.begintag("CFFFont", name=tostr(fontName))
     80 			xmlWriter.newline()
     81 			font = self[fontName]
     82 			font.toXML(xmlWriter, progress)
     83 			xmlWriter.endtag("CFFFont")
     84 			xmlWriter.newline()
     85 		xmlWriter.newline()
     86 		xmlWriter.begintag("GlobalSubrs")
     87 		xmlWriter.newline()
     88 		self.GlobalSubrs.toXML(xmlWriter, progress)
     89 		xmlWriter.endtag("GlobalSubrs")
     90 		xmlWriter.newline()
     91 	
     92 	def fromXML(self, name, attrs, content):
     93 		if not hasattr(self, "GlobalSubrs"):
     94 			self.GlobalSubrs = GlobalSubrsIndex()
     95 			self.major = 1
     96 			self.minor = 0
     97 			self.hdrSize = 4
     98 			self.offSize = 4  # XXX ??
     99 		if name == "CFFFont":
    100 			if not hasattr(self, "fontNames"):
    101 				self.fontNames = []
    102 				self.topDictIndex = TopDictIndex()
    103 			fontName = attrs["name"]
    104 			topDict = TopDict(GlobalSubrs=self.GlobalSubrs)
    105 			topDict.charset = None  # gets filled in later
    106 			self.fontNames.append(fontName)
    107 			self.topDictIndex.append(topDict)
    108 			for element in content:
    109 				if isinstance(element, basestring):
    110 					continue
    111 				name, attrs, content = element
    112 				topDict.fromXML(name, attrs, content)
    113 		elif name == "GlobalSubrs":
    114 			for element in content:
    115 				if isinstance(element, basestring):
    116 					continue
    117 				name, attrs, content = element
    118 				subr = psCharStrings.T2CharString()
    119 				subr.fromXML(name, attrs, content)
    120 				self.GlobalSubrs.append(subr)
    121 
    122 
    123 class CFFWriter(object):
    124 	
    125 	def __init__(self):
    126 		self.data = []
    127 	
    128 	def add(self, table):
    129 		self.data.append(table)
    130 	
    131 	def toFile(self, file):
    132 		lastPosList = None
    133 		count = 1
    134 		while True:
    135 			if DEBUG:
    136 				print("CFFWriter.toFile() iteration:", count)
    137 			count = count + 1
    138 			pos = 0
    139 			posList = [pos]
    140 			for item in self.data:
    141 				if hasattr(item, "getDataLength"):
    142 					endPos = pos + item.getDataLength()
    143 				else:
    144 					endPos = pos + len(item)
    145 				if hasattr(item, "setPos"):
    146 					item.setPos(pos, endPos)
    147 				pos = endPos
    148 				posList.append(pos)
    149 			if posList == lastPosList:
    150 				break
    151 			lastPosList = posList
    152 		if DEBUG:
    153 			print("CFFWriter.toFile() writing to file.")
    154 		begin = file.tell()
    155 		posList = [0]
    156 		for item in self.data:
    157 			if hasattr(item, "toFile"):
    158 				item.toFile(file)
    159 			else:
    160 				file.write(item)
    161 			posList.append(file.tell() - begin)
    162 		assert posList == lastPosList
    163 
    164 
    165 def calcOffSize(largestOffset):
    166 	if largestOffset < 0x100:
    167 		offSize = 1
    168 	elif largestOffset < 0x10000:
    169 		offSize = 2
    170 	elif largestOffset < 0x1000000:
    171 		offSize = 3
    172 	else:
    173 		offSize = 4
    174 	return offSize
    175 
    176 
    177 class IndexCompiler(object):
    178 	
    179 	def __init__(self, items, strings, parent):
    180 		self.items = self.getItems(items, strings)
    181 		self.parent = parent
    182 	
    183 	def getItems(self, items, strings):
    184 		return items
    185 	
    186 	def getOffsets(self):
    187 		pos = 1
    188 		offsets = [pos]
    189 		for item in self.items:
    190 			if hasattr(item, "getDataLength"):
    191 				pos = pos + item.getDataLength()
    192 			else:
    193 				pos = pos + len(item)
    194 			offsets.append(pos)
    195 		return offsets
    196 	
    197 	def getDataLength(self):
    198 		lastOffset = self.getOffsets()[-1]
    199 		offSize = calcOffSize(lastOffset)
    200 		dataLength = (
    201 			2 +                                # count
    202 			1 +                                # offSize
    203 			(len(self.items) + 1) * offSize +  # the offsets
    204 			lastOffset - 1                     # size of object data
    205 		)
    206 		return dataLength
    207 	
    208 	def toFile(self, file):
    209 		offsets = self.getOffsets()
    210 		writeCard16(file, len(self.items))
    211 		offSize = calcOffSize(offsets[-1])
    212 		writeCard8(file, offSize)
    213 		offSize = -offSize
    214 		pack = struct.pack
    215 		for offset in offsets:
    216 			binOffset = pack(">l", offset)[offSize:]
    217 			assert len(binOffset) == -offSize
    218 			file.write(binOffset)
    219 		for item in self.items:
    220 			if hasattr(item, "toFile"):
    221 				item.toFile(file)
    222 			else:
    223 				file.write(tobytes(item, encoding="latin1"))
    224 
    225 
    226 class IndexedStringsCompiler(IndexCompiler):
    227 	
    228 	def getItems(self, items, strings):
    229 		return items.strings
    230 
    231 
    232 class TopDictIndexCompiler(IndexCompiler):
    233 	
    234 	def getItems(self, items, strings):
    235 		out = []
    236 		for item in items:
    237 			out.append(item.getCompiler(strings, self))
    238 		return out
    239 	
    240 	def getChildren(self, strings):
    241 		children = []
    242 		for topDict in self.items:
    243 			children.extend(topDict.getChildren(strings))
    244 		return children
    245 
    246 
    247 class FDArrayIndexCompiler(IndexCompiler):
    248 	
    249 	def getItems(self, items, strings):
    250 		out = []
    251 		for item in items:
    252 			out.append(item.getCompiler(strings, self))
    253 		return out
    254 	
    255 	def getChildren(self, strings):
    256 		children = []
    257 		for fontDict in self.items:
    258 			children.extend(fontDict.getChildren(strings))
    259 		return children
    260 
    261 	def toFile(self, file):
    262 		offsets = self.getOffsets()
    263 		writeCard16(file, len(self.items))
    264 		offSize = calcOffSize(offsets[-1])
    265 		writeCard8(file, offSize)
    266 		offSize = -offSize
    267 		pack = struct.pack
    268 		for offset in offsets:
    269 			binOffset = pack(">l", offset)[offSize:]
    270 			assert len(binOffset) == -offSize
    271 			file.write(binOffset)
    272 		for item in self.items:
    273 			if hasattr(item, "toFile"):
    274 				item.toFile(file)
    275 			else:
    276 				file.write(item)
    277 
    278 	def setPos(self, pos, endPos):
    279 		self.parent.rawDict["FDArray"] = pos
    280 
    281 
    282 class GlobalSubrsCompiler(IndexCompiler):
    283 	def getItems(self, items, strings):
    284 		out = []
    285 		for cs in items:
    286 			cs.compile()
    287 			out.append(cs.bytecode)
    288 		return out
    289 
    290 class SubrsCompiler(GlobalSubrsCompiler):
    291 	def setPos(self, pos, endPos):
    292 		offset = pos - self.parent.pos
    293 		self.parent.rawDict["Subrs"] = offset
    294 
    295 class CharStringsCompiler(GlobalSubrsCompiler):
    296 	def setPos(self, pos, endPos):
    297 		self.parent.rawDict["CharStrings"] = pos
    298 
    299 
    300 class Index(object):
    301 	
    302 	"""This class represents what the CFF spec calls an INDEX."""
    303 	
    304 	compilerClass = IndexCompiler
    305 	
    306 	def __init__(self, file=None):
    307 		name = self.__class__.__name__
    308 		if file is None:
    309 			self.items = []
    310 			return
    311 		if DEBUG:
    312 			print("loading %s at %s" % (name, file.tell()))
    313 		self.file = file
    314 		count = readCard16(file)
    315 		self.count = count
    316 		self.items = [None] * count
    317 		if count == 0:
    318 			self.items = []
    319 			return
    320 		offSize = readCard8(file)
    321 		if DEBUG:
    322 			print("    index count: %s offSize: %s" % (count, offSize))
    323 		assert offSize <= 4, "offSize too large: %s" % offSize
    324 		self.offsets = offsets = []
    325 		pad = b'\0' * (4 - offSize)
    326 		for index in range(count+1):
    327 			chunk = file.read(offSize)
    328 			chunk = pad + chunk
    329 			offset, = struct.unpack(">L", chunk)
    330 			offsets.append(int(offset))
    331 		self.offsetBase = file.tell() - 1
    332 		file.seek(self.offsetBase + offsets[-1])  # pretend we've read the whole lot
    333 		if DEBUG:
    334 			print("    end of %s at %s" % (name, file.tell()))
    335 	
    336 	def __len__(self):
    337 		return len(self.items)
    338 	
    339 	def __getitem__(self, index):
    340 		item = self.items[index]
    341 		if item is not None:
    342 			return item
    343 		offset = self.offsets[index] + self.offsetBase
    344 		size = self.offsets[index+1] - self.offsets[index]
    345 		file = self.file
    346 		file.seek(offset)
    347 		data = file.read(size)
    348 		assert len(data) == size
    349 		item = self.produceItem(index, data, file, offset, size)
    350 		self.items[index] = item
    351 		return item
    352 	
    353 	def produceItem(self, index, data, file, offset, size):
    354 		return data
    355 	
    356 	def append(self, item):
    357 		self.items.append(item)
    358 	
    359 	def getCompiler(self, strings, parent):
    360 		return self.compilerClass(self, strings, parent)
    361 
    362 
    363 class GlobalSubrsIndex(Index):
    364 	
    365 	compilerClass = GlobalSubrsCompiler
    366 	
    367 	def __init__(self, file=None, globalSubrs=None, private=None, fdSelect=None, fdArray=None):
    368 		Index.__init__(self, file)
    369 		self.globalSubrs = globalSubrs
    370 		self.private = private
    371 		if fdSelect:
    372 			self.fdSelect = fdSelect
    373 		if fdArray:
    374 			self.fdArray = fdArray
    375 	
    376 	def produceItem(self, index, data, file, offset, size):
    377 		if self.private is not None:
    378 			private = self.private
    379 		elif hasattr(self, 'fdArray') and self.fdArray is not None:
    380 			private = self.fdArray[self.fdSelect[index]].Private
    381 		else:
    382 			private = None
    383 		return psCharStrings.T2CharString(data, private=private, globalSubrs=self.globalSubrs)
    384 	
    385 	def toXML(self, xmlWriter, progress):
    386 		xmlWriter.comment("The 'index' attribute is only for humans; it is ignored when parsed.")
    387 		xmlWriter.newline()
    388 		for i in range(len(self)):
    389 			subr = self[i]
    390 			if subr.needsDecompilation():
    391 				xmlWriter.begintag("CharString", index=i, raw=1)
    392 			else:
    393 				xmlWriter.begintag("CharString", index=i)
    394 			xmlWriter.newline()
    395 			subr.toXML(xmlWriter)
    396 			xmlWriter.endtag("CharString")
    397 			xmlWriter.newline()
    398 	
    399 	def fromXML(self, name, attrs, content):
    400 		if name != "CharString":
    401 			return
    402 		subr = psCharStrings.T2CharString()
    403 		subr.fromXML(name, attrs, content)
    404 		self.append(subr)
    405 	
    406 	def getItemAndSelector(self, index):
    407 		sel = None
    408 		if hasattr(self, 'fdSelect'):
    409 			sel = self.fdSelect[index]
    410 		return self[index], sel
    411 
    412 
    413 class SubrsIndex(GlobalSubrsIndex):
    414 	compilerClass = SubrsCompiler
    415 
    416 
    417 class TopDictIndex(Index):
    418 	
    419 	compilerClass = TopDictIndexCompiler
    420 	
    421 	def produceItem(self, index, data, file, offset, size):
    422 		top = TopDict(self.strings, file, offset, self.GlobalSubrs)
    423 		top.decompile(data)
    424 		return top
    425 	
    426 	def toXML(self, xmlWriter, progress):
    427 		for i in range(len(self)):
    428 			xmlWriter.begintag("FontDict", index=i)
    429 			xmlWriter.newline()
    430 			self[i].toXML(xmlWriter, progress)
    431 			xmlWriter.endtag("FontDict")
    432 			xmlWriter.newline()
    433 
    434 
    435 class FDArrayIndex(TopDictIndex):
    436 	
    437 	compilerClass = FDArrayIndexCompiler
    438 
    439 	def fromXML(self, name, attrs, content):
    440 		if name != "FontDict":
    441 			return
    442 		fontDict = FontDict()
    443 		for element in content:
    444 			if isinstance(element, basestring):
    445 				continue
    446 			name, attrs, content = element
    447 			fontDict.fromXML(name, attrs, content)
    448 		self.append(fontDict)
    449 
    450 
    451 class	FDSelect:
    452 	def __init__(self, file = None, numGlyphs = None, format=None):
    453 		if file:
    454 			# read data in from file
    455 			self.format = readCard8(file)
    456 			if self.format == 0:
    457 				from array import array
    458 				self.gidArray = array("B", file.read(numGlyphs)).tolist()
    459 			elif self.format == 3:
    460 				gidArray = [None] * numGlyphs
    461 				nRanges = readCard16(file)
    462 				prev = None
    463 				for i in range(nRanges):
    464 					first = readCard16(file)
    465 					if prev is not None:
    466 						for glyphID in range(prev, first):
    467 							gidArray[glyphID] = fd
    468 					prev = first
    469 					fd = readCard8(file)
    470 				if prev is not None:
    471 					first = readCard16(file)
    472 					for glyphID in range(prev, first):
    473 						gidArray[glyphID] = fd
    474 				self.gidArray = gidArray
    475 			else:
    476 				assert False, "unsupported FDSelect format: %s" % format
    477 		else:
    478 			# reading from XML. Make empty gidArray,, and leave format as passed in.
    479 			# format is None will result in the smallest representation being used.
    480 			self.format = format
    481 			self.gidArray = []
    482 
    483 
    484 	def __len__(self):
    485 		return len(self.gidArray)
    486 	
    487 	def __getitem__(self, index):
    488 		return self.gidArray[index]
    489 	
    490 	def __setitem__(self, index, fdSelectValue):
    491 		self.gidArray[index] = fdSelectValue
    492 
    493 	def append(self, fdSelectValue):
    494 		self.gidArray.append(fdSelectValue)
    495 	
    496 
    497 class CharStrings(object):
    498 	
    499 	def __init__(self, file, charset, globalSubrs, private, fdSelect, fdArray):
    500 		if file is not None:
    501 			self.charStringsIndex = SubrsIndex(file, globalSubrs, private, fdSelect, fdArray)
    502 			self.charStrings = charStrings = {}
    503 			for i in range(len(charset)):
    504 				charStrings[charset[i]] = i
    505 			self.charStringsAreIndexed = 1
    506 		else:
    507 			self.charStrings = {}
    508 			self.charStringsAreIndexed = 0
    509 			self.globalSubrs = globalSubrs
    510 			self.private = private
    511 			if fdSelect is not None:
    512 				self.fdSelect = fdSelect
    513 			if fdArray is not None:
    514 				self.fdArray = fdArray
    515 	
    516 	def keys(self):
    517 		return list(self.charStrings.keys())
    518 	
    519 	def values(self):
    520 		if self.charStringsAreIndexed:
    521 			return self.charStringsIndex
    522 		else:
    523 			return list(self.charStrings.values())
    524 	
    525 	def has_key(self, name):
    526 		return name in self.charStrings
    527 	
    528 	def __len__(self):
    529 		return len(self.charStrings)
    530 	
    531 	def __getitem__(self, name):
    532 		charString = self.charStrings[name]
    533 		if self.charStringsAreIndexed:
    534 			charString = self.charStringsIndex[charString]
    535 		return charString
    536 	
    537 	def __setitem__(self, name, charString):
    538 		if self.charStringsAreIndexed:
    539 			index = self.charStrings[name]
    540 			self.charStringsIndex[index] = charString
    541 		else:
    542 			self.charStrings[name] = charString
    543 	
    544 	def getItemAndSelector(self, name):
    545 		if self.charStringsAreIndexed:
    546 			index = self.charStrings[name]
    547 			return self.charStringsIndex.getItemAndSelector(index)
    548 		else:
    549 			if hasattr(self, 'fdSelect'):
    550 				sel = self.fdSelect[index]  # index is not defined at this point. Read R. ?
    551 			else:
    552 				raise KeyError("fdSelect array not yet defined.")
    553 			return self.charStrings[name], sel
    554 	
    555 	def toXML(self, xmlWriter, progress):
    556 		names = sorted(self.keys())
    557 		i = 0
    558 		step = 10
    559 		numGlyphs = len(names)
    560 		for name in names:
    561 			charStr, fdSelectIndex = self.getItemAndSelector(name)
    562 			if charStr.needsDecompilation():
    563 				raw = [("raw", 1)]
    564 			else:
    565 				raw = []
    566 			if fdSelectIndex is None:
    567 				xmlWriter.begintag("CharString", [('name', name)] + raw)
    568 			else:
    569 				xmlWriter.begintag("CharString",
    570 						[('name', name), ('fdSelectIndex', fdSelectIndex)] + raw)
    571 			xmlWriter.newline()
    572 			charStr.toXML(xmlWriter)
    573 			xmlWriter.endtag("CharString")
    574 			xmlWriter.newline()
    575 			if not i % step and progress is not None:
    576 				progress.setLabel("Dumping 'CFF ' table... (%s)" % name)
    577 				progress.increment(step / numGlyphs)
    578 			i = i + 1
    579 	
    580 	def fromXML(self, name, attrs, content):
    581 		for element in content:
    582 			if isinstance(element, basestring):
    583 				continue
    584 			name, attrs, content = element
    585 			if name != "CharString":
    586 				continue
    587 			fdID = -1
    588 			if hasattr(self, "fdArray"):
    589 				fdID = safeEval(attrs["fdSelectIndex"])
    590 				private = self.fdArray[fdID].Private
    591 			else:
    592 				private = self.private
    593 				
    594 			glyphName = attrs["name"]
    595 			charString = psCharStrings.T2CharString(
    596 					private=private,
    597 					globalSubrs=self.globalSubrs)
    598 			charString.fromXML(name, attrs, content)
    599 			if fdID >= 0:
    600 				charString.fdSelectIndex = fdID
    601 			self[glyphName] = charString
    602 
    603 
    604 def readCard8(file):
    605 	return byteord(file.read(1))
    606 
    607 def readCard16(file):
    608 	value, = struct.unpack(">H", file.read(2))
    609 	return value
    610 
    611 def writeCard8(file, value):
    612 	file.write(bytechr(value))
    613 
    614 def writeCard16(file, value):
    615 	file.write(struct.pack(">H", value))
    616 
    617 def packCard8(value):
    618 	return bytechr(value)
    619 
    620 def packCard16(value):
    621 	return struct.pack(">H", value)
    622 
    623 def buildOperatorDict(table):
    624 	d = {}
    625 	for op, name, arg, default, conv in table:
    626 		d[op] = (name, arg)
    627 	return d
    628 
    629 def buildOpcodeDict(table):
    630 	d = {}
    631 	for op, name, arg, default, conv in table:
    632 		if isinstance(op, tuple):
    633 			op = bytechr(op[0]) + bytechr(op[1])
    634 		else:
    635 			op = bytechr(op)
    636 		d[name] = (op, arg)
    637 	return d
    638 
    639 def buildOrder(table):
    640 	l = []
    641 	for op, name, arg, default, conv in table:
    642 		l.append(name)
    643 	return l
    644 
    645 def buildDefaults(table):
    646 	d = {}
    647 	for op, name, arg, default, conv in table:
    648 		if default is not None:
    649 			d[name] = default
    650 	return d
    651 
    652 def buildConverters(table):
    653 	d = {}
    654 	for op, name, arg, default, conv in table:
    655 		d[name] = conv
    656 	return d
    657 
    658 
    659 class SimpleConverter(object):
    660 	def read(self, parent, value):
    661 		return value
    662 	def write(self, parent, value):
    663 		return value
    664 	def xmlWrite(self, xmlWriter, name, value, progress):
    665 		xmlWriter.simpletag(name, value=value)
    666 		xmlWriter.newline()
    667 	def xmlRead(self, name, attrs, content, parent):
    668 		return attrs["value"]
    669 
    670 class ASCIIConverter(SimpleConverter):
    671 	def read(self, parent, value):
    672 		return tostr(value, encoding='ascii')
    673 	def write(self, parent, value):
    674 		return tobytes(value, encoding='ascii')
    675 	def xmlWrite(self, xmlWriter, name, value, progress):
    676 		xmlWriter.simpletag(name, value=tostr(value, encoding="ascii"))
    677 		xmlWriter.newline()
    678 	def xmlRead(self, name, attrs, content, parent):
    679 		return tobytes(attrs["value"], encoding=("ascii"))
    680 
    681 class Latin1Converter(SimpleConverter):
    682 	def read(self, parent, value):
    683 		return tostr(value, encoding='latin1')
    684 	def write(self, parent, value):
    685 		return tobytes(value, encoding='latin1')
    686 	def xmlWrite(self, xmlWriter, name, value, progress):
    687 		xmlWriter.simpletag(name, value=tostr(value, encoding="latin1"))
    688 		xmlWriter.newline()
    689 	def xmlRead(self, name, attrs, content, parent):
    690 		return tobytes(attrs["value"], encoding=("latin1"))
    691 
    692 
    693 def parseNum(s):
    694 	try:
    695 		value = int(s)
    696 	except:
    697 		value = float(s)
    698 	return value
    699 
    700 class NumberConverter(SimpleConverter):
    701 	def xmlRead(self, name, attrs, content, parent):
    702 		return parseNum(attrs["value"])
    703 
    704 class ArrayConverter(SimpleConverter):
    705 	def xmlWrite(self, xmlWriter, name, value, progress):
    706 		value = " ".join(map(str, value))
    707 		xmlWriter.simpletag(name, value=value)
    708 		xmlWriter.newline()
    709 	def xmlRead(self, name, attrs, content, parent):
    710 		values = attrs["value"].split()
    711 		return [parseNum(value) for value in values]
    712 
    713 class TableConverter(SimpleConverter):
    714 	def xmlWrite(self, xmlWriter, name, value, progress):
    715 		xmlWriter.begintag(name)
    716 		xmlWriter.newline()
    717 		value.toXML(xmlWriter, progress)
    718 		xmlWriter.endtag(name)
    719 		xmlWriter.newline()
    720 	def xmlRead(self, name, attrs, content, parent):
    721 		ob = self.getClass()()
    722 		for element in content:
    723 			if isinstance(element, basestring):
    724 				continue
    725 			name, attrs, content = element
    726 			ob.fromXML(name, attrs, content)
    727 		return ob
    728 
    729 class PrivateDictConverter(TableConverter):
    730 	def getClass(self):
    731 		return PrivateDict
    732 	def read(self, parent, value):
    733 		size, offset = value
    734 		file = parent.file
    735 		priv = PrivateDict(parent.strings, file, offset)
    736 		file.seek(offset)
    737 		data = file.read(size)
    738 		assert len(data) == size
    739 		priv.decompile(data)
    740 		return priv
    741 	def write(self, parent, value):
    742 		return (0, 0)  # dummy value
    743 
    744 class SubrsConverter(TableConverter):
    745 	def getClass(self):
    746 		return SubrsIndex
    747 	def read(self, parent, value):
    748 		file = parent.file
    749 		file.seek(parent.offset + value)  # Offset(self)
    750 		return SubrsIndex(file)
    751 	def write(self, parent, value):
    752 		return 0  # dummy value
    753 
    754 class CharStringsConverter(TableConverter):
    755 	def read(self, parent, value):
    756 		file = parent.file
    757 		charset = parent.charset
    758 		globalSubrs = parent.GlobalSubrs
    759 		if hasattr(parent, "ROS"):
    760 			fdSelect, fdArray = parent.FDSelect, parent.FDArray
    761 			private = None
    762 		else:
    763 			fdSelect, fdArray = None, None
    764 			private = parent.Private
    765 		file.seek(value)  # Offset(0)
    766 		return CharStrings(file, charset, globalSubrs, private, fdSelect, fdArray)
    767 	def write(self, parent, value):
    768 		return 0  # dummy value
    769 	def xmlRead(self, name, attrs, content, parent):
    770 		if hasattr(parent, "ROS"):
    771 			# if it is a CID-keyed font, then the private Dict is extracted from the parent.FDArray 
    772 			private, fdSelect, fdArray = None, parent.FDSelect, parent.FDArray
    773 		else:
    774 			# if it is a name-keyed font, then the private dict is in the top dict, and there is no fdArray. 
    775 			private, fdSelect, fdArray = parent.Private, None, None
    776 		charStrings = CharStrings(None, None, parent.GlobalSubrs, private, fdSelect, fdArray)
    777 		charStrings.fromXML(name, attrs, content)
    778 		return charStrings
    779 
    780 class CharsetConverter(object):
    781 	def read(self, parent, value):
    782 		isCID = hasattr(parent, "ROS")
    783 		if value > 2:
    784 			numGlyphs = parent.numGlyphs
    785 			file = parent.file
    786 			file.seek(value)
    787 			if DEBUG:
    788 				print("loading charset at %s" % value)
    789 			format = readCard8(file)
    790 			if format == 0:
    791 				charset = parseCharset0(numGlyphs, file, parent.strings, isCID)
    792 			elif format == 1 or format == 2:
    793 				charset = parseCharset(numGlyphs, file, parent.strings, isCID, format)
    794 			else:
    795 				raise NotImplementedError
    796 			assert len(charset) == numGlyphs
    797 			if DEBUG:
    798 				print("    charset end at %s" % file.tell())
    799 		else: # offset == 0 -> no charset data.
    800 			if isCID or "CharStrings" not in parent.rawDict: 
    801 				assert value == 0 # We get here only when processing fontDicts from the FDArray of CFF-CID fonts. Only the real topDict references the chrset.
    802 				charset = None
    803 			elif value == 0:
    804 				charset = cffISOAdobeStrings
    805 			elif value == 1:
    806 				charset = cffIExpertStrings
    807 			elif value == 2:
    808 				charset = cffExpertSubsetStrings
    809 		return charset
    810 
    811 	def write(self, parent, value):
    812 		return 0  # dummy value
    813 	def xmlWrite(self, xmlWriter, name, value, progress):
    814 		# XXX only write charset when not in OT/TTX context, where we
    815 		# dump charset as a separate "GlyphOrder" table.
    816 		##xmlWriter.simpletag("charset")
    817 		xmlWriter.comment("charset is dumped separately as the 'GlyphOrder' element")
    818 		xmlWriter.newline()
    819 	def xmlRead(self, name, attrs, content, parent):
    820 		if 0:
    821 			return safeEval(attrs["value"])
    822 
    823 
    824 class CharsetCompiler(object):
    825 	
    826 	def __init__(self, strings, charset, parent):
    827 		assert charset[0] == '.notdef'
    828 		isCID = hasattr(parent.dictObj, "ROS")
    829 		data0 = packCharset0(charset, isCID, strings)
    830 		data = packCharset(charset, isCID, strings)
    831 		if len(data) < len(data0):
    832 			self.data = data
    833 		else:
    834 			self.data = data0
    835 		self.parent = parent
    836 	
    837 	def setPos(self, pos, endPos):
    838 		self.parent.rawDict["charset"] = pos
    839 	
    840 	def getDataLength(self):
    841 		return len(self.data)
    842 	
    843 	def toFile(self, file):
    844 		file.write(self.data)
    845 
    846 
    847 def getCIDfromName(name, strings):
    848 	return int(name[3:])
    849 
    850 def getSIDfromName(name, strings):
    851 	return strings.getSID(name)
    852 
    853 def packCharset0(charset, isCID, strings):
    854 	fmt = 0
    855 	data = [packCard8(fmt)]
    856 	if isCID:
    857 		getNameID = getCIDfromName
    858 	else:
    859 		getNameID = getSIDfromName
    860 
    861 	for name in charset[1:]:
    862 		data.append(packCard16(getNameID(name,strings)))
    863 	return bytesjoin(data)
    864 
    865 
    866 def packCharset(charset, isCID, strings):
    867 	fmt = 1
    868 	ranges = []
    869 	first = None
    870 	end = 0
    871 	if isCID:
    872 		getNameID = getCIDfromName
    873 	else:
    874 		getNameID = getSIDfromName
    875 	
    876 	for name in charset[1:]:
    877 		SID = getNameID(name, strings)
    878 		if first is None:
    879 			first = SID
    880 		elif end + 1 != SID:
    881 			nLeft = end - first
    882 			if nLeft > 255:
    883 				fmt = 2
    884 			ranges.append((first, nLeft))
    885 			first = SID
    886 		end = SID
    887 	nLeft = end - first
    888 	if nLeft > 255:
    889 		fmt = 2
    890 	ranges.append((first, nLeft))
    891 	
    892 	data = [packCard8(fmt)]
    893 	if fmt == 1:
    894 		nLeftFunc = packCard8
    895 	else:
    896 		nLeftFunc = packCard16
    897 	for first, nLeft in ranges:
    898 		data.append(packCard16(first) + nLeftFunc(nLeft))
    899 	return bytesjoin(data)
    900 
    901 def parseCharset0(numGlyphs, file, strings, isCID):
    902 	charset = [".notdef"]
    903 	if isCID:
    904 		for i in range(numGlyphs - 1):
    905 			CID = readCard16(file)
    906 			charset.append("cid" + str(CID).zfill(5))
    907 	else:
    908 		for i in range(numGlyphs - 1):
    909 			SID = readCard16(file)
    910 			charset.append(strings[SID])
    911 	return charset
    912 
    913 def parseCharset(numGlyphs, file, strings, isCID, fmt):
    914 	charset = ['.notdef']
    915 	count = 1
    916 	if fmt == 1:
    917 		nLeftFunc = readCard8
    918 	else:
    919 		nLeftFunc = readCard16
    920 	while count < numGlyphs:
    921 		first = readCard16(file)
    922 		nLeft = nLeftFunc(file)
    923 		if isCID:
    924 			for CID in range(first, first+nLeft+1):
    925 				charset.append("cid" + str(CID).zfill(5))
    926 		else:
    927 			for SID in range(first, first+nLeft+1):
    928 				charset.append(strings[SID])
    929 		count = count + nLeft + 1
    930 	return charset
    931 
    932 
    933 class EncodingCompiler(object):
    934 
    935 	def __init__(self, strings, encoding, parent):
    936 		assert not isinstance(encoding, basestring)
    937 		data0 = packEncoding0(parent.dictObj.charset, encoding, parent.strings)
    938 		data1 = packEncoding1(parent.dictObj.charset, encoding, parent.strings)
    939 		if len(data0) < len(data1):
    940 			self.data = data0
    941 		else:
    942 			self.data = data1
    943 		self.parent = parent
    944 
    945 	def setPos(self, pos, endPos):
    946 		self.parent.rawDict["Encoding"] = pos
    947 	
    948 	def getDataLength(self):
    949 		return len(self.data)
    950 	
    951 	def toFile(self, file):
    952 		file.write(self.data)
    953 
    954 
    955 class EncodingConverter(SimpleConverter):
    956 
    957 	def read(self, parent, value):
    958 		if value == 0:
    959 			return "StandardEncoding"
    960 		elif value == 1:
    961 			return "ExpertEncoding"
    962 		else:
    963 			assert value > 1
    964 			file = parent.file
    965 			file.seek(value)
    966 			if DEBUG:
    967 				print("loading Encoding at %s" % value)
    968 			fmt = readCard8(file)
    969 			haveSupplement = fmt & 0x80
    970 			if haveSupplement:
    971 				raise NotImplementedError("Encoding supplements are not yet supported")
    972 			fmt = fmt & 0x7f
    973 			if fmt == 0:
    974 				encoding = parseEncoding0(parent.charset, file, haveSupplement,
    975 						parent.strings)
    976 			elif fmt == 1:
    977 				encoding = parseEncoding1(parent.charset, file, haveSupplement,
    978 						parent.strings)
    979 			return encoding
    980 
    981 	def write(self, parent, value):
    982 		if value == "StandardEncoding":
    983 			return 0
    984 		elif value == "ExpertEncoding":
    985 			return 1
    986 		return 0  # dummy value
    987 
    988 	def xmlWrite(self, xmlWriter, name, value, progress):
    989 		if value in ("StandardEncoding", "ExpertEncoding"):
    990 			xmlWriter.simpletag(name, name=value)
    991 			xmlWriter.newline()
    992 			return
    993 		xmlWriter.begintag(name)
    994 		xmlWriter.newline()
    995 		for code in range(len(value)):
    996 			glyphName = value[code]
    997 			if glyphName != ".notdef":
    998 				xmlWriter.simpletag("map", code=hex(code), name=glyphName)
    999 				xmlWriter.newline()
   1000 		xmlWriter.endtag(name)
   1001 		xmlWriter.newline()
   1002 
   1003 	def xmlRead(self, name, attrs, content, parent):
   1004 		if "name" in attrs:
   1005 			return attrs["name"]
   1006 		encoding = [".notdef"] * 256
   1007 		for element in content:
   1008 			if isinstance(element, basestring):
   1009 				continue
   1010 			name, attrs, content = element
   1011 			code = safeEval(attrs["code"])
   1012 			glyphName = attrs["name"]
   1013 			encoding[code] = glyphName
   1014 		return encoding
   1015 
   1016 
   1017 def parseEncoding0(charset, file, haveSupplement, strings):
   1018 	nCodes = readCard8(file)
   1019 	encoding = [".notdef"] * 256
   1020 	for glyphID in range(1, nCodes + 1):
   1021 		code = readCard8(file)
   1022 		if code != 0:
   1023 			encoding[code] = charset[glyphID]
   1024 	return encoding
   1025 
   1026 def parseEncoding1(charset, file, haveSupplement, strings):
   1027 	nRanges = readCard8(file)
   1028 	encoding = [".notdef"] * 256
   1029 	glyphID = 1
   1030 	for i in range(nRanges):
   1031 		code = readCard8(file)
   1032 		nLeft = readCard8(file)
   1033 		for glyphID in range(glyphID, glyphID + nLeft + 1):
   1034 			encoding[code] = charset[glyphID]
   1035 			code = code + 1
   1036 		glyphID = glyphID + 1
   1037 	return encoding
   1038 
   1039 def packEncoding0(charset, encoding, strings):
   1040 	fmt = 0
   1041 	m = {}
   1042 	for code in range(len(encoding)):
   1043 		name = encoding[code]
   1044 		if name != ".notdef":
   1045 			m[name] = code
   1046 	codes = []
   1047 	for name in charset[1:]:
   1048 		code = m.get(name)
   1049 		codes.append(code)
   1050 	
   1051 	while codes and codes[-1] is None:
   1052 		codes.pop()
   1053 
   1054 	data = [packCard8(fmt), packCard8(len(codes))]
   1055 	for code in codes:
   1056 		if code is None:
   1057 			code = 0
   1058 		data.append(packCard8(code))
   1059 	return bytesjoin(data)
   1060 
   1061 def packEncoding1(charset, encoding, strings):
   1062 	fmt = 1
   1063 	m = {}
   1064 	for code in range(len(encoding)):
   1065 		name = encoding[code]
   1066 		if name != ".notdef":
   1067 			m[name] = code
   1068 	ranges = []
   1069 	first = None
   1070 	end = 0
   1071 	for name in charset[1:]:
   1072 		code = m.get(name, -1)
   1073 		if first is None:
   1074 			first = code
   1075 		elif end + 1 != code:
   1076 			nLeft = end - first
   1077 			ranges.append((first, nLeft))
   1078 			first = code
   1079 		end = code
   1080 	nLeft = end - first
   1081 	ranges.append((first, nLeft))
   1082 	
   1083 	# remove unencoded glyphs at the end.
   1084 	while ranges and ranges[-1][0] == -1:
   1085 		ranges.pop()
   1086 
   1087 	data = [packCard8(fmt), packCard8(len(ranges))]
   1088 	for first, nLeft in ranges:
   1089 		if first == -1:  # unencoded
   1090 			first = 0
   1091 		data.append(packCard8(first) + packCard8(nLeft))
   1092 	return bytesjoin(data)
   1093 
   1094 
   1095 class FDArrayConverter(TableConverter):
   1096 
   1097 	def read(self, parent, value):
   1098 		file = parent.file
   1099 		file.seek(value)
   1100 		fdArray = FDArrayIndex(file)
   1101 		fdArray.strings = parent.strings
   1102 		fdArray.GlobalSubrs = parent.GlobalSubrs
   1103 		return fdArray
   1104 
   1105 	def write(self, parent, value):
   1106 		return 0  # dummy value
   1107 
   1108 	def xmlRead(self, name, attrs, content, parent):
   1109 		fdArray = FDArrayIndex()
   1110 		for element in content:
   1111 			if isinstance(element, basestring):
   1112 				continue
   1113 			name, attrs, content = element
   1114 			fdArray.fromXML(name, attrs, content)
   1115 		return fdArray
   1116 
   1117 
   1118 class FDSelectConverter(object):
   1119 
   1120 	def read(self, parent, value):
   1121 		file = parent.file
   1122 		file.seek(value)
   1123 		fdSelect = FDSelect(file, parent.numGlyphs)
   1124 		return 	fdSelect
   1125 
   1126 	def write(self, parent, value):
   1127 		return 0  # dummy value
   1128 
   1129 	# The FDSelect glyph data is written out to XML in the charstring keys,
   1130 	# so we write out only the format selector
   1131 	def xmlWrite(self, xmlWriter, name, value, progress):
   1132 		xmlWriter.simpletag(name, [('format', value.format)])
   1133 		xmlWriter.newline()
   1134 
   1135 	def xmlRead(self, name, attrs, content, parent):
   1136 		fmt = safeEval(attrs["format"])
   1137 		file = None
   1138 		numGlyphs = None
   1139 		fdSelect = FDSelect(file, numGlyphs, fmt)
   1140 		return fdSelect
   1141 		
   1142 
   1143 def packFDSelect0(fdSelectArray):
   1144 	fmt = 0
   1145 	data = [packCard8(fmt)]
   1146 	for index in fdSelectArray:
   1147 		data.append(packCard8(index))
   1148 	return bytesjoin(data)
   1149 
   1150 
   1151 def packFDSelect3(fdSelectArray):
   1152 	fmt = 3
   1153 	fdRanges = []
   1154 	first = None
   1155 	end = 0
   1156 	lenArray = len(fdSelectArray)
   1157 	lastFDIndex = -1
   1158 	for i in range(lenArray):
   1159 		fdIndex = fdSelectArray[i]
   1160 		if lastFDIndex != fdIndex:
   1161 			fdRanges.append([i, fdIndex])
   1162 			lastFDIndex = fdIndex
   1163 	sentinelGID = i + 1
   1164 		
   1165 	data = [packCard8(fmt)]
   1166 	data.append(packCard16( len(fdRanges) ))
   1167 	for fdRange in fdRanges:
   1168 		data.append(packCard16(fdRange[0]))
   1169 		data.append(packCard8(fdRange[1]))
   1170 	data.append(packCard16(sentinelGID))
   1171 	return bytesjoin(data)
   1172 
   1173 
   1174 class FDSelectCompiler(object):
   1175 	
   1176 	def __init__(self, fdSelect, parent):
   1177 		fmt = fdSelect.format
   1178 		fdSelectArray = fdSelect.gidArray
   1179 		if fmt == 0:
   1180 			self.data = packFDSelect0(fdSelectArray)
   1181 		elif fmt == 3:
   1182 			self.data = packFDSelect3(fdSelectArray)
   1183 		else:
   1184 			# choose smaller of the two formats
   1185 			data0 = packFDSelect0(fdSelectArray)
   1186 			data3 = packFDSelect3(fdSelectArray)
   1187 			if len(data0) < len(data3):
   1188 				self.data = data0
   1189 				fdSelect.format = 0
   1190 			else:
   1191 				self.data = data3
   1192 				fdSelect.format = 3
   1193 
   1194 		self.parent = parent
   1195 	
   1196 	def setPos(self, pos, endPos):
   1197 		self.parent.rawDict["FDSelect"] = pos
   1198 	
   1199 	def getDataLength(self):
   1200 		return len(self.data)
   1201 	
   1202 	def toFile(self, file):
   1203 		file.write(self.data)
   1204 
   1205 
   1206 class ROSConverter(SimpleConverter):
   1207 
   1208 	def xmlWrite(self, xmlWriter, name, value, progress):
   1209 		registry, order, supplement = value
   1210 		xmlWriter.simpletag(name, [('Registry', tostr(registry)), ('Order', tostr(order)),
   1211 			('Supplement', supplement)])
   1212 		xmlWriter.newline()
   1213 
   1214 	def xmlRead(self, name, attrs, content, parent):
   1215 		return (attrs['Registry'], attrs['Order'], safeEval(attrs['Supplement']))
   1216 
   1217 
   1218 
   1219 topDictOperators = [
   1220 #	opcode     name                  argument type   default    converter
   1221 	((12, 30), 'ROS',        ('SID','SID','number'), None,      ROSConverter()),
   1222 	((12, 20), 'SyntheticBase',      'number',       None,      None),
   1223 	(0,        'version',            'SID',          None,      None),
   1224 	(1,        'Notice',             'SID',          None,      Latin1Converter()),
   1225 	((12, 0),  'Copyright',          'SID',          None,      Latin1Converter()),
   1226 	(2,        'FullName',           'SID',          None,      None),
   1227 	((12, 38), 'FontName',           'SID',          None,      None),
   1228 	(3,        'FamilyName',         'SID',          None,      None),
   1229 	(4,        'Weight',             'SID',          None,      None),
   1230 	((12, 1),  'isFixedPitch',       'number',       0,         None),
   1231 	((12, 2),  'ItalicAngle',        'number',       0,         None),
   1232 	((12, 3),  'UnderlinePosition',  'number',       None,      None),
   1233 	((12, 4),  'UnderlineThickness', 'number',       50,        None),
   1234 	((12, 5),  'PaintType',          'number',       0,         None),
   1235 	((12, 6),  'CharstringType',     'number',       2,         None),
   1236 	((12, 7),  'FontMatrix',         'array',  [0.001,0,0,0.001,0,0],  None),
   1237 	(13,       'UniqueID',           'number',       None,      None),
   1238 	(5,        'FontBBox',           'array',  [0,0,0,0],       None),
   1239 	((12, 8),  'StrokeWidth',        'number',       0,         None),
   1240 	(14,       'XUID',               'array',        None,      None),
   1241 	((12, 21), 'PostScript',         'SID',          None,      None),
   1242 	((12, 22), 'BaseFontName',       'SID',          None,      None),
   1243 	((12, 23), 'BaseFontBlend',      'delta',        None,      None),
   1244 	((12, 31), 'CIDFontVersion',     'number',       0,         None),
   1245 	((12, 32), 'CIDFontRevision',    'number',       0,         None),
   1246 	((12, 33), 'CIDFontType',        'number',       0,         None),
   1247 	((12, 34), 'CIDCount',           'number',       8720,      None),
   1248 	(15,       'charset',            'number',       0,         CharsetConverter()),
   1249 	((12, 35), 'UIDBase',            'number',       None,      None),
   1250 	(16,       'Encoding',           'number',       0,         EncodingConverter()),
   1251 	(18,       'Private',       ('number','number'), None,      PrivateDictConverter()),
   1252 	((12, 37), 'FDSelect',           'number',       None,      FDSelectConverter()),
   1253 	((12, 36), 'FDArray',            'number',       None,      FDArrayConverter()),
   1254 	(17,       'CharStrings',        'number',       None,      CharStringsConverter()),
   1255 ]
   1256 
   1257 # Note! FDSelect and FDArray must both preceed CharStrings in the output XML build order,
   1258 # in order for the font to compile back from xml.
   1259 
   1260 
   1261 privateDictOperators = [
   1262 #	opcode     name                  argument type   default    converter
   1263 	(6,        'BlueValues',         'delta',        None,      None),
   1264 	(7,        'OtherBlues',         'delta',        None,      None),
   1265 	(8,        'FamilyBlues',        'delta',        None,      None),
   1266 	(9,        'FamilyOtherBlues',   'delta',        None,      None),
   1267 	((12, 9),  'BlueScale',          'number',       0.039625,  None),
   1268 	((12, 10), 'BlueShift',          'number',       7,         None),
   1269 	((12, 11), 'BlueFuzz',           'number',       1,         None),
   1270 	(10,       'StdHW',              'number',       None,      None),
   1271 	(11,       'StdVW',              'number',       None,      None),
   1272 	((12, 12), 'StemSnapH',          'delta',        None,      None),
   1273 	((12, 13), 'StemSnapV',          'delta',        None,      None),
   1274 	((12, 14), 'ForceBold',          'number',       0,         None),
   1275 	((12, 15), 'ForceBoldThreshold', 'number',       None,      None),  # deprecated
   1276 	((12, 16), 'lenIV',              'number',       None,      None),  # deprecated
   1277 	((12, 17), 'LanguageGroup',      'number',       0,         None),
   1278 	((12, 18), 'ExpansionFactor',    'number',       0.06,      None),
   1279 	((12, 19), 'initialRandomSeed',  'number',       0,         None),
   1280 	(20,       'defaultWidthX',      'number',       0,         None),
   1281 	(21,       'nominalWidthX',      'number',       0,         None),
   1282 	(19,       'Subrs',              'number',       None,      SubrsConverter()),
   1283 ]
   1284 
   1285 def addConverters(table):
   1286 	for i in range(len(table)):
   1287 		op, name, arg, default, conv = table[i]
   1288 		if conv is not None:
   1289 			continue
   1290 		if arg in ("delta", "array"):
   1291 			conv = ArrayConverter()
   1292 		elif arg == "number":
   1293 			conv = NumberConverter()
   1294 		elif arg == "SID":
   1295 			conv = ASCIIConverter()
   1296 		else:
   1297 			assert False
   1298 		table[i] = op, name, arg, default, conv
   1299 
   1300 addConverters(privateDictOperators)
   1301 addConverters(topDictOperators)
   1302 
   1303 
   1304 class TopDictDecompiler(psCharStrings.DictDecompiler):
   1305 	operators = buildOperatorDict(topDictOperators)
   1306 
   1307 
   1308 class PrivateDictDecompiler(psCharStrings.DictDecompiler):
   1309 	operators = buildOperatorDict(privateDictOperators)
   1310 
   1311 
   1312 class DictCompiler(object):
   1313 	
   1314 	def __init__(self, dictObj, strings, parent):
   1315 		assert isinstance(strings, IndexedStrings)
   1316 		self.dictObj = dictObj
   1317 		self.strings = strings
   1318 		self.parent = parent
   1319 		rawDict = {}
   1320 		for name in dictObj.order:
   1321 			value = getattr(dictObj, name, None)
   1322 			if value is None:
   1323 				continue
   1324 			conv = dictObj.converters[name]
   1325 			value = conv.write(dictObj, value)
   1326 			if value == dictObj.defaults.get(name):
   1327 				continue
   1328 			rawDict[name] = value
   1329 		self.rawDict = rawDict
   1330 	
   1331 	def setPos(self, pos, endPos):
   1332 		pass
   1333 	
   1334 	def getDataLength(self):
   1335 		return len(self.compile("getDataLength"))
   1336 	
   1337 	def compile(self, reason):
   1338 		if DEBUG:
   1339 			print("-- compiling %s for %s" % (self.__class__.__name__, reason))
   1340 			print("in baseDict: ", self)
   1341 		rawDict = self.rawDict
   1342 		data = []
   1343 		for name in self.dictObj.order:
   1344 			value = rawDict.get(name)
   1345 			if value is None:
   1346 				continue
   1347 			op, argType = self.opcodes[name]
   1348 			if isinstance(argType, tuple):
   1349 				l = len(argType)
   1350 				assert len(value) == l, "value doesn't match arg type"
   1351 				for i in range(l):
   1352 					arg = argType[i]
   1353 					v = value[i]
   1354 					arghandler = getattr(self, "arg_" + arg)
   1355 					data.append(arghandler(v))
   1356 			else:
   1357 				arghandler = getattr(self, "arg_" + argType)
   1358 				data.append(arghandler(value))
   1359 			data.append(op)
   1360 		return bytesjoin(data)
   1361 	
   1362 	def toFile(self, file):
   1363 		file.write(self.compile("toFile"))
   1364 	
   1365 	def arg_number(self, num):
   1366 		return encodeNumber(num)
   1367 	def arg_SID(self, s):
   1368 		return psCharStrings.encodeIntCFF(self.strings.getSID(s))
   1369 	def arg_array(self, value):
   1370 		data = []
   1371 		for num in value:
   1372 			data.append(encodeNumber(num))
   1373 		return bytesjoin(data)
   1374 	def arg_delta(self, value):
   1375 		out = []
   1376 		last = 0
   1377 		for v in value:
   1378 			out.append(v - last)
   1379 			last = v
   1380 		data = []
   1381 		for num in out:
   1382 			data.append(encodeNumber(num))
   1383 		return bytesjoin(data)
   1384 
   1385 
   1386 def encodeNumber(num):
   1387 	if isinstance(num, float):
   1388 		return psCharStrings.encodeFloat(num)
   1389 	else:
   1390 		return psCharStrings.encodeIntCFF(num)
   1391 
   1392 
   1393 class TopDictCompiler(DictCompiler):
   1394 	
   1395 	opcodes = buildOpcodeDict(topDictOperators)
   1396 	
   1397 	def getChildren(self, strings):
   1398 		children = []
   1399 		if hasattr(self.dictObj, "charset") and self.dictObj.charset:
   1400 			children.append(CharsetCompiler(strings, self.dictObj.charset, self))
   1401 		if hasattr(self.dictObj, "Encoding"):
   1402 			encoding = self.dictObj.Encoding
   1403 			if not isinstance(encoding, basestring):
   1404 				children.append(EncodingCompiler(strings, encoding, self))
   1405 		if hasattr(self.dictObj, "FDSelect"):
   1406 			# I have not yet supported merging a ttx CFF-CID font, as there are interesting
   1407 			# issues about merging the FDArrays. Here I assume that
   1408 			# either the font was read from XML, and teh FDSelect indices are all
   1409 			# in the charstring data, or the FDSelect array is already fully defined.
   1410 			fdSelect = self.dictObj.FDSelect
   1411 			if len(fdSelect) == 0: # probably read in from XML; assume fdIndex in CharString data
   1412 				charStrings = self.dictObj.CharStrings
   1413 				for name in self.dictObj.charset:
   1414 					fdSelect.append(charStrings[name].fdSelectIndex)
   1415 			fdSelectComp = FDSelectCompiler(fdSelect, self)
   1416 			children.append(fdSelectComp)
   1417 		if hasattr(self.dictObj, "CharStrings"):
   1418 			items = []
   1419 			charStrings = self.dictObj.CharStrings
   1420 			for name in self.dictObj.charset:
   1421 				items.append(charStrings[name])
   1422 			charStringsComp = CharStringsCompiler(items, strings, self)
   1423 			children.append(charStringsComp)
   1424 		if hasattr(self.dictObj, "FDArray"):
   1425 			# I have not yet supported merging a ttx CFF-CID font, as there are interesting
   1426 			# issues about merging the FDArrays. Here I assume that the FDArray info is correct
   1427 			# and complete.
   1428 			fdArrayIndexComp = self.dictObj.FDArray.getCompiler(strings, self)
   1429 			children.append(fdArrayIndexComp)
   1430 			children.extend(fdArrayIndexComp.getChildren(strings))
   1431 		if hasattr(self.dictObj, "Private"):
   1432 			privComp = self.dictObj.Private.getCompiler(strings, self)
   1433 			children.append(privComp)
   1434 			children.extend(privComp.getChildren(strings))
   1435 		return children
   1436 
   1437 
   1438 class FontDictCompiler(DictCompiler):
   1439 	
   1440 	opcodes = buildOpcodeDict(topDictOperators)
   1441 	
   1442 	def getChildren(self, strings):
   1443 		children = []
   1444 		if hasattr(self.dictObj, "Private"):
   1445 			privComp = self.dictObj.Private.getCompiler(strings, self)
   1446 			children.append(privComp)
   1447 			children.extend(privComp.getChildren(strings))
   1448 		return children
   1449 
   1450 
   1451 class PrivateDictCompiler(DictCompiler):
   1452 	
   1453 	opcodes = buildOpcodeDict(privateDictOperators)
   1454 	
   1455 	def setPos(self, pos, endPos):
   1456 		size = endPos - pos
   1457 		self.parent.rawDict["Private"] = size, pos
   1458 		self.pos = pos
   1459 	
   1460 	def getChildren(self, strings):
   1461 		children = []
   1462 		if hasattr(self.dictObj, "Subrs"):
   1463 			children.append(self.dictObj.Subrs.getCompiler(strings, self))
   1464 		return children
   1465 
   1466 
   1467 class BaseDict(object):
   1468 	
   1469 	def __init__(self, strings=None, file=None, offset=None):
   1470 		self.rawDict = {}
   1471 		if DEBUG:
   1472 			print("loading %s at %s" % (self.__class__.__name__, offset))
   1473 		self.file = file
   1474 		self.offset = offset
   1475 		self.strings = strings
   1476 		self.skipNames = []
   1477 	
   1478 	def decompile(self, data):
   1479 		if DEBUG:
   1480 			print("    length %s is %s" % (self.__class__.__name__, len(data)))
   1481 		dec = self.decompilerClass(self.strings)
   1482 		dec.decompile(data)
   1483 		self.rawDict = dec.getDict()
   1484 		self.postDecompile()
   1485 	
   1486 	def postDecompile(self):
   1487 		pass
   1488 	
   1489 	def getCompiler(self, strings, parent):
   1490 		return self.compilerClass(self, strings, parent)
   1491 	
   1492 	def __getattr__(self, name):
   1493 		value = self.rawDict.get(name)
   1494 		if value is None:
   1495 			value = self.defaults.get(name)
   1496 		if value is None:
   1497 			raise AttributeError(name)
   1498 		conv = self.converters[name]
   1499 		value = conv.read(self, value)
   1500 		setattr(self, name, value)
   1501 		return value
   1502 	
   1503 	def toXML(self, xmlWriter, progress):
   1504 		for name in self.order:
   1505 			if name in self.skipNames:
   1506 				continue
   1507 			value = getattr(self, name, None)
   1508 			if value is None:
   1509 				continue
   1510 			conv = self.converters[name]
   1511 			conv.xmlWrite(xmlWriter, name, value, progress)
   1512 	
   1513 	def fromXML(self, name, attrs, content):
   1514 		conv = self.converters[name]
   1515 		value = conv.xmlRead(name, attrs, content, self)
   1516 		setattr(self, name, value)
   1517 
   1518 
   1519 class TopDict(BaseDict):
   1520 	
   1521 	defaults = buildDefaults(topDictOperators)
   1522 	converters = buildConverters(topDictOperators)
   1523 	order = buildOrder(topDictOperators)
   1524 	decompilerClass = TopDictDecompiler
   1525 	compilerClass = TopDictCompiler
   1526 	
   1527 	def __init__(self, strings=None, file=None, offset=None, GlobalSubrs=None):
   1528 		BaseDict.__init__(self, strings, file, offset)
   1529 		self.GlobalSubrs = GlobalSubrs
   1530 	
   1531 	def getGlyphOrder(self):
   1532 		return self.charset
   1533 	
   1534 	def postDecompile(self):
   1535 		offset = self.rawDict.get("CharStrings")
   1536 		if offset is None:
   1537 			return
   1538 		# get the number of glyphs beforehand.
   1539 		self.file.seek(offset)
   1540 		self.numGlyphs = readCard16(self.file)
   1541 	
   1542 	def toXML(self, xmlWriter, progress):
   1543 		if hasattr(self, "CharStrings"):
   1544 			self.decompileAllCharStrings(progress)
   1545 		if hasattr(self, "ROS"):
   1546 			self.skipNames = ['Encoding']
   1547 		if not hasattr(self, "ROS") or not hasattr(self, "CharStrings"):
   1548 			# these values have default values, but I only want them to show up
   1549 			# in CID fonts.
   1550 			self.skipNames = ['CIDFontVersion', 'CIDFontRevision', 'CIDFontType',
   1551 					'CIDCount']
   1552 		BaseDict.toXML(self, xmlWriter, progress)
   1553 	
   1554 	def decompileAllCharStrings(self, progress):
   1555 		# XXX only when doing ttdump -i?
   1556 		i = 0
   1557 		for charString in self.CharStrings.values():
   1558 			try:
   1559 				charString.decompile()
   1560 			except:
   1561 				print("Error in charstring ", i)
   1562 				import sys
   1563 				typ, value = sys.exc_info()[0:2]
   1564 				raise typ(value)
   1565 			if not i % 30 and progress:
   1566 				progress.increment(0)  # update
   1567 			i = i + 1
   1568 
   1569 
   1570 class FontDict(BaseDict):
   1571 	
   1572 	defaults = buildDefaults(topDictOperators)
   1573 	converters = buildConverters(topDictOperators)
   1574 	order = buildOrder(topDictOperators)
   1575 	decompilerClass = None
   1576 	compilerClass = FontDictCompiler
   1577 	
   1578 	def __init__(self, strings=None, file=None, offset=None, GlobalSubrs=None):
   1579 		BaseDict.__init__(self, strings, file, offset)
   1580 		self.GlobalSubrs = GlobalSubrs
   1581 	
   1582 	def getGlyphOrder(self):
   1583 		return self.charset
   1584 	
   1585 	def toXML(self, xmlWriter, progress):
   1586 		self.skipNames = ['Encoding']
   1587 		BaseDict.toXML(self, xmlWriter, progress)
   1588 	
   1589 
   1590 
   1591 class PrivateDict(BaseDict):
   1592 	defaults = buildDefaults(privateDictOperators)
   1593 	converters = buildConverters(privateDictOperators)
   1594 	order = buildOrder(privateDictOperators)
   1595 	decompilerClass = PrivateDictDecompiler
   1596 	compilerClass = PrivateDictCompiler
   1597 
   1598 
   1599 class IndexedStrings(object):
   1600 	
   1601 	"""SID -> string mapping."""
   1602 	
   1603 	def __init__(self, file=None):
   1604 		if file is None:
   1605 			strings = []
   1606 		else:
   1607 			strings = [tostr(s, encoding="latin1") for s in Index(file)]
   1608 		self.strings = strings
   1609 	
   1610 	def getCompiler(self):
   1611 		return IndexedStringsCompiler(self, None, None)
   1612 	
   1613 	def __len__(self):
   1614 		return len(self.strings)
   1615 	
   1616 	def __getitem__(self, SID):
   1617 		if SID < cffStandardStringCount:
   1618 			return cffStandardStrings[SID]
   1619 		else:
   1620 			return self.strings[SID - cffStandardStringCount]
   1621 	
   1622 	def getSID(self, s):
   1623 		if not hasattr(self, "stringMapping"):
   1624 			self.buildStringMapping()
   1625 		if s in cffStandardStringMapping:
   1626 			SID = cffStandardStringMapping[s]
   1627 		elif s in self.stringMapping:
   1628 			SID = self.stringMapping[s]
   1629 		else:
   1630 			SID = len(self.strings) + cffStandardStringCount
   1631 			self.strings.append(s)
   1632 			self.stringMapping[s] = SID
   1633 		return SID
   1634 	
   1635 	def getStrings(self):
   1636 		return self.strings
   1637 	
   1638 	def buildStringMapping(self):
   1639 		self.stringMapping = {}
   1640 		for index in range(len(self.strings)):
   1641 			self.stringMapping[self.strings[index]] = index + cffStandardStringCount
   1642 
   1643 
   1644 # The 391 Standard Strings as used in the CFF format.
   1645 # from Adobe Technical None #5176, version 1.0, 18 March 1998
   1646 
   1647 cffStandardStrings = ['.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 
   1648 		'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright', 
   1649 		'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 
   1650 		'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 
   1651 		'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 
   1652 		'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 
   1653 		'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 
   1654 		'bracketright', 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c', 
   1655 		'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 
   1656 		's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 
   1657 		'asciitilde', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 
   1658 		'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft', 
   1659 		'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 
   1660 		'daggerdbl', 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase', 
   1661 		'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', 
   1662 		'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 
   1663 		'dotaccent', 'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 
   1664 		'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae', 
   1665 		'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior', 
   1666 		'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 
   1667 		'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters', 
   1668 		'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior', 
   1669 		'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 
   1670 		'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave', 
   1671 		'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 
   1672 		'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 
   1673 		'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron', 
   1674 		'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla', 
   1675 		'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex', 
   1676 		'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis', 
   1677 		'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave', 
   1678 		'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall', 
   1679 		'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall', 
   1680 		'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', 
   1681 		'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 
   1682 		'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 
   1683 		'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior', 
   1684 		'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 
   1685 		'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior', 
   1686 		'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', 'parenleftinferior', 
   1687 		'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 
   1688 		'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 
   1689 		'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 
   1690 		'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall', 
   1691 		'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', 
   1692 		'exclamdownsmall', 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 
   1693 		'Dieresissmall', 'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall', 
   1694 		'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', 
   1695 		'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 
   1696 		'onethird', 'twothirds', 'zerosuperior', 'foursuperior', 'fivesuperior', 
   1697 		'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 
   1698 		'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 
   1699 		'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 
   1700 		'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall', 
   1701 		'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', 
   1702 		'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 
   1703 		'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 
   1704 		'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', 
   1705 		'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 
   1706 		'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 
   1707 		'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', '001.001', '001.002', 
   1708 		'001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman', 
   1709 		'Semibold'
   1710 ]
   1711 
   1712 cffStandardStringCount = 391
   1713 assert len(cffStandardStrings) == cffStandardStringCount
   1714 # build reverse mapping
   1715 cffStandardStringMapping = {}
   1716 for _i in range(cffStandardStringCount):
   1717 	cffStandardStringMapping[cffStandardStrings[_i]] = _i
   1718 
   1719 cffISOAdobeStrings = [".notdef", "space", "exclam", "quotedbl", "numbersign",
   1720 "dollar", "percent", "ampersand", "quoteright", "parenleft", "parenright",
   1721 "asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two",
   1722 "three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon",
   1723 "less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G",
   1724 "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W",
   1725 "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum",
   1726 "underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j",
   1727 "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
   1728 "braceleft", "bar", "braceright", "asciitilde", "exclamdown", "cent",
   1729 "sterling", "fraction", "yen", "florin", "section", "currency", "quotesingle",
   1730 "quotedblleft", "guillemotleft", "guilsinglleft", "guilsinglright", "fi", "fl",
   1731 "endash", "dagger", "daggerdbl", "periodcentered", "paragraph", "bullet",
   1732 "quotesinglbase", "quotedblbase", "quotedblright", "guillemotright", "ellipsis",
   1733 "perthousand", "questiondown", "grave", "acute", "circumflex", "tilde",
   1734 "macron", "breve", "dotaccent", "dieresis", "ring", "cedilla", "hungarumlaut",
   1735 "ogonek", "caron", "emdash", "AE", "ordfeminine", "Lslash", "Oslash", "OE",
   1736 "ordmasculine", "ae", "dotlessi", "lslash", "oslash", "oe", "germandbls",
   1737 "onesuperior", "logicalnot", "mu", "trademark", "Eth", "onehalf", "plusminus",
   1738 "Thorn", "onequarter", "divide", "brokenbar", "degree", "thorn",
   1739 "threequarters", "twosuperior", "registered", "minus", "eth", "multiply",
   1740 "threesuperior", "copyright", "Aacute", "Acircumflex", "Adieresis", "Agrave",
   1741 "Aring", "Atilde", "Ccedilla", "Eacute", "Ecircumflex", "Edieresis", "Egrave",
   1742 "Iacute", "Icircumflex", "Idieresis", "Igrave", "Ntilde", "Oacute",
   1743 "Ocircumflex", "Odieresis", "Ograve", "Otilde", "Scaron", "Uacute",
   1744 "Ucircumflex", "Udieresis", "Ugrave", "Yacute", "Ydieresis", "Zcaron", "aacute",
   1745 "acircumflex", "adieresis", "agrave", "aring", "atilde", "ccedilla", "eacute",
   1746 "ecircumflex", "edieresis", "egrave", "iacute", "icircumflex", "idieresis",
   1747 "igrave", "ntilde", "oacute", "ocircumflex", "odieresis", "ograve", "otilde",
   1748 "scaron", "uacute", "ucircumflex", "udieresis", "ugrave", "yacute", "ydieresis",
   1749 "zcaron"]
   1750 
   1751 cffISOAdobeStringCount = 229
   1752 assert len(cffISOAdobeStrings) == cffISOAdobeStringCount
   1753 
   1754 cffIExpertStrings = [".notdef", "space", "exclamsmall", "Hungarumlautsmall",
   1755 "dollaroldstyle", "dollarsuperior", "ampersandsmall", "Acutesmall",
   1756 "parenleftsuperior", "parenrightsuperior", "twodotenleader", "onedotenleader",
   1757 "comma", "hyphen", "period", "fraction", "zerooldstyle", "oneoldstyle",
   1758 "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle",
   1759 "sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", "semicolon",
   1760 "commasuperior", "threequartersemdash", "periodsuperior", "questionsmall",
   1761 "asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior",
   1762 "lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior",
   1763 "tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior",
   1764 "parenrightinferior", "Circumflexsmall", "hyphensuperior", "Gravesmall",
   1765 "Asmall", "Bsmall", "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall",
   1766 "Ismall", "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall",
   1767 "Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall", "Xsmall",
   1768 "Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah", "Tildesmall",
   1769 "exclamdownsmall", "centoldstyle", "Lslashsmall", "Scaronsmall", "Zcaronsmall",
   1770 "Dieresissmall", "Brevesmall", "Caronsmall", "Dotaccentsmall", "Macronsmall",
   1771 "figuredash", "hypheninferior", "Ogoneksmall", "Ringsmall", "Cedillasmall",
   1772 "onequarter", "onehalf", "threequarters", "questiondownsmall", "oneeighth",
   1773 "threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds",
   1774 "zerosuperior", "onesuperior", "twosuperior", "threesuperior", "foursuperior",
   1775 "fivesuperior", "sixsuperior", "sevensuperior", "eightsuperior", "ninesuperior",
   1776 "zeroinferior", "oneinferior", "twoinferior", "threeinferior", "fourinferior",
   1777 "fiveinferior", "sixinferior", "seveninferior", "eightinferior", "nineinferior",
   1778 "centinferior", "dollarinferior", "periodinferior", "commainferior",
   1779 "Agravesmall", "Aacutesmall", "Acircumflexsmall", "Atildesmall",
   1780 "Adieresissmall", "Aringsmall", "AEsmall", "Ccedillasmall", "Egravesmall",
   1781 "Eacutesmall", "Ecircumflexsmall", "Edieresissmall", "Igravesmall",
   1782 "Iacutesmall", "Icircumflexsmall", "Idieresissmall", "Ethsmall", "Ntildesmall",
   1783 "Ogravesmall", "Oacutesmall", "Ocircumflexsmall", "Otildesmall",
   1784 "Odieresissmall", "OEsmall", "Oslashsmall", "Ugravesmall", "Uacutesmall",
   1785 "Ucircumflexsmall", "Udieresissmall", "Yacutesmall", "Thornsmall",
   1786 "Ydieresissmall"]
   1787 
   1788 cffExpertStringCount = 166
   1789 assert len(cffIExpertStrings) == cffExpertStringCount
   1790 
   1791 cffExpertSubsetStrings = [".notdef", "space", "dollaroldstyle",
   1792 "dollarsuperior", "parenleftsuperior", "parenrightsuperior", "twodotenleader",
   1793 "onedotenleader", "comma", "hyphen", "period", "fraction", "zerooldstyle",
   1794 "oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle",
   1795 "sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon",
   1796 "semicolon", "commasuperior", "threequartersemdash", "periodsuperior",
   1797 "asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior",
   1798 "lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior",
   1799 "tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior",
   1800 "parenrightinferior", "hyphensuperior", "colonmonetary", "onefitted", "rupiah",
   1801 "centoldstyle", "figuredash", "hypheninferior", "onequarter", "onehalf",
   1802 "threequarters", "oneeighth", "threeeighths", "fiveeighths", "seveneighths",
   1803 "onethird", "twothirds", "zerosuperior", "onesuperior", "twosuperior",
   1804 "threesuperior", "foursuperior", "fivesuperior", "sixsuperior", "sevensuperior",
   1805 "eightsuperior", "ninesuperior", "zeroinferior", "oneinferior", "twoinferior",
   1806 "threeinferior", "fourinferior", "fiveinferior", "sixinferior", "seveninferior",
   1807 "eightinferior", "nineinferior", "centinferior", "dollarinferior",
   1808 "periodinferior", "commainferior"]
   1809 
   1810 cffExpertSubsetStringCount = 87
   1811 assert len(cffExpertSubsetStrings) == cffExpertSubsetStringCount
   1812