1"""cffLib: read/write Adobe CFF fonts 2 3OpenType fonts with PostScript outlines contain a completely independent 4font file, Adobe's *Compact Font Format*. So dealing with OpenType fonts 5requires also dealing with CFF. This module allows you to read and write 6fonts written in the CFF format. 7 8In 2016, OpenType 1.8 introduced the `CFF2 <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2>`_ 9format which, along with other changes, extended the CFF format to deal with 10the demands of variable fonts. This module parses both original CFF and CFF2. 11 12""" 13 14from fontTools.misc import sstruct 15from fontTools.misc import psCharStrings 16from fontTools.misc.arrayTools import unionRect, intRect 17from fontTools.misc.textTools import ( 18 bytechr, 19 byteord, 20 bytesjoin, 21 tobytes, 22 tostr, 23 safeEval, 24) 25from fontTools.ttLib import TTFont 26from fontTools.ttLib.tables.otBase import OTTableWriter 27from fontTools.ttLib.tables.otBase import OTTableReader 28from fontTools.ttLib.tables import otTables as ot 29from io import BytesIO 30import struct 31import logging 32import re 33 34# mute cffLib debug messages when running ttx in verbose mode 35DEBUG = logging.DEBUG - 1 36log = logging.getLogger(__name__) 37 38cffHeaderFormat = """ 39 major: B 40 minor: B 41 hdrSize: B 42""" 43 44maxStackLimit = 513 45# maxstack operator has been deprecated. max stack is now always 513. 46 47 48class StopHintCountEvent(Exception): 49 pass 50 51 52class _DesubroutinizingT2Decompiler(psCharStrings.SimpleT2Decompiler): 53 stop_hintcount_ops = ( 54 "op_hintmask", 55 "op_cntrmask", 56 "op_rmoveto", 57 "op_hmoveto", 58 "op_vmoveto", 59 ) 60 61 def __init__(self, localSubrs, globalSubrs, private=None): 62 psCharStrings.SimpleT2Decompiler.__init__( 63 self, localSubrs, globalSubrs, private 64 ) 65 66 def execute(self, charString): 67 self.need_hintcount = True # until proven otherwise 68 for op_name in self.stop_hintcount_ops: 69 setattr(self, op_name, self.stop_hint_count) 70 71 if hasattr(charString, "_desubroutinized"): 72 # If a charstring has already been desubroutinized, we will still 73 # need to execute it if we need to count hints in order to 74 # compute the byte length for mask arguments, and haven't finished 75 # counting hints pairs. 76 if self.need_hintcount and self.callingStack: 77 try: 78 psCharStrings.SimpleT2Decompiler.execute(self, charString) 79 except StopHintCountEvent: 80 del self.callingStack[-1] 81 return 82 83 charString._patches = [] 84 psCharStrings.SimpleT2Decompiler.execute(self, charString) 85 desubroutinized = charString.program[:] 86 for idx, expansion in reversed(charString._patches): 87 assert idx >= 2 88 assert desubroutinized[idx - 1] in [ 89 "callsubr", 90 "callgsubr", 91 ], desubroutinized[idx - 1] 92 assert type(desubroutinized[idx - 2]) == int 93 if expansion[-1] == "return": 94 expansion = expansion[:-1] 95 desubroutinized[idx - 2 : idx] = expansion 96 if not self.private.in_cff2: 97 if "endchar" in desubroutinized: 98 # Cut off after first endchar 99 desubroutinized = desubroutinized[ 100 : desubroutinized.index("endchar") + 1 101 ] 102 else: 103 if not len(desubroutinized) or desubroutinized[-1] != "return": 104 desubroutinized.append("return") 105 106 charString._desubroutinized = desubroutinized 107 del charString._patches 108 109 def op_callsubr(self, index): 110 subr = self.localSubrs[self.operandStack[-1] + self.localBias] 111 psCharStrings.SimpleT2Decompiler.op_callsubr(self, index) 112 self.processSubr(index, subr) 113 114 def op_callgsubr(self, index): 115 subr = self.globalSubrs[self.operandStack[-1] + self.globalBias] 116 psCharStrings.SimpleT2Decompiler.op_callgsubr(self, index) 117 self.processSubr(index, subr) 118 119 def stop_hint_count(self, *args): 120 self.need_hintcount = False 121 for op_name in self.stop_hintcount_ops: 122 setattr(self, op_name, None) 123 cs = self.callingStack[-1] 124 if hasattr(cs, "_desubroutinized"): 125 raise StopHintCountEvent() 126 127 def op_hintmask(self, index): 128 psCharStrings.SimpleT2Decompiler.op_hintmask(self, index) 129 if self.need_hintcount: 130 self.stop_hint_count() 131 132 def processSubr(self, index, subr): 133 cs = self.callingStack[-1] 134 if not hasattr(cs, "_desubroutinized"): 135 cs._patches.append((index, subr._desubroutinized)) 136 137 138class CFFFontSet(object): 139 """A CFF font "file" can contain more than one font, although this is 140 extremely rare (and not allowed within OpenType fonts). 141 142 This class is the entry point for parsing a CFF table. To actually 143 manipulate the data inside the CFF font, you will want to access the 144 ``CFFFontSet``'s :class:`TopDict` object. To do this, a ``CFFFontSet`` 145 object can either be treated as a dictionary (with appropriate 146 ``keys()`` and ``values()`` methods) mapping font names to :class:`TopDict` 147 objects, or as a list. 148 149 .. code:: python 150 151 from fontTools import ttLib 152 tt = ttLib.TTFont("Tests/cffLib/data/LinLibertine_RBI.otf") 153 tt["CFF "].cff 154 # <fontTools.cffLib.CFFFontSet object at 0x101e24c90> 155 tt["CFF "].cff[0] # Here's your actual font data 156 # <fontTools.cffLib.TopDict object at 0x1020f1fd0> 157 158 """ 159 160 def decompile(self, file, otFont, isCFF2=None): 161 """Parse a binary CFF file into an internal representation. ``file`` 162 should be a file handle object. ``otFont`` is the top-level 163 :py:class:`fontTools.ttLib.ttFont.TTFont` object containing this CFF file. 164 165 If ``isCFF2`` is passed and set to ``True`` or ``False``, then the 166 library makes an assertion that the CFF header is of the appropriate 167 version. 168 """ 169 170 self.otFont = otFont 171 sstruct.unpack(cffHeaderFormat, file.read(3), self) 172 if isCFF2 is not None: 173 # called from ttLib: assert 'major' as read from file matches the 174 # expected version 175 expected_major = 2 if isCFF2 else 1 176 if self.major != expected_major: 177 raise ValueError( 178 "Invalid CFF 'major' version: expected %d, found %d" 179 % (expected_major, self.major) 180 ) 181 else: 182 # use 'major' version from file to determine if isCFF2 183 assert self.major in (1, 2), "Unknown CFF format" 184 isCFF2 = self.major == 2 185 if not isCFF2: 186 self.offSize = struct.unpack("B", file.read(1))[0] 187 file.seek(self.hdrSize) 188 self.fontNames = list(tostr(s) for s in Index(file, isCFF2=isCFF2)) 189 self.topDictIndex = TopDictIndex(file, isCFF2=isCFF2) 190 self.strings = IndexedStrings(file) 191 else: # isCFF2 192 self.topDictSize = struct.unpack(">H", file.read(2))[0] 193 file.seek(self.hdrSize) 194 self.fontNames = ["CFF2Font"] 195 cff2GetGlyphOrder = otFont.getGlyphOrder 196 # in CFF2, offsetSize is the size of the TopDict data. 197 self.topDictIndex = TopDictIndex( 198 file, cff2GetGlyphOrder, self.topDictSize, isCFF2=isCFF2 199 ) 200 self.strings = None 201 self.GlobalSubrs = GlobalSubrsIndex(file, isCFF2=isCFF2) 202 self.topDictIndex.strings = self.strings 203 self.topDictIndex.GlobalSubrs = self.GlobalSubrs 204 205 def __len__(self): 206 return len(self.fontNames) 207 208 def keys(self): 209 return list(self.fontNames) 210 211 def values(self): 212 return self.topDictIndex 213 214 def __getitem__(self, nameOrIndex): 215 """Return TopDict instance identified by name (str) or index (int 216 or any object that implements `__index__`). 217 """ 218 if hasattr(nameOrIndex, "__index__"): 219 index = nameOrIndex.__index__() 220 elif isinstance(nameOrIndex, str): 221 name = nameOrIndex 222 try: 223 index = self.fontNames.index(name) 224 except ValueError: 225 raise KeyError(nameOrIndex) 226 else: 227 raise TypeError(nameOrIndex) 228 return self.topDictIndex[index] 229 230 def compile(self, file, otFont, isCFF2=None): 231 """Write the object back into binary representation onto the given file. 232 ``file`` should be a file handle object. ``otFont`` is the top-level 233 :py:class:`fontTools.ttLib.ttFont.TTFont` object containing this CFF file. 234 235 If ``isCFF2`` is passed and set to ``True`` or ``False``, then the 236 library makes an assertion that the CFF header is of the appropriate 237 version. 238 """ 239 self.otFont = otFont 240 if isCFF2 is not None: 241 # called from ttLib: assert 'major' value matches expected version 242 expected_major = 2 if isCFF2 else 1 243 if self.major != expected_major: 244 raise ValueError( 245 "Invalid CFF 'major' version: expected %d, found %d" 246 % (expected_major, self.major) 247 ) 248 else: 249 # use current 'major' value to determine output format 250 assert self.major in (1, 2), "Unknown CFF format" 251 isCFF2 = self.major == 2 252 253 if otFont.recalcBBoxes and not isCFF2: 254 for topDict in self.topDictIndex: 255 topDict.recalcFontBBox() 256 257 if not isCFF2: 258 strings = IndexedStrings() 259 else: 260 strings = None 261 writer = CFFWriter(isCFF2) 262 topCompiler = self.topDictIndex.getCompiler(strings, self, isCFF2=isCFF2) 263 if isCFF2: 264 self.hdrSize = 5 265 writer.add(sstruct.pack(cffHeaderFormat, self)) 266 # Note: topDictSize will most likely change in CFFWriter.toFile(). 267 self.topDictSize = topCompiler.getDataLength() 268 writer.add(struct.pack(">H", self.topDictSize)) 269 else: 270 self.hdrSize = 4 271 self.offSize = 4 # will most likely change in CFFWriter.toFile(). 272 writer.add(sstruct.pack(cffHeaderFormat, self)) 273 writer.add(struct.pack("B", self.offSize)) 274 if not isCFF2: 275 fontNames = Index() 276 for name in self.fontNames: 277 fontNames.append(name) 278 writer.add(fontNames.getCompiler(strings, self, isCFF2=isCFF2)) 279 writer.add(topCompiler) 280 if not isCFF2: 281 writer.add(strings.getCompiler()) 282 writer.add(self.GlobalSubrs.getCompiler(strings, self, isCFF2=isCFF2)) 283 284 for topDict in self.topDictIndex: 285 if not hasattr(topDict, "charset") or topDict.charset is None: 286 charset = otFont.getGlyphOrder() 287 topDict.charset = charset 288 children = topCompiler.getChildren(strings) 289 for child in children: 290 writer.add(child) 291 292 writer.toFile(file) 293 294 def toXML(self, xmlWriter): 295 """Write the object into XML representation onto the given 296 :class:`fontTools.misc.xmlWriter.XMLWriter`. 297 298 .. code:: python 299 300 writer = xmlWriter.XMLWriter(sys.stdout) 301 tt["CFF "].cff.toXML(writer) 302 303 """ 304 305 xmlWriter.simpletag("major", value=self.major) 306 xmlWriter.newline() 307 xmlWriter.simpletag("minor", value=self.minor) 308 xmlWriter.newline() 309 for fontName in self.fontNames: 310 xmlWriter.begintag("CFFFont", name=tostr(fontName)) 311 xmlWriter.newline() 312 font = self[fontName] 313 font.toXML(xmlWriter) 314 xmlWriter.endtag("CFFFont") 315 xmlWriter.newline() 316 xmlWriter.newline() 317 xmlWriter.begintag("GlobalSubrs") 318 xmlWriter.newline() 319 self.GlobalSubrs.toXML(xmlWriter) 320 xmlWriter.endtag("GlobalSubrs") 321 xmlWriter.newline() 322 323 def fromXML(self, name, attrs, content, otFont=None): 324 """Reads data from the XML element into the ``CFFFontSet`` object.""" 325 self.otFont = otFont 326 327 # set defaults. These will be replaced if there are entries for them 328 # in the XML file. 329 if not hasattr(self, "major"): 330 self.major = 1 331 if not hasattr(self, "minor"): 332 self.minor = 0 333 334 if name == "CFFFont": 335 if self.major == 1: 336 if not hasattr(self, "offSize"): 337 # this will be recalculated when the cff is compiled. 338 self.offSize = 4 339 if not hasattr(self, "hdrSize"): 340 self.hdrSize = 4 341 if not hasattr(self, "GlobalSubrs"): 342 self.GlobalSubrs = GlobalSubrsIndex() 343 if not hasattr(self, "fontNames"): 344 self.fontNames = [] 345 self.topDictIndex = TopDictIndex() 346 fontName = attrs["name"] 347 self.fontNames.append(fontName) 348 topDict = TopDict(GlobalSubrs=self.GlobalSubrs) 349 topDict.charset = None # gets filled in later 350 elif self.major == 2: 351 if not hasattr(self, "hdrSize"): 352 self.hdrSize = 5 353 if not hasattr(self, "GlobalSubrs"): 354 self.GlobalSubrs = GlobalSubrsIndex() 355 if not hasattr(self, "fontNames"): 356 self.fontNames = ["CFF2Font"] 357 cff2GetGlyphOrder = self.otFont.getGlyphOrder 358 topDict = TopDict( 359 GlobalSubrs=self.GlobalSubrs, cff2GetGlyphOrder=cff2GetGlyphOrder 360 ) 361 self.topDictIndex = TopDictIndex(None, cff2GetGlyphOrder) 362 self.topDictIndex.append(topDict) 363 for element in content: 364 if isinstance(element, str): 365 continue 366 name, attrs, content = element 367 topDict.fromXML(name, attrs, content) 368 369 if hasattr(topDict, "VarStore") and topDict.FDArray[0].vstore is None: 370 fdArray = topDict.FDArray 371 for fontDict in fdArray: 372 if hasattr(fontDict, "Private"): 373 fontDict.Private.vstore = topDict.VarStore 374 375 elif name == "GlobalSubrs": 376 subrCharStringClass = psCharStrings.T2CharString 377 if not hasattr(self, "GlobalSubrs"): 378 self.GlobalSubrs = GlobalSubrsIndex() 379 for element in content: 380 if isinstance(element, str): 381 continue 382 name, attrs, content = element 383 subr = subrCharStringClass() 384 subr.fromXML(name, attrs, content) 385 self.GlobalSubrs.append(subr) 386 elif name == "major": 387 self.major = int(attrs["value"]) 388 elif name == "minor": 389 self.minor = int(attrs["value"]) 390 391 def convertCFFToCFF2(self, otFont): 392 """Converts this object from CFF format to CFF2 format. This conversion 393 is done 'in-place'. The conversion cannot be reversed. 394 395 This assumes a decompiled CFF table. (i.e. that the object has been 396 filled via :meth:`decompile`.)""" 397 self.major = 2 398 cff2GetGlyphOrder = self.otFont.getGlyphOrder 399 topDictData = TopDictIndex(None, cff2GetGlyphOrder) 400 topDictData.items = self.topDictIndex.items 401 self.topDictIndex = topDictData 402 topDict = topDictData[0] 403 if hasattr(topDict, "Private"): 404 privateDict = topDict.Private 405 else: 406 privateDict = None 407 opOrder = buildOrder(topDictOperators2) 408 topDict.order = opOrder 409 topDict.cff2GetGlyphOrder = cff2GetGlyphOrder 410 for entry in topDictOperators: 411 key = entry[1] 412 if key not in opOrder: 413 if key in topDict.rawDict: 414 del topDict.rawDict[key] 415 if hasattr(topDict, key): 416 delattr(topDict, key) 417 418 if not hasattr(topDict, "FDArray"): 419 fdArray = topDict.FDArray = FDArrayIndex() 420 fdArray.strings = None 421 fdArray.GlobalSubrs = topDict.GlobalSubrs 422 topDict.GlobalSubrs.fdArray = fdArray 423 charStrings = topDict.CharStrings 424 if charStrings.charStringsAreIndexed: 425 charStrings.charStringsIndex.fdArray = fdArray 426 else: 427 charStrings.fdArray = fdArray 428 fontDict = FontDict() 429 fontDict.setCFF2(True) 430 fdArray.append(fontDict) 431 fontDict.Private = privateDict 432 privateOpOrder = buildOrder(privateDictOperators2) 433 for entry in privateDictOperators: 434 key = entry[1] 435 if key not in privateOpOrder: 436 if key in privateDict.rawDict: 437 # print "Removing private dict", key 438 del privateDict.rawDict[key] 439 if hasattr(privateDict, key): 440 delattr(privateDict, key) 441 # print "Removing privateDict attr", key 442 else: 443 # clean up the PrivateDicts in the fdArray 444 fdArray = topDict.FDArray 445 privateOpOrder = buildOrder(privateDictOperators2) 446 for fontDict in fdArray: 447 fontDict.setCFF2(True) 448 for key in fontDict.rawDict.keys(): 449 if key not in fontDict.order: 450 del fontDict.rawDict[key] 451 if hasattr(fontDict, key): 452 delattr(fontDict, key) 453 454 privateDict = fontDict.Private 455 for entry in privateDictOperators: 456 key = entry[1] 457 if key not in privateOpOrder: 458 if key in privateDict.rawDict: 459 # print "Removing private dict", key 460 del privateDict.rawDict[key] 461 if hasattr(privateDict, key): 462 delattr(privateDict, key) 463 # print "Removing privateDict attr", key 464 # At this point, the Subrs and Charstrings are all still T2Charstring class 465 # easiest to fix this by compiling, then decompiling again 466 file = BytesIO() 467 self.compile(file, otFont, isCFF2=True) 468 file.seek(0) 469 self.decompile(file, otFont, isCFF2=True) 470 471 def desubroutinize(self): 472 for fontName in self.fontNames: 473 font = self[fontName] 474 cs = font.CharStrings 475 for g in font.charset: 476 c, _ = cs.getItemAndSelector(g) 477 c.decompile() 478 subrs = getattr(c.private, "Subrs", []) 479 decompiler = _DesubroutinizingT2Decompiler( 480 subrs, c.globalSubrs, c.private 481 ) 482 decompiler.execute(c) 483 c.program = c._desubroutinized 484 del c._desubroutinized 485 # Delete all the local subrs 486 if hasattr(font, "FDArray"): 487 for fd in font.FDArray: 488 pd = fd.Private 489 if hasattr(pd, "Subrs"): 490 del pd.Subrs 491 if "Subrs" in pd.rawDict: 492 del pd.rawDict["Subrs"] 493 else: 494 pd = font.Private 495 if hasattr(pd, "Subrs"): 496 del pd.Subrs 497 if "Subrs" in pd.rawDict: 498 del pd.rawDict["Subrs"] 499 # as well as the global subrs 500 self.GlobalSubrs.clear() 501 502 503class CFFWriter(object): 504 """Helper class for serializing CFF data to binary. Used by 505 :meth:`CFFFontSet.compile`.""" 506 507 def __init__(self, isCFF2): 508 self.data = [] 509 self.isCFF2 = isCFF2 510 511 def add(self, table): 512 self.data.append(table) 513 514 def toFile(self, file): 515 lastPosList = None 516 count = 1 517 while True: 518 log.log(DEBUG, "CFFWriter.toFile() iteration: %d", count) 519 count = count + 1 520 pos = 0 521 posList = [pos] 522 for item in self.data: 523 if hasattr(item, "getDataLength"): 524 endPos = pos + item.getDataLength() 525 if isinstance(item, TopDictIndexCompiler) and item.isCFF2: 526 self.topDictSize = item.getDataLength() 527 else: 528 endPos = pos + len(item) 529 if hasattr(item, "setPos"): 530 item.setPos(pos, endPos) 531 pos = endPos 532 posList.append(pos) 533 if posList == lastPosList: 534 break 535 lastPosList = posList 536 log.log(DEBUG, "CFFWriter.toFile() writing to file.") 537 begin = file.tell() 538 if self.isCFF2: 539 self.data[1] = struct.pack(">H", self.topDictSize) 540 else: 541 self.offSize = calcOffSize(lastPosList[-1]) 542 self.data[1] = struct.pack("B", self.offSize) 543 posList = [0] 544 for item in self.data: 545 if hasattr(item, "toFile"): 546 item.toFile(file) 547 else: 548 file.write(item) 549 posList.append(file.tell() - begin) 550 assert posList == lastPosList 551 552 553def calcOffSize(largestOffset): 554 if largestOffset < 0x100: 555 offSize = 1 556 elif largestOffset < 0x10000: 557 offSize = 2 558 elif largestOffset < 0x1000000: 559 offSize = 3 560 else: 561 offSize = 4 562 return offSize 563 564 565class IndexCompiler(object): 566 """Base class for writing CFF `INDEX data <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#5-index-data>`_ 567 to binary.""" 568 569 def __init__(self, items, strings, parent, isCFF2=None): 570 if isCFF2 is None and hasattr(parent, "isCFF2"): 571 isCFF2 = parent.isCFF2 572 assert isCFF2 is not None 573 self.isCFF2 = isCFF2 574 self.items = self.getItems(items, strings) 575 self.parent = parent 576 577 def getItems(self, items, strings): 578 return items 579 580 def getOffsets(self): 581 # An empty INDEX contains only the count field. 582 if self.items: 583 pos = 1 584 offsets = [pos] 585 for item in self.items: 586 if hasattr(item, "getDataLength"): 587 pos = pos + item.getDataLength() 588 else: 589 pos = pos + len(item) 590 offsets.append(pos) 591 else: 592 offsets = [] 593 return offsets 594 595 def getDataLength(self): 596 if self.isCFF2: 597 countSize = 4 598 else: 599 countSize = 2 600 601 if self.items: 602 lastOffset = self.getOffsets()[-1] 603 offSize = calcOffSize(lastOffset) 604 dataLength = ( 605 countSize 606 + 1 # count 607 + (len(self.items) + 1) * offSize # offSize 608 + lastOffset # the offsets 609 - 1 # size of object data 610 ) 611 else: 612 # count. For empty INDEX tables, this is the only entry. 613 dataLength = countSize 614 615 return dataLength 616 617 def toFile(self, file): 618 offsets = self.getOffsets() 619 if self.isCFF2: 620 writeCard32(file, len(self.items)) 621 else: 622 writeCard16(file, len(self.items)) 623 # An empty INDEX contains only the count field. 624 if self.items: 625 offSize = calcOffSize(offsets[-1]) 626 writeCard8(file, offSize) 627 offSize = -offSize 628 pack = struct.pack 629 for offset in offsets: 630 binOffset = pack(">l", offset)[offSize:] 631 assert len(binOffset) == -offSize 632 file.write(binOffset) 633 for item in self.items: 634 if hasattr(item, "toFile"): 635 item.toFile(file) 636 else: 637 data = tobytes(item, encoding="latin1") 638 file.write(data) 639 640 641class IndexedStringsCompiler(IndexCompiler): 642 def getItems(self, items, strings): 643 return items.strings 644 645 646class TopDictIndexCompiler(IndexCompiler): 647 """Helper class for writing the TopDict to binary.""" 648 649 def getItems(self, items, strings): 650 out = [] 651 for item in items: 652 out.append(item.getCompiler(strings, self)) 653 return out 654 655 def getChildren(self, strings): 656 children = [] 657 for topDict in self.items: 658 children.extend(topDict.getChildren(strings)) 659 return children 660 661 def getOffsets(self): 662 if self.isCFF2: 663 offsets = [0, self.items[0].getDataLength()] 664 return offsets 665 else: 666 return super(TopDictIndexCompiler, self).getOffsets() 667 668 def getDataLength(self): 669 if self.isCFF2: 670 dataLength = self.items[0].getDataLength() 671 return dataLength 672 else: 673 return super(TopDictIndexCompiler, self).getDataLength() 674 675 def toFile(self, file): 676 if self.isCFF2: 677 self.items[0].toFile(file) 678 else: 679 super(TopDictIndexCompiler, self).toFile(file) 680 681 682class FDArrayIndexCompiler(IndexCompiler): 683 """Helper class for writing the 684 `Font DICT INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#10-font-dict-index-font-dicts-and-fdselect>`_ 685 to binary.""" 686 687 def getItems(self, items, strings): 688 out = [] 689 for item in items: 690 out.append(item.getCompiler(strings, self)) 691 return out 692 693 def getChildren(self, strings): 694 children = [] 695 for fontDict in self.items: 696 children.extend(fontDict.getChildren(strings)) 697 return children 698 699 def toFile(self, file): 700 offsets = self.getOffsets() 701 if self.isCFF2: 702 writeCard32(file, len(self.items)) 703 else: 704 writeCard16(file, len(self.items)) 705 offSize = calcOffSize(offsets[-1]) 706 writeCard8(file, offSize) 707 offSize = -offSize 708 pack = struct.pack 709 for offset in offsets: 710 binOffset = pack(">l", offset)[offSize:] 711 assert len(binOffset) == -offSize 712 file.write(binOffset) 713 for item in self.items: 714 if hasattr(item, "toFile"): 715 item.toFile(file) 716 else: 717 file.write(item) 718 719 def setPos(self, pos, endPos): 720 self.parent.rawDict["FDArray"] = pos 721 722 723class GlobalSubrsCompiler(IndexCompiler): 724 """Helper class for writing the `global subroutine INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#9-local-and-global-subr-indexes>`_ 725 to binary.""" 726 727 def getItems(self, items, strings): 728 out = [] 729 for cs in items: 730 cs.compile(self.isCFF2) 731 out.append(cs.bytecode) 732 return out 733 734 735class SubrsCompiler(GlobalSubrsCompiler): 736 """Helper class for writing the `local subroutine INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#9-local-and-global-subr-indexes>`_ 737 to binary.""" 738 739 def setPos(self, pos, endPos): 740 offset = pos - self.parent.pos 741 self.parent.rawDict["Subrs"] = offset 742 743 744class CharStringsCompiler(GlobalSubrsCompiler): 745 """Helper class for writing the `CharStrings INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#9-local-and-global-subr-indexes>`_ 746 to binary.""" 747 748 def getItems(self, items, strings): 749 out = [] 750 for cs in items: 751 cs.compile(self.isCFF2) 752 out.append(cs.bytecode) 753 return out 754 755 def setPos(self, pos, endPos): 756 self.parent.rawDict["CharStrings"] = pos 757 758 759class Index(object): 760 """This class represents what the CFF spec calls an INDEX (an array of 761 variable-sized objects). `Index` items can be addressed and set using 762 Python list indexing.""" 763 764 compilerClass = IndexCompiler 765 766 def __init__(self, file=None, isCFF2=None): 767 assert (isCFF2 is None) == (file is None) 768 self.items = [] 769 name = self.__class__.__name__ 770 if file is None: 771 return 772 self._isCFF2 = isCFF2 773 log.log(DEBUG, "loading %s at %s", name, file.tell()) 774 self.file = file 775 if isCFF2: 776 count = readCard32(file) 777 else: 778 count = readCard16(file) 779 if count == 0: 780 return 781 self.items = [None] * count 782 offSize = readCard8(file) 783 log.log(DEBUG, " index count: %s offSize: %s", count, offSize) 784 assert offSize <= 4, "offSize too large: %s" % offSize 785 self.offsets = offsets = [] 786 pad = b"\0" * (4 - offSize) 787 for index in range(count + 1): 788 chunk = file.read(offSize) 789 chunk = pad + chunk 790 (offset,) = struct.unpack(">L", chunk) 791 offsets.append(int(offset)) 792 self.offsetBase = file.tell() - 1 793 file.seek(self.offsetBase + offsets[-1]) # pretend we've read the whole lot 794 log.log(DEBUG, " end of %s at %s", name, file.tell()) 795 796 def __len__(self): 797 return len(self.items) 798 799 def __getitem__(self, index): 800 item = self.items[index] 801 if item is not None: 802 return item 803 offset = self.offsets[index] + self.offsetBase 804 size = self.offsets[index + 1] - self.offsets[index] 805 file = self.file 806 file.seek(offset) 807 data = file.read(size) 808 assert len(data) == size 809 item = self.produceItem(index, data, file, offset) 810 self.items[index] = item 811 return item 812 813 def __setitem__(self, index, item): 814 self.items[index] = item 815 816 def produceItem(self, index, data, file, offset): 817 return data 818 819 def append(self, item): 820 """Add an item to an INDEX.""" 821 self.items.append(item) 822 823 def getCompiler(self, strings, parent, isCFF2=None): 824 return self.compilerClass(self, strings, parent, isCFF2=isCFF2) 825 826 def clear(self): 827 """Empty the INDEX.""" 828 del self.items[:] 829 830 831class GlobalSubrsIndex(Index): 832 """This index contains all the global subroutines in the font. A global 833 subroutine is a set of ``CharString`` data which is accessible to any 834 glyph in the font, and are used to store repeated instructions - for 835 example, components may be encoded as global subroutines, but so could 836 hinting instructions. 837 838 Remember that when interpreting a ``callgsubr`` instruction (or indeed 839 a ``callsubr`` instruction) that you will need to add the "subroutine 840 number bias" to number given: 841 842 .. code:: python 843 844 tt = ttLib.TTFont("Almendra-Bold.otf") 845 u = tt["CFF "].cff[0].CharStrings["udieresis"] 846 u.decompile() 847 848 u.toXML(XMLWriter(sys.stdout)) 849 # <some stuff> 850 # -64 callgsubr <-- Subroutine which implements the dieresis mark 851 # <other stuff> 852 853 tt["CFF "].cff[0].GlobalSubrs[-64] # <-- WRONG 854 # <T2CharString (bytecode) at 103451d10> 855 856 tt["CFF "].cff[0].GlobalSubrs[-64 + 107] # <-- RIGHT 857 # <T2CharString (source) at 103451390> 858 859 ("The bias applied depends on the number of subrs (gsubrs). If the number of 860 subrs (gsubrs) is less than 1240, the bias is 107. Otherwise if it is less 861 than 33900, it is 1131; otherwise it is 32768.", 862 `Subroutine Operators <https://docs.microsoft.com/en-us/typography/opentype/otspec180/cff2charstr#section4.4>`) 863 """ 864 865 compilerClass = GlobalSubrsCompiler 866 subrClass = psCharStrings.T2CharString 867 charStringClass = psCharStrings.T2CharString 868 869 def __init__( 870 self, 871 file=None, 872 globalSubrs=None, 873 private=None, 874 fdSelect=None, 875 fdArray=None, 876 isCFF2=None, 877 ): 878 super(GlobalSubrsIndex, self).__init__(file, isCFF2=isCFF2) 879 self.globalSubrs = globalSubrs 880 self.private = private 881 if fdSelect: 882 self.fdSelect = fdSelect 883 if fdArray: 884 self.fdArray = fdArray 885 886 def produceItem(self, index, data, file, offset): 887 if self.private is not None: 888 private = self.private 889 elif hasattr(self, "fdArray") and self.fdArray is not None: 890 if hasattr(self, "fdSelect") and self.fdSelect is not None: 891 fdIndex = self.fdSelect[index] 892 else: 893 fdIndex = 0 894 private = self.fdArray[fdIndex].Private 895 else: 896 private = None 897 return self.subrClass(data, private=private, globalSubrs=self.globalSubrs) 898 899 def toXML(self, xmlWriter): 900 """Write the subroutines index into XML representation onto the given 901 :class:`fontTools.misc.xmlWriter.XMLWriter`. 902 903 .. code:: python 904 905 writer = xmlWriter.XMLWriter(sys.stdout) 906 tt["CFF "].cff[0].GlobalSubrs.toXML(writer) 907 908 """ 909 xmlWriter.comment( 910 "The 'index' attribute is only for humans; " "it is ignored when parsed." 911 ) 912 xmlWriter.newline() 913 for i in range(len(self)): 914 subr = self[i] 915 if subr.needsDecompilation(): 916 xmlWriter.begintag("CharString", index=i, raw=1) 917 else: 918 xmlWriter.begintag("CharString", index=i) 919 xmlWriter.newline() 920 subr.toXML(xmlWriter) 921 xmlWriter.endtag("CharString") 922 xmlWriter.newline() 923 924 def fromXML(self, name, attrs, content): 925 if name != "CharString": 926 return 927 subr = self.subrClass() 928 subr.fromXML(name, attrs, content) 929 self.append(subr) 930 931 def getItemAndSelector(self, index): 932 sel = None 933 if hasattr(self, "fdSelect"): 934 sel = self.fdSelect[index] 935 return self[index], sel 936 937 938class SubrsIndex(GlobalSubrsIndex): 939 """This index contains a glyph's local subroutines. A local subroutine is a 940 private set of ``CharString`` data which is accessible only to the glyph to 941 which the index is attached.""" 942 943 compilerClass = SubrsCompiler 944 945 946class TopDictIndex(Index): 947 """This index represents the array of ``TopDict`` structures in the font 948 (again, usually only one entry is present). Hence the following calls are 949 equivalent: 950 951 .. code:: python 952 953 tt["CFF "].cff[0] 954 # <fontTools.cffLib.TopDict object at 0x102ed6e50> 955 tt["CFF "].cff.topDictIndex[0] 956 # <fontTools.cffLib.TopDict object at 0x102ed6e50> 957 958 """ 959 960 compilerClass = TopDictIndexCompiler 961 962 def __init__(self, file=None, cff2GetGlyphOrder=None, topSize=0, isCFF2=None): 963 assert (isCFF2 is None) == (file is None) 964 self.cff2GetGlyphOrder = cff2GetGlyphOrder 965 if file is not None and isCFF2: 966 self._isCFF2 = isCFF2 967 self.items = [] 968 name = self.__class__.__name__ 969 log.log(DEBUG, "loading %s at %s", name, file.tell()) 970 self.file = file 971 count = 1 972 self.items = [None] * count 973 self.offsets = [0, topSize] 974 self.offsetBase = file.tell() 975 # pretend we've read the whole lot 976 file.seek(self.offsetBase + topSize) 977 log.log(DEBUG, " end of %s at %s", name, file.tell()) 978 else: 979 super(TopDictIndex, self).__init__(file, isCFF2=isCFF2) 980 981 def produceItem(self, index, data, file, offset): 982 top = TopDict( 983 self.strings, 984 file, 985 offset, 986 self.GlobalSubrs, 987 self.cff2GetGlyphOrder, 988 isCFF2=self._isCFF2, 989 ) 990 top.decompile(data) 991 return top 992 993 def toXML(self, xmlWriter): 994 for i in range(len(self)): 995 xmlWriter.begintag("FontDict", index=i) 996 xmlWriter.newline() 997 self[i].toXML(xmlWriter) 998 xmlWriter.endtag("FontDict") 999 xmlWriter.newline() 1000 1001 1002class FDArrayIndex(Index): 1003 compilerClass = FDArrayIndexCompiler 1004 1005 def toXML(self, xmlWriter): 1006 for i in range(len(self)): 1007 xmlWriter.begintag("FontDict", index=i) 1008 xmlWriter.newline() 1009 self[i].toXML(xmlWriter) 1010 xmlWriter.endtag("FontDict") 1011 xmlWriter.newline() 1012 1013 def produceItem(self, index, data, file, offset): 1014 fontDict = FontDict( 1015 self.strings, 1016 file, 1017 offset, 1018 self.GlobalSubrs, 1019 isCFF2=self._isCFF2, 1020 vstore=self.vstore, 1021 ) 1022 fontDict.decompile(data) 1023 return fontDict 1024 1025 def fromXML(self, name, attrs, content): 1026 if name != "FontDict": 1027 return 1028 fontDict = FontDict() 1029 for element in content: 1030 if isinstance(element, str): 1031 continue 1032 name, attrs, content = element 1033 fontDict.fromXML(name, attrs, content) 1034 self.append(fontDict) 1035 1036 1037class VarStoreData(object): 1038 def __init__(self, file=None, otVarStore=None): 1039 self.file = file 1040 self.data = None 1041 self.otVarStore = otVarStore 1042 self.font = TTFont() # dummy font for the decompile function. 1043 1044 def decompile(self): 1045 if self.file: 1046 # read data in from file. Assume position is correct. 1047 length = readCard16(self.file) 1048 self.data = self.file.read(length) 1049 globalState = {} 1050 reader = OTTableReader(self.data, globalState) 1051 self.otVarStore = ot.VarStore() 1052 self.otVarStore.decompile(reader, self.font) 1053 return self 1054 1055 def compile(self): 1056 writer = OTTableWriter() 1057 self.otVarStore.compile(writer, self.font) 1058 # Note that this omits the initial Card16 length from the CFF2 1059 # VarStore data block 1060 self.data = writer.getAllData() 1061 1062 def writeXML(self, xmlWriter, name): 1063 self.otVarStore.toXML(xmlWriter, self.font) 1064 1065 def xmlRead(self, name, attrs, content, parent): 1066 self.otVarStore = ot.VarStore() 1067 for element in content: 1068 if isinstance(element, tuple): 1069 name, attrs, content = element 1070 self.otVarStore.fromXML(name, attrs, content, self.font) 1071 else: 1072 pass 1073 return None 1074 1075 def __len__(self): 1076 return len(self.data) 1077 1078 def getNumRegions(self, vsIndex): 1079 if vsIndex is None: 1080 vsIndex = 0 1081 varData = self.otVarStore.VarData[vsIndex] 1082 numRegions = varData.VarRegionCount 1083 return numRegions 1084 1085 1086class FDSelect(object): 1087 def __init__(self, file=None, numGlyphs=None, format=None): 1088 if file: 1089 # read data in from file 1090 self.format = readCard8(file) 1091 if self.format == 0: 1092 from array import array 1093 1094 self.gidArray = array("B", file.read(numGlyphs)).tolist() 1095 elif self.format == 3: 1096 gidArray = [None] * numGlyphs 1097 nRanges = readCard16(file) 1098 fd = None 1099 prev = None 1100 for i in range(nRanges): 1101 first = readCard16(file) 1102 if prev is not None: 1103 for glyphID in range(prev, first): 1104 gidArray[glyphID] = fd 1105 prev = first 1106 fd = readCard8(file) 1107 if prev is not None: 1108 first = readCard16(file) 1109 for glyphID in range(prev, first): 1110 gidArray[glyphID] = fd 1111 self.gidArray = gidArray 1112 elif self.format == 4: 1113 gidArray = [None] * numGlyphs 1114 nRanges = readCard32(file) 1115 fd = None 1116 prev = None 1117 for i in range(nRanges): 1118 first = readCard32(file) 1119 if prev is not None: 1120 for glyphID in range(prev, first): 1121 gidArray[glyphID] = fd 1122 prev = first 1123 fd = readCard16(file) 1124 if prev is not None: 1125 first = readCard32(file) 1126 for glyphID in range(prev, first): 1127 gidArray[glyphID] = fd 1128 self.gidArray = gidArray 1129 else: 1130 assert False, "unsupported FDSelect format: %s" % format 1131 else: 1132 # reading from XML. Make empty gidArray, and leave format as passed in. 1133 # format is None will result in the smallest representation being used. 1134 self.format = format 1135 self.gidArray = [] 1136 1137 def __len__(self): 1138 return len(self.gidArray) 1139 1140 def __getitem__(self, index): 1141 return self.gidArray[index] 1142 1143 def __setitem__(self, index, fdSelectValue): 1144 self.gidArray[index] = fdSelectValue 1145 1146 def append(self, fdSelectValue): 1147 self.gidArray.append(fdSelectValue) 1148 1149 1150class CharStrings(object): 1151 """The ``CharStrings`` in the font represent the instructions for drawing 1152 each glyph. This object presents a dictionary interface to the font's 1153 CharStrings, indexed by glyph name: 1154 1155 .. code:: python 1156 1157 tt["CFF "].cff[0].CharStrings["a"] 1158 # <T2CharString (bytecode) at 103451e90> 1159 1160 See :class:`fontTools.misc.psCharStrings.T1CharString` and 1161 :class:`fontTools.misc.psCharStrings.T2CharString` for how to decompile, 1162 compile and interpret the glyph drawing instructions in the returned objects. 1163 1164 """ 1165 1166 def __init__( 1167 self, 1168 file, 1169 charset, 1170 globalSubrs, 1171 private, 1172 fdSelect, 1173 fdArray, 1174 isCFF2=None, 1175 varStore=None, 1176 ): 1177 self.globalSubrs = globalSubrs 1178 self.varStore = varStore 1179 if file is not None: 1180 self.charStringsIndex = SubrsIndex( 1181 file, globalSubrs, private, fdSelect, fdArray, isCFF2=isCFF2 1182 ) 1183 self.charStrings = charStrings = {} 1184 for i in range(len(charset)): 1185 charStrings[charset[i]] = i 1186 # read from OTF file: charStrings.values() are indices into 1187 # charStringsIndex. 1188 self.charStringsAreIndexed = 1 1189 else: 1190 self.charStrings = {} 1191 # read from ttx file: charStrings.values() are actual charstrings 1192 self.charStringsAreIndexed = 0 1193 self.private = private 1194 if fdSelect is not None: 1195 self.fdSelect = fdSelect 1196 if fdArray is not None: 1197 self.fdArray = fdArray 1198 1199 def keys(self): 1200 return list(self.charStrings.keys()) 1201 1202 def values(self): 1203 if self.charStringsAreIndexed: 1204 return self.charStringsIndex 1205 else: 1206 return list(self.charStrings.values()) 1207 1208 def has_key(self, name): 1209 return name in self.charStrings 1210 1211 __contains__ = has_key 1212 1213 def __len__(self): 1214 return len(self.charStrings) 1215 1216 def __getitem__(self, name): 1217 charString = self.charStrings[name] 1218 if self.charStringsAreIndexed: 1219 charString = self.charStringsIndex[charString] 1220 return charString 1221 1222 def __setitem__(self, name, charString): 1223 if self.charStringsAreIndexed: 1224 index = self.charStrings[name] 1225 self.charStringsIndex[index] = charString 1226 else: 1227 self.charStrings[name] = charString 1228 1229 def getItemAndSelector(self, name): 1230 if self.charStringsAreIndexed: 1231 index = self.charStrings[name] 1232 return self.charStringsIndex.getItemAndSelector(index) 1233 else: 1234 if hasattr(self, "fdArray"): 1235 if hasattr(self, "fdSelect"): 1236 sel = self.charStrings[name].fdSelectIndex 1237 else: 1238 sel = 0 1239 else: 1240 sel = None 1241 return self.charStrings[name], sel 1242 1243 def toXML(self, xmlWriter): 1244 names = sorted(self.keys()) 1245 for name in names: 1246 charStr, fdSelectIndex = self.getItemAndSelector(name) 1247 if charStr.needsDecompilation(): 1248 raw = [("raw", 1)] 1249 else: 1250 raw = [] 1251 if fdSelectIndex is None: 1252 xmlWriter.begintag("CharString", [("name", name)] + raw) 1253 else: 1254 xmlWriter.begintag( 1255 "CharString", 1256 [("name", name), ("fdSelectIndex", fdSelectIndex)] + raw, 1257 ) 1258 xmlWriter.newline() 1259 charStr.toXML(xmlWriter) 1260 xmlWriter.endtag("CharString") 1261 xmlWriter.newline() 1262 1263 def fromXML(self, name, attrs, content): 1264 for element in content: 1265 if isinstance(element, str): 1266 continue 1267 name, attrs, content = element 1268 if name != "CharString": 1269 continue 1270 fdID = -1 1271 if hasattr(self, "fdArray"): 1272 try: 1273 fdID = safeEval(attrs["fdSelectIndex"]) 1274 except KeyError: 1275 fdID = 0 1276 private = self.fdArray[fdID].Private 1277 else: 1278 private = self.private 1279 1280 glyphName = attrs["name"] 1281 charStringClass = psCharStrings.T2CharString 1282 charString = charStringClass(private=private, globalSubrs=self.globalSubrs) 1283 charString.fromXML(name, attrs, content) 1284 if fdID >= 0: 1285 charString.fdSelectIndex = fdID 1286 self[glyphName] = charString 1287 1288 1289def readCard8(file): 1290 return byteord(file.read(1)) 1291 1292 1293def readCard16(file): 1294 (value,) = struct.unpack(">H", file.read(2)) 1295 return value 1296 1297 1298def readCard32(file): 1299 (value,) = struct.unpack(">L", file.read(4)) 1300 return value 1301 1302 1303def writeCard8(file, value): 1304 file.write(bytechr(value)) 1305 1306 1307def writeCard16(file, value): 1308 file.write(struct.pack(">H", value)) 1309 1310 1311def writeCard32(file, value): 1312 file.write(struct.pack(">L", value)) 1313 1314 1315def packCard8(value): 1316 return bytechr(value) 1317 1318 1319def packCard16(value): 1320 return struct.pack(">H", value) 1321 1322 1323def packCard32(value): 1324 return struct.pack(">L", value) 1325 1326 1327def buildOperatorDict(table): 1328 d = {} 1329 for op, name, arg, default, conv in table: 1330 d[op] = (name, arg) 1331 return d 1332 1333 1334def buildOpcodeDict(table): 1335 d = {} 1336 for op, name, arg, default, conv in table: 1337 if isinstance(op, tuple): 1338 op = bytechr(op[0]) + bytechr(op[1]) 1339 else: 1340 op = bytechr(op) 1341 d[name] = (op, arg) 1342 return d 1343 1344 1345def buildOrder(table): 1346 l = [] 1347 for op, name, arg, default, conv in table: 1348 l.append(name) 1349 return l 1350 1351 1352def buildDefaults(table): 1353 d = {} 1354 for op, name, arg, default, conv in table: 1355 if default is not None: 1356 d[name] = default 1357 return d 1358 1359 1360def buildConverters(table): 1361 d = {} 1362 for op, name, arg, default, conv in table: 1363 d[name] = conv 1364 return d 1365 1366 1367class SimpleConverter(object): 1368 def read(self, parent, value): 1369 if not hasattr(parent, "file"): 1370 return self._read(parent, value) 1371 file = parent.file 1372 pos = file.tell() 1373 try: 1374 return self._read(parent, value) 1375 finally: 1376 file.seek(pos) 1377 1378 def _read(self, parent, value): 1379 return value 1380 1381 def write(self, parent, value): 1382 return value 1383 1384 def xmlWrite(self, xmlWriter, name, value): 1385 xmlWriter.simpletag(name, value=value) 1386 xmlWriter.newline() 1387 1388 def xmlRead(self, name, attrs, content, parent): 1389 return attrs["value"] 1390 1391 1392class ASCIIConverter(SimpleConverter): 1393 def _read(self, parent, value): 1394 return tostr(value, encoding="ascii") 1395 1396 def write(self, parent, value): 1397 return tobytes(value, encoding="ascii") 1398 1399 def xmlWrite(self, xmlWriter, name, value): 1400 xmlWriter.simpletag(name, value=tostr(value, encoding="ascii")) 1401 xmlWriter.newline() 1402 1403 def xmlRead(self, name, attrs, content, parent): 1404 return tobytes(attrs["value"], encoding=("ascii")) 1405 1406 1407class Latin1Converter(SimpleConverter): 1408 def _read(self, parent, value): 1409 return tostr(value, encoding="latin1") 1410 1411 def write(self, parent, value): 1412 return tobytes(value, encoding="latin1") 1413 1414 def xmlWrite(self, xmlWriter, name, value): 1415 value = tostr(value, encoding="latin1") 1416 if name in ["Notice", "Copyright"]: 1417 value = re.sub(r"[\r\n]\s+", " ", value) 1418 xmlWriter.simpletag(name, value=value) 1419 xmlWriter.newline() 1420 1421 def xmlRead(self, name, attrs, content, parent): 1422 return tobytes(attrs["value"], encoding=("latin1")) 1423 1424 1425def parseNum(s): 1426 try: 1427 value = int(s) 1428 except: 1429 value = float(s) 1430 return value 1431 1432 1433def parseBlendList(s): 1434 valueList = [] 1435 for element in s: 1436 if isinstance(element, str): 1437 continue 1438 name, attrs, content = element 1439 blendList = attrs["value"].split() 1440 blendList = [eval(val) for val in blendList] 1441 valueList.append(blendList) 1442 if len(valueList) == 1: 1443 valueList = valueList[0] 1444 return valueList 1445 1446 1447class NumberConverter(SimpleConverter): 1448 def xmlWrite(self, xmlWriter, name, value): 1449 if isinstance(value, list): 1450 xmlWriter.begintag(name) 1451 xmlWriter.newline() 1452 xmlWriter.indent() 1453 blendValue = " ".join([str(val) for val in value]) 1454 xmlWriter.simpletag(kBlendDictOpName, value=blendValue) 1455 xmlWriter.newline() 1456 xmlWriter.dedent() 1457 xmlWriter.endtag(name) 1458 xmlWriter.newline() 1459 else: 1460 xmlWriter.simpletag(name, value=value) 1461 xmlWriter.newline() 1462 1463 def xmlRead(self, name, attrs, content, parent): 1464 valueString = attrs.get("value", None) 1465 if valueString is None: 1466 value = parseBlendList(content) 1467 else: 1468 value = parseNum(attrs["value"]) 1469 return value 1470 1471 1472class ArrayConverter(SimpleConverter): 1473 def xmlWrite(self, xmlWriter, name, value): 1474 if value and isinstance(value[0], list): 1475 xmlWriter.begintag(name) 1476 xmlWriter.newline() 1477 xmlWriter.indent() 1478 for valueList in value: 1479 blendValue = " ".join([str(val) for val in valueList]) 1480 xmlWriter.simpletag(kBlendDictOpName, value=blendValue) 1481 xmlWriter.newline() 1482 xmlWriter.dedent() 1483 xmlWriter.endtag(name) 1484 xmlWriter.newline() 1485 else: 1486 value = " ".join([str(val) for val in value]) 1487 xmlWriter.simpletag(name, value=value) 1488 xmlWriter.newline() 1489 1490 def xmlRead(self, name, attrs, content, parent): 1491 valueString = attrs.get("value", None) 1492 if valueString is None: 1493 valueList = parseBlendList(content) 1494 else: 1495 values = valueString.split() 1496 valueList = [parseNum(value) for value in values] 1497 return valueList 1498 1499 1500class TableConverter(SimpleConverter): 1501 def xmlWrite(self, xmlWriter, name, value): 1502 xmlWriter.begintag(name) 1503 xmlWriter.newline() 1504 value.toXML(xmlWriter) 1505 xmlWriter.endtag(name) 1506 xmlWriter.newline() 1507 1508 def xmlRead(self, name, attrs, content, parent): 1509 ob = self.getClass()() 1510 for element in content: 1511 if isinstance(element, str): 1512 continue 1513 name, attrs, content = element 1514 ob.fromXML(name, attrs, content) 1515 return ob 1516 1517 1518class PrivateDictConverter(TableConverter): 1519 def getClass(self): 1520 return PrivateDict 1521 1522 def _read(self, parent, value): 1523 size, offset = value 1524 file = parent.file 1525 isCFF2 = parent._isCFF2 1526 try: 1527 vstore = parent.vstore 1528 except AttributeError: 1529 vstore = None 1530 priv = PrivateDict(parent.strings, file, offset, isCFF2=isCFF2, vstore=vstore) 1531 file.seek(offset) 1532 data = file.read(size) 1533 assert len(data) == size 1534 priv.decompile(data) 1535 return priv 1536 1537 def write(self, parent, value): 1538 return (0, 0) # dummy value 1539 1540 1541class SubrsConverter(TableConverter): 1542 def getClass(self): 1543 return SubrsIndex 1544 1545 def _read(self, parent, value): 1546 file = parent.file 1547 isCFF2 = parent._isCFF2 1548 file.seek(parent.offset + value) # Offset(self) 1549 return SubrsIndex(file, isCFF2=isCFF2) 1550 1551 def write(self, parent, value): 1552 return 0 # dummy value 1553 1554 1555class CharStringsConverter(TableConverter): 1556 def _read(self, parent, value): 1557 file = parent.file 1558 isCFF2 = parent._isCFF2 1559 charset = parent.charset 1560 varStore = getattr(parent, "VarStore", None) 1561 globalSubrs = parent.GlobalSubrs 1562 if hasattr(parent, "FDArray"): 1563 fdArray = parent.FDArray 1564 if hasattr(parent, "FDSelect"): 1565 fdSelect = parent.FDSelect 1566 else: 1567 fdSelect = None 1568 private = None 1569 else: 1570 fdSelect, fdArray = None, None 1571 private = parent.Private 1572 file.seek(value) # Offset(0) 1573 charStrings = CharStrings( 1574 file, 1575 charset, 1576 globalSubrs, 1577 private, 1578 fdSelect, 1579 fdArray, 1580 isCFF2=isCFF2, 1581 varStore=varStore, 1582 ) 1583 return charStrings 1584 1585 def write(self, parent, value): 1586 return 0 # dummy value 1587 1588 def xmlRead(self, name, attrs, content, parent): 1589 if hasattr(parent, "FDArray"): 1590 # if it is a CID-keyed font, then the private Dict is extracted from the 1591 # parent.FDArray 1592 fdArray = parent.FDArray 1593 if hasattr(parent, "FDSelect"): 1594 fdSelect = parent.FDSelect 1595 else: 1596 fdSelect = None 1597 private = None 1598 else: 1599 # if it is a name-keyed font, then the private dict is in the top dict, 1600 # and 1601 # there is no fdArray. 1602 private, fdSelect, fdArray = parent.Private, None, None 1603 charStrings = CharStrings( 1604 None, 1605 None, 1606 parent.GlobalSubrs, 1607 private, 1608 fdSelect, 1609 fdArray, 1610 varStore=getattr(parent, "VarStore", None), 1611 ) 1612 charStrings.fromXML(name, attrs, content) 1613 return charStrings 1614 1615 1616class CharsetConverter(SimpleConverter): 1617 def _read(self, parent, value): 1618 isCID = hasattr(parent, "ROS") 1619 if value > 2: 1620 numGlyphs = parent.numGlyphs 1621 file = parent.file 1622 file.seek(value) 1623 log.log(DEBUG, "loading charset at %s", value) 1624 format = readCard8(file) 1625 if format == 0: 1626 charset = parseCharset0(numGlyphs, file, parent.strings, isCID) 1627 elif format == 1 or format == 2: 1628 charset = parseCharset(numGlyphs, file, parent.strings, isCID, format) 1629 else: 1630 raise NotImplementedError 1631 assert len(charset) == numGlyphs 1632 log.log(DEBUG, " charset end at %s", file.tell()) 1633 # make sure glyph names are unique 1634 allNames = {} 1635 newCharset = [] 1636 for glyphName in charset: 1637 if glyphName in allNames: 1638 # make up a new glyphName that's unique 1639 n = allNames[glyphName] 1640 while (glyphName + "#" + str(n)) in allNames: 1641 n += 1 1642 allNames[glyphName] = n + 1 1643 glyphName = glyphName + "#" + str(n) 1644 allNames[glyphName] = 1 1645 newCharset.append(glyphName) 1646 charset = newCharset 1647 else: # offset == 0 -> no charset data. 1648 if isCID or "CharStrings" not in parent.rawDict: 1649 # We get here only when processing fontDicts from the FDArray of 1650 # CFF-CID fonts. Only the real topDict references the chrset. 1651 assert value == 0 1652 charset = None 1653 elif value == 0: 1654 charset = cffISOAdobeStrings 1655 elif value == 1: 1656 charset = cffIExpertStrings 1657 elif value == 2: 1658 charset = cffExpertSubsetStrings 1659 if charset and (len(charset) != parent.numGlyphs): 1660 charset = charset[: parent.numGlyphs] 1661 return charset 1662 1663 def write(self, parent, value): 1664 return 0 # dummy value 1665 1666 def xmlWrite(self, xmlWriter, name, value): 1667 # XXX only write charset when not in OT/TTX context, where we 1668 # dump charset as a separate "GlyphOrder" table. 1669 # # xmlWriter.simpletag("charset") 1670 xmlWriter.comment("charset is dumped separately as the 'GlyphOrder' element") 1671 xmlWriter.newline() 1672 1673 def xmlRead(self, name, attrs, content, parent): 1674 pass 1675 1676 1677class CharsetCompiler(object): 1678 def __init__(self, strings, charset, parent): 1679 assert charset[0] == ".notdef" 1680 isCID = hasattr(parent.dictObj, "ROS") 1681 data0 = packCharset0(charset, isCID, strings) 1682 data = packCharset(charset, isCID, strings) 1683 if len(data) < len(data0): 1684 self.data = data 1685 else: 1686 self.data = data0 1687 self.parent = parent 1688 1689 def setPos(self, pos, endPos): 1690 self.parent.rawDict["charset"] = pos 1691 1692 def getDataLength(self): 1693 return len(self.data) 1694 1695 def toFile(self, file): 1696 file.write(self.data) 1697 1698 1699def getStdCharSet(charset): 1700 # check to see if we can use a predefined charset value. 1701 predefinedCharSetVal = None 1702 predefinedCharSets = [ 1703 (cffISOAdobeStringCount, cffISOAdobeStrings, 0), 1704 (cffExpertStringCount, cffIExpertStrings, 1), 1705 (cffExpertSubsetStringCount, cffExpertSubsetStrings, 2), 1706 ] 1707 lcs = len(charset) 1708 for cnt, pcs, csv in predefinedCharSets: 1709 if predefinedCharSetVal is not None: 1710 break 1711 if lcs > cnt: 1712 continue 1713 predefinedCharSetVal = csv 1714 for i in range(lcs): 1715 if charset[i] != pcs[i]: 1716 predefinedCharSetVal = None 1717 break 1718 return predefinedCharSetVal 1719 1720 1721def getCIDfromName(name, strings): 1722 return int(name[3:]) 1723 1724 1725def getSIDfromName(name, strings): 1726 return strings.getSID(name) 1727 1728 1729def packCharset0(charset, isCID, strings): 1730 fmt = 0 1731 data = [packCard8(fmt)] 1732 if isCID: 1733 getNameID = getCIDfromName 1734 else: 1735 getNameID = getSIDfromName 1736 1737 for name in charset[1:]: 1738 data.append(packCard16(getNameID(name, strings))) 1739 return bytesjoin(data) 1740 1741 1742def packCharset(charset, isCID, strings): 1743 fmt = 1 1744 ranges = [] 1745 first = None 1746 end = 0 1747 if isCID: 1748 getNameID = getCIDfromName 1749 else: 1750 getNameID = getSIDfromName 1751 1752 for name in charset[1:]: 1753 SID = getNameID(name, strings) 1754 if first is None: 1755 first = SID 1756 elif end + 1 != SID: 1757 nLeft = end - first 1758 if nLeft > 255: 1759 fmt = 2 1760 ranges.append((first, nLeft)) 1761 first = SID 1762 end = SID 1763 if end: 1764 nLeft = end - first 1765 if nLeft > 255: 1766 fmt = 2 1767 ranges.append((first, nLeft)) 1768 1769 data = [packCard8(fmt)] 1770 if fmt == 1: 1771 nLeftFunc = packCard8 1772 else: 1773 nLeftFunc = packCard16 1774 for first, nLeft in ranges: 1775 data.append(packCard16(first) + nLeftFunc(nLeft)) 1776 return bytesjoin(data) 1777 1778 1779def parseCharset0(numGlyphs, file, strings, isCID): 1780 charset = [".notdef"] 1781 if isCID: 1782 for i in range(numGlyphs - 1): 1783 CID = readCard16(file) 1784 charset.append("cid" + str(CID).zfill(5)) 1785 else: 1786 for i in range(numGlyphs - 1): 1787 SID = readCard16(file) 1788 charset.append(strings[SID]) 1789 return charset 1790 1791 1792def parseCharset(numGlyphs, file, strings, isCID, fmt): 1793 charset = [".notdef"] 1794 count = 1 1795 if fmt == 1: 1796 nLeftFunc = readCard8 1797 else: 1798 nLeftFunc = readCard16 1799 while count < numGlyphs: 1800 first = readCard16(file) 1801 nLeft = nLeftFunc(file) 1802 if isCID: 1803 for CID in range(first, first + nLeft + 1): 1804 charset.append("cid" + str(CID).zfill(5)) 1805 else: 1806 for SID in range(first, first + nLeft + 1): 1807 charset.append(strings[SID]) 1808 count = count + nLeft + 1 1809 return charset 1810 1811 1812class EncodingCompiler(object): 1813 def __init__(self, strings, encoding, parent): 1814 assert not isinstance(encoding, str) 1815 data0 = packEncoding0(parent.dictObj.charset, encoding, parent.strings) 1816 data1 = packEncoding1(parent.dictObj.charset, encoding, parent.strings) 1817 if len(data0) < len(data1): 1818 self.data = data0 1819 else: 1820 self.data = data1 1821 self.parent = parent 1822 1823 def setPos(self, pos, endPos): 1824 self.parent.rawDict["Encoding"] = pos 1825 1826 def getDataLength(self): 1827 return len(self.data) 1828 1829 def toFile(self, file): 1830 file.write(self.data) 1831 1832 1833class EncodingConverter(SimpleConverter): 1834 def _read(self, parent, value): 1835 if value == 0: 1836 return "StandardEncoding" 1837 elif value == 1: 1838 return "ExpertEncoding" 1839 else: 1840 assert value > 1 1841 file = parent.file 1842 file.seek(value) 1843 log.log(DEBUG, "loading Encoding at %s", value) 1844 fmt = readCard8(file) 1845 haveSupplement = fmt & 0x80 1846 if haveSupplement: 1847 raise NotImplementedError("Encoding supplements are not yet supported") 1848 fmt = fmt & 0x7F 1849 if fmt == 0: 1850 encoding = parseEncoding0( 1851 parent.charset, file, haveSupplement, parent.strings 1852 ) 1853 elif fmt == 1: 1854 encoding = parseEncoding1( 1855 parent.charset, file, haveSupplement, parent.strings 1856 ) 1857 return encoding 1858 1859 def write(self, parent, value): 1860 if value == "StandardEncoding": 1861 return 0 1862 elif value == "ExpertEncoding": 1863 return 1 1864 return 0 # dummy value 1865 1866 def xmlWrite(self, xmlWriter, name, value): 1867 if value in ("StandardEncoding", "ExpertEncoding"): 1868 xmlWriter.simpletag(name, name=value) 1869 xmlWriter.newline() 1870 return 1871 xmlWriter.begintag(name) 1872 xmlWriter.newline() 1873 for code in range(len(value)): 1874 glyphName = value[code] 1875 if glyphName != ".notdef": 1876 xmlWriter.simpletag("map", code=hex(code), name=glyphName) 1877 xmlWriter.newline() 1878 xmlWriter.endtag(name) 1879 xmlWriter.newline() 1880 1881 def xmlRead(self, name, attrs, content, parent): 1882 if "name" in attrs: 1883 return attrs["name"] 1884 encoding = [".notdef"] * 256 1885 for element in content: 1886 if isinstance(element, str): 1887 continue 1888 name, attrs, content = element 1889 code = safeEval(attrs["code"]) 1890 glyphName = attrs["name"] 1891 encoding[code] = glyphName 1892 return encoding 1893 1894 1895def parseEncoding0(charset, file, haveSupplement, strings): 1896 nCodes = readCard8(file) 1897 encoding = [".notdef"] * 256 1898 for glyphID in range(1, nCodes + 1): 1899 code = readCard8(file) 1900 if code != 0: 1901 encoding[code] = charset[glyphID] 1902 return encoding 1903 1904 1905def parseEncoding1(charset, file, haveSupplement, strings): 1906 nRanges = readCard8(file) 1907 encoding = [".notdef"] * 256 1908 glyphID = 1 1909 for i in range(nRanges): 1910 code = readCard8(file) 1911 nLeft = readCard8(file) 1912 for glyphID in range(glyphID, glyphID + nLeft + 1): 1913 encoding[code] = charset[glyphID] 1914 code = code + 1 1915 glyphID = glyphID + 1 1916 return encoding 1917 1918 1919def packEncoding0(charset, encoding, strings): 1920 fmt = 0 1921 m = {} 1922 for code in range(len(encoding)): 1923 name = encoding[code] 1924 if name != ".notdef": 1925 m[name] = code 1926 codes = [] 1927 for name in charset[1:]: 1928 code = m.get(name) 1929 codes.append(code) 1930 1931 while codes and codes[-1] is None: 1932 codes.pop() 1933 1934 data = [packCard8(fmt), packCard8(len(codes))] 1935 for code in codes: 1936 if code is None: 1937 code = 0 1938 data.append(packCard8(code)) 1939 return bytesjoin(data) 1940 1941 1942def packEncoding1(charset, encoding, strings): 1943 fmt = 1 1944 m = {} 1945 for code in range(len(encoding)): 1946 name = encoding[code] 1947 if name != ".notdef": 1948 m[name] = code 1949 ranges = [] 1950 first = None 1951 end = 0 1952 for name in charset[1:]: 1953 code = m.get(name, -1) 1954 if first is None: 1955 first = code 1956 elif end + 1 != code: 1957 nLeft = end - first 1958 ranges.append((first, nLeft)) 1959 first = code 1960 end = code 1961 nLeft = end - first 1962 ranges.append((first, nLeft)) 1963 1964 # remove unencoded glyphs at the end. 1965 while ranges and ranges[-1][0] == -1: 1966 ranges.pop() 1967 1968 data = [packCard8(fmt), packCard8(len(ranges))] 1969 for first, nLeft in ranges: 1970 if first == -1: # unencoded 1971 first = 0 1972 data.append(packCard8(first) + packCard8(nLeft)) 1973 return bytesjoin(data) 1974 1975 1976class FDArrayConverter(TableConverter): 1977 def _read(self, parent, value): 1978 try: 1979 vstore = parent.VarStore 1980 except AttributeError: 1981 vstore = None 1982 file = parent.file 1983 isCFF2 = parent._isCFF2 1984 file.seek(value) 1985 fdArray = FDArrayIndex(file, isCFF2=isCFF2) 1986 fdArray.vstore = vstore 1987 fdArray.strings = parent.strings 1988 fdArray.GlobalSubrs = parent.GlobalSubrs 1989 return fdArray 1990 1991 def write(self, parent, value): 1992 return 0 # dummy value 1993 1994 def xmlRead(self, name, attrs, content, parent): 1995 fdArray = FDArrayIndex() 1996 for element in content: 1997 if isinstance(element, str): 1998 continue 1999 name, attrs, content = element 2000 fdArray.fromXML(name, attrs, content) 2001 return fdArray 2002 2003 2004class FDSelectConverter(SimpleConverter): 2005 def _read(self, parent, value): 2006 file = parent.file 2007 file.seek(value) 2008 fdSelect = FDSelect(file, parent.numGlyphs) 2009 return fdSelect 2010 2011 def write(self, parent, value): 2012 return 0 # dummy value 2013 2014 # The FDSelect glyph data is written out to XML in the charstring keys, 2015 # so we write out only the format selector 2016 def xmlWrite(self, xmlWriter, name, value): 2017 xmlWriter.simpletag(name, [("format", value.format)]) 2018 xmlWriter.newline() 2019 2020 def xmlRead(self, name, attrs, content, parent): 2021 fmt = safeEval(attrs["format"]) 2022 file = None 2023 numGlyphs = None 2024 fdSelect = FDSelect(file, numGlyphs, fmt) 2025 return fdSelect 2026 2027 2028class VarStoreConverter(SimpleConverter): 2029 def _read(self, parent, value): 2030 file = parent.file 2031 file.seek(value) 2032 varStore = VarStoreData(file) 2033 varStore.decompile() 2034 return varStore 2035 2036 def write(self, parent, value): 2037 return 0 # dummy value 2038 2039 def xmlWrite(self, xmlWriter, name, value): 2040 value.writeXML(xmlWriter, name) 2041 2042 def xmlRead(self, name, attrs, content, parent): 2043 varStore = VarStoreData() 2044 varStore.xmlRead(name, attrs, content, parent) 2045 return varStore 2046 2047 2048def packFDSelect0(fdSelectArray): 2049 fmt = 0 2050 data = [packCard8(fmt)] 2051 for index in fdSelectArray: 2052 data.append(packCard8(index)) 2053 return bytesjoin(data) 2054 2055 2056def packFDSelect3(fdSelectArray): 2057 fmt = 3 2058 fdRanges = [] 2059 lenArray = len(fdSelectArray) 2060 lastFDIndex = -1 2061 for i in range(lenArray): 2062 fdIndex = fdSelectArray[i] 2063 if lastFDIndex != fdIndex: 2064 fdRanges.append([i, fdIndex]) 2065 lastFDIndex = fdIndex 2066 sentinelGID = i + 1 2067 2068 data = [packCard8(fmt)] 2069 data.append(packCard16(len(fdRanges))) 2070 for fdRange in fdRanges: 2071 data.append(packCard16(fdRange[0])) 2072 data.append(packCard8(fdRange[1])) 2073 data.append(packCard16(sentinelGID)) 2074 return bytesjoin(data) 2075 2076 2077def packFDSelect4(fdSelectArray): 2078 fmt = 4 2079 fdRanges = [] 2080 lenArray = len(fdSelectArray) 2081 lastFDIndex = -1 2082 for i in range(lenArray): 2083 fdIndex = fdSelectArray[i] 2084 if lastFDIndex != fdIndex: 2085 fdRanges.append([i, fdIndex]) 2086 lastFDIndex = fdIndex 2087 sentinelGID = i + 1 2088 2089 data = [packCard8(fmt)] 2090 data.append(packCard32(len(fdRanges))) 2091 for fdRange in fdRanges: 2092 data.append(packCard32(fdRange[0])) 2093 data.append(packCard16(fdRange[1])) 2094 data.append(packCard32(sentinelGID)) 2095 return bytesjoin(data) 2096 2097 2098class FDSelectCompiler(object): 2099 def __init__(self, fdSelect, parent): 2100 fmt = fdSelect.format 2101 fdSelectArray = fdSelect.gidArray 2102 if fmt == 0: 2103 self.data = packFDSelect0(fdSelectArray) 2104 elif fmt == 3: 2105 self.data = packFDSelect3(fdSelectArray) 2106 elif fmt == 4: 2107 self.data = packFDSelect4(fdSelectArray) 2108 else: 2109 # choose smaller of the two formats 2110 data0 = packFDSelect0(fdSelectArray) 2111 data3 = packFDSelect3(fdSelectArray) 2112 if len(data0) < len(data3): 2113 self.data = data0 2114 fdSelect.format = 0 2115 else: 2116 self.data = data3 2117 fdSelect.format = 3 2118 2119 self.parent = parent 2120 2121 def setPos(self, pos, endPos): 2122 self.parent.rawDict["FDSelect"] = pos 2123 2124 def getDataLength(self): 2125 return len(self.data) 2126 2127 def toFile(self, file): 2128 file.write(self.data) 2129 2130 2131class VarStoreCompiler(object): 2132 def __init__(self, varStoreData, parent): 2133 self.parent = parent 2134 if not varStoreData.data: 2135 varStoreData.compile() 2136 data = [packCard16(len(varStoreData.data)), varStoreData.data] 2137 self.data = bytesjoin(data) 2138 2139 def setPos(self, pos, endPos): 2140 self.parent.rawDict["VarStore"] = pos 2141 2142 def getDataLength(self): 2143 return len(self.data) 2144 2145 def toFile(self, file): 2146 file.write(self.data) 2147 2148 2149class ROSConverter(SimpleConverter): 2150 def xmlWrite(self, xmlWriter, name, value): 2151 registry, order, supplement = value 2152 xmlWriter.simpletag( 2153 name, 2154 [ 2155 ("Registry", tostr(registry)), 2156 ("Order", tostr(order)), 2157 ("Supplement", supplement), 2158 ], 2159 ) 2160 xmlWriter.newline() 2161 2162 def xmlRead(self, name, attrs, content, parent): 2163 return (attrs["Registry"], attrs["Order"], safeEval(attrs["Supplement"])) 2164 2165 2166topDictOperators = [ 2167 # opcode name argument type default converter 2168 (25, "maxstack", "number", None, None), 2169 ((12, 30), "ROS", ("SID", "SID", "number"), None, ROSConverter()), 2170 ((12, 20), "SyntheticBase", "number", None, None), 2171 (0, "version", "SID", None, None), 2172 (1, "Notice", "SID", None, Latin1Converter()), 2173 ((12, 0), "Copyright", "SID", None, Latin1Converter()), 2174 (2, "FullName", "SID", None, Latin1Converter()), 2175 ((12, 38), "FontName", "SID", None, Latin1Converter()), 2176 (3, "FamilyName", "SID", None, Latin1Converter()), 2177 (4, "Weight", "SID", None, None), 2178 ((12, 1), "isFixedPitch", "number", 0, None), 2179 ((12, 2), "ItalicAngle", "number", 0, None), 2180 ((12, 3), "UnderlinePosition", "number", -100, None), 2181 ((12, 4), "UnderlineThickness", "number", 50, None), 2182 ((12, 5), "PaintType", "number", 0, None), 2183 ((12, 6), "CharstringType", "number", 2, None), 2184 ((12, 7), "FontMatrix", "array", [0.001, 0, 0, 0.001, 0, 0], None), 2185 (13, "UniqueID", "number", None, None), 2186 (5, "FontBBox", "array", [0, 0, 0, 0], None), 2187 ((12, 8), "StrokeWidth", "number", 0, None), 2188 (14, "XUID", "array", None, None), 2189 ((12, 21), "PostScript", "SID", None, None), 2190 ((12, 22), "BaseFontName", "SID", None, None), 2191 ((12, 23), "BaseFontBlend", "delta", None, None), 2192 ((12, 31), "CIDFontVersion", "number", 0, None), 2193 ((12, 32), "CIDFontRevision", "number", 0, None), 2194 ((12, 33), "CIDFontType", "number", 0, None), 2195 ((12, 34), "CIDCount", "number", 8720, None), 2196 (15, "charset", "number", None, CharsetConverter()), 2197 ((12, 35), "UIDBase", "number", None, None), 2198 (16, "Encoding", "number", 0, EncodingConverter()), 2199 (18, "Private", ("number", "number"), None, PrivateDictConverter()), 2200 ((12, 37), "FDSelect", "number", None, FDSelectConverter()), 2201 ((12, 36), "FDArray", "number", None, FDArrayConverter()), 2202 (17, "CharStrings", "number", None, CharStringsConverter()), 2203 (24, "VarStore", "number", None, VarStoreConverter()), 2204] 2205 2206topDictOperators2 = [ 2207 # opcode name argument type default converter 2208 (25, "maxstack", "number", None, None), 2209 ((12, 7), "FontMatrix", "array", [0.001, 0, 0, 0.001, 0, 0], None), 2210 ((12, 37), "FDSelect", "number", None, FDSelectConverter()), 2211 ((12, 36), "FDArray", "number", None, FDArrayConverter()), 2212 (17, "CharStrings", "number", None, CharStringsConverter()), 2213 (24, "VarStore", "number", None, VarStoreConverter()), 2214] 2215 2216# Note! FDSelect and FDArray must both preceed CharStrings in the output XML build order, 2217# in order for the font to compile back from xml. 2218 2219kBlendDictOpName = "blend" 2220blendOp = 23 2221 2222privateDictOperators = [ 2223 # opcode name argument type default converter 2224 (22, "vsindex", "number", None, None), 2225 ( 2226 blendOp, 2227 kBlendDictOpName, 2228 "blendList", 2229 None, 2230 None, 2231 ), # This is for reading to/from XML: it not written to CFF. 2232 (6, "BlueValues", "delta", None, None), 2233 (7, "OtherBlues", "delta", None, None), 2234 (8, "FamilyBlues", "delta", None, None), 2235 (9, "FamilyOtherBlues", "delta", None, None), 2236 ((12, 9), "BlueScale", "number", 0.039625, None), 2237 ((12, 10), "BlueShift", "number", 7, None), 2238 ((12, 11), "BlueFuzz", "number", 1, None), 2239 (10, "StdHW", "number", None, None), 2240 (11, "StdVW", "number", None, None), 2241 ((12, 12), "StemSnapH", "delta", None, None), 2242 ((12, 13), "StemSnapV", "delta", None, None), 2243 ((12, 14), "ForceBold", "number", 0, None), 2244 ((12, 15), "ForceBoldThreshold", "number", None, None), # deprecated 2245 ((12, 16), "lenIV", "number", None, None), # deprecated 2246 ((12, 17), "LanguageGroup", "number", 0, None), 2247 ((12, 18), "ExpansionFactor", "number", 0.06, None), 2248 ((12, 19), "initialRandomSeed", "number", 0, None), 2249 (20, "defaultWidthX", "number", 0, None), 2250 (21, "nominalWidthX", "number", 0, None), 2251 (19, "Subrs", "number", None, SubrsConverter()), 2252] 2253 2254privateDictOperators2 = [ 2255 # opcode name argument type default converter 2256 (22, "vsindex", "number", None, None), 2257 ( 2258 blendOp, 2259 kBlendDictOpName, 2260 "blendList", 2261 None, 2262 None, 2263 ), # This is for reading to/from XML: it not written to CFF. 2264 (6, "BlueValues", "delta", None, None), 2265 (7, "OtherBlues", "delta", None, None), 2266 (8, "FamilyBlues", "delta", None, None), 2267 (9, "FamilyOtherBlues", "delta", None, None), 2268 ((12, 9), "BlueScale", "number", 0.039625, None), 2269 ((12, 10), "BlueShift", "number", 7, None), 2270 ((12, 11), "BlueFuzz", "number", 1, None), 2271 (10, "StdHW", "number", None, None), 2272 (11, "StdVW", "number", None, None), 2273 ((12, 12), "StemSnapH", "delta", None, None), 2274 ((12, 13), "StemSnapV", "delta", None, None), 2275 ((12, 17), "LanguageGroup", "number", 0, None), 2276 ((12, 18), "ExpansionFactor", "number", 0.06, None), 2277 (19, "Subrs", "number", None, SubrsConverter()), 2278] 2279 2280 2281def addConverters(table): 2282 for i in range(len(table)): 2283 op, name, arg, default, conv = table[i] 2284 if conv is not None: 2285 continue 2286 if arg in ("delta", "array"): 2287 conv = ArrayConverter() 2288 elif arg == "number": 2289 conv = NumberConverter() 2290 elif arg == "SID": 2291 conv = ASCIIConverter() 2292 elif arg == "blendList": 2293 conv = None 2294 else: 2295 assert False 2296 table[i] = op, name, arg, default, conv 2297 2298 2299addConverters(privateDictOperators) 2300addConverters(topDictOperators) 2301 2302 2303class TopDictDecompiler(psCharStrings.DictDecompiler): 2304 operators = buildOperatorDict(topDictOperators) 2305 2306 2307class PrivateDictDecompiler(psCharStrings.DictDecompiler): 2308 operators = buildOperatorDict(privateDictOperators) 2309 2310 2311class DictCompiler(object): 2312 maxBlendStack = 0 2313 2314 def __init__(self, dictObj, strings, parent, isCFF2=None): 2315 if strings: 2316 assert isinstance(strings, IndexedStrings) 2317 if isCFF2 is None and hasattr(parent, "isCFF2"): 2318 isCFF2 = parent.isCFF2 2319 assert isCFF2 is not None 2320 self.isCFF2 = isCFF2 2321 self.dictObj = dictObj 2322 self.strings = strings 2323 self.parent = parent 2324 rawDict = {} 2325 for name in dictObj.order: 2326 value = getattr(dictObj, name, None) 2327 if value is None: 2328 continue 2329 conv = dictObj.converters[name] 2330 value = conv.write(dictObj, value) 2331 if value == dictObj.defaults.get(name): 2332 continue 2333 rawDict[name] = value 2334 self.rawDict = rawDict 2335 2336 def setPos(self, pos, endPos): 2337 pass 2338 2339 def getDataLength(self): 2340 return len(self.compile("getDataLength")) 2341 2342 def compile(self, reason): 2343 log.log(DEBUG, "-- compiling %s for %s", self.__class__.__name__, reason) 2344 rawDict = self.rawDict 2345 data = [] 2346 for name in self.dictObj.order: 2347 value = rawDict.get(name) 2348 if value is None: 2349 continue 2350 op, argType = self.opcodes[name] 2351 if isinstance(argType, tuple): 2352 l = len(argType) 2353 assert len(value) == l, "value doesn't match arg type" 2354 for i in range(l): 2355 arg = argType[i] 2356 v = value[i] 2357 arghandler = getattr(self, "arg_" + arg) 2358 data.append(arghandler(v)) 2359 else: 2360 arghandler = getattr(self, "arg_" + argType) 2361 data.append(arghandler(value)) 2362 data.append(op) 2363 data = bytesjoin(data) 2364 return data 2365 2366 def toFile(self, file): 2367 data = self.compile("toFile") 2368 file.write(data) 2369 2370 def arg_number(self, num): 2371 if isinstance(num, list): 2372 data = [encodeNumber(val) for val in num] 2373 data.append(encodeNumber(1)) 2374 data.append(bytechr(blendOp)) 2375 datum = bytesjoin(data) 2376 else: 2377 datum = encodeNumber(num) 2378 return datum 2379 2380 def arg_SID(self, s): 2381 return psCharStrings.encodeIntCFF(self.strings.getSID(s)) 2382 2383 def arg_array(self, value): 2384 data = [] 2385 for num in value: 2386 data.append(self.arg_number(num)) 2387 return bytesjoin(data) 2388 2389 def arg_delta(self, value): 2390 if not value: 2391 return b"" 2392 val0 = value[0] 2393 if isinstance(val0, list): 2394 data = self.arg_delta_blend(value) 2395 else: 2396 out = [] 2397 last = 0 2398 for v in value: 2399 out.append(v - last) 2400 last = v 2401 data = [] 2402 for num in out: 2403 data.append(encodeNumber(num)) 2404 return bytesjoin(data) 2405 2406 def arg_delta_blend(self, value): 2407 """A delta list with blend lists has to be *all* blend lists. 2408 2409 The value is a list is arranged as follows:: 2410 2411 [ 2412 [V0, d0..dn] 2413 [V1, d0..dn] 2414 ... 2415 [Vm, d0..dn] 2416 ] 2417 2418 ``V`` is the absolute coordinate value from the default font, and ``d0-dn`` 2419 are the delta values from the *n* regions. Each ``V`` is an absolute 2420 coordinate from the default font. 2421 2422 We want to return a list:: 2423 2424 [ 2425 [v0, v1..vm] 2426 [d0..dn] 2427 ... 2428 [d0..dn] 2429 numBlends 2430 blendOp 2431 ] 2432 2433 where each ``v`` is relative to the previous default font value. 2434 """ 2435 numMasters = len(value[0]) 2436 numBlends = len(value) 2437 numStack = (numBlends * numMasters) + 1 2438 if numStack > self.maxBlendStack: 2439 # Figure out the max number of value we can blend 2440 # and divide this list up into chunks of that size. 2441 2442 numBlendValues = int((self.maxBlendStack - 1) / numMasters) 2443 out = [] 2444 while True: 2445 numVal = min(len(value), numBlendValues) 2446 if numVal == 0: 2447 break 2448 valList = value[0:numVal] 2449 out1 = self.arg_delta_blend(valList) 2450 out.extend(out1) 2451 value = value[numVal:] 2452 else: 2453 firstList = [0] * numBlends 2454 deltaList = [None] * numBlends 2455 i = 0 2456 prevVal = 0 2457 while i < numBlends: 2458 # For PrivateDict BlueValues, the default font 2459 # values are absolute, not relative. 2460 # Must convert these back to relative coordinates 2461 # befor writing to CFF2. 2462 defaultValue = value[i][0] 2463 firstList[i] = defaultValue - prevVal 2464 prevVal = defaultValue 2465 deltaList[i] = value[i][1:] 2466 i += 1 2467 2468 relValueList = firstList 2469 for blendList in deltaList: 2470 relValueList.extend(blendList) 2471 out = [encodeNumber(val) for val in relValueList] 2472 out.append(encodeNumber(numBlends)) 2473 out.append(bytechr(blendOp)) 2474 return out 2475 2476 2477def encodeNumber(num): 2478 if isinstance(num, float): 2479 return psCharStrings.encodeFloat(num) 2480 else: 2481 return psCharStrings.encodeIntCFF(num) 2482 2483 2484class TopDictCompiler(DictCompiler): 2485 opcodes = buildOpcodeDict(topDictOperators) 2486 2487 def getChildren(self, strings): 2488 isCFF2 = self.isCFF2 2489 children = [] 2490 if self.dictObj.cff2GetGlyphOrder is None: 2491 if hasattr(self.dictObj, "charset") and self.dictObj.charset: 2492 if hasattr(self.dictObj, "ROS"): # aka isCID 2493 charsetCode = None 2494 else: 2495 charsetCode = getStdCharSet(self.dictObj.charset) 2496 if charsetCode is None: 2497 children.append( 2498 CharsetCompiler(strings, self.dictObj.charset, self) 2499 ) 2500 else: 2501 self.rawDict["charset"] = charsetCode 2502 if hasattr(self.dictObj, "Encoding") and self.dictObj.Encoding: 2503 encoding = self.dictObj.Encoding 2504 if not isinstance(encoding, str): 2505 children.append(EncodingCompiler(strings, encoding, self)) 2506 else: 2507 if hasattr(self.dictObj, "VarStore"): 2508 varStoreData = self.dictObj.VarStore 2509 varStoreComp = VarStoreCompiler(varStoreData, self) 2510 children.append(varStoreComp) 2511 if hasattr(self.dictObj, "FDSelect"): 2512 # I have not yet supported merging a ttx CFF-CID font, as there are 2513 # interesting issues about merging the FDArrays. Here I assume that 2514 # either the font was read from XML, and the FDSelect indices are all 2515 # in the charstring data, or the FDSelect array is already fully defined. 2516 fdSelect = self.dictObj.FDSelect 2517 # probably read in from XML; assume fdIndex in CharString data 2518 if len(fdSelect) == 0: 2519 charStrings = self.dictObj.CharStrings 2520 for name in self.dictObj.charset: 2521 fdSelect.append(charStrings[name].fdSelectIndex) 2522 fdSelectComp = FDSelectCompiler(fdSelect, self) 2523 children.append(fdSelectComp) 2524 if hasattr(self.dictObj, "CharStrings"): 2525 items = [] 2526 charStrings = self.dictObj.CharStrings 2527 for name in self.dictObj.charset: 2528 items.append(charStrings[name]) 2529 charStringsComp = CharStringsCompiler(items, strings, self, isCFF2=isCFF2) 2530 children.append(charStringsComp) 2531 if hasattr(self.dictObj, "FDArray"): 2532 # I have not yet supported merging a ttx CFF-CID font, as there are 2533 # interesting issues about merging the FDArrays. Here I assume that the 2534 # FDArray info is correct and complete. 2535 fdArrayIndexComp = self.dictObj.FDArray.getCompiler(strings, self) 2536 children.append(fdArrayIndexComp) 2537 children.extend(fdArrayIndexComp.getChildren(strings)) 2538 if hasattr(self.dictObj, "Private"): 2539 privComp = self.dictObj.Private.getCompiler(strings, self) 2540 children.append(privComp) 2541 children.extend(privComp.getChildren(strings)) 2542 return children 2543 2544 2545class FontDictCompiler(DictCompiler): 2546 opcodes = buildOpcodeDict(topDictOperators) 2547 2548 def __init__(self, dictObj, strings, parent, isCFF2=None): 2549 super(FontDictCompiler, self).__init__(dictObj, strings, parent, isCFF2=isCFF2) 2550 # 2551 # We now take some effort to detect if there were any key/value pairs 2552 # supplied that were ignored in the FontDict context, and issue a warning 2553 # for those cases. 2554 # 2555 ignoredNames = [] 2556 dictObj = self.dictObj 2557 for name in sorted(set(dictObj.converters) - set(dictObj.order)): 2558 if name in dictObj.rawDict: 2559 # The font was directly read from binary. In this 2560 # case, we want to report *all* "useless" key/value 2561 # pairs that are in the font, not just the ones that 2562 # are different from the default. 2563 ignoredNames.append(name) 2564 else: 2565 # The font was probably read from a TTX file. We only 2566 # warn about keys whos value is not the default. The 2567 # ones that have the default value will not be written 2568 # to binary anyway. 2569 default = dictObj.defaults.get(name) 2570 if default is not None: 2571 conv = dictObj.converters[name] 2572 default = conv.read(dictObj, default) 2573 if getattr(dictObj, name, None) != default: 2574 ignoredNames.append(name) 2575 if ignoredNames: 2576 log.warning( 2577 "Some CFF FDArray/FontDict keys were ignored upon compile: " 2578 + " ".join(sorted(ignoredNames)) 2579 ) 2580 2581 def getChildren(self, strings): 2582 children = [] 2583 if hasattr(self.dictObj, "Private"): 2584 privComp = self.dictObj.Private.getCompiler(strings, self) 2585 children.append(privComp) 2586 children.extend(privComp.getChildren(strings)) 2587 return children 2588 2589 2590class PrivateDictCompiler(DictCompiler): 2591 maxBlendStack = maxStackLimit 2592 opcodes = buildOpcodeDict(privateDictOperators) 2593 2594 def setPos(self, pos, endPos): 2595 size = endPos - pos 2596 self.parent.rawDict["Private"] = size, pos 2597 self.pos = pos 2598 2599 def getChildren(self, strings): 2600 children = [] 2601 if hasattr(self.dictObj, "Subrs"): 2602 children.append(self.dictObj.Subrs.getCompiler(strings, self)) 2603 return children 2604 2605 2606class BaseDict(object): 2607 def __init__(self, strings=None, file=None, offset=None, isCFF2=None): 2608 assert (isCFF2 is None) == (file is None) 2609 self.rawDict = {} 2610 self.skipNames = [] 2611 self.strings = strings 2612 if file is None: 2613 return 2614 self._isCFF2 = isCFF2 2615 self.file = file 2616 if offset is not None: 2617 log.log(DEBUG, "loading %s at %s", self.__class__.__name__, offset) 2618 self.offset = offset 2619 2620 def decompile(self, data): 2621 log.log(DEBUG, " length %s is %d", self.__class__.__name__, len(data)) 2622 dec = self.decompilerClass(self.strings, self) 2623 dec.decompile(data) 2624 self.rawDict = dec.getDict() 2625 self.postDecompile() 2626 2627 def postDecompile(self): 2628 pass 2629 2630 def getCompiler(self, strings, parent, isCFF2=None): 2631 return self.compilerClass(self, strings, parent, isCFF2=isCFF2) 2632 2633 def __getattr__(self, name): 2634 if name[:2] == name[-2:] == "__": 2635 # to make deepcopy() and pickle.load() work, we need to signal with 2636 # AttributeError that dunder methods like '__deepcopy__' or '__getstate__' 2637 # aren't implemented. For more details, see: 2638 # https://github.com/fonttools/fonttools/pull/1488 2639 raise AttributeError(name) 2640 value = self.rawDict.get(name, None) 2641 if value is None: 2642 value = self.defaults.get(name) 2643 if value is None: 2644 raise AttributeError(name) 2645 conv = self.converters[name] 2646 value = conv.read(self, value) 2647 setattr(self, name, value) 2648 return value 2649 2650 def toXML(self, xmlWriter): 2651 for name in self.order: 2652 if name in self.skipNames: 2653 continue 2654 value = getattr(self, name, None) 2655 # XXX For "charset" we never skip calling xmlWrite even if the 2656 # value is None, so we always write the following XML comment: 2657 # 2658 # <!-- charset is dumped separately as the 'GlyphOrder' element --> 2659 # 2660 # Charset is None when 'CFF ' table is imported from XML into an 2661 # empty TTFont(). By writing this comment all the time, we obtain 2662 # the same XML output whether roundtripping XML-to-XML or 2663 # dumping binary-to-XML 2664 if value is None and name != "charset": 2665 continue 2666 conv = self.converters[name] 2667 conv.xmlWrite(xmlWriter, name, value) 2668 ignoredNames = set(self.rawDict) - set(self.order) 2669 if ignoredNames: 2670 xmlWriter.comment( 2671 "some keys were ignored: %s" % " ".join(sorted(ignoredNames)) 2672 ) 2673 xmlWriter.newline() 2674 2675 def fromXML(self, name, attrs, content): 2676 conv = self.converters[name] 2677 value = conv.xmlRead(name, attrs, content, self) 2678 setattr(self, name, value) 2679 2680 2681class TopDict(BaseDict): 2682 """The ``TopDict`` represents the top-level dictionary holding font 2683 information. CFF2 tables contain a restricted set of top-level entries 2684 as described `here <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#7-top-dict-data>`_, 2685 but CFF tables may contain a wider range of information. This information 2686 can be accessed through attributes or through the dictionary returned 2687 through the ``rawDict`` property: 2688 2689 .. code:: python 2690 2691 font = tt["CFF "].cff[0] 2692 font.FamilyName 2693 # 'Linux Libertine O' 2694 font.rawDict["FamilyName"] 2695 # 'Linux Libertine O' 2696 2697 More information is available in the CFF file's private dictionary, accessed 2698 via the ``Private`` property: 2699 2700 .. code:: python 2701 2702 tt["CFF "].cff[0].Private.BlueValues 2703 # [-15, 0, 515, 515, 666, 666] 2704 2705 """ 2706 2707 defaults = buildDefaults(topDictOperators) 2708 converters = buildConverters(topDictOperators) 2709 compilerClass = TopDictCompiler 2710 order = buildOrder(topDictOperators) 2711 decompilerClass = TopDictDecompiler 2712 2713 def __init__( 2714 self, 2715 strings=None, 2716 file=None, 2717 offset=None, 2718 GlobalSubrs=None, 2719 cff2GetGlyphOrder=None, 2720 isCFF2=None, 2721 ): 2722 super(TopDict, self).__init__(strings, file, offset, isCFF2=isCFF2) 2723 self.cff2GetGlyphOrder = cff2GetGlyphOrder 2724 self.GlobalSubrs = GlobalSubrs 2725 if isCFF2: 2726 self.defaults = buildDefaults(topDictOperators2) 2727 self.charset = cff2GetGlyphOrder() 2728 self.order = buildOrder(topDictOperators2) 2729 else: 2730 self.defaults = buildDefaults(topDictOperators) 2731 self.order = buildOrder(topDictOperators) 2732 2733 def getGlyphOrder(self): 2734 """Returns a list of glyph names in the CFF font.""" 2735 return self.charset 2736 2737 def postDecompile(self): 2738 offset = self.rawDict.get("CharStrings") 2739 if offset is None: 2740 return 2741 # get the number of glyphs beforehand. 2742 self.file.seek(offset) 2743 if self._isCFF2: 2744 self.numGlyphs = readCard32(self.file) 2745 else: 2746 self.numGlyphs = readCard16(self.file) 2747 2748 def toXML(self, xmlWriter): 2749 if hasattr(self, "CharStrings"): 2750 self.decompileAllCharStrings() 2751 if hasattr(self, "ROS"): 2752 self.skipNames = ["Encoding"] 2753 if not hasattr(self, "ROS") or not hasattr(self, "CharStrings"): 2754 # these values have default values, but I only want them to show up 2755 # in CID fonts. 2756 self.skipNames = [ 2757 "CIDFontVersion", 2758 "CIDFontRevision", 2759 "CIDFontType", 2760 "CIDCount", 2761 ] 2762 BaseDict.toXML(self, xmlWriter) 2763 2764 def decompileAllCharStrings(self): 2765 # Make sure that all the Private Dicts have been instantiated. 2766 for i, charString in enumerate(self.CharStrings.values()): 2767 try: 2768 charString.decompile() 2769 except: 2770 log.error("Error in charstring %s", i) 2771 raise 2772 2773 def recalcFontBBox(self): 2774 fontBBox = None 2775 for charString in self.CharStrings.values(): 2776 bounds = charString.calcBounds(self.CharStrings) 2777 if bounds is not None: 2778 if fontBBox is not None: 2779 fontBBox = unionRect(fontBBox, bounds) 2780 else: 2781 fontBBox = bounds 2782 2783 if fontBBox is None: 2784 self.FontBBox = self.defaults["FontBBox"][:] 2785 else: 2786 self.FontBBox = list(intRect(fontBBox)) 2787 2788 2789class FontDict(BaseDict): 2790 # 2791 # Since fonttools used to pass a lot of fields that are not relevant in the FDArray 2792 # FontDict, there are 'ttx' files in the wild that contain all these. These got in 2793 # the ttx files because fonttools writes explicit values for all the TopDict default 2794 # values. These are not actually illegal in the context of an FDArray FontDict - you 2795 # can legally, per spec, put any arbitrary key/value pair in a FontDict - but are 2796 # useless since current major company CFF interpreters ignore anything but the set 2797 # listed in this file. So, we just silently skip them. An exception is Weight: this 2798 # is not used by any interpreter, but some foundries have asked that this be 2799 # supported in FDArray FontDicts just to preserve information about the design when 2800 # the font is being inspected. 2801 # 2802 # On top of that, there are fonts out there that contain such useless FontDict values. 2803 # 2804 # By subclassing TopDict, we *allow* all key/values from TopDict, both when reading 2805 # from binary or when reading from XML, but by overriding `order` with a limited 2806 # list of names, we ensure that only the useful names ever get exported to XML and 2807 # ever get compiled into the binary font. 2808 # 2809 # We override compilerClass so we can warn about "useless" key/value pairs, either 2810 # from the original binary font or from TTX input. 2811 # 2812 # See: 2813 # - https://github.com/fonttools/fonttools/issues/740 2814 # - https://github.com/fonttools/fonttools/issues/601 2815 # - https://github.com/adobe-type-tools/afdko/issues/137 2816 # 2817 defaults = {} 2818 converters = buildConverters(topDictOperators) 2819 compilerClass = FontDictCompiler 2820 orderCFF = ["FontName", "FontMatrix", "Weight", "Private"] 2821 orderCFF2 = ["Private"] 2822 decompilerClass = TopDictDecompiler 2823 2824 def __init__( 2825 self, 2826 strings=None, 2827 file=None, 2828 offset=None, 2829 GlobalSubrs=None, 2830 isCFF2=None, 2831 vstore=None, 2832 ): 2833 super(FontDict, self).__init__(strings, file, offset, isCFF2=isCFF2) 2834 self.vstore = vstore 2835 self.setCFF2(isCFF2) 2836 2837 def setCFF2(self, isCFF2): 2838 # isCFF2 may be None. 2839 if isCFF2: 2840 self.order = self.orderCFF2 2841 self._isCFF2 = True 2842 else: 2843 self.order = self.orderCFF 2844 self._isCFF2 = False 2845 2846 2847class PrivateDict(BaseDict): 2848 defaults = buildDefaults(privateDictOperators) 2849 converters = buildConverters(privateDictOperators) 2850 order = buildOrder(privateDictOperators) 2851 decompilerClass = PrivateDictDecompiler 2852 compilerClass = PrivateDictCompiler 2853 2854 def __init__(self, strings=None, file=None, offset=None, isCFF2=None, vstore=None): 2855 super(PrivateDict, self).__init__(strings, file, offset, isCFF2=isCFF2) 2856 self.vstore = vstore 2857 if isCFF2: 2858 self.defaults = buildDefaults(privateDictOperators2) 2859 self.order = buildOrder(privateDictOperators2) 2860 # Provide dummy values. This avoids needing to provide 2861 # an isCFF2 state in a lot of places. 2862 self.nominalWidthX = self.defaultWidthX = None 2863 else: 2864 self.defaults = buildDefaults(privateDictOperators) 2865 self.order = buildOrder(privateDictOperators) 2866 2867 @property 2868 def in_cff2(self): 2869 return self._isCFF2 2870 2871 def getNumRegions(self, vi=None): # called from misc/psCharStrings.py 2872 # if getNumRegions is being called, we can assume that VarStore exists. 2873 if vi is None: 2874 if hasattr(self, "vsindex"): 2875 vi = self.vsindex 2876 else: 2877 vi = 0 2878 numRegions = self.vstore.getNumRegions(vi) 2879 return numRegions 2880 2881 2882class IndexedStrings(object): 2883 """SID -> string mapping.""" 2884 2885 def __init__(self, file=None): 2886 if file is None: 2887 strings = [] 2888 else: 2889 strings = [tostr(s, encoding="latin1") for s in Index(file, isCFF2=False)] 2890 self.strings = strings 2891 2892 def getCompiler(self): 2893 return IndexedStringsCompiler(self, None, self, isCFF2=False) 2894 2895 def __len__(self): 2896 return len(self.strings) 2897 2898 def __getitem__(self, SID): 2899 if SID < cffStandardStringCount: 2900 return cffStandardStrings[SID] 2901 else: 2902 return self.strings[SID - cffStandardStringCount] 2903 2904 def getSID(self, s): 2905 if not hasattr(self, "stringMapping"): 2906 self.buildStringMapping() 2907 s = tostr(s, encoding="latin1") 2908 if s in cffStandardStringMapping: 2909 SID = cffStandardStringMapping[s] 2910 elif s in self.stringMapping: 2911 SID = self.stringMapping[s] 2912 else: 2913 SID = len(self.strings) + cffStandardStringCount 2914 self.strings.append(s) 2915 self.stringMapping[s] = SID 2916 return SID 2917 2918 def getStrings(self): 2919 return self.strings 2920 2921 def buildStringMapping(self): 2922 self.stringMapping = {} 2923 for index in range(len(self.strings)): 2924 self.stringMapping[self.strings[index]] = index + cffStandardStringCount 2925 2926 2927# The 391 Standard Strings as used in the CFF format. 2928# from Adobe Technical None #5176, version 1.0, 18 March 1998 2929 2930cffStandardStrings = [ 2931 ".notdef", 2932 "space", 2933 "exclam", 2934 "quotedbl", 2935 "numbersign", 2936 "dollar", 2937 "percent", 2938 "ampersand", 2939 "quoteright", 2940 "parenleft", 2941 "parenright", 2942 "asterisk", 2943 "plus", 2944 "comma", 2945 "hyphen", 2946 "period", 2947 "slash", 2948 "zero", 2949 "one", 2950 "two", 2951 "three", 2952 "four", 2953 "five", 2954 "six", 2955 "seven", 2956 "eight", 2957 "nine", 2958 "colon", 2959 "semicolon", 2960 "less", 2961 "equal", 2962 "greater", 2963 "question", 2964 "at", 2965 "A", 2966 "B", 2967 "C", 2968 "D", 2969 "E", 2970 "F", 2971 "G", 2972 "H", 2973 "I", 2974 "J", 2975 "K", 2976 "L", 2977 "M", 2978 "N", 2979 "O", 2980 "P", 2981 "Q", 2982 "R", 2983 "S", 2984 "T", 2985 "U", 2986 "V", 2987 "W", 2988 "X", 2989 "Y", 2990 "Z", 2991 "bracketleft", 2992 "backslash", 2993 "bracketright", 2994 "asciicircum", 2995 "underscore", 2996 "quoteleft", 2997 "a", 2998 "b", 2999 "c", 3000 "d", 3001 "e", 3002 "f", 3003 "g", 3004 "h", 3005 "i", 3006 "j", 3007 "k", 3008 "l", 3009 "m", 3010 "n", 3011 "o", 3012 "p", 3013 "q", 3014 "r", 3015 "s", 3016 "t", 3017 "u", 3018 "v", 3019 "w", 3020 "x", 3021 "y", 3022 "z", 3023 "braceleft", 3024 "bar", 3025 "braceright", 3026 "asciitilde", 3027 "exclamdown", 3028 "cent", 3029 "sterling", 3030 "fraction", 3031 "yen", 3032 "florin", 3033 "section", 3034 "currency", 3035 "quotesingle", 3036 "quotedblleft", 3037 "guillemotleft", 3038 "guilsinglleft", 3039 "guilsinglright", 3040 "fi", 3041 "fl", 3042 "endash", 3043 "dagger", 3044 "daggerdbl", 3045 "periodcentered", 3046 "paragraph", 3047 "bullet", 3048 "quotesinglbase", 3049 "quotedblbase", 3050 "quotedblright", 3051 "guillemotright", 3052 "ellipsis", 3053 "perthousand", 3054 "questiondown", 3055 "grave", 3056 "acute", 3057 "circumflex", 3058 "tilde", 3059 "macron", 3060 "breve", 3061 "dotaccent", 3062 "dieresis", 3063 "ring", 3064 "cedilla", 3065 "hungarumlaut", 3066 "ogonek", 3067 "caron", 3068 "emdash", 3069 "AE", 3070 "ordfeminine", 3071 "Lslash", 3072 "Oslash", 3073 "OE", 3074 "ordmasculine", 3075 "ae", 3076 "dotlessi", 3077 "lslash", 3078 "oslash", 3079 "oe", 3080 "germandbls", 3081 "onesuperior", 3082 "logicalnot", 3083 "mu", 3084 "trademark", 3085 "Eth", 3086 "onehalf", 3087 "plusminus", 3088 "Thorn", 3089 "onequarter", 3090 "divide", 3091 "brokenbar", 3092 "degree", 3093 "thorn", 3094 "threequarters", 3095 "twosuperior", 3096 "registered", 3097 "minus", 3098 "eth", 3099 "multiply", 3100 "threesuperior", 3101 "copyright", 3102 "Aacute", 3103 "Acircumflex", 3104 "Adieresis", 3105 "Agrave", 3106 "Aring", 3107 "Atilde", 3108 "Ccedilla", 3109 "Eacute", 3110 "Ecircumflex", 3111 "Edieresis", 3112 "Egrave", 3113 "Iacute", 3114 "Icircumflex", 3115 "Idieresis", 3116 "Igrave", 3117 "Ntilde", 3118 "Oacute", 3119 "Ocircumflex", 3120 "Odieresis", 3121 "Ograve", 3122 "Otilde", 3123 "Scaron", 3124 "Uacute", 3125 "Ucircumflex", 3126 "Udieresis", 3127 "Ugrave", 3128 "Yacute", 3129 "Ydieresis", 3130 "Zcaron", 3131 "aacute", 3132 "acircumflex", 3133 "adieresis", 3134 "agrave", 3135 "aring", 3136 "atilde", 3137 "ccedilla", 3138 "eacute", 3139 "ecircumflex", 3140 "edieresis", 3141 "egrave", 3142 "iacute", 3143 "icircumflex", 3144 "idieresis", 3145 "igrave", 3146 "ntilde", 3147 "oacute", 3148 "ocircumflex", 3149 "odieresis", 3150 "ograve", 3151 "otilde", 3152 "scaron", 3153 "uacute", 3154 "ucircumflex", 3155 "udieresis", 3156 "ugrave", 3157 "yacute", 3158 "ydieresis", 3159 "zcaron", 3160 "exclamsmall", 3161 "Hungarumlautsmall", 3162 "dollaroldstyle", 3163 "dollarsuperior", 3164 "ampersandsmall", 3165 "Acutesmall", 3166 "parenleftsuperior", 3167 "parenrightsuperior", 3168 "twodotenleader", 3169 "onedotenleader", 3170 "zerooldstyle", 3171 "oneoldstyle", 3172 "twooldstyle", 3173 "threeoldstyle", 3174 "fouroldstyle", 3175 "fiveoldstyle", 3176 "sixoldstyle", 3177 "sevenoldstyle", 3178 "eightoldstyle", 3179 "nineoldstyle", 3180 "commasuperior", 3181 "threequartersemdash", 3182 "periodsuperior", 3183 "questionsmall", 3184 "asuperior", 3185 "bsuperior", 3186 "centsuperior", 3187 "dsuperior", 3188 "esuperior", 3189 "isuperior", 3190 "lsuperior", 3191 "msuperior", 3192 "nsuperior", 3193 "osuperior", 3194 "rsuperior", 3195 "ssuperior", 3196 "tsuperior", 3197 "ff", 3198 "ffi", 3199 "ffl", 3200 "parenleftinferior", 3201 "parenrightinferior", 3202 "Circumflexsmall", 3203 "hyphensuperior", 3204 "Gravesmall", 3205 "Asmall", 3206 "Bsmall", 3207 "Csmall", 3208 "Dsmall", 3209 "Esmall", 3210 "Fsmall", 3211 "Gsmall", 3212 "Hsmall", 3213 "Ismall", 3214 "Jsmall", 3215 "Ksmall", 3216 "Lsmall", 3217 "Msmall", 3218 "Nsmall", 3219 "Osmall", 3220 "Psmall", 3221 "Qsmall", 3222 "Rsmall", 3223 "Ssmall", 3224 "Tsmall", 3225 "Usmall", 3226 "Vsmall", 3227 "Wsmall", 3228 "Xsmall", 3229 "Ysmall", 3230 "Zsmall", 3231 "colonmonetary", 3232 "onefitted", 3233 "rupiah", 3234 "Tildesmall", 3235 "exclamdownsmall", 3236 "centoldstyle", 3237 "Lslashsmall", 3238 "Scaronsmall", 3239 "Zcaronsmall", 3240 "Dieresissmall", 3241 "Brevesmall", 3242 "Caronsmall", 3243 "Dotaccentsmall", 3244 "Macronsmall", 3245 "figuredash", 3246 "hypheninferior", 3247 "Ogoneksmall", 3248 "Ringsmall", 3249 "Cedillasmall", 3250 "questiondownsmall", 3251 "oneeighth", 3252 "threeeighths", 3253 "fiveeighths", 3254 "seveneighths", 3255 "onethird", 3256 "twothirds", 3257 "zerosuperior", 3258 "foursuperior", 3259 "fivesuperior", 3260 "sixsuperior", 3261 "sevensuperior", 3262 "eightsuperior", 3263 "ninesuperior", 3264 "zeroinferior", 3265 "oneinferior", 3266 "twoinferior", 3267 "threeinferior", 3268 "fourinferior", 3269 "fiveinferior", 3270 "sixinferior", 3271 "seveninferior", 3272 "eightinferior", 3273 "nineinferior", 3274 "centinferior", 3275 "dollarinferior", 3276 "periodinferior", 3277 "commainferior", 3278 "Agravesmall", 3279 "Aacutesmall", 3280 "Acircumflexsmall", 3281 "Atildesmall", 3282 "Adieresissmall", 3283 "Aringsmall", 3284 "AEsmall", 3285 "Ccedillasmall", 3286 "Egravesmall", 3287 "Eacutesmall", 3288 "Ecircumflexsmall", 3289 "Edieresissmall", 3290 "Igravesmall", 3291 "Iacutesmall", 3292 "Icircumflexsmall", 3293 "Idieresissmall", 3294 "Ethsmall", 3295 "Ntildesmall", 3296 "Ogravesmall", 3297 "Oacutesmall", 3298 "Ocircumflexsmall", 3299 "Otildesmall", 3300 "Odieresissmall", 3301 "OEsmall", 3302 "Oslashsmall", 3303 "Ugravesmall", 3304 "Uacutesmall", 3305 "Ucircumflexsmall", 3306 "Udieresissmall", 3307 "Yacutesmall", 3308 "Thornsmall", 3309 "Ydieresissmall", 3310 "001.000", 3311 "001.001", 3312 "001.002", 3313 "001.003", 3314 "Black", 3315 "Bold", 3316 "Book", 3317 "Light", 3318 "Medium", 3319 "Regular", 3320 "Roman", 3321 "Semibold", 3322] 3323 3324cffStandardStringCount = 391 3325assert len(cffStandardStrings) == cffStandardStringCount 3326# build reverse mapping 3327cffStandardStringMapping = {} 3328for _i in range(cffStandardStringCount): 3329 cffStandardStringMapping[cffStandardStrings[_i]] = _i 3330 3331cffISOAdobeStrings = [ 3332 ".notdef", 3333 "space", 3334 "exclam", 3335 "quotedbl", 3336 "numbersign", 3337 "dollar", 3338 "percent", 3339 "ampersand", 3340 "quoteright", 3341 "parenleft", 3342 "parenright", 3343 "asterisk", 3344 "plus", 3345 "comma", 3346 "hyphen", 3347 "period", 3348 "slash", 3349 "zero", 3350 "one", 3351 "two", 3352 "three", 3353 "four", 3354 "five", 3355 "six", 3356 "seven", 3357 "eight", 3358 "nine", 3359 "colon", 3360 "semicolon", 3361 "less", 3362 "equal", 3363 "greater", 3364 "question", 3365 "at", 3366 "A", 3367 "B", 3368 "C", 3369 "D", 3370 "E", 3371 "F", 3372 "G", 3373 "H", 3374 "I", 3375 "J", 3376 "K", 3377 "L", 3378 "M", 3379 "N", 3380 "O", 3381 "P", 3382 "Q", 3383 "R", 3384 "S", 3385 "T", 3386 "U", 3387 "V", 3388 "W", 3389 "X", 3390 "Y", 3391 "Z", 3392 "bracketleft", 3393 "backslash", 3394 "bracketright", 3395 "asciicircum", 3396 "underscore", 3397 "quoteleft", 3398 "a", 3399 "b", 3400 "c", 3401 "d", 3402 "e", 3403 "f", 3404 "g", 3405 "h", 3406 "i", 3407 "j", 3408 "k", 3409 "l", 3410 "m", 3411 "n", 3412 "o", 3413 "p", 3414 "q", 3415 "r", 3416 "s", 3417 "t", 3418 "u", 3419 "v", 3420 "w", 3421 "x", 3422 "y", 3423 "z", 3424 "braceleft", 3425 "bar", 3426 "braceright", 3427 "asciitilde", 3428 "exclamdown", 3429 "cent", 3430 "sterling", 3431 "fraction", 3432 "yen", 3433 "florin", 3434 "section", 3435 "currency", 3436 "quotesingle", 3437 "quotedblleft", 3438 "guillemotleft", 3439 "guilsinglleft", 3440 "guilsinglright", 3441 "fi", 3442 "fl", 3443 "endash", 3444 "dagger", 3445 "daggerdbl", 3446 "periodcentered", 3447 "paragraph", 3448 "bullet", 3449 "quotesinglbase", 3450 "quotedblbase", 3451 "quotedblright", 3452 "guillemotright", 3453 "ellipsis", 3454 "perthousand", 3455 "questiondown", 3456 "grave", 3457 "acute", 3458 "circumflex", 3459 "tilde", 3460 "macron", 3461 "breve", 3462 "dotaccent", 3463 "dieresis", 3464 "ring", 3465 "cedilla", 3466 "hungarumlaut", 3467 "ogonek", 3468 "caron", 3469 "emdash", 3470 "AE", 3471 "ordfeminine", 3472 "Lslash", 3473 "Oslash", 3474 "OE", 3475 "ordmasculine", 3476 "ae", 3477 "dotlessi", 3478 "lslash", 3479 "oslash", 3480 "oe", 3481 "germandbls", 3482 "onesuperior", 3483 "logicalnot", 3484 "mu", 3485 "trademark", 3486 "Eth", 3487 "onehalf", 3488 "plusminus", 3489 "Thorn", 3490 "onequarter", 3491 "divide", 3492 "brokenbar", 3493 "degree", 3494 "thorn", 3495 "threequarters", 3496 "twosuperior", 3497 "registered", 3498 "minus", 3499 "eth", 3500 "multiply", 3501 "threesuperior", 3502 "copyright", 3503 "Aacute", 3504 "Acircumflex", 3505 "Adieresis", 3506 "Agrave", 3507 "Aring", 3508 "Atilde", 3509 "Ccedilla", 3510 "Eacute", 3511 "Ecircumflex", 3512 "Edieresis", 3513 "Egrave", 3514 "Iacute", 3515 "Icircumflex", 3516 "Idieresis", 3517 "Igrave", 3518 "Ntilde", 3519 "Oacute", 3520 "Ocircumflex", 3521 "Odieresis", 3522 "Ograve", 3523 "Otilde", 3524 "Scaron", 3525 "Uacute", 3526 "Ucircumflex", 3527 "Udieresis", 3528 "Ugrave", 3529 "Yacute", 3530 "Ydieresis", 3531 "Zcaron", 3532 "aacute", 3533 "acircumflex", 3534 "adieresis", 3535 "agrave", 3536 "aring", 3537 "atilde", 3538 "ccedilla", 3539 "eacute", 3540 "ecircumflex", 3541 "edieresis", 3542 "egrave", 3543 "iacute", 3544 "icircumflex", 3545 "idieresis", 3546 "igrave", 3547 "ntilde", 3548 "oacute", 3549 "ocircumflex", 3550 "odieresis", 3551 "ograve", 3552 "otilde", 3553 "scaron", 3554 "uacute", 3555 "ucircumflex", 3556 "udieresis", 3557 "ugrave", 3558 "yacute", 3559 "ydieresis", 3560 "zcaron", 3561] 3562 3563cffISOAdobeStringCount = 229 3564assert len(cffISOAdobeStrings) == cffISOAdobeStringCount 3565 3566cffIExpertStrings = [ 3567 ".notdef", 3568 "space", 3569 "exclamsmall", 3570 "Hungarumlautsmall", 3571 "dollaroldstyle", 3572 "dollarsuperior", 3573 "ampersandsmall", 3574 "Acutesmall", 3575 "parenleftsuperior", 3576 "parenrightsuperior", 3577 "twodotenleader", 3578 "onedotenleader", 3579 "comma", 3580 "hyphen", 3581 "period", 3582 "fraction", 3583 "zerooldstyle", 3584 "oneoldstyle", 3585 "twooldstyle", 3586 "threeoldstyle", 3587 "fouroldstyle", 3588 "fiveoldstyle", 3589 "sixoldstyle", 3590 "sevenoldstyle", 3591 "eightoldstyle", 3592 "nineoldstyle", 3593 "colon", 3594 "semicolon", 3595 "commasuperior", 3596 "threequartersemdash", 3597 "periodsuperior", 3598 "questionsmall", 3599 "asuperior", 3600 "bsuperior", 3601 "centsuperior", 3602 "dsuperior", 3603 "esuperior", 3604 "isuperior", 3605 "lsuperior", 3606 "msuperior", 3607 "nsuperior", 3608 "osuperior", 3609 "rsuperior", 3610 "ssuperior", 3611 "tsuperior", 3612 "ff", 3613 "fi", 3614 "fl", 3615 "ffi", 3616 "ffl", 3617 "parenleftinferior", 3618 "parenrightinferior", 3619 "Circumflexsmall", 3620 "hyphensuperior", 3621 "Gravesmall", 3622 "Asmall", 3623 "Bsmall", 3624 "Csmall", 3625 "Dsmall", 3626 "Esmall", 3627 "Fsmall", 3628 "Gsmall", 3629 "Hsmall", 3630 "Ismall", 3631 "Jsmall", 3632 "Ksmall", 3633 "Lsmall", 3634 "Msmall", 3635 "Nsmall", 3636 "Osmall", 3637 "Psmall", 3638 "Qsmall", 3639 "Rsmall", 3640 "Ssmall", 3641 "Tsmall", 3642 "Usmall", 3643 "Vsmall", 3644 "Wsmall", 3645 "Xsmall", 3646 "Ysmall", 3647 "Zsmall", 3648 "colonmonetary", 3649 "onefitted", 3650 "rupiah", 3651 "Tildesmall", 3652 "exclamdownsmall", 3653 "centoldstyle", 3654 "Lslashsmall", 3655 "Scaronsmall", 3656 "Zcaronsmall", 3657 "Dieresissmall", 3658 "Brevesmall", 3659 "Caronsmall", 3660 "Dotaccentsmall", 3661 "Macronsmall", 3662 "figuredash", 3663 "hypheninferior", 3664 "Ogoneksmall", 3665 "Ringsmall", 3666 "Cedillasmall", 3667 "onequarter", 3668 "onehalf", 3669 "threequarters", 3670 "questiondownsmall", 3671 "oneeighth", 3672 "threeeighths", 3673 "fiveeighths", 3674 "seveneighths", 3675 "onethird", 3676 "twothirds", 3677 "zerosuperior", 3678 "onesuperior", 3679 "twosuperior", 3680 "threesuperior", 3681 "foursuperior", 3682 "fivesuperior", 3683 "sixsuperior", 3684 "sevensuperior", 3685 "eightsuperior", 3686 "ninesuperior", 3687 "zeroinferior", 3688 "oneinferior", 3689 "twoinferior", 3690 "threeinferior", 3691 "fourinferior", 3692 "fiveinferior", 3693 "sixinferior", 3694 "seveninferior", 3695 "eightinferior", 3696 "nineinferior", 3697 "centinferior", 3698 "dollarinferior", 3699 "periodinferior", 3700 "commainferior", 3701 "Agravesmall", 3702 "Aacutesmall", 3703 "Acircumflexsmall", 3704 "Atildesmall", 3705 "Adieresissmall", 3706 "Aringsmall", 3707 "AEsmall", 3708 "Ccedillasmall", 3709 "Egravesmall", 3710 "Eacutesmall", 3711 "Ecircumflexsmall", 3712 "Edieresissmall", 3713 "Igravesmall", 3714 "Iacutesmall", 3715 "Icircumflexsmall", 3716 "Idieresissmall", 3717 "Ethsmall", 3718 "Ntildesmall", 3719 "Ogravesmall", 3720 "Oacutesmall", 3721 "Ocircumflexsmall", 3722 "Otildesmall", 3723 "Odieresissmall", 3724 "OEsmall", 3725 "Oslashsmall", 3726 "Ugravesmall", 3727 "Uacutesmall", 3728 "Ucircumflexsmall", 3729 "Udieresissmall", 3730 "Yacutesmall", 3731 "Thornsmall", 3732 "Ydieresissmall", 3733] 3734 3735cffExpertStringCount = 166 3736assert len(cffIExpertStrings) == cffExpertStringCount 3737 3738cffExpertSubsetStrings = [ 3739 ".notdef", 3740 "space", 3741 "dollaroldstyle", 3742 "dollarsuperior", 3743 "parenleftsuperior", 3744 "parenrightsuperior", 3745 "twodotenleader", 3746 "onedotenleader", 3747 "comma", 3748 "hyphen", 3749 "period", 3750 "fraction", 3751 "zerooldstyle", 3752 "oneoldstyle", 3753 "twooldstyle", 3754 "threeoldstyle", 3755 "fouroldstyle", 3756 "fiveoldstyle", 3757 "sixoldstyle", 3758 "sevenoldstyle", 3759 "eightoldstyle", 3760 "nineoldstyle", 3761 "colon", 3762 "semicolon", 3763 "commasuperior", 3764 "threequartersemdash", 3765 "periodsuperior", 3766 "asuperior", 3767 "bsuperior", 3768 "centsuperior", 3769 "dsuperior", 3770 "esuperior", 3771 "isuperior", 3772 "lsuperior", 3773 "msuperior", 3774 "nsuperior", 3775 "osuperior", 3776 "rsuperior", 3777 "ssuperior", 3778 "tsuperior", 3779 "ff", 3780 "fi", 3781 "fl", 3782 "ffi", 3783 "ffl", 3784 "parenleftinferior", 3785 "parenrightinferior", 3786 "hyphensuperior", 3787 "colonmonetary", 3788 "onefitted", 3789 "rupiah", 3790 "centoldstyle", 3791 "figuredash", 3792 "hypheninferior", 3793 "onequarter", 3794 "onehalf", 3795 "threequarters", 3796 "oneeighth", 3797 "threeeighths", 3798 "fiveeighths", 3799 "seveneighths", 3800 "onethird", 3801 "twothirds", 3802 "zerosuperior", 3803 "onesuperior", 3804 "twosuperior", 3805 "threesuperior", 3806 "foursuperior", 3807 "fivesuperior", 3808 "sixsuperior", 3809 "sevensuperior", 3810 "eightsuperior", 3811 "ninesuperior", 3812 "zeroinferior", 3813 "oneinferior", 3814 "twoinferior", 3815 "threeinferior", 3816 "fourinferior", 3817 "fiveinferior", 3818 "sixinferior", 3819 "seveninferior", 3820 "eightinferior", 3821 "nineinferior", 3822 "centinferior", 3823 "dollarinferior", 3824 "periodinferior", 3825 "commainferior", 3826] 3827 3828cffExpertSubsetStringCount = 87 3829assert len(cffExpertSubsetStrings) == cffExpertSubsetStringCount 3830