blob: 4c7243856fade0e9bfa2c9b8d6c90358c98f6e57 [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#
jvr4756b3a2002-05-16 18:17:32 +00004# $Id: cffLib.py,v 1.13 2002-05-16 18:17:32 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):
jvr4756b3a2002-05-16 18:17:32 +000023 pass
Just7842e561999-12-16 21:34:53 +000024
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
jvr4756b3a2002-05-16 18:17:32 +000030 self.fontNames = list(Index(file))
31 self.topDictIndex = TopDictIndex(file)
32 self.strings = IndexedStrings(list(Index(file)))
33 self.GlobalSubrs = SubrsIndex(file)
34 self.topDictIndex.strings = self.strings
35
36 def __len__(self):
37 return len(self.fontNames)
38
39 def keys(self):
40 return self.fontNames[:]
41
42 def __getitem__(self, name):
43 try:
44 index = self.fontNames.index(name)
45 except ValueError:
46 raise KeyError, name
47 font = self.topDictIndex[index]
48 if not hasattr(font, "GlobalSubrs"):
jvrecf5a792002-05-14 13:51:51 +000049 font.GlobalSubrs = self.GlobalSubrs
jvr4756b3a2002-05-16 18:17:32 +000050 return font
Just7842e561999-12-16 21:34:53 +000051
52 def compile(self):
53 strings = IndexedStrings()
54 XXXX
55
56 def toXML(self, xmlWriter, progress=None):
57 xmlWriter.newline()
58 for fontName in self.fontNames:
59 xmlWriter.begintag("CFFFont", name=fontName)
60 xmlWriter.newline()
jvr4756b3a2002-05-16 18:17:32 +000061 font = self[fontName]
Just7842e561999-12-16 21:34:53 +000062 font.toXML(xmlWriter, progress)
63 xmlWriter.endtag("CFFFont")
64 xmlWriter.newline()
65 xmlWriter.newline()
66 xmlWriter.begintag("GlobalSubrs")
67 xmlWriter.newline()
jvr4756b3a2002-05-16 18:17:32 +000068 self.GlobalSubrs.toXML(xmlWriter, progress)
Just7842e561999-12-16 21:34:53 +000069 xmlWriter.endtag("GlobalSubrs")
70 xmlWriter.newline()
71 xmlWriter.newline()
72
73 def fromXML(self, (name, attrs, content)):
74 xxx
75
76
jvr4756b3a2002-05-16 18:17:32 +000077class Index:
Just7842e561999-12-16 21:34:53 +000078
jvr4756b3a2002-05-16 18:17:32 +000079 """This class represents what the CFF spec calls an INDEX."""
Just7842e561999-12-16 21:34:53 +000080
jvr4756b3a2002-05-16 18:17:32 +000081 def __init__(self, file):
82 self.file = file
83 count, = struct.unpack(">H", file.read(2))
84 self.count = count
85 self.items = [None] * count
86 if count == 0:
87 self.offsets = []
88 return
89 offSize = ord(file.read(1))
90 self.offsets = offsets = []
91 pad = '\0' * (4 - offSize)
92 for index in range(count+1):
93 chunk = file.read(offSize)
94 chunk = pad + chunk
95 offset, = struct.unpack(">L", chunk)
96 offsets.append(int(offset))
97 self.offsetBase = file.tell() - 1
98 file.seek(self.offsetBase + offsets[-1]) # pretend we've read the whole lot
Just7842e561999-12-16 21:34:53 +000099
jvr4756b3a2002-05-16 18:17:32 +0000100 def __len__(self):
101 return self.count
jvra2a75b32002-05-13 11:25:17 +0000102
jvr4756b3a2002-05-16 18:17:32 +0000103 def __getitem__(self, index):
104 item = self.items[index]
105 if item is not None:
106 return item
107 offset = self.offsets[index] + self.offsetBase
108 size = self.offsets[index+1] - self.offsets[index]
109 file = self.file
110 file.seek(offset)
111 data = file.read(size)
112 assert len(data) == size
113 item = self.produceItem(data, file, offset, size)
114 self.items[index] = item
115 return item
116
117 def produceItem(self, data, file, offset, size):
118 return data
119
120
121class SubrsIndex(Index):
122
123 def produceItem(self, data, file, offset, size):
124 return psCharStrings.T2CharString(data)
125
126 def toXML(self, xmlWriter, progress):
127 for i in range(len(self)):
128 xmlWriter.begintag("CharString", index=i)
129 xmlWriter.newline()
130 self[i].toXML(xmlWriter)
131 xmlWriter.endtag("CharString")
132 xmlWriter.newline()
133
134
135class CharStrings:
136
137 def __init__(self, file, charset):
138 self.charStringsIndex = SubrsIndex(file)
139 self.nameToIndex = nameToIndex = {}
140 for i in range(len(charset)):
141 nameToIndex[charset[i]] = i
142
143 def keys(self):
144 return self.nameToIndex.keys()
145
146 def has_key(self, name):
147 return self.nameToIndex.has_key(name)
148
149 def __getitem__(self, name):
150 index = self.nameToIndex[name]
151 return self.charStringsIndex[index]
152
153 def toXML(self, xmlWriter, progress):
154 names = self.keys()
155 names.sort()
156 for name in names:
157 xmlWriter.begintag("CharString", name=name)
158 xmlWriter.newline()
159 self[name].toXML(xmlWriter)
160 xmlWriter.endtag("CharString")
161 xmlWriter.newline()
162
163
164class TopDictIndex(Index):
165 def produceItem(self, data, file, offset, size):
166 top = TopDict(self.strings, file, offset)
167 top.decompile(data)
168 return top
169
170
171def buildOperatorDict(table):
172 d = {}
173 for op, name, arg, default, conv in table:
174 d[op] = (name, arg)
175 return d
176
177def buildOrder(table):
178 l = []
179 for op, name, arg, default, conv in table:
180 l.append(name)
181 return l
182
183def buildDefaults(table):
184 d = {}
185 for op, name, arg, default, conv in table:
186 if default is not None:
187 d[name] = default
188 return d
189
190def buildConverters(table):
191 d = {}
192 for op, name, arg, default, conv in table:
193 d[name] = conv
194 return d
195
196
197class PrivateDictConverter:
198 def read(self, parent, value):
199 size, offset = value
200 file = parent.file
201 pr = PrivateDict(parent.strings, file, offset)
202 file.seek(offset)
203 data = file.read(size)
204 len(data) == size
205 pr.decompile(data)
206 return pr
207 def xmlWrite(self, xmlWriter, name, value):
208 xmlWriter.begintag(name)
209 xmlWriter.newline()
210 value.toXML(xmlWriter, None)
211 xmlWriter.endtag(name)
212 xmlWriter.newline()
213
214class SubrsConverter(PrivateDictConverter):
215 def read(self, parent, value):
216 file = parent.file
217 file.seek(parent.offset + value) # Offset(self)
218 return SubrsIndex(file)
219
220class CharStringsConverter(PrivateDictConverter):
221 def read(self, parent, value):
222 file = parent.file
223 file.seek(value) # Offset(0)
224 return CharStrings(file, parent.charset)
225
226class CharsetConverter:
227 def read(self, parent, value):
228 isCID = hasattr(parent, "ROS")
229 if value > 2:
230 numGlyphs = parent.numGlyphs
231 file = parent.file
232 file.seek(value)
jvra2a75b32002-05-13 11:25:17 +0000233 format = ord(file.read(1))
Just7842e561999-12-16 21:34:53 +0000234 if format == 0:
jvr1890b952002-05-15 07:41:30 +0000235 raise NotImplementedError
jvr4756b3a2002-05-16 18:17:32 +0000236 elif format == 1 or format == 2:
237 charset = parseCharset(numGlyphs, file, parent.strings, isCID, format)
Just7842e561999-12-16 21:34:53 +0000238 elif format == 3:
jvr1890b952002-05-15 07:41:30 +0000239 raise NotImplementedError
Just7842e561999-12-16 21:34:53 +0000240 else:
jvr1890b952002-05-15 07:41:30 +0000241 raise NotImplementedError
jvr4756b3a2002-05-16 18:17:32 +0000242 assert len(charset) == numGlyphs
jvr1890b952002-05-15 07:41:30 +0000243 else:
jvr4756b3a2002-05-16 18:17:32 +0000244 if isCID:
245 assert value == 0
246 charset = None
247 elif value == 0:
248 charset = ISOAdobe
249 elif value == 1:
250 charset = Expert
251 elif value == 2:
252 charset = ExpertSubset
jvr1890b952002-05-15 07:41:30 +0000253 # self.charset:
254 # 0: ISOAdobe (or CID font!)
255 # 1: Expert
256 # 2: ExpertSubset
jvr4756b3a2002-05-16 18:17:32 +0000257 charset = None #
258 return charset
259 def xmlWrite(self, xmlWriter, name, value):
260 # XXX GlyphOrder needs to be stored *somewhere*, but not here...
261 xmlWriter.simpletag("charset", value=value)
262 xmlWriter.newline()
263
264
265def parseCharset(numGlyphs, file, strings, isCID, format):
266 charset = ['.notdef']
267 count = 1
268 if format == 1:
269 def nLeftFunc(file):
270 return ord(file.read(1))
271 else:
272 def nLeftFunc(file):
273 return struct.unpack(">H", file.read(2))[0]
274 while count < numGlyphs:
275 first, = struct.unpack(">H", file.read(2))
276 nLeft = nLeftFunc(file)
277 if isCID:
278 for CID in range(first, first+nLeft+1):
279 charset.append(CID)
jvr1890b952002-05-15 07:41:30 +0000280 else:
jvr4756b3a2002-05-16 18:17:32 +0000281 for SID in range(first, first+nLeft+1):
282 charset.append(strings[SID])
283 count = count + nLeft + 1
284 return charset
285
286
287topDictOperators = [
288# opcode name argument type default converter
289 (0, 'version', 'SID', None, None),
290 (1, 'Notice', 'SID', None, None),
291 ((12, 0), 'Copyright', 'SID', None, None),
292 (2, 'FullName', 'SID', None, None),
293 (3, 'FamilyName', 'SID', None, None),
294 (4, 'Weight', 'SID', None, None),
295 ((12, 1), 'isFixedPitch', 'number', 0, None),
296 ((12, 2), 'ItalicAngle', 'number', 0, None),
297 ((12, 3), 'UnderlinePosition', 'number', None, None),
298 ((12, 4), 'UnderlineThickness', 'number', 50, None),
299 ((12, 5), 'PaintType', 'number', 0, None),
300 ((12, 6), 'CharstringType', 'number', 2, None),
301 ((12, 7), 'FontMatrix', 'array', [0.001,0,0,0.001,0,0], None),
302 (13, 'UniqueID', 'number', None, None),
303 (5, 'FontBBox', 'array', [0,0,0,0], None),
304 ((12, 8), 'StrokeWidth', 'number', 0, None),
305 (14, 'XUID', 'array', None, None),
306 (15, 'charset', 'number', 0, CharsetConverter()),
307 (16, 'Encoding', 'number', 0, None),
308 (18, 'Private', ('number','number'), None, PrivateDictConverter()),
309 (17, 'CharStrings', 'number', None, CharStringsConverter()), # XXX
310 ((12, 20), 'SyntheticBase', 'number', None, None),
311 ((12, 21), 'PostScript', 'SID', None, None),
312 ((12, 22), 'BaseFontName', 'SID', None, None),
313 ((12, 23), 'BaseFontBlend', 'delta', None, None),
314 ((12, 30), 'ROS', ('SID','SID','number'), None, None),
315 ((12, 31), 'CIDFontVersion', 'number', 0, None),
316 ((12, 32), 'CIDFontRevision', 'number', 0, None),
317 ((12, 33), 'CIDFontType', 'number', 0, None),
318 ((12, 34), 'CIDCount', 'number', 8720, None),
319 ((12, 35), 'UIDBase', 'number', None, None),
320 ((12, 36), 'FDArray', 'number', None, None),
321 ((12, 37), 'FDSelect', 'number', None, None),
322 ((12, 38), 'FontName', 'SID', None, None),
323]
324
325privateDictOperators = [
326# opcode name argument type default converter
327 (6, 'BlueValues', 'delta', None, None),
328 (7, 'OtherBlues', 'delta', None, None),
329 (8, 'FamilyBlues', 'delta', None, None),
330 (9, 'FamilyOtherBlues', 'delta', None, None),
331 ((12, 9), 'BlueScale', 'number', 0.039625, None),
332 ((12, 10), 'BlueShift', 'number', 7, None),
333 ((12, 11), 'BlueFuzz', 'number', 1, None),
334 (10, 'StdHW', 'number', None, None),
335 (11, 'StdVW', 'number', None, None),
336 ((12, 12), 'StemSnapH', 'delta', None, None),
337 ((12, 13), 'StemSnapV', 'delta', None, None),
338 ((12, 14), 'ForceBold', 'number', 0, None),
339 ((12, 17), 'LanguageGroup', 'number', 0, None),
340 ((12, 18), 'ExpansionFactor', 'number', 0.06, None),
341 ((12, 19), 'initialRandomSeed', 'number', 0, None),
342 (20, 'defaultWidthX', 'number', 0, None),
343 (21, 'nominalWidthX', 'number', 0, None),
344 (19, 'Subrs', 'number', None, SubrsConverter()),
345]
346
347
348class TopDictDecompiler(psCharStrings.DictDecompiler):
349 operators = buildOperatorDict(topDictOperators)
350
351
352class PrivateDictDecompiler(psCharStrings.DictDecompiler):
353 operators = buildOperatorDict(privateDictOperators)
354
355
356
357class BaseDict:
358
359 def __init__(self, strings, file, offset):
360 self.rawDict = {}
361 self.file = file
362 self.offset = offset
363 self.strings = strings
364
365 def decompile(self, data):
366 dec = self.decompiler(self.strings)
367 dec.decompile(data)
368 self.rawDict = dec.getDict()
369 self.postDecompile()
370
371 def postDecompile(self):
372 pass
373
374 def __getattr__(self, name):
375 value = self.rawDict.get(name)
376 if value is None:
377 value = self.defaults.get(name)
378 if value is None:
379 raise AttributeError, name
380 conv = self.converters[name]
381 if conv is not None:
382 value = conv.read(self, value)
383 setattr(self, name, value)
384 return value
385
386 def toXML(self, xmlWriter, progress):
387 for name in self.order:
388 value = getattr(self, name, None)
389 if value is None:
390 continue
391 conv = self.converters.get(name)
392 if conv is not None:
393 conv.xmlWrite(xmlWriter, name, value)
394 else:
395 if isinstance(value, types.ListType):
396 value = " ".join(map(str, value))
397 xmlWriter.simpletag(name, value=value)
398 xmlWriter.newline()
399
400
401class TopDict(BaseDict):
402
403 defaults = buildDefaults(topDictOperators)
404 converters = buildConverters(topDictOperators)
405 order = buildOrder(topDictOperators)
406 decompiler = TopDictDecompiler
Just7842e561999-12-16 21:34:53 +0000407
408 def getGlyphOrder(self):
409 return self.charset
410
jvr4756b3a2002-05-16 18:17:32 +0000411 def postDecompile(self):
412 offset = self.rawDict.get("CharStrings")
413 if offset is None:
414 return
415 # get the number of glyphs beforehand.
416 self.file.seek(offset)
417 self.numGlyphs, = struct.unpack(">H", self.file.read(2))
Just7842e561999-12-16 21:34:53 +0000418
419 def decompileAllCharStrings(self):
420 if self.CharstringType == 2:
421 # Type 2 CharStrings
422 decompiler = psCharStrings.SimpleT2Decompiler(self.Private.Subrs, self.GlobalSubrs)
423 for charString in self.CharStrings.values():
424 if charString.needsDecompilation():
425 decompiler.reset()
426 decompiler.execute(charString)
427 else:
428 # Type 1 CharStrings
429 for charString in self.CharStrings.values():
430 charString.decompile()
Just7842e561999-12-16 21:34:53 +0000431
432
jvr4756b3a2002-05-16 18:17:32 +0000433class PrivateDict(BaseDict):
434 defaults = buildDefaults(privateDictOperators)
435 converters = buildConverters(privateDictOperators)
436 order = buildOrder(privateDictOperators)
437 decompiler = PrivateDictDecompiler
Just7842e561999-12-16 21:34:53 +0000438
439
Just7842e561999-12-16 21:34:53 +0000440
jvr4756b3a2002-05-16 18:17:32 +0000441# SID
jvre3275582002-05-14 12:22:03 +0000442
443class IndexedStrings:
444
445 def __init__(self, strings=None):
446 if strings is None:
447 strings = []
448 self.strings = strings
449
450 def __getitem__(self, SID):
451 if SID < cffStandardStringCount:
452 return cffStandardStrings[SID]
453 else:
454 return self.strings[SID - cffStandardStringCount]
455
456 def getSID(self, s):
457 if not hasattr(self, "stringMapping"):
458 self.buildStringMapping()
459 if cffStandardStringMapping.has_key(s):
460 SID = cffStandardStringMapping[s]
461 if self.stringMapping.has_key(s):
462 SID = self.stringMapping[s]
463 else:
464 SID = len(self.strings) + cffStandardStringCount
465 self.strings.append(s)
466 self.stringMapping[s] = SID
467 return SID
468
469 def getStrings(self):
470 return self.strings
471
472 def buildStringMapping(self):
473 self.stringMapping = {}
474 for index in range(len(self.strings)):
475 self.stringMapping[self.strings[index]] = index + cffStandardStringCount
476
477
Just7842e561999-12-16 21:34:53 +0000478# The 391 Standard Strings as used in the CFF format.
479# from Adobe Technical None #5176, version 1.0, 18 March 1998
480
481cffStandardStrings = ['.notdef', 'space', 'exclam', 'quotedbl', 'numbersign',
482 'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright',
483 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one',
484 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon',
485 'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C',
486 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
487 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash',
488 'bracketright', 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c',
489 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
490 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright',
491 'asciitilde', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin',
492 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft',
493 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger',
494 'daggerdbl', 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase',
495 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand',
496 'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve',
497 'dotaccent', 'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron',
498 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae',
499 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior',
500 'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn',
501 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters',
502 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior',
503 'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring',
504 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave',
505 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute',
506 'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute',
507 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron',
508 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla',
509 'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex',
510 'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis',
511 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave',
512 'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall',
513 'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall',
514 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader',
515 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle',
516 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle',
517 'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior',
518 'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior',
519 'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior',
520 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', 'parenleftinferior',
521 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall',
522 'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall',
523 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall',
524 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall',
525 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall',
526 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall',
527 'Dieresissmall', 'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall',
528 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall',
529 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths',
530 'onethird', 'twothirds', 'zerosuperior', 'foursuperior', 'fivesuperior',
531 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior',
532 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior',
533 'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior',
534 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall',
535 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall',
536 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall',
537 'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall',
538 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall',
539 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall',
540 'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall',
541 'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', '001.001', '001.002',
542 '001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman',
543 'Semibold'
544]
545
546cffStandardStringCount = 391
547assert len(cffStandardStrings) == cffStandardStringCount
548# build reverse mapping
549cffStandardStringMapping = {}
550for _i in range(cffStandardStringCount):
551 cffStandardStringMapping[cffStandardStrings[_i]] = _i
552
553