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