cffLib.py revision 7ce02ea9dfa1236002aaf61c972a8f31544d070e
1"""cffLib.py -- read/write tools for Adobe CFF fonts."""
2
3#
4# $Id: cffLib.py,v 1.19 2002-05-17 20:04:05 jvr Exp $
5#
6
7import struct, sstruct
8import string
9import types
10from fontTools.misc import psCharStrings
11
12
13DEBUG = 0
14
15
16cffHeaderFormat = """
17	major:   B
18	minor:   B
19	hdrSize: B
20	offSize: B
21"""
22
23class CFFFontSet:
24
25	def __init__(self):
26		pass
27
28	def decompile(self, file):
29		sstruct.unpack(cffHeaderFormat, file.read(4), self)
30		assert self.major == 1 and self.minor == 0, \
31				"unknown CFF format: %d.%d" % (self.major, self.minor)
32
33		self.fontNames = list(Index(file, "fontNames"))
34		self.topDictIndex = TopDictIndex(file)
35		self.strings = IndexedStrings(file)
36		self.GlobalSubrs = CharStringIndex(file, name="GlobalSubrsIndex")
37		self.topDictIndex.strings = self.strings
38		self.topDictIndex.GlobalSubrs = self.GlobalSubrs
39
40	def __len__(self):
41		return len(self.fontNames)
42
43	def keys(self):
44		return self.fontNames[:]
45
46	def values(self):
47		return self.topDictIndex
48
49	def __getitem__(self, name):
50		try:
51			index = self.fontNames.index(name)
52		except ValueError:
53			raise KeyError, name
54		return self.topDictIndex[index]
55
56	def compile(self):
57		strings = IndexedStrings()
58		XXXX
59
60	def toXML(self, xmlWriter, progress=None):
61		xmlWriter.newline()
62		for fontName in self.fontNames:
63			xmlWriter.begintag("CFFFont", name=fontName)
64			xmlWriter.newline()
65			font = self[fontName]
66			font.toXML(xmlWriter, progress)
67			xmlWriter.endtag("CFFFont")
68			xmlWriter.newline()
69		xmlWriter.newline()
70		xmlWriter.begintag("GlobalSubrs")
71		xmlWriter.newline()
72		self.GlobalSubrs.toXML(xmlWriter, progress)
73		xmlWriter.endtag("GlobalSubrs")
74		xmlWriter.newline()
75		xmlWriter.newline()
76
77	def fromXML(self, (name, attrs, content)):
78		xxx
79
80
81class Index:
82
83	"""This class represents what the CFF spec calls an INDEX."""
84
85	def __init__(self, file, name=None):
86		if name is None:
87			name = self.__class__.__name__
88		if DEBUG:
89			print "loading %s at %s" % (name, file.tell())
90		self.file = file
91		count = readCard16(file)
92		self.count = count
93		self.items = [None] * count
94		if count == 0:
95			self.offsets = []
96			return
97		offSize = readCard8(file)
98		if DEBUG:
99			print "index count: %s offSize: %s" % (count, offSize)
100		assert offSize <= 4, "offSize too large: %s" % offSize
101		self.offsets = offsets = []
102		pad = '\0' * (4 - offSize)
103		for index in range(count+1):
104			chunk = file.read(offSize)
105			chunk = pad + chunk
106			offset, = struct.unpack(">L", chunk)
107			offsets.append(int(offset))
108		self.offsetBase = file.tell() - 1
109		file.seek(self.offsetBase + offsets[-1])  # pretend we've read the whole lot
110
111	def __len__(self):
112		return self.count
113
114	def __getitem__(self, index):
115		item = self.items[index]
116		if item is not None:
117			return item
118		offset = self.offsets[index] + self.offsetBase
119		size = self.offsets[index+1] - self.offsets[index]
120		file = self.file
121		file.seek(offset)
122		data = file.read(size)
123		assert len(data) == size
124		item = self.produceItem(index, data, file, offset, size)
125		self.items[index] = item
126		return item
127
128	def produceItem(self, index, data, file, offset, size):
129		return data
130
131
132class CharStringIndex(Index):
133
134	def __init__(self, file, globalSubrs=None, private=None, fdSelect=None, fdArray=None,
135			name=None):
136		Index.__init__(self, file, name)
137		self.globalSubrs = globalSubrs
138		self.private = private
139		self.fdSelect = fdSelect
140		self.fdArray = fdArray
141
142	def produceItem(self, index, data, file, offset, size):
143		if self.private is not None:
144			private = self.private
145		elif self.fdArray is not None:
146			private = self.fdArray[self.fdSelect[index]].Private
147		else:
148			private = None
149		if hasattr(private, "Subrs"):
150			subrs = private.Subrs
151		else:
152			subrs = []
153		return psCharStrings.T2CharString(data, subrs=subrs, globalSubrs=self.globalSubrs)
154
155	def toXML(self, xmlWriter, progress):
156		fdSelect = self.fdSelect
157		for i in range(len(self)):
158			xmlWriter.begintag("CharString", index=i)
159			xmlWriter.newline()
160			self[i].toXML(xmlWriter)
161			xmlWriter.endtag("CharString")
162			xmlWriter.newline()
163
164	def getItemAndSelector(self, index):
165		fdSelect = self.fdSelect
166		if fdSelect is None:
167			sel = None
168		else:
169			sel = fdSelect[index]
170		return self[index], sel
171
172
173class TopDictIndex(Index):
174
175	def produceItem(self, index, data, file, offset, size):
176		top = TopDict(self.strings, file, offset, self.GlobalSubrs)
177		top.decompile(data)
178		return top
179
180	def toXML(self, xmlWriter, progress):
181		for i in range(len(self)):
182			xmlWriter.begintag("FontDict", index=i)
183			xmlWriter.newline()
184			self[i].toXML(xmlWriter, progress)
185			xmlWriter.endtag("FontDict")
186			xmlWriter.newline()
187
188
189class CharStrings:
190
191	def __init__(self, file, charset, globalSubrs, private, fdSelect, fdArray):
192		self.charStringsIndex = CharStringIndex(file, globalSubrs, private, fdSelect, fdArray)
193		self.nameToIndex = nameToIndex = {}
194		for i in range(len(charset)):
195			nameToIndex[charset[i]] = i
196
197	def keys(self):
198		return self.nameToIndex.keys()
199
200	def values(self):
201		return self.charStringsIndex
202
203	def has_key(self, name):
204		return self.nameToIndex.has_key(name)
205
206	def __len__(self):
207		return len(self.charStringsIndex)
208
209	def __getitem__(self, name):
210		index = self.nameToIndex[name]
211		return self.charStringsIndex[index]
212
213	def getItemAndSelector(self, name):
214		index = self.nameToIndex[name]
215		return self.charStringsIndex.getItemAndSelector(index)
216
217	def toXML(self, xmlWriter, progress):
218		names = self.keys()
219		names.sort()
220		for name in names:
221			charStr, fdSelect = self.getItemAndSelector(name)
222			if fdSelect is None:
223				xmlWriter.begintag("CharString", name=name)
224			else:
225				xmlWriter.begintag("CharString",
226						[('name', name), ('fdSelect', fdSelect)])
227			xmlWriter.newline()
228			self[name].toXML(xmlWriter)
229			xmlWriter.endtag("CharString")
230			xmlWriter.newline()
231
232
233def readCard8(file):
234	return ord(file.read(1))
235
236def readCard16(file):
237	value, = struct.unpack(">H", file.read(2))
238	return value
239
240def buildOperatorDict(table):
241	d = {}
242	for op, name, arg, default, conv in table:
243		d[op] = (name, arg)
244	return d
245
246def buildOrder(table):
247	l = []
248	for op, name, arg, default, conv in table:
249		l.append(name)
250	return l
251
252def buildDefaults(table):
253	d = {}
254	for op, name, arg, default, conv in table:
255		if default is not None:
256			d[name] = default
257	return d
258
259def buildConverters(table):
260	d = {}
261	for op, name, arg, default, conv in table:
262		d[name] = conv
263	return d
264
265
266class BaseConverter:
267	def read(self, parent, value):
268		return value
269	def xmlWrite(self, xmlWriter, name, value):
270		xmlWriter.begintag(name)
271		xmlWriter.newline()
272		value.toXML(xmlWriter, None)
273		xmlWriter.endtag(name)
274		xmlWriter.newline()
275
276class PrivateDictConverter(BaseConverter):
277	def read(self, parent, value):
278		size, offset = value
279		file = parent.file
280		pr = PrivateDict(parent.strings, file, offset)
281		file.seek(offset)
282		data = file.read(size)
283		len(data) == size
284		pr.decompile(data)
285		return pr
286
287class SubrsConverter(BaseConverter):
288	def read(self, parent, value):
289		file = parent.file
290		file.seek(parent.offset + value)  # Offset(self)
291		return CharStringIndex(file, name="SubrsIndex")
292
293class CharStringsConverter(BaseConverter):
294	def read(self, parent, value):
295		file = parent.file
296		charset = parent.charset
297		globalSubrs = parent.GlobalSubrs
298		if hasattr(parent, "ROS"):
299			fdSelect, fdArray = parent.FDSelect, parent.FDArray
300			private = None
301		else:
302			fdSelect, fdArray = None, None
303			private = parent.Private
304		file.seek(value)  # Offset(0)
305		return CharStrings(file, charset, globalSubrs, private, fdSelect, fdArray)
306
307class CharsetConverter:
308	def read(self, parent, value):
309		isCID = hasattr(parent, "ROS")
310		if value > 2:
311			numGlyphs = parent.numGlyphs
312			file = parent.file
313			file.seek(value)
314			format = readCard8(file)
315			if format == 0:
316				raise NotImplementedError
317			elif format == 1 or format == 2:
318				charset = parseCharset(numGlyphs, file, parent.strings, isCID, format)
319			elif format == 3:
320				raise NotImplementedError
321			else:
322				raise NotImplementedError
323			assert len(charset) == numGlyphs
324		else:
325			if isCID or not hasattr(parent, "CharStrings"):
326				assert value == 0
327				charset = None
328			elif value == 0:
329				charset = ISOAdobe
330			elif value == 1:
331				charset = Expert
332			elif value == 2:
333				charset = ExpertSubset
334			# self.charset:
335			#   0: ISOAdobe (or CID font!)
336			#   1: Expert
337			#   2: ExpertSubset
338			charset = None  #
339		return charset
340	def xmlWrite(self, xmlWriter, name, value):
341		# XXX GlyphOrder needs to be stored *somewhere*, but not here...
342		xmlWriter.simpletag("charset", value=value)
343		xmlWriter.newline()
344
345
346def parseCharset(numGlyphs, file, strings, isCID, format):
347	charset = ['.notdef']
348	count = 1
349	if format == 1:
350		nLeftFunc = readCard8
351	else:
352		nLeftFunc = readCard16
353	while count < numGlyphs:
354		first = readCard16(file)
355		nLeft = nLeftFunc(file)
356		if isCID:
357			for CID in range(first, first+nLeft+1):
358				charset.append(CID)
359		else:
360			for SID in range(first, first+nLeft+1):
361				charset.append(strings[SID])
362		count = count + nLeft + 1
363	return charset
364
365
366class FDArrayConverter(BaseConverter):
367	def read(self, parent, value):
368		file = parent.file
369		file.seek(value)
370		fdArray = TopDictIndex(file)
371		fdArray.strings = parent.strings
372		fdArray.GlobalSubrs = parent.GlobalSubrs
373		return fdArray
374
375
376class FDSelectConverter:
377	def read(self, parent, value):
378		file = parent.file
379		file.seek(value)
380		format = readCard8(file)
381		numGlyphs = parent.numGlyphs
382		if format == 0:
383			from array import array
384			fdSelect = array("B", file.read(numGlyphs)).tolist()
385		elif format == 3:
386			fdSelect = [None] * numGlyphs
387			nRanges = readCard16(file)
388			prev = None
389			for i in range(nRanges):
390				first = readCard16(file)
391				if prev is not None:
392					for glyphID in range(prev, first):
393						fdSelect[glyphID] = fd
394				prev = first
395				fd = readCard8(file)
396			if prev is not None:
397				first = readCard16(file)
398				for glyphID in range(prev, first):
399					fdSelect[glyphID] = fd
400		else:
401			assert 0, "unsupported FDSelect format: %s" % format
402		return fdSelect
403	def xmlWrite(self, xmlWriter, name, value):
404		pass
405
406
407class ROSConverter(BaseConverter):
408	def xmlWrite(self, xmlWriter, name, value):
409		registry, order, supplement = value
410		xmlWriter.simpletag(name, [('registry', registry), ('order', order),
411			('supplement', supplement)])
412
413
414topDictOperators = [
415#	opcode     name                  argument type   default    converter
416	((12, 30), 'ROS',        ('SID','SID','number'), None,      ROSConverter()),
417	(0,        'version',            'SID',          None,      None),
418	(1,        'Notice',             'SID',          None,      None),
419	((12, 0),  'Copyright',          'SID',          None,      None),
420	(2,        'FullName',           'SID',          None,      None),
421	((12, 38), 'FontName',           'SID',          None,      None),
422	(3,        'FamilyName',         'SID',          None,      None),
423	(4,        'Weight',             'SID',          None,      None),
424	((12, 1),  'isFixedPitch',       'number',       0,         None),
425	((12, 2),  'ItalicAngle',        'number',       0,         None),
426	((12, 3),  'UnderlinePosition',  'number',       None,      None),
427	((12, 4),  'UnderlineThickness', 'number',       50,        None),
428	((12, 5),  'PaintType',          'number',       0,         None),
429	((12, 6),  'CharstringType',     'number',       2,         None),
430	((12, 7),  'FontMatrix',         'array',  [0.001,0,0,0.001,0,0],  None),
431	(13,       'UniqueID',           'number',       None,      None),
432	(5,        'FontBBox',           'array',  [0,0,0,0],       None),
433	((12, 8),  'StrokeWidth',        'number',       0,         None),
434	(14,       'XUID',               'array',        None,      None),
435	(15,       'charset',            'number',       0,         CharsetConverter()),
436	((12, 20), 'SyntheticBase',      'number',       None,      None),
437	((12, 21), 'PostScript',         'SID',          None,      None),
438	((12, 22), 'BaseFontName',       'SID',          None,      None),
439	((12, 23), 'BaseFontBlend',      'delta',        None,      None),
440	((12, 31), 'CIDFontVersion',     'number',       0,         None),
441	((12, 32), 'CIDFontRevision',    'number',       0,         None),
442	((12, 33), 'CIDFontType',        'number',       0,         None),
443	((12, 34), 'CIDCount',           'number',       8720,      None),
444	((12, 35), 'UIDBase',            'number',       None,      None),
445	(16,       'Encoding',           'number',       0,         None), # XXX
446	((12, 36), 'FDArray',            'number',       None,      FDArrayConverter()),
447	((12, 37), 'FDSelect',           'number',       None,      FDSelectConverter()),
448	(18,       'Private',       ('number','number'), None,      PrivateDictConverter()),
449	(17,       'CharStrings',        'number',       None,      CharStringsConverter()),
450]
451
452privateDictOperators = [
453#	opcode     name                  argument type   default    converter
454	(6,        'BlueValues',         'delta',        None,      None),
455	(7,        'OtherBlues',         'delta',        None,      None),
456	(8,        'FamilyBlues',        'delta',        None,      None),
457	(9,        'FamilyOtherBlues',   'delta',        None,      None),
458	((12, 9),  'BlueScale',          'number',       0.039625,  None),
459	((12, 10), 'BlueShift',          'number',       7,         None),
460	((12, 11), 'BlueFuzz',           'number',       1,         None),
461	(10,       'StdHW',              'number',       None,      None),
462	(11,       'StdVW',              'number',       None,      None),
463	((12, 12), 'StemSnapH',          'delta',        None,      None),
464	((12, 13), 'StemSnapV',          'delta',        None,      None),
465	((12, 14), 'ForceBold',          'number',       0,         None),
466	((12, 17), 'LanguageGroup',      'number',       0,         None),
467	((12, 18), 'ExpansionFactor',    'number',       0.06,      None),
468	((12, 19), 'initialRandomSeed',  'number',       0,         None),
469	(20,       'defaultWidthX',      'number',       0,         None),
470	(21,       'nominalWidthX',      'number',       0,         None),
471	(19,       'Subrs',              'number',       None,      SubrsConverter()),
472]
473
474
475class TopDictDecompiler(psCharStrings.DictDecompiler):
476	operators = buildOperatorDict(topDictOperators)
477
478
479class PrivateDictDecompiler(psCharStrings.DictDecompiler):
480	operators = buildOperatorDict(privateDictOperators)
481
482
483
484class BaseDict:
485
486	def __init__(self, strings, file, offset):
487		self.rawDict = {}
488		if DEBUG:
489			print "loading %s at %s" % (self, offset)
490		self.file = file
491		self.offset = offset
492		self.strings = strings
493		self.skipNames = []
494
495	def decompile(self, data):
496		dec = self.decompiler(self.strings)
497		dec.decompile(data)
498		self.rawDict = dec.getDict()
499		self.postDecompile()
500
501	def postDecompile(self):
502		pass
503
504	def __getattr__(self, name):
505		value = self.rawDict.get(name)
506		if value is None:
507			value = self.defaults.get(name)
508		if value is None:
509			raise AttributeError, name
510		conv = self.converters[name]
511		if conv is not None:
512			value = conv.read(self, value)
513		setattr(self, name, value)
514		return value
515
516	def toXML(self, xmlWriter, progress):
517		for name in self.order:
518			if name in self.skipNames:
519				continue
520			value = getattr(self, name, None)
521			if value is None:
522				continue
523			conv = self.converters.get(name)
524			if conv is not None:
525				conv.xmlWrite(xmlWriter, name, value)
526			else:
527				if isinstance(value, types.ListType):
528					value = " ".join(map(str, value))
529				xmlWriter.simpletag(name, value=value)
530				xmlWriter.newline()
531
532
533class TopDict(BaseDict):
534
535	defaults = buildDefaults(topDictOperators)
536	converters = buildConverters(topDictOperators)
537	order = buildOrder(topDictOperators)
538	decompiler = TopDictDecompiler
539
540	def __init__(self, strings, file, offset, GlobalSubrs):
541		BaseDict.__init__(self, strings, file, offset)
542		self.GlobalSubrs = GlobalSubrs
543
544	def getGlyphOrder(self):
545		return self.charset
546
547	def postDecompile(self):
548		offset = self.rawDict.get("CharStrings")
549		if offset is None:
550			return
551		# get the number of glyphs beforehand.
552		self.file.seek(offset)
553		self.numGlyphs = readCard16(self.file)
554
555	def toXML(self, xmlWriter, progress):
556		if hasattr(self, "CharStrings"):
557			self.decompileAllCharStrings()
558		if not hasattr(self, "ROS") or not hasattr(self, "CharStrings"):
559			# these values have default values, but I only want them to show up
560			# in CID fonts.
561			self.skipNames = ['CIDFontVersion', 'CIDFontRevision', 'CIDFontType',
562					'CIDCount']
563		BaseDict.toXML(self, xmlWriter, progress)
564
565	def decompileAllCharStrings(self):
566		for charString in self.CharStrings.values():
567			charString.decompile()
568
569
570class PrivateDict(BaseDict):
571	defaults = buildDefaults(privateDictOperators)
572	converters = buildConverters(privateDictOperators)
573	order = buildOrder(privateDictOperators)
574	decompiler = PrivateDictDecompiler
575
576
577class IndexedStrings:
578
579	"""SID -> string mapping."""
580
581	def __init__(self, file=None):
582		if file is None:
583			strings = []
584		else:
585			strings = list(Index(file, "IndexedStrings"))
586		self.strings = strings
587
588	def __getitem__(self, SID):
589		if SID < cffStandardStringCount:
590			return cffStandardStrings[SID]
591		else:
592			return self.strings[SID - cffStandardStringCount]
593
594	def getSID(self, s):
595		if not hasattr(self, "stringMapping"):
596			self.buildStringMapping()
597		if cffStandardStringMapping.has_key(s):
598			SID = cffStandardStringMapping[s]
599		if self.stringMapping.has_key(s):
600			SID = self.stringMapping[s]
601		else:
602			SID = len(self.strings) + cffStandardStringCount
603			self.strings.append(s)
604			self.stringMapping[s] = SID
605		return SID
606
607	def getStrings(self):
608		return self.strings
609
610	def buildStringMapping(self):
611		self.stringMapping = {}
612		for index in range(len(self.strings)):
613			self.stringMapping[self.strings[index]] = index + cffStandardStringCount
614
615
616# The 391 Standard Strings as used in the CFF format.
617# from Adobe Technical None #5176, version 1.0, 18 March 1998
618
619cffStandardStrings = ['.notdef', 'space', 'exclam', 'quotedbl', 'numbersign',
620		'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright',
621		'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one',
622		'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon',
623		'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C',
624		'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
625		'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash',
626		'bracketright', 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c',
627		'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
628		's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright',
629		'asciitilde', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin',
630		'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft',
631		'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger',
632		'daggerdbl', 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase',
633		'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand',
634		'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve',
635		'dotaccent', 'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron',
636		'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae',
637		'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior',
638		'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn',
639		'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters',
640		'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior',
641		'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring',
642		'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave',
643		'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute',
644		'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute',
645		'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron',
646		'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla',
647		'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex',
648		'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis',
649		'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave',
650		'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall',
651		'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall',
652		'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader',
653		'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle',
654		'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle',
655		'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior',
656		'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior',
657		'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior',
658		'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', 'parenleftinferior',
659		'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall',
660		'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall',
661		'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall',
662		'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall',
663		'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall',
664		'exclamdownsmall', 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall',
665		'Dieresissmall', 'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall',
666		'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall',
667		'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths',
668		'onethird', 'twothirds', 'zerosuperior', 'foursuperior', 'fivesuperior',
669		'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior',
670		'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior',
671		'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior',
672		'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall',
673		'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall',
674		'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall',
675		'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall',
676		'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall',
677		'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall',
678		'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall',
679		'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', '001.001', '001.002',
680		'001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman',
681		'Semibold'
682]
683
684cffStandardStringCount = 391
685assert len(cffStandardStrings) == cffStandardStringCount
686# build reverse mapping
687cffStandardStringMapping = {}
688for _i in range(cffStandardStringCount):
689	cffStandardStringMapping[cffStandardStrings[_i]] = _i
690
691
692