blob: 8d4336600b46597ac2355feb4032248e4a6542c0 [file] [log] [blame]
Just7842e561999-12-16 21:34:53 +00001"""cffLib.py -- read/write tools for Adobe CFF fonts."""
2
Justec46d161999-12-20 22:02:10 +00003#
jvre3275582002-05-14 12:22:03 +00004# $Id: cffLib.py,v 1.9 2002-05-14 12:22:03 jvr Exp $
Justec46d161999-12-20 22:02:10 +00005#
Just7842e561999-12-16 21:34:53 +00006
7import struct, sstruct
8import string
9import types
Just528614e2000-01-16 22:14:02 +000010from fontTools.misc import psCharStrings
Just7842e561999-12-16 21:34:53 +000011
12
13cffHeaderFormat = """
14 major: B
15 minor: B
16 hdrSize: B
17 offSize: B
18"""
19
20class CFFFontSet:
21
22 def __init__(self):
23 self.fonts = {}
24
jvra2a75b32002-05-13 11:25:17 +000025 def decompile(self, file):
26 sstruct.unpack(cffHeaderFormat, file.read(4), self)
Just7842e561999-12-16 21:34:53 +000027 assert self.major == 1 and self.minor == 0, \
28 "unknown CFF format: %d.%d" % (self.major, self.minor)
Just7842e561999-12-16 21:34:53 +000029
jvra2a75b32002-05-13 11:25:17 +000030 self.fontNames = readINDEX(file)
31 topDicts = readINDEX(file)
32 strings = IndexedStrings(readINDEX(file))
33 globalSubrs = readINDEX(file)
Just7842e561999-12-16 21:34:53 +000034 self.GlobalSubrs = map(psCharStrings.T2CharString, globalSubrs)
35
36 for i in range(len(topDicts)):
37 font = self.fonts[self.fontNames[i]] = CFFFont()
38 font.GlobalSubrs = self.GlobalSubrs # Hmm.
jvrdbc2c172002-05-13 16:19:51 +000039 file.seek(0, 0)
jvra2a75b32002-05-13 11:25:17 +000040 font.decompile(file, topDicts[i], strings, self) # maybe only 'on demand'?
Just7842e561999-12-16 21:34:53 +000041
42 def compile(self):
43 strings = IndexedStrings()
44 XXXX
45
46 def toXML(self, xmlWriter, progress=None):
47 xmlWriter.newline()
48 for fontName in self.fontNames:
49 xmlWriter.begintag("CFFFont", name=fontName)
50 xmlWriter.newline()
51 font = self.fonts[fontName]
52 font.toXML(xmlWriter, progress)
53 xmlWriter.endtag("CFFFont")
54 xmlWriter.newline()
55 xmlWriter.newline()
56 xmlWriter.begintag("GlobalSubrs")
57 xmlWriter.newline()
58 for i in range(len(self.GlobalSubrs)):
59 xmlWriter.newline()
60 xmlWriter.begintag("CharString", id=i)
61 xmlWriter.newline()
62 self.GlobalSubrs[i].toXML(xmlWriter)
63 xmlWriter.endtag("CharString")
64 xmlWriter.newline()
65 xmlWriter.newline()
66 xmlWriter.endtag("GlobalSubrs")
67 xmlWriter.newline()
68 xmlWriter.newline()
69
70 def fromXML(self, (name, attrs, content)):
71 xxx
72
73
Just7842e561999-12-16 21:34:53 +000074class CFFFont:
75
jvrdbc2c172002-05-13 16:19:51 +000076 defaults = topDictDefaults
Just7842e561999-12-16 21:34:53 +000077
78 def __init__(self):
79 pass
80
81 def __getattr__(self, attr):
82 if not self.defaults.has_key(attr):
83 raise AttributeError, attr
84 return self.defaults[attr]
85
86 def fromDict(self, dict):
87 self.__dict__.update(dict)
88
jvra2a75b32002-05-13 11:25:17 +000089 def decompileCID(self, data, strings):
90 offset = self.FDArray
91 fontDicts, restdata = readINDEX(data[offset:])
92 subFonts = []
93 for topDictData in fontDicts:
94 subFont = CFFFont()
95 subFonts.append(subFont)
96 subFont.decompile(data, topDictData, strings, None)
97
98 raise NotImplementedError
99
100 def decompile(self, file, topDictData, strings, fontSet):
jvrdbc2c172002-05-13 16:19:51 +0000101 top = TopDictDecompiler(strings)
Just7842e561999-12-16 21:34:53 +0000102 top.decompile(topDictData)
103 self.fromDict(top.getDict())
104
jvra2a75b32002-05-13 11:25:17 +0000105 if hasattr(self, "ROS"):
106 isCID = 1
107 # XXX CID subFonts
108 else:
109 isCID = 0
110 size, offset = self.Private
111 file.seek(offset, 0)
112 privateData = file.read(size)
113 file.seek(offset, 0)
114 assert len(privateData) == size
115 self.Private = PrivateDict()
116 self.Private.decompile(file, privateData, strings)
Just7842e561999-12-16 21:34:53 +0000117
jvra2a75b32002-05-13 11:25:17 +0000118 file.seek(self.CharStrings)
119 rawCharStrings = readINDEX(file)
Just7842e561999-12-16 21:34:53 +0000120 nGlyphs = len(rawCharStrings)
121
122 # get charset (or rather: get glyphNames)
jvra2a75b32002-05-13 11:25:17 +0000123 if self.charset == 0:
Just7842e561999-12-16 21:34:53 +0000124 xxx # standard charset
125 else:
jvra2a75b32002-05-13 11:25:17 +0000126 file.seek(self.charset)
127 format = ord(file.read(1))
Just7842e561999-12-16 21:34:53 +0000128 if format == 0:
129 xxx
130 elif format == 1:
jvra2a75b32002-05-13 11:25:17 +0000131 charset = parseCharsetFormat1(nGlyphs, file, strings, isCID)
Just7842e561999-12-16 21:34:53 +0000132 elif format == 2:
jvra2a75b32002-05-13 11:25:17 +0000133 charset = parseCharsetFormat2(nGlyphs, file, strings, isCID)
Just7842e561999-12-16 21:34:53 +0000134 elif format == 3:
135 xxx
136 else:
137 xxx
jvra2a75b32002-05-13 11:25:17 +0000138 self.charset = charset
Just7842e561999-12-16 21:34:53 +0000139
jvra2a75b32002-05-13 11:25:17 +0000140 assert len(charset) == nGlyphs
Just7842e561999-12-16 21:34:53 +0000141 self.CharStrings = charStrings = {}
142 if self.CharstringType == 2:
143 # Type 2 CharStrings
144 charStringClass = psCharStrings.T2CharString
145 else:
146 # Type 1 CharStrings
147 charStringClass = psCharStrings.T1CharString
148 for i in range(nGlyphs):
jvra2a75b32002-05-13 11:25:17 +0000149 charStrings[charset[i]] = charStringClass(rawCharStrings[i])
Just7842e561999-12-16 21:34:53 +0000150 assert len(charStrings) == nGlyphs
151
152 # XXX Encoding!
153 encoding = self.Encoding
154 if encoding not in (0, 1):
155 # encoding is an _offset_ from the beginning of 'data' to an encoding subtable
156 XXX
157 self.Encoding = encoding
158
159 def getGlyphOrder(self):
160 return self.charset
161
162 def setGlyphOrder(self, glyphOrder):
163 self.charset = glyphOrder
164
165 def decompileAllCharStrings(self):
166 if self.CharstringType == 2:
167 # Type 2 CharStrings
168 decompiler = psCharStrings.SimpleT2Decompiler(self.Private.Subrs, self.GlobalSubrs)
169 for charString in self.CharStrings.values():
170 if charString.needsDecompilation():
171 decompiler.reset()
172 decompiler.execute(charString)
173 else:
174 # Type 1 CharStrings
175 for charString in self.CharStrings.values():
176 charString.decompile()
177
178 def toXML(self, xmlWriter, progress=None):
179 xmlWriter.newline()
180 # first dump the simple values
181 self.toXMLSimpleValues(xmlWriter)
182
183 # dump charset
184 # XXX
185
186 # decompile all charstrings
187 if progress:
188 progress.setlabel("Decompiling CharStrings...")
189 self.decompileAllCharStrings()
190
191 # dump private dict
192 xmlWriter.begintag("Private")
193 xmlWriter.newline()
194 self.Private.toXML(xmlWriter)
195 xmlWriter.endtag("Private")
196 xmlWriter.newline()
197
198 self.toXMLCharStrings(xmlWriter, progress)
199
200 def toXMLSimpleValues(self, xmlWriter):
201 keys = self.__dict__.keys()
202 keys.remove("CharStrings")
203 keys.remove("Private")
204 keys.remove("charset")
205 keys.remove("GlobalSubrs")
206 keys.sort()
207 for key in keys:
208 value = getattr(self, key)
209 if key == "Encoding":
210 if value == 0:
211 # encoding is (Adobe) Standard Encoding
212 value = "StandardEncoding"
213 elif value == 1:
214 # encoding is Expert Encoding
215 value = "ExpertEncoding"
216 if type(value) == types.ListType:
217 value = string.join(map(str, value), " ")
218 else:
219 value = str(value)
220 xmlWriter.begintag(key)
221 if hasattr(value, "toXML"):
222 xmlWriter.newline()
223 value.toXML(xmlWriter)
224 xmlWriter.newline()
225 else:
226 xmlWriter.write(value)
227 xmlWriter.endtag(key)
228 xmlWriter.newline()
229 xmlWriter.newline()
230
231 def toXMLCharStrings(self, xmlWriter, progress=None):
232 charStrings = self.CharStrings
233 xmlWriter.newline()
234 xmlWriter.begintag("CharStrings")
235 xmlWriter.newline()
236 glyphNames = charStrings.keys()
237 glyphNames.sort()
238 for glyphName in glyphNames:
239 if progress:
240 progress.setlabel("Dumping 'CFF ' table... (%s)" % glyphName)
241 progress.increment()
242 xmlWriter.newline()
243 charString = charStrings[glyphName]
244 xmlWriter.begintag("CharString", name=glyphName)
245 xmlWriter.newline()
246 charString.toXML(xmlWriter)
247 xmlWriter.endtag("CharString")
248 xmlWriter.newline()
249 xmlWriter.newline()
250 xmlWriter.endtag("CharStrings")
251 xmlWriter.newline()
252
253
254class PrivateDict:
255
jvrdbc2c172002-05-13 16:19:51 +0000256 defaults = privateDictDefaults
Just7842e561999-12-16 21:34:53 +0000257
258 def __init__(self):
259 pass
260
261 def decompile(self, data, privateData, strings):
jvrdbc2c172002-05-13 16:19:51 +0000262 p = PrivateDictDecompiler(strings)
Just7842e561999-12-16 21:34:53 +0000263 p.decompile(privateData)
264 self.fromDict(p.getDict())
265
266 # get local subrs
267 #print "YYY Private.Subrs:", self.Subrs
Just8ab68262000-03-28 10:37:25 +0000268 if hasattr(self, "Subrs"):
269 chunk = data[self.Subrs:]
270 localSubrs, restdata = readINDEX(chunk)
271 self.Subrs = map(psCharStrings.T2CharString, localSubrs)
272 else:
273 self.Subrs = []
Just7842e561999-12-16 21:34:53 +0000274
275 def toXML(self, xmlWriter):
276 xmlWriter.newline()
277 keys = self.__dict__.keys()
278 keys.remove("Subrs")
279 for key in keys:
280 value = getattr(self, key)
281 if type(value) == types.ListType:
282 value = string.join(map(str, value), " ")
283 else:
284 value = str(value)
285 xmlWriter.begintag(key)
286 xmlWriter.write(value)
287 xmlWriter.endtag(key)
288 xmlWriter.newline()
289 # write subroutines
290 xmlWriter.newline()
291 xmlWriter.begintag("Subrs")
292 xmlWriter.newline()
293 for i in range(len(self.Subrs)):
294 xmlWriter.newline()
295 xmlWriter.begintag("CharString", id=i)
296 xmlWriter.newline()
297 self.Subrs[i].toXML(xmlWriter)
298 xmlWriter.endtag("CharString")
299 xmlWriter.newline()
300 xmlWriter.newline()
301 xmlWriter.endtag("Subrs")
302 xmlWriter.newline()
303 xmlWriter.newline()
304
305 def __getattr__(self, attr):
306 if not self.defaults.has_key(attr):
307 raise AttributeError, attr
308 return self.defaults[attr]
309
310 def fromDict(self, dict):
311 self.__dict__.update(dict)
312
313
jvra2a75b32002-05-13 11:25:17 +0000314def readINDEX(file):
315 count, = struct.unpack(">H", file.read(2))
316 offSize = ord(file.read(1))
Just7842e561999-12-16 21:34:53 +0000317 offsets = []
318 for index in range(count+1):
jvra2a75b32002-05-13 11:25:17 +0000319 chunk = file.read(offSize)
Just7842e561999-12-16 21:34:53 +0000320 chunk = '\0' * (4 - offSize) + chunk
321 offset, = struct.unpack(">L", chunk)
322 offset = int(offset)
323 offsets.append(offset)
Just7842e561999-12-16 21:34:53 +0000324 prev = offsets[0]
325 stuff = []
Just8ab68262000-03-28 10:37:25 +0000326 next = offsets[0]
Just7842e561999-12-16 21:34:53 +0000327 for next in offsets[1:]:
jvra2a75b32002-05-13 11:25:17 +0000328 chunk = file.read(next - prev)
Just7842e561999-12-16 21:34:53 +0000329 assert len(chunk) == next - prev
330 stuff.append(chunk)
331 prev = next
jvra2a75b32002-05-13 11:25:17 +0000332 return stuff
Just7842e561999-12-16 21:34:53 +0000333
334
jvra2a75b32002-05-13 11:25:17 +0000335def parseCharsetFormat1(nGlyphs, file, strings, isCID):
336 charset = ['.notdef']
Just7842e561999-12-16 21:34:53 +0000337 count = 1
338 while count < nGlyphs:
jvra2a75b32002-05-13 11:25:17 +0000339 first, = struct.unpack(">H", file.read(2))
340 nLeft = ord(file.read(1))
341 if isCID:
342 for CID in range(first, first+nLeft+1):
343 charset.append(CID)
344 else:
345 for SID in range(first, first+nLeft+1):
346 charset.append(strings[SID])
Just7842e561999-12-16 21:34:53 +0000347 count = count + nLeft + 1
jvra2a75b32002-05-13 11:25:17 +0000348 return charset
Just7842e561999-12-16 21:34:53 +0000349
350
jvra2a75b32002-05-13 11:25:17 +0000351def parseCharsetFormat2(nGlyphs, file, strings, isCID):
352 charset = ['.notdef']
Just7842e561999-12-16 21:34:53 +0000353 count = 1
354 while count < nGlyphs:
jvra2a75b32002-05-13 11:25:17 +0000355 first, = struct.unpack(">H", file.read(2))
356 nLeft, = struct.unpack(">H", file.read(2))
357 if isCID:
358 for CID in range(first, first+nLeft+1):
359 charset.append(CID)
360 else:
361 for SID in range(first, first+nLeft+1):
362 charset.append(strings[SID])
Just7842e561999-12-16 21:34:53 +0000363 count = count + nLeft + 1
jvra2a75b32002-05-13 11:25:17 +0000364 return charset
Just7842e561999-12-16 21:34:53 +0000365
366
jvre3275582002-05-14 12:22:03 +0000367topDictOperators = [
368# opcode name argument type
369 (0, 'version', 'SID'),
370 (1, 'Notice', 'SID'),
371 (2, 'FullName', 'SID'),
372 (3, 'FamilyName', 'SID'),
373 (4, 'Weight', 'SID'),
374 (5, 'FontBBox', 'array'),
375 (13, 'UniqueID', 'number'),
376 (14, 'XUID', 'array'),
377 (15, 'charset', 'number'),
378 (16, 'Encoding', 'number'),
379 (17, 'CharStrings', 'number'),
380 (18, 'Private', ('number', 'number')),
381 ((12, 0), 'Copyright', 'SID'),
382 ((12, 1), 'isFixedPitch', 'number'),
383 ((12, 2), 'ItalicAngle', 'number'),
384 ((12, 3), 'UnderlinePosition', 'number'),
385 ((12, 4), 'UnderlineThickness', 'number'),
386 ((12, 5), 'PaintType', 'number'),
387 ((12, 6), 'CharstringType', 'number'),
388 ((12, 7), 'FontMatrix', 'array'),
389 ((12, 8), 'StrokeWidth', 'number'),
390 ((12, 20), 'SyntheticBase', 'number'),
391 ((12, 21), 'PostScript', 'SID'),
392 ((12, 22), 'BaseFontName', 'SID'),
393 # CID additions
394 ((12, 30), 'ROS', ('SID', 'SID', 'number')),
395 ((12, 31), 'CIDFontVersion', 'number'),
396 ((12, 32), 'CIDFontRevision', 'number'),
397 ((12, 33), 'CIDFontType', 'number'),
398 ((12, 34), 'CIDCount', 'number'),
399 ((12, 35), 'UIDBase', 'number'),
400 ((12, 36), 'FDArray', 'number'),
401 ((12, 37), 'FDSelect', 'number'),
402 ((12, 38), 'FontName', 'SID'),
403]
404
405topDictDefaults = {
406 'isFixedPitch': 0,
407 'ItalicAngle': 0,
408 'UnderlineThickness': 50,
409 'PaintType': 0,
410 'CharstringType': 2,
411 'FontMatrix': [0.001, 0, 0, 0.001, 0, 0],
412 'FontBBox': [0, 0, 0, 0],
413 'StrokeWidth': 0,
414 'charset': 0,
415 'Encoding': 0,
416 # CID defaults
417 'CIDFontVersion': 0,
418 'CIDFontRevision': 0,
419 'CIDFontType': 0,
420 'CIDCount': 8720,
421}
422
423class TopDictDecompiler(psCharStrings.DictDecompiler):
424
425 operators = psCharStrings.buildOperatorDict(topDictOperators)
426 dictDefaults = topDictDefaults
427
428
429privateDictOperators = [
430# opcode name argument type
431 (6, 'BlueValues', 'array'),
432 (7, 'OtherBlues', 'array'),
433 (8, 'FamilyBlues', 'array'),
434 (9, 'FamilyOtherBlues', 'array'),
435 (10, 'StdHW', 'number'),
436 (11, 'StdVW', 'number'),
437 (19, 'Subrs', 'number'),
438 (20, 'defaultWidthX', 'number'),
439 (21, 'nominalWidthX', 'number'),
440 ((12, 9), 'BlueScale', 'number'),
441 ((12, 10), 'BlueShift', 'number'),
442 ((12, 11), 'BlueFuzz', 'number'),
443 ((12, 12), 'StemSnapH', 'array'),
444 ((12, 13), 'StemSnapV', 'array'),
445 ((12, 14), 'ForceBold', 'number'),
446 ((12, 17), 'LanguageGroup', 'number'),
447 ((12, 18), 'ExpansionFactor', 'number'),
448 ((12, 19), 'initialRandomSeed', 'number'),
449]
450
451privateDictDefaults = {
452 'defaultWidthX': 0,
453 'nominalWidthX': 0,
454 'BlueScale': 0.039625,
455 'BlueShift': 7,
456 'BlueFuzz': 1,
457 'ForceBold': 0,
458 'LanguageGroup': 0,
459 'ExpansionFactor': 0.06,
460 'initialRandomSeed': 0,
461}
462
463class PrivateDictDecompiler(psCharStrings.DictDecompiler):
464
465 operators = psCharStrings.buildOperatorDict(privateDictOperators)
466 dictDefaults = privateDictDefaults
467
468
469class IndexedStrings:
470
471 def __init__(self, strings=None):
472 if strings is None:
473 strings = []
474 self.strings = strings
475
476 def __getitem__(self, SID):
477 if SID < cffStandardStringCount:
478 return cffStandardStrings[SID]
479 else:
480 return self.strings[SID - cffStandardStringCount]
481
482 def getSID(self, s):
483 if not hasattr(self, "stringMapping"):
484 self.buildStringMapping()
485 if cffStandardStringMapping.has_key(s):
486 SID = cffStandardStringMapping[s]
487 if self.stringMapping.has_key(s):
488 SID = self.stringMapping[s]
489 else:
490 SID = len(self.strings) + cffStandardStringCount
491 self.strings.append(s)
492 self.stringMapping[s] = SID
493 return SID
494
495 def getStrings(self):
496 return self.strings
497
498 def buildStringMapping(self):
499 self.stringMapping = {}
500 for index in range(len(self.strings)):
501 self.stringMapping[self.strings[index]] = index + cffStandardStringCount
502
503
Just7842e561999-12-16 21:34:53 +0000504# The 391 Standard Strings as used in the CFF format.
505# from Adobe Technical None #5176, version 1.0, 18 March 1998
506
507cffStandardStrings = ['.notdef', 'space', 'exclam', 'quotedbl', 'numbersign',
508 'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright',
509 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one',
510 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon',
511 'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C',
512 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
513 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash',
514 'bracketright', 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c',
515 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
516 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright',
517 'asciitilde', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin',
518 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft',
519 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger',
520 'daggerdbl', 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase',
521 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand',
522 'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve',
523 'dotaccent', 'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron',
524 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae',
525 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior',
526 'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn',
527 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters',
528 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior',
529 'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring',
530 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave',
531 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute',
532 'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute',
533 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron',
534 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla',
535 'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex',
536 'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis',
537 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave',
538 'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall',
539 'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall',
540 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader',
541 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle',
542 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle',
543 'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior',
544 'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior',
545 'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior',
546 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', 'parenleftinferior',
547 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall',
548 'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall',
549 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall',
550 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall',
551 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall',
552 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall',
553 'Dieresissmall', 'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall',
554 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall',
555 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths',
556 'onethird', 'twothirds', 'zerosuperior', 'foursuperior', 'fivesuperior',
557 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior',
558 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior',
559 'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior',
560 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall',
561 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall',
562 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall',
563 'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall',
564 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall',
565 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall',
566 'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall',
567 'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', '001.001', '001.002',
568 '001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman',
569 'Semibold'
570]
571
572cffStandardStringCount = 391
573assert len(cffStandardStrings) == cffStandardStringCount
574# build reverse mapping
575cffStandardStringMapping = {}
576for _i in range(cffStandardStringCount):
577 cffStandardStringMapping[cffStandardStrings[_i]] = _i
578
579