cffLib.py revision 4afb2573876b6ad2ee03fc696cb045e8d1ddb237
1"""cffLib.py -- read/write tools for Adobe CFF fonts."""
2
3#
4# $Id: cffLib.py,v 1.20 2002-05-18 20:07:01 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			else:
320				raise NotImplementedError
321			assert len(charset) == numGlyphs
322		else:
323			if isCID or not hasattr(parent, "CharStrings"):
324				assert value == 0
325				charset = None
326			elif value == 0:
327				charset = ISOAdobe
328			elif value == 1:
329				charset = Expert
330			elif value == 2:
331				charset = ExpertSubset
332			# self.charset:
333			#   0: ISOAdobe (or CID font!)
334			#   1: Expert
335			#   2: ExpertSubset
336			charset = None  #
337		return charset
338	def xmlWrite(self, xmlWriter, name, value):
339		# XXX GlyphOrder needs to be stored *somewhere*, but not here...
340		xmlWriter.simpletag("charset", value=value)
341		xmlWriter.newline()
342
343
344def parseCharset(numGlyphs, file, strings, isCID, format):
345	charset = ['.notdef']
346	count = 1
347	if format == 1:
348		nLeftFunc = readCard8
349	else:
350		nLeftFunc = readCard16
351	while count < numGlyphs:
352		first = readCard16(file)
353		nLeft = nLeftFunc(file)
354		if isCID:
355			for CID in range(first, first+nLeft+1):
356				charset.append(CID)
357		else:
358			for SID in range(first, first+nLeft+1):
359				charset.append(strings[SID])
360		count = count + nLeft + 1
361	return charset
362
363
364class FDArrayConverter(BaseConverter):
365	def read(self, parent, value):
366		file = parent.file
367		file.seek(value)
368		fdArray = TopDictIndex(file)
369		fdArray.strings = parent.strings
370		fdArray.GlobalSubrs = parent.GlobalSubrs
371		return fdArray
372
373
374class FDSelectConverter:
375	def read(self, parent, value):
376		file = parent.file
377		file.seek(value)
378		format = readCard8(file)
379		numGlyphs = parent.numGlyphs
380		if format == 0:
381			from array import array
382			fdSelect = array("B", file.read(numGlyphs)).tolist()
383		elif format == 3:
384			fdSelect = [None] * numGlyphs
385			nRanges = readCard16(file)
386			prev = None
387			for i in range(nRanges):
388				first = readCard16(file)
389				if prev is not None:
390					for glyphID in range(prev, first):
391						fdSelect[glyphID] = fd
392				prev = first
393				fd = readCard8(file)
394			if prev is not None:
395				first = readCard16(file)
396				for glyphID in range(prev, first):
397					fdSelect[glyphID] = fd
398		else:
399			assert 0, "unsupported FDSelect format: %s" % format
400		return fdSelect
401	def xmlWrite(self, xmlWriter, name, value):
402		pass
403
404
405class ROSConverter(BaseConverter):
406	def xmlWrite(self, xmlWriter, name, value):
407		registry, order, supplement = value
408		xmlWriter.simpletag(name, [('registry', registry), ('order', order),
409			('supplement', supplement)])
410		xmlWriter.newline()
411
412
413topDictOperators = [
414#	opcode     name                  argument type   default    converter
415	((12, 30), 'ROS',        ('SID','SID','number'), None,      ROSConverter()),
416	(0,        'version',            'SID',          None,      None),
417	(1,        'Notice',             'SID',          None,      None),
418	((12, 0),  'Copyright',          'SID',          None,      None),
419	(2,        'FullName',           'SID',          None,      None),
420	((12, 38), 'FontName',           'SID',          None,      None),
421	(3,        'FamilyName',         'SID',          None,      None),
422	(4,        'Weight',             'SID',          None,      None),
423	((12, 1),  'isFixedPitch',       'number',       0,         None),
424	((12, 2),  'ItalicAngle',        'number',       0,         None),
425	((12, 3),  'UnderlinePosition',  'number',       None,      None),
426	((12, 4),  'UnderlineThickness', 'number',       50,        None),
427	((12, 5),  'PaintType',          'number',       0,         None),
428	((12, 6),  'CharstringType',     'number',       2,         None),
429	((12, 7),  'FontMatrix',         'array',  [0.001,0,0,0.001,0,0],  None),
430	(13,       'UniqueID',           'number',       None,      None),
431	(5,        'FontBBox',           'array',  [0,0,0,0],       None),
432	((12, 8),  'StrokeWidth',        'number',       0,         None),
433	(14,       'XUID',               'array',        None,      None),
434	(15,       'charset',            'number',       0,         CharsetConverter()),
435	((12, 20), 'SyntheticBase',      'number',       None,      None),
436	((12, 21), 'PostScript',         'SID',          None,      None),
437	((12, 22), 'BaseFontName',       'SID',          None,      None),
438	((12, 23), 'BaseFontBlend',      'delta',        None,      None),
439	((12, 31), 'CIDFontVersion',     'number',       0,         None),
440	((12, 32), 'CIDFontRevision',    'number',       0,         None),
441	((12, 33), 'CIDFontType',        'number',       0,         None),
442	((12, 34), 'CIDCount',           'number',       8720,      None),
443	((12, 35), 'UIDBase',            'number',       None,      None),
444	(16,       'Encoding',           'number',       0,         None), # XXX
445	((12, 36), 'FDArray',            'number',       None,      FDArrayConverter()),
446	((12, 37), 'FDSelect',           'number',       None,      FDSelectConverter()),
447	(18,       'Private',       ('number','number'), None,      PrivateDictConverter()),
448	(17,       'CharStrings',        'number',       None,      CharStringsConverter()),
449]
450
451privateDictOperators = [
452#	opcode     name                  argument type   default    converter
453	(6,        'BlueValues',         'delta',        None,      None),
454	(7,        'OtherBlues',         'delta',        None,      None),
455	(8,        'FamilyBlues',        'delta',        None,      None),
456	(9,        'FamilyOtherBlues',   'delta',        None,      None),
457	((12, 9),  'BlueScale',          'number',       0.039625,  None),
458	((12, 10), 'BlueShift',          'number',       7,         None),
459	((12, 11), 'BlueFuzz',           'number',       1,         None),
460	(10,       'StdHW',              'number',       None,      None),
461	(11,       'StdVW',              'number',       None,      None),
462	((12, 12), 'StemSnapH',          'delta',        None,      None),
463	((12, 13), 'StemSnapV',          'delta',        None,      None),
464	((12, 14), 'ForceBold',          'number',       0,         None),
465	((12, 17), 'LanguageGroup',      'number',       0,         None),
466	((12, 18), 'ExpansionFactor',    'number',       0.06,      None),
467	((12, 19), 'initialRandomSeed',  'number',       0,         None),
468	(20,       'defaultWidthX',      'number',       0,         None),
469	(21,       'nominalWidthX',      'number',       0,         None),
470	(19,       'Subrs',              'number',       None,      SubrsConverter()),
471]
472
473
474class TopDictDecompiler(psCharStrings.DictDecompiler):
475	operators = buildOperatorDict(topDictOperators)
476
477
478class PrivateDictDecompiler(psCharStrings.DictDecompiler):
479	operators = buildOperatorDict(privateDictOperators)
480
481
482
483class BaseDict:
484
485	def __init__(self, strings, file, offset):
486		self.rawDict = {}
487		if DEBUG:
488			print "loading %s at %s" % (self, offset)
489		self.file = file
490		self.offset = offset
491		self.strings = strings
492		self.skipNames = []
493
494	def decompile(self, data):
495		dec = self.decompiler(self.strings)
496		dec.decompile(data)
497		self.rawDict = dec.getDict()
498		self.postDecompile()
499
500	def postDecompile(self):
501		pass
502
503	def __getattr__(self, name):
504		value = self.rawDict.get(name)
505		if value is None:
506			value = self.defaults.get(name)
507		if value is None:
508			raise AttributeError, name
509		conv = self.converters[name]
510		if conv is not None:
511			value = conv.read(self, value)
512		setattr(self, name, value)
513		return value
514
515	def toXML(self, xmlWriter, progress):
516		for name in self.order:
517			if name in self.skipNames:
518				continue
519			value = getattr(self, name, None)
520			if value is None:
521				continue
522			conv = self.converters.get(name)
523			if conv is not None:
524				conv.xmlWrite(xmlWriter, name, value)
525			else:
526				if isinstance(value, types.ListType):
527					value = " ".join(map(str, value))
528				xmlWriter.simpletag(name, value=value)
529				xmlWriter.newline()
530
531
532class TopDict(BaseDict):
533
534	defaults = buildDefaults(topDictOperators)
535	converters = buildConverters(topDictOperators)
536	order = buildOrder(topDictOperators)
537	decompiler = TopDictDecompiler
538
539	def __init__(self, strings, file, offset, GlobalSubrs):
540		BaseDict.__init__(self, strings, file, offset)
541		self.GlobalSubrs = GlobalSubrs
542
543	def getGlyphOrder(self):
544		return self.charset
545
546	def postDecompile(self):
547		offset = self.rawDict.get("CharStrings")
548		if offset is None:
549			return
550		# get the number of glyphs beforehand.
551		self.file.seek(offset)
552		self.numGlyphs = readCard16(self.file)
553
554	def toXML(self, xmlWriter, progress):
555		if hasattr(self, "CharStrings"):
556			self.decompileAllCharStrings()
557		if not hasattr(self, "ROS") or not hasattr(self, "CharStrings"):
558			# these values have default values, but I only want them to show up
559			# in CID fonts.
560			self.skipNames = ['CIDFontVersion', 'CIDFontRevision', 'CIDFontType',
561					'CIDCount']
562		BaseDict.toXML(self, xmlWriter, progress)
563
564	def decompileAllCharStrings(self):
565		for charString in self.CharStrings.values():
566			charString.decompile()
567
568
569class PrivateDict(BaseDict):
570	defaults = buildDefaults(privateDictOperators)
571	converters = buildConverters(privateDictOperators)
572	order = buildOrder(privateDictOperators)
573	decompiler = PrivateDictDecompiler
574
575
576class IndexedStrings:
577
578	"""SID -> string mapping."""
579
580	def __init__(self, file=None):
581		if file is None:
582			strings = []
583		else:
584			strings = list(Index(file, "IndexedStrings"))
585		self.strings = strings
586
587	def __getitem__(self, SID):
588		if SID < cffStandardStringCount:
589			return cffStandardStrings[SID]
590		else:
591			return self.strings[SID - cffStandardStringCount]
592
593	def getSID(self, s):
594		if not hasattr(self, "stringMapping"):
595			self.buildStringMapping()
596		if cffStandardStringMapping.has_key(s):
597			SID = cffStandardStringMapping[s]
598		if self.stringMapping.has_key(s):
599			SID = self.stringMapping[s]
600		else:
601			SID = len(self.strings) + cffStandardStringCount
602			self.strings.append(s)
603			self.stringMapping[s] = SID
604		return SID
605
606	def getStrings(self):
607		return self.strings
608
609	def buildStringMapping(self):
610		self.stringMapping = {}
611		for index in range(len(self.strings)):
612			self.stringMapping[self.strings[index]] = index + cffStandardStringCount
613
614
615# The 391 Standard Strings as used in the CFF format.
616# from Adobe Technical None #5176, version 1.0, 18 March 1998
617
618cffStandardStrings = ['.notdef', 'space', 'exclam', 'quotedbl', 'numbersign',
619		'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright',
620		'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one',
621		'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon',
622		'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C',
623		'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
624		'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash',
625		'bracketright', 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c',
626		'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
627		's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright',
628		'asciitilde', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin',
629		'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft',
630		'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger',
631		'daggerdbl', 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase',
632		'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand',
633		'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve',
634		'dotaccent', 'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron',
635		'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae',
636		'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior',
637		'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn',
638		'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters',
639		'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior',
640		'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring',
641		'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave',
642		'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute',
643		'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute',
644		'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron',
645		'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla',
646		'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex',
647		'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis',
648		'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave',
649		'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall',
650		'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall',
651		'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader',
652		'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle',
653		'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle',
654		'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior',
655		'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior',
656		'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior',
657		'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', 'parenleftinferior',
658		'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall',
659		'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall',
660		'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall',
661		'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall',
662		'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall',
663		'exclamdownsmall', 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall',
664		'Dieresissmall', 'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall',
665		'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall',
666		'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths',
667		'onethird', 'twothirds', 'zerosuperior', 'foursuperior', 'fivesuperior',
668		'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior',
669		'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior',
670		'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior',
671		'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall',
672		'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall',
673		'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall',
674		'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall',
675		'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall',
676		'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall',
677		'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall',
678		'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', '001.001', '001.002',
679		'001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman',
680		'Semibold'
681]
682
683cffStandardStringCount = 391
684assert len(cffStandardStrings) == cffStandardStringCount
685# build reverse mapping
686cffStandardStringMapping = {}
687for _i in range(cffStandardStringCount):
688	cffStandardStringMapping[cffStandardStrings[_i]] = _i
689
690
691