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