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