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