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