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.textTools import safeEval 8 import struct 9 10 DEBUG = 0 11 12 13 cffHeaderFormat = """ 14 major: B 15 minor: B 16 hdrSize: B 17 offSize: B 18 """ 19 20 class CFFFontSet(object): 21 22 def __init__(self): 23 pass 24 25 def decompile(self, file, otFont): 26 sstruct.unpack(cffHeaderFormat, file.read(4), self) 27 assert self.major == 1 and self.minor == 0, \ 28 "unknown CFF format: %d.%d" % (self.major, self.minor) 29 30 file.seek(self.hdrSize) 31 self.fontNames = list(Index(file)) 32 self.topDictIndex = TopDictIndex(file) 33 self.strings = IndexedStrings(file) 34 self.GlobalSubrs = GlobalSubrsIndex(file) 35 self.topDictIndex.strings = self.strings 36 self.topDictIndex.GlobalSubrs = self.GlobalSubrs 37 38 def __len__(self): 39 return len(self.fontNames) 40 41 def keys(self): 42 return list(self.fontNames) 43 44 def values(self): 45 return self.topDictIndex 46 47 def __getitem__(self, name): 48 try: 49 index = self.fontNames.index(name) 50 except ValueError: 51 raise KeyError(name) 52 return self.topDictIndex[index] 53 54 def compile(self, file, otFont): 55 strings = IndexedStrings() 56 writer = CFFWriter() 57 writer.add(sstruct.pack(cffHeaderFormat, self)) 58 fontNames = Index() 59 for name in self.fontNames: 60 fontNames.append(name) 61 writer.add(fontNames.getCompiler(strings, None)) 62 topCompiler = self.topDictIndex.getCompiler(strings, None) 63 writer.add(topCompiler) 64 writer.add(strings.getCompiler()) 65 writer.add(self.GlobalSubrs.getCompiler(strings, None)) 66 67 for topDict in self.topDictIndex: 68 if not hasattr(topDict, "charset") or topDict.charset is None: 69 charset = otFont.getGlyphOrder() 70 topDict.charset = charset 71 72 for child in topCompiler.getChildren(strings): 73 writer.add(child) 74 75 writer.toFile(file) 76 77 def toXML(self, xmlWriter, progress=None): 78 for fontName in self.fontNames: 79 xmlWriter.begintag("CFFFont", name=tostr(fontName)) 80 xmlWriter.newline() 81 font = self[fontName] 82 font.toXML(xmlWriter, progress) 83 xmlWriter.endtag("CFFFont") 84 xmlWriter.newline() 85 xmlWriter.newline() 86 xmlWriter.begintag("GlobalSubrs") 87 xmlWriter.newline() 88 self.GlobalSubrs.toXML(xmlWriter, progress) 89 xmlWriter.endtag("GlobalSubrs") 90 xmlWriter.newline() 91 92 def fromXML(self, name, attrs, content): 93 if not hasattr(self, "GlobalSubrs"): 94 self.GlobalSubrs = GlobalSubrsIndex() 95 self.major = 1 96 self.minor = 0 97 self.hdrSize = 4 98 self.offSize = 4 # XXX ?? 99 if name == "CFFFont": 100 if not hasattr(self, "fontNames"): 101 self.fontNames = [] 102 self.topDictIndex = TopDictIndex() 103 fontName = attrs["name"] 104 topDict = TopDict(GlobalSubrs=self.GlobalSubrs) 105 topDict.charset = None # gets filled in later 106 self.fontNames.append(fontName) 107 self.topDictIndex.append(topDict) 108 for element in content: 109 if isinstance(element, basestring): 110 continue 111 name, attrs, content = element 112 topDict.fromXML(name, attrs, content) 113 elif name == "GlobalSubrs": 114 for element in content: 115 if isinstance(element, basestring): 116 continue 117 name, attrs, content = element 118 subr = psCharStrings.T2CharString() 119 subr.fromXML(name, attrs, content) 120 self.GlobalSubrs.append(subr) 121 122 123 class CFFWriter(object): 124 125 def __init__(self): 126 self.data = [] 127 128 def add(self, table): 129 self.data.append(table) 130 131 def toFile(self, file): 132 lastPosList = None 133 count = 1 134 while True: 135 if DEBUG: 136 print("CFFWriter.toFile() iteration:", count) 137 count = count + 1 138 pos = 0 139 posList = [pos] 140 for item in self.data: 141 if hasattr(item, "getDataLength"): 142 endPos = pos + item.getDataLength() 143 else: 144 endPos = pos + len(item) 145 if hasattr(item, "setPos"): 146 item.setPos(pos, endPos) 147 pos = endPos 148 posList.append(pos) 149 if posList == lastPosList: 150 break 151 lastPosList = posList 152 if DEBUG: 153 print("CFFWriter.toFile() writing to file.") 154 begin = file.tell() 155 posList = [0] 156 for item in self.data: 157 if hasattr(item, "toFile"): 158 item.toFile(file) 159 else: 160 file.write(item) 161 posList.append(file.tell() - begin) 162 assert posList == lastPosList 163 164 165 def calcOffSize(largestOffset): 166 if largestOffset < 0x100: 167 offSize = 1 168 elif largestOffset < 0x10000: 169 offSize = 2 170 elif largestOffset < 0x1000000: 171 offSize = 3 172 else: 173 offSize = 4 174 return offSize 175 176 177 class IndexCompiler(object): 178 179 def __init__(self, items, strings, parent): 180 self.items = self.getItems(items, strings) 181 self.parent = parent 182 183 def getItems(self, items, strings): 184 return items 185 186 def getOffsets(self): 187 pos = 1 188 offsets = [pos] 189 for item in self.items: 190 if hasattr(item, "getDataLength"): 191 pos = pos + item.getDataLength() 192 else: 193 pos = pos + len(item) 194 offsets.append(pos) 195 return offsets 196 197 def getDataLength(self): 198 lastOffset = self.getOffsets()[-1] 199 offSize = calcOffSize(lastOffset) 200 dataLength = ( 201 2 + # count 202 1 + # offSize 203 (len(self.items) + 1) * offSize + # the offsets 204 lastOffset - 1 # size of object data 205 ) 206 return dataLength 207 208 def toFile(self, file): 209 offsets = self.getOffsets() 210 writeCard16(file, len(self.items)) 211 offSize = calcOffSize(offsets[-1]) 212 writeCard8(file, offSize) 213 offSize = -offSize 214 pack = struct.pack 215 for offset in offsets: 216 binOffset = pack(">l", offset)[offSize:] 217 assert len(binOffset) == -offSize 218 file.write(binOffset) 219 for item in self.items: 220 if hasattr(item, "toFile"): 221 item.toFile(file) 222 else: 223 file.write(tobytes(item, encoding="latin1")) 224 225 226 class IndexedStringsCompiler(IndexCompiler): 227 228 def getItems(self, items, strings): 229 return items.strings 230 231 232 class TopDictIndexCompiler(IndexCompiler): 233 234 def getItems(self, items, strings): 235 out = [] 236 for item in items: 237 out.append(item.getCompiler(strings, self)) 238 return out 239 240 def getChildren(self, strings): 241 children = [] 242 for topDict in self.items: 243 children.extend(topDict.getChildren(strings)) 244 return children 245 246 247 class FDArrayIndexCompiler(IndexCompiler): 248 249 def getItems(self, items, strings): 250 out = [] 251 for item in items: 252 out.append(item.getCompiler(strings, self)) 253 return out 254 255 def getChildren(self, strings): 256 children = [] 257 for fontDict in self.items: 258 children.extend(fontDict.getChildren(strings)) 259 return children 260 261 def toFile(self, file): 262 offsets = self.getOffsets() 263 writeCard16(file, len(self.items)) 264 offSize = calcOffSize(offsets[-1]) 265 writeCard8(file, offSize) 266 offSize = -offSize 267 pack = struct.pack 268 for offset in offsets: 269 binOffset = pack(">l", offset)[offSize:] 270 assert len(binOffset) == -offSize 271 file.write(binOffset) 272 for item in self.items: 273 if hasattr(item, "toFile"): 274 item.toFile(file) 275 else: 276 file.write(item) 277 278 def setPos(self, pos, endPos): 279 self.parent.rawDict["FDArray"] = pos 280 281 282 class GlobalSubrsCompiler(IndexCompiler): 283 def getItems(self, items, strings): 284 out = [] 285 for cs in items: 286 cs.compile() 287 out.append(cs.bytecode) 288 return out 289 290 class SubrsCompiler(GlobalSubrsCompiler): 291 def setPos(self, pos, endPos): 292 offset = pos - self.parent.pos 293 self.parent.rawDict["Subrs"] = offset 294 295 class CharStringsCompiler(GlobalSubrsCompiler): 296 def setPos(self, pos, endPos): 297 self.parent.rawDict["CharStrings"] = pos 298 299 300 class Index(object): 301 302 """This class represents what the CFF spec calls an INDEX.""" 303 304 compilerClass = IndexCompiler 305 306 def __init__(self, file=None): 307 name = self.__class__.__name__ 308 if file is None: 309 self.items = [] 310 return 311 if DEBUG: 312 print("loading %s at %s" % (name, file.tell())) 313 self.file = file 314 count = readCard16(file) 315 self.count = count 316 self.items = [None] * count 317 if count == 0: 318 self.items = [] 319 return 320 offSize = readCard8(file) 321 if DEBUG: 322 print(" index count: %s offSize: %s" % (count, offSize)) 323 assert offSize <= 4, "offSize too large: %s" % offSize 324 self.offsets = offsets = [] 325 pad = b'\0' * (4 - offSize) 326 for index in range(count+1): 327 chunk = file.read(offSize) 328 chunk = pad + chunk 329 offset, = struct.unpack(">L", chunk) 330 offsets.append(int(offset)) 331 self.offsetBase = file.tell() - 1 332 file.seek(self.offsetBase + offsets[-1]) # pretend we've read the whole lot 333 if DEBUG: 334 print(" end of %s at %s" % (name, file.tell())) 335 336 def __len__(self): 337 return len(self.items) 338 339 def __getitem__(self, index): 340 item = self.items[index] 341 if item is not None: 342 return item 343 offset = self.offsets[index] + self.offsetBase 344 size = self.offsets[index+1] - self.offsets[index] 345 file = self.file 346 file.seek(offset) 347 data = file.read(size) 348 assert len(data) == size 349 item = self.produceItem(index, data, file, offset, size) 350 self.items[index] = item 351 return item 352 353 def produceItem(self, index, data, file, offset, size): 354 return data 355 356 def append(self, item): 357 self.items.append(item) 358 359 def getCompiler(self, strings, parent): 360 return self.compilerClass(self, strings, parent) 361 362 363 class GlobalSubrsIndex(Index): 364 365 compilerClass = GlobalSubrsCompiler 366 367 def __init__(self, file=None, globalSubrs=None, private=None, fdSelect=None, fdArray=None): 368 Index.__init__(self, file) 369 self.globalSubrs = globalSubrs 370 self.private = private 371 if fdSelect: 372 self.fdSelect = fdSelect 373 if fdArray: 374 self.fdArray = fdArray 375 376 def produceItem(self, index, data, file, offset, size): 377 if self.private is not None: 378 private = self.private 379 elif hasattr(self, 'fdArray') and self.fdArray is not None: 380 private = self.fdArray[self.fdSelect[index]].Private 381 else: 382 private = None 383 return psCharStrings.T2CharString(data, private=private, globalSubrs=self.globalSubrs) 384 385 def toXML(self, xmlWriter, progress): 386 xmlWriter.comment("The 'index' attribute is only for humans; it is ignored when parsed.") 387 xmlWriter.newline() 388 for i in range(len(self)): 389 subr = self[i] 390 if subr.needsDecompilation(): 391 xmlWriter.begintag("CharString", index=i, raw=1) 392 else: 393 xmlWriter.begintag("CharString", index=i) 394 xmlWriter.newline() 395 subr.toXML(xmlWriter) 396 xmlWriter.endtag("CharString") 397 xmlWriter.newline() 398 399 def fromXML(self, name, attrs, content): 400 if name != "CharString": 401 return 402 subr = psCharStrings.T2CharString() 403 subr.fromXML(name, attrs, content) 404 self.append(subr) 405 406 def getItemAndSelector(self, index): 407 sel = None 408 if hasattr(self, 'fdSelect'): 409 sel = self.fdSelect[index] 410 return self[index], sel 411 412 413 class SubrsIndex(GlobalSubrsIndex): 414 compilerClass = SubrsCompiler 415 416 417 class TopDictIndex(Index): 418 419 compilerClass = TopDictIndexCompiler 420 421 def produceItem(self, index, data, file, offset, size): 422 top = TopDict(self.strings, file, offset, self.GlobalSubrs) 423 top.decompile(data) 424 return top 425 426 def toXML(self, xmlWriter, progress): 427 for i in range(len(self)): 428 xmlWriter.begintag("FontDict", index=i) 429 xmlWriter.newline() 430 self[i].toXML(xmlWriter, progress) 431 xmlWriter.endtag("FontDict") 432 xmlWriter.newline() 433 434 435 class FDArrayIndex(TopDictIndex): 436 437 compilerClass = FDArrayIndexCompiler 438 439 def fromXML(self, name, attrs, content): 440 if name != "FontDict": 441 return 442 fontDict = FontDict() 443 for element in content: 444 if isinstance(element, basestring): 445 continue 446 name, attrs, content = element 447 fontDict.fromXML(name, attrs, content) 448 self.append(fontDict) 449 450 451 class FDSelect: 452 def __init__(self, file = None, numGlyphs = None, format=None): 453 if file: 454 # read data in from file 455 self.format = readCard8(file) 456 if self.format == 0: 457 from array import array 458 self.gidArray = array("B", file.read(numGlyphs)).tolist() 459 elif self.format == 3: 460 gidArray = [None] * numGlyphs 461 nRanges = readCard16(file) 462 prev = None 463 for i in range(nRanges): 464 first = readCard16(file) 465 if prev is not None: 466 for glyphID in range(prev, first): 467 gidArray[glyphID] = fd 468 prev = first 469 fd = readCard8(file) 470 if prev is not None: 471 first = readCard16(file) 472 for glyphID in range(prev, first): 473 gidArray[glyphID] = fd 474 self.gidArray = gidArray 475 else: 476 assert False, "unsupported FDSelect format: %s" % format 477 else: 478 # reading from XML. Make empty gidArray,, and leave format as passed in. 479 # format is None will result in the smallest representation being used. 480 self.format = format 481 self.gidArray = [] 482 483 484 def __len__(self): 485 return len(self.gidArray) 486 487 def __getitem__(self, index): 488 return self.gidArray[index] 489 490 def __setitem__(self, index, fdSelectValue): 491 self.gidArray[index] = fdSelectValue 492 493 def append(self, fdSelectValue): 494 self.gidArray.append(fdSelectValue) 495 496 497 class CharStrings(object): 498 499 def __init__(self, file, charset, globalSubrs, private, fdSelect, fdArray): 500 if file is not None: 501 self.charStringsIndex = SubrsIndex(file, globalSubrs, private, fdSelect, fdArray) 502 self.charStrings = charStrings = {} 503 for i in range(len(charset)): 504 charStrings[charset[i]] = i 505 self.charStringsAreIndexed = 1 506 else: 507 self.charStrings = {} 508 self.charStringsAreIndexed = 0 509 self.globalSubrs = globalSubrs 510 self.private = private 511 if fdSelect is not None: 512 self.fdSelect = fdSelect 513 if fdArray is not None: 514 self.fdArray = fdArray 515 516 def keys(self): 517 return list(self.charStrings.keys()) 518 519 def values(self): 520 if self.charStringsAreIndexed: 521 return self.charStringsIndex 522 else: 523 return list(self.charStrings.values()) 524 525 def has_key(self, name): 526 return name in self.charStrings 527 528 def __len__(self): 529 return len(self.charStrings) 530 531 def __getitem__(self, name): 532 charString = self.charStrings[name] 533 if self.charStringsAreIndexed: 534 charString = self.charStringsIndex[charString] 535 return charString 536 537 def __setitem__(self, name, charString): 538 if self.charStringsAreIndexed: 539 index = self.charStrings[name] 540 self.charStringsIndex[index] = charString 541 else: 542 self.charStrings[name] = charString 543 544 def getItemAndSelector(self, name): 545 if self.charStringsAreIndexed: 546 index = self.charStrings[name] 547 return self.charStringsIndex.getItemAndSelector(index) 548 else: 549 if hasattr(self, 'fdSelect'): 550 sel = self.fdSelect[index] # index is not defined at this point. Read R. ? 551 else: 552 raise KeyError("fdSelect array not yet defined.") 553 return self.charStrings[name], sel 554 555 def toXML(self, xmlWriter, progress): 556 names = sorted(self.keys()) 557 i = 0 558 step = 10 559 numGlyphs = len(names) 560 for name in names: 561 charStr, fdSelectIndex = self.getItemAndSelector(name) 562 if charStr.needsDecompilation(): 563 raw = [("raw", 1)] 564 else: 565 raw = [] 566 if fdSelectIndex is None: 567 xmlWriter.begintag("CharString", [('name', name)] + raw) 568 else: 569 xmlWriter.begintag("CharString", 570 [('name', name), ('fdSelectIndex', fdSelectIndex)] + raw) 571 xmlWriter.newline() 572 charStr.toXML(xmlWriter) 573 xmlWriter.endtag("CharString") 574 xmlWriter.newline() 575 if not i % step and progress is not None: 576 progress.setLabel("Dumping 'CFF ' table... (%s)" % name) 577 progress.increment(step / numGlyphs) 578 i = i + 1 579 580 def fromXML(self, name, attrs, content): 581 for element in content: 582 if isinstance(element, basestring): 583 continue 584 name, attrs, content = element 585 if name != "CharString": 586 continue 587 fdID = -1 588 if hasattr(self, "fdArray"): 589 fdID = safeEval(attrs["fdSelectIndex"]) 590 private = self.fdArray[fdID].Private 591 else: 592 private = self.private 593 594 glyphName = attrs["name"] 595 charString = psCharStrings.T2CharString( 596 private=private, 597 globalSubrs=self.globalSubrs) 598 charString.fromXML(name, attrs, content) 599 if fdID >= 0: 600 charString.fdSelectIndex = fdID 601 self[glyphName] = charString 602 603 604 def readCard8(file): 605 return byteord(file.read(1)) 606 607 def readCard16(file): 608 value, = struct.unpack(">H", file.read(2)) 609 return value 610 611 def writeCard8(file, value): 612 file.write(bytechr(value)) 613 614 def writeCard16(file, value): 615 file.write(struct.pack(">H", value)) 616 617 def packCard8(value): 618 return bytechr(value) 619 620 def packCard16(value): 621 return struct.pack(">H", value) 622 623 def buildOperatorDict(table): 624 d = {} 625 for op, name, arg, default, conv in table: 626 d[op] = (name, arg) 627 return d 628 629 def buildOpcodeDict(table): 630 d = {} 631 for op, name, arg, default, conv in table: 632 if isinstance(op, tuple): 633 op = bytechr(op[0]) + bytechr(op[1]) 634 else: 635 op = bytechr(op) 636 d[name] = (op, arg) 637 return d 638 639 def buildOrder(table): 640 l = [] 641 for op, name, arg, default, conv in table: 642 l.append(name) 643 return l 644 645 def buildDefaults(table): 646 d = {} 647 for op, name, arg, default, conv in table: 648 if default is not None: 649 d[name] = default 650 return d 651 652 def buildConverters(table): 653 d = {} 654 for op, name, arg, default, conv in table: 655 d[name] = conv 656 return d 657 658 659 class SimpleConverter(object): 660 def read(self, parent, value): 661 return value 662 def write(self, parent, value): 663 return value 664 def xmlWrite(self, xmlWriter, name, value, progress): 665 xmlWriter.simpletag(name, value=value) 666 xmlWriter.newline() 667 def xmlRead(self, name, attrs, content, parent): 668 return attrs["value"] 669 670 class ASCIIConverter(SimpleConverter): 671 def read(self, parent, value): 672 return tostr(value, encoding='ascii') 673 def write(self, parent, value): 674 return tobytes(value, encoding='ascii') 675 def xmlWrite(self, xmlWriter, name, value, progress): 676 xmlWriter.simpletag(name, value=tostr(value, encoding="ascii")) 677 xmlWriter.newline() 678 def xmlRead(self, name, attrs, content, parent): 679 return tobytes(attrs["value"], encoding=("ascii")) 680 681 class Latin1Converter(SimpleConverter): 682 def read(self, parent, value): 683 return tostr(value, encoding='latin1') 684 def write(self, parent, value): 685 return tobytes(value, encoding='latin1') 686 def xmlWrite(self, xmlWriter, name, value, progress): 687 xmlWriter.simpletag(name, value=tostr(value, encoding="latin1")) 688 xmlWriter.newline() 689 def xmlRead(self, name, attrs, content, parent): 690 return tobytes(attrs["value"], encoding=("latin1")) 691 692 693 def parseNum(s): 694 try: 695 value = int(s) 696 except: 697 value = float(s) 698 return value 699 700 class NumberConverter(SimpleConverter): 701 def xmlRead(self, name, attrs, content, parent): 702 return parseNum(attrs["value"]) 703 704 class ArrayConverter(SimpleConverter): 705 def xmlWrite(self, xmlWriter, name, value, progress): 706 value = " ".join(map(str, value)) 707 xmlWriter.simpletag(name, value=value) 708 xmlWriter.newline() 709 def xmlRead(self, name, attrs, content, parent): 710 values = attrs["value"].split() 711 return [parseNum(value) for value in values] 712 713 class TableConverter(SimpleConverter): 714 def xmlWrite(self, xmlWriter, name, value, progress): 715 xmlWriter.begintag(name) 716 xmlWriter.newline() 717 value.toXML(xmlWriter, progress) 718 xmlWriter.endtag(name) 719 xmlWriter.newline() 720 def xmlRead(self, name, attrs, content, parent): 721 ob = self.getClass()() 722 for element in content: 723 if isinstance(element, basestring): 724 continue 725 name, attrs, content = element 726 ob.fromXML(name, attrs, content) 727 return ob 728 729 class PrivateDictConverter(TableConverter): 730 def getClass(self): 731 return PrivateDict 732 def read(self, parent, value): 733 size, offset = value 734 file = parent.file 735 priv = PrivateDict(parent.strings, file, offset) 736 file.seek(offset) 737 data = file.read(size) 738 assert len(data) == size 739 priv.decompile(data) 740 return priv 741 def write(self, parent, value): 742 return (0, 0) # dummy value 743 744 class SubrsConverter(TableConverter): 745 def getClass(self): 746 return SubrsIndex 747 def read(self, parent, value): 748 file = parent.file 749 file.seek(parent.offset + value) # Offset(self) 750 return SubrsIndex(file) 751 def write(self, parent, value): 752 return 0 # dummy value 753 754 class CharStringsConverter(TableConverter): 755 def read(self, parent, value): 756 file = parent.file 757 charset = parent.charset 758 globalSubrs = parent.GlobalSubrs 759 if hasattr(parent, "ROS"): 760 fdSelect, fdArray = parent.FDSelect, parent.FDArray 761 private = None 762 else: 763 fdSelect, fdArray = None, None 764 private = parent.Private 765 file.seek(value) # Offset(0) 766 return CharStrings(file, charset, globalSubrs, private, fdSelect, fdArray) 767 def write(self, parent, value): 768 return 0 # dummy value 769 def xmlRead(self, name, attrs, content, parent): 770 if hasattr(parent, "ROS"): 771 # if it is a CID-keyed font, then the private Dict is extracted from the parent.FDArray 772 private, fdSelect, fdArray = None, parent.FDSelect, parent.FDArray 773 else: 774 # if it is a name-keyed font, then the private dict is in the top dict, and there is no fdArray. 775 private, fdSelect, fdArray = parent.Private, None, None 776 charStrings = CharStrings(None, None, parent.GlobalSubrs, private, fdSelect, fdArray) 777 charStrings.fromXML(name, attrs, content) 778 return charStrings 779 780 class CharsetConverter(object): 781 def read(self, parent, value): 782 isCID = hasattr(parent, "ROS") 783 if value > 2: 784 numGlyphs = parent.numGlyphs 785 file = parent.file 786 file.seek(value) 787 if DEBUG: 788 print("loading charset at %s" % value) 789 format = readCard8(file) 790 if format == 0: 791 charset = parseCharset0(numGlyphs, file, parent.strings, isCID) 792 elif format == 1 or format == 2: 793 charset = parseCharset(numGlyphs, file, parent.strings, isCID, format) 794 else: 795 raise NotImplementedError 796 assert len(charset) == numGlyphs 797 if DEBUG: 798 print(" charset end at %s" % file.tell()) 799 else: # offset == 0 -> no charset data. 800 if isCID or "CharStrings" not in parent.rawDict: 801 assert value == 0 # We get here only when processing fontDicts from the FDArray of CFF-CID fonts. Only the real topDict references the chrset. 802 charset = None 803 elif value == 0: 804 charset = cffISOAdobeStrings 805 elif value == 1: 806 charset = cffIExpertStrings 807 elif value == 2: 808 charset = cffExpertSubsetStrings 809 return charset 810 811 def write(self, parent, value): 812 return 0 # dummy value 813 def xmlWrite(self, xmlWriter, name, value, progress): 814 # XXX only write charset when not in OT/TTX context, where we 815 # dump charset as a separate "GlyphOrder" table. 816 ##xmlWriter.simpletag("charset") 817 xmlWriter.comment("charset is dumped separately as the 'GlyphOrder' element") 818 xmlWriter.newline() 819 def xmlRead(self, name, attrs, content, parent): 820 if 0: 821 return safeEval(attrs["value"]) 822 823 824 class CharsetCompiler(object): 825 826 def __init__(self, strings, charset, parent): 827 assert charset[0] == '.notdef' 828 isCID = hasattr(parent.dictObj, "ROS") 829 data0 = packCharset0(charset, isCID, strings) 830 data = packCharset(charset, isCID, strings) 831 if len(data) < len(data0): 832 self.data = data 833 else: 834 self.data = data0 835 self.parent = parent 836 837 def setPos(self, pos, endPos): 838 self.parent.rawDict["charset"] = pos 839 840 def getDataLength(self): 841 return len(self.data) 842 843 def toFile(self, file): 844 file.write(self.data) 845 846 847 def getCIDfromName(name, strings): 848 return int(name[3:]) 849 850 def getSIDfromName(name, strings): 851 return strings.getSID(name) 852 853 def packCharset0(charset, isCID, strings): 854 fmt = 0 855 data = [packCard8(fmt)] 856 if isCID: 857 getNameID = getCIDfromName 858 else: 859 getNameID = getSIDfromName 860 861 for name in charset[1:]: 862 data.append(packCard16(getNameID(name,strings))) 863 return bytesjoin(data) 864 865 866 def packCharset(charset, isCID, strings): 867 fmt = 1 868 ranges = [] 869 first = None 870 end = 0 871 if isCID: 872 getNameID = getCIDfromName 873 else: 874 getNameID = getSIDfromName 875 876 for name in charset[1:]: 877 SID = getNameID(name, strings) 878 if first is None: 879 first = SID 880 elif end + 1 != SID: 881 nLeft = end - first 882 if nLeft > 255: 883 fmt = 2 884 ranges.append((first, nLeft)) 885 first = SID 886 end = SID 887 nLeft = end - first 888 if nLeft > 255: 889 fmt = 2 890 ranges.append((first, nLeft)) 891 892 data = [packCard8(fmt)] 893 if fmt == 1: 894 nLeftFunc = packCard8 895 else: 896 nLeftFunc = packCard16 897 for first, nLeft in ranges: 898 data.append(packCard16(first) + nLeftFunc(nLeft)) 899 return bytesjoin(data) 900 901 def parseCharset0(numGlyphs, file, strings, isCID): 902 charset = [".notdef"] 903 if isCID: 904 for i in range(numGlyphs - 1): 905 CID = readCard16(file) 906 charset.append("cid" + str(CID).zfill(5)) 907 else: 908 for i in range(numGlyphs - 1): 909 SID = readCard16(file) 910 charset.append(strings[SID]) 911 return charset 912 913 def parseCharset(numGlyphs, file, strings, isCID, fmt): 914 charset = ['.notdef'] 915 count = 1 916 if fmt == 1: 917 nLeftFunc = readCard8 918 else: 919 nLeftFunc = readCard16 920 while count < numGlyphs: 921 first = readCard16(file) 922 nLeft = nLeftFunc(file) 923 if isCID: 924 for CID in range(first, first+nLeft+1): 925 charset.append("cid" + str(CID).zfill(5)) 926 else: 927 for SID in range(first, first+nLeft+1): 928 charset.append(strings[SID]) 929 count = count + nLeft + 1 930 return charset 931 932 933 class EncodingCompiler(object): 934 935 def __init__(self, strings, encoding, parent): 936 assert not isinstance(encoding, basestring) 937 data0 = packEncoding0(parent.dictObj.charset, encoding, parent.strings) 938 data1 = packEncoding1(parent.dictObj.charset, encoding, parent.strings) 939 if len(data0) < len(data1): 940 self.data = data0 941 else: 942 self.data = data1 943 self.parent = parent 944 945 def setPos(self, pos, endPos): 946 self.parent.rawDict["Encoding"] = pos 947 948 def getDataLength(self): 949 return len(self.data) 950 951 def toFile(self, file): 952 file.write(self.data) 953 954 955 class EncodingConverter(SimpleConverter): 956 957 def read(self, parent, value): 958 if value == 0: 959 return "StandardEncoding" 960 elif value == 1: 961 return "ExpertEncoding" 962 else: 963 assert value > 1 964 file = parent.file 965 file.seek(value) 966 if DEBUG: 967 print("loading Encoding at %s" % value) 968 fmt = readCard8(file) 969 haveSupplement = fmt & 0x80 970 if haveSupplement: 971 raise NotImplementedError("Encoding supplements are not yet supported") 972 fmt = fmt & 0x7f 973 if fmt == 0: 974 encoding = parseEncoding0(parent.charset, file, haveSupplement, 975 parent.strings) 976 elif fmt == 1: 977 encoding = parseEncoding1(parent.charset, file, haveSupplement, 978 parent.strings) 979 return encoding 980 981 def write(self, parent, value): 982 if value == "StandardEncoding": 983 return 0 984 elif value == "ExpertEncoding": 985 return 1 986 return 0 # dummy value 987 988 def xmlWrite(self, xmlWriter, name, value, progress): 989 if value in ("StandardEncoding", "ExpertEncoding"): 990 xmlWriter.simpletag(name, name=value) 991 xmlWriter.newline() 992 return 993 xmlWriter.begintag(name) 994 xmlWriter.newline() 995 for code in range(len(value)): 996 glyphName = value[code] 997 if glyphName != ".notdef": 998 xmlWriter.simpletag("map", code=hex(code), name=glyphName) 999 xmlWriter.newline() 1000 xmlWriter.endtag(name) 1001 xmlWriter.newline() 1002 1003 def xmlRead(self, name, attrs, content, parent): 1004 if "name" in attrs: 1005 return attrs["name"] 1006 encoding = [".notdef"] * 256 1007 for element in content: 1008 if isinstance(element, basestring): 1009 continue 1010 name, attrs, content = element 1011 code = safeEval(attrs["code"]) 1012 glyphName = attrs["name"] 1013 encoding[code] = glyphName 1014 return encoding 1015 1016 1017 def parseEncoding0(charset, file, haveSupplement, strings): 1018 nCodes = readCard8(file) 1019 encoding = [".notdef"] * 256 1020 for glyphID in range(1, nCodes + 1): 1021 code = readCard8(file) 1022 if code != 0: 1023 encoding[code] = charset[glyphID] 1024 return encoding 1025 1026 def parseEncoding1(charset, file, haveSupplement, strings): 1027 nRanges = readCard8(file) 1028 encoding = [".notdef"] * 256 1029 glyphID = 1 1030 for i in range(nRanges): 1031 code = readCard8(file) 1032 nLeft = readCard8(file) 1033 for glyphID in range(glyphID, glyphID + nLeft + 1): 1034 encoding[code] = charset[glyphID] 1035 code = code + 1 1036 glyphID = glyphID + 1 1037 return encoding 1038 1039 def packEncoding0(charset, encoding, strings): 1040 fmt = 0 1041 m = {} 1042 for code in range(len(encoding)): 1043 name = encoding[code] 1044 if name != ".notdef": 1045 m[name] = code 1046 codes = [] 1047 for name in charset[1:]: 1048 code = m.get(name) 1049 codes.append(code) 1050 1051 while codes and codes[-1] is None: 1052 codes.pop() 1053 1054 data = [packCard8(fmt), packCard8(len(codes))] 1055 for code in codes: 1056 if code is None: 1057 code = 0 1058 data.append(packCard8(code)) 1059 return bytesjoin(data) 1060 1061 def packEncoding1(charset, encoding, strings): 1062 fmt = 1 1063 m = {} 1064 for code in range(len(encoding)): 1065 name = encoding[code] 1066 if name != ".notdef": 1067 m[name] = code 1068 ranges = [] 1069 first = None 1070 end = 0 1071 for name in charset[1:]: 1072 code = m.get(name, -1) 1073 if first is None: 1074 first = code 1075 elif end + 1 != code: 1076 nLeft = end - first 1077 ranges.append((first, nLeft)) 1078 first = code 1079 end = code 1080 nLeft = end - first 1081 ranges.append((first, nLeft)) 1082 1083 # remove unencoded glyphs at the end. 1084 while ranges and ranges[-1][0] == -1: 1085 ranges.pop() 1086 1087 data = [packCard8(fmt), packCard8(len(ranges))] 1088 for first, nLeft in ranges: 1089 if first == -1: # unencoded 1090 first = 0 1091 data.append(packCard8(first) + packCard8(nLeft)) 1092 return bytesjoin(data) 1093 1094 1095 class FDArrayConverter(TableConverter): 1096 1097 def read(self, parent, value): 1098 file = parent.file 1099 file.seek(value) 1100 fdArray = FDArrayIndex(file) 1101 fdArray.strings = parent.strings 1102 fdArray.GlobalSubrs = parent.GlobalSubrs 1103 return fdArray 1104 1105 def write(self, parent, value): 1106 return 0 # dummy value 1107 1108 def xmlRead(self, name, attrs, content, parent): 1109 fdArray = FDArrayIndex() 1110 for element in content: 1111 if isinstance(element, basestring): 1112 continue 1113 name, attrs, content = element 1114 fdArray.fromXML(name, attrs, content) 1115 return fdArray 1116 1117 1118 class FDSelectConverter(object): 1119 1120 def read(self, parent, value): 1121 file = parent.file 1122 file.seek(value) 1123 fdSelect = FDSelect(file, parent.numGlyphs) 1124 return fdSelect 1125 1126 def write(self, parent, value): 1127 return 0 # dummy value 1128 1129 # The FDSelect glyph data is written out to XML in the charstring keys, 1130 # so we write out only the format selector 1131 def xmlWrite(self, xmlWriter, name, value, progress): 1132 xmlWriter.simpletag(name, [('format', value.format)]) 1133 xmlWriter.newline() 1134 1135 def xmlRead(self, name, attrs, content, parent): 1136 fmt = safeEval(attrs["format"]) 1137 file = None 1138 numGlyphs = None 1139 fdSelect = FDSelect(file, numGlyphs, fmt) 1140 return fdSelect 1141 1142 1143 def packFDSelect0(fdSelectArray): 1144 fmt = 0 1145 data = [packCard8(fmt)] 1146 for index in fdSelectArray: 1147 data.append(packCard8(index)) 1148 return bytesjoin(data) 1149 1150 1151 def packFDSelect3(fdSelectArray): 1152 fmt = 3 1153 fdRanges = [] 1154 first = None 1155 end = 0 1156 lenArray = len(fdSelectArray) 1157 lastFDIndex = -1 1158 for i in range(lenArray): 1159 fdIndex = fdSelectArray[i] 1160 if lastFDIndex != fdIndex: 1161 fdRanges.append([i, fdIndex]) 1162 lastFDIndex = fdIndex 1163 sentinelGID = i + 1 1164 1165 data = [packCard8(fmt)] 1166 data.append(packCard16( len(fdRanges) )) 1167 for fdRange in fdRanges: 1168 data.append(packCard16(fdRange[0])) 1169 data.append(packCard8(fdRange[1])) 1170 data.append(packCard16(sentinelGID)) 1171 return bytesjoin(data) 1172 1173 1174 class FDSelectCompiler(object): 1175 1176 def __init__(self, fdSelect, parent): 1177 fmt = fdSelect.format 1178 fdSelectArray = fdSelect.gidArray 1179 if fmt == 0: 1180 self.data = packFDSelect0(fdSelectArray) 1181 elif fmt == 3: 1182 self.data = packFDSelect3(fdSelectArray) 1183 else: 1184 # choose smaller of the two formats 1185 data0 = packFDSelect0(fdSelectArray) 1186 data3 = packFDSelect3(fdSelectArray) 1187 if len(data0) < len(data3): 1188 self.data = data0 1189 fdSelect.format = 0 1190 else: 1191 self.data = data3 1192 fdSelect.format = 3 1193 1194 self.parent = parent 1195 1196 def setPos(self, pos, endPos): 1197 self.parent.rawDict["FDSelect"] = pos 1198 1199 def getDataLength(self): 1200 return len(self.data) 1201 1202 def toFile(self, file): 1203 file.write(self.data) 1204 1205 1206 class ROSConverter(SimpleConverter): 1207 1208 def xmlWrite(self, xmlWriter, name, value, progress): 1209 registry, order, supplement = value 1210 xmlWriter.simpletag(name, [('Registry', tostr(registry)), ('Order', tostr(order)), 1211 ('Supplement', supplement)]) 1212 xmlWriter.newline() 1213 1214 def xmlRead(self, name, attrs, content, parent): 1215 return (attrs['Registry'], attrs['Order'], safeEval(attrs['Supplement'])) 1216 1217 1218 1219 topDictOperators = [ 1220 # opcode name argument type default converter 1221 ((12, 30), 'ROS', ('SID','SID','number'), None, ROSConverter()), 1222 ((12, 20), 'SyntheticBase', 'number', None, None), 1223 (0, 'version', 'SID', None, None), 1224 (1, 'Notice', 'SID', None, Latin1Converter()), 1225 ((12, 0), 'Copyright', 'SID', None, Latin1Converter()), 1226 (2, 'FullName', 'SID', None, None), 1227 ((12, 38), 'FontName', 'SID', None, None), 1228 (3, 'FamilyName', 'SID', None, None), 1229 (4, 'Weight', 'SID', None, None), 1230 ((12, 1), 'isFixedPitch', 'number', 0, None), 1231 ((12, 2), 'ItalicAngle', 'number', 0, None), 1232 ((12, 3), 'UnderlinePosition', 'number', None, None), 1233 ((12, 4), 'UnderlineThickness', 'number', 50, None), 1234 ((12, 5), 'PaintType', 'number', 0, None), 1235 ((12, 6), 'CharstringType', 'number', 2, None), 1236 ((12, 7), 'FontMatrix', 'array', [0.001,0,0,0.001,0,0], None), 1237 (13, 'UniqueID', 'number', None, None), 1238 (5, 'FontBBox', 'array', [0,0,0,0], None), 1239 ((12, 8), 'StrokeWidth', 'number', 0, None), 1240 (14, 'XUID', 'array', None, None), 1241 ((12, 21), 'PostScript', 'SID', None, None), 1242 ((12, 22), 'BaseFontName', 'SID', None, None), 1243 ((12, 23), 'BaseFontBlend', 'delta', None, None), 1244 ((12, 31), 'CIDFontVersion', 'number', 0, None), 1245 ((12, 32), 'CIDFontRevision', 'number', 0, None), 1246 ((12, 33), 'CIDFontType', 'number', 0, None), 1247 ((12, 34), 'CIDCount', 'number', 8720, None), 1248 (15, 'charset', 'number', 0, CharsetConverter()), 1249 ((12, 35), 'UIDBase', 'number', None, None), 1250 (16, 'Encoding', 'number', 0, EncodingConverter()), 1251 (18, 'Private', ('number','number'), None, PrivateDictConverter()), 1252 ((12, 37), 'FDSelect', 'number', None, FDSelectConverter()), 1253 ((12, 36), 'FDArray', 'number', None, FDArrayConverter()), 1254 (17, 'CharStrings', 'number', None, CharStringsConverter()), 1255 ] 1256 1257 # Note! FDSelect and FDArray must both preceed CharStrings in the output XML build order, 1258 # in order for the font to compile back from xml. 1259 1260 1261 privateDictOperators = [ 1262 # opcode name argument type default converter 1263 (6, 'BlueValues', 'delta', None, None), 1264 (7, 'OtherBlues', 'delta', None, None), 1265 (8, 'FamilyBlues', 'delta', None, None), 1266 (9, 'FamilyOtherBlues', 'delta', None, None), 1267 ((12, 9), 'BlueScale', 'number', 0.039625, None), 1268 ((12, 10), 'BlueShift', 'number', 7, None), 1269 ((12, 11), 'BlueFuzz', 'number', 1, None), 1270 (10, 'StdHW', 'number', None, None), 1271 (11, 'StdVW', 'number', None, None), 1272 ((12, 12), 'StemSnapH', 'delta', None, None), 1273 ((12, 13), 'StemSnapV', 'delta', None, None), 1274 ((12, 14), 'ForceBold', 'number', 0, None), 1275 ((12, 15), 'ForceBoldThreshold', 'number', None, None), # deprecated 1276 ((12, 16), 'lenIV', 'number', None, None), # deprecated 1277 ((12, 17), 'LanguageGroup', 'number', 0, None), 1278 ((12, 18), 'ExpansionFactor', 'number', 0.06, None), 1279 ((12, 19), 'initialRandomSeed', 'number', 0, None), 1280 (20, 'defaultWidthX', 'number', 0, None), 1281 (21, 'nominalWidthX', 'number', 0, None), 1282 (19, 'Subrs', 'number', None, SubrsConverter()), 1283 ] 1284 1285 def addConverters(table): 1286 for i in range(len(table)): 1287 op, name, arg, default, conv = table[i] 1288 if conv is not None: 1289 continue 1290 if arg in ("delta", "array"): 1291 conv = ArrayConverter() 1292 elif arg == "number": 1293 conv = NumberConverter() 1294 elif arg == "SID": 1295 conv = ASCIIConverter() 1296 else: 1297 assert False 1298 table[i] = op, name, arg, default, conv 1299 1300 addConverters(privateDictOperators) 1301 addConverters(topDictOperators) 1302 1303 1304 class TopDictDecompiler(psCharStrings.DictDecompiler): 1305 operators = buildOperatorDict(topDictOperators) 1306 1307 1308 class PrivateDictDecompiler(psCharStrings.DictDecompiler): 1309 operators = buildOperatorDict(privateDictOperators) 1310 1311 1312 class DictCompiler(object): 1313 1314 def __init__(self, dictObj, strings, parent): 1315 assert isinstance(strings, IndexedStrings) 1316 self.dictObj = dictObj 1317 self.strings = strings 1318 self.parent = parent 1319 rawDict = {} 1320 for name in dictObj.order: 1321 value = getattr(dictObj, name, None) 1322 if value is None: 1323 continue 1324 conv = dictObj.converters[name] 1325 value = conv.write(dictObj, value) 1326 if value == dictObj.defaults.get(name): 1327 continue 1328 rawDict[name] = value 1329 self.rawDict = rawDict 1330 1331 def setPos(self, pos, endPos): 1332 pass 1333 1334 def getDataLength(self): 1335 return len(self.compile("getDataLength")) 1336 1337 def compile(self, reason): 1338 if DEBUG: 1339 print("-- compiling %s for %s" % (self.__class__.__name__, reason)) 1340 print("in baseDict: ", self) 1341 rawDict = self.rawDict 1342 data = [] 1343 for name in self.dictObj.order: 1344 value = rawDict.get(name) 1345 if value is None: 1346 continue 1347 op, argType = self.opcodes[name] 1348 if isinstance(argType, tuple): 1349 l = len(argType) 1350 assert len(value) == l, "value doesn't match arg type" 1351 for i in range(l): 1352 arg = argType[i] 1353 v = value[i] 1354 arghandler = getattr(self, "arg_" + arg) 1355 data.append(arghandler(v)) 1356 else: 1357 arghandler = getattr(self, "arg_" + argType) 1358 data.append(arghandler(value)) 1359 data.append(op) 1360 return bytesjoin(data) 1361 1362 def toFile(self, file): 1363 file.write(self.compile("toFile")) 1364 1365 def arg_number(self, num): 1366 return encodeNumber(num) 1367 def arg_SID(self, s): 1368 return psCharStrings.encodeIntCFF(self.strings.getSID(s)) 1369 def arg_array(self, value): 1370 data = [] 1371 for num in value: 1372 data.append(encodeNumber(num)) 1373 return bytesjoin(data) 1374 def arg_delta(self, value): 1375 out = [] 1376 last = 0 1377 for v in value: 1378 out.append(v - last) 1379 last = v 1380 data = [] 1381 for num in out: 1382 data.append(encodeNumber(num)) 1383 return bytesjoin(data) 1384 1385 1386 def encodeNumber(num): 1387 if isinstance(num, float): 1388 return psCharStrings.encodeFloat(num) 1389 else: 1390 return psCharStrings.encodeIntCFF(num) 1391 1392 1393 class TopDictCompiler(DictCompiler): 1394 1395 opcodes = buildOpcodeDict(topDictOperators) 1396 1397 def getChildren(self, strings): 1398 children = [] 1399 if hasattr(self.dictObj, "charset") and self.dictObj.charset: 1400 children.append(CharsetCompiler(strings, self.dictObj.charset, self)) 1401 if hasattr(self.dictObj, "Encoding"): 1402 encoding = self.dictObj.Encoding 1403 if not isinstance(encoding, basestring): 1404 children.append(EncodingCompiler(strings, encoding, self)) 1405 if hasattr(self.dictObj, "FDSelect"): 1406 # I have not yet supported merging a ttx CFF-CID font, as there are interesting 1407 # issues about merging the FDArrays. Here I assume that 1408 # either the font was read from XML, and teh FDSelect indices are all 1409 # in the charstring data, or the FDSelect array is already fully defined. 1410 fdSelect = self.dictObj.FDSelect 1411 if len(fdSelect) == 0: # probably read in from XML; assume fdIndex in CharString data 1412 charStrings = self.dictObj.CharStrings 1413 for name in self.dictObj.charset: 1414 fdSelect.append(charStrings[name].fdSelectIndex) 1415 fdSelectComp = FDSelectCompiler(fdSelect, self) 1416 children.append(fdSelectComp) 1417 if hasattr(self.dictObj, "CharStrings"): 1418 items = [] 1419 charStrings = self.dictObj.CharStrings 1420 for name in self.dictObj.charset: 1421 items.append(charStrings[name]) 1422 charStringsComp = CharStringsCompiler(items, strings, self) 1423 children.append(charStringsComp) 1424 if hasattr(self.dictObj, "FDArray"): 1425 # I have not yet supported merging a ttx CFF-CID font, as there are interesting 1426 # issues about merging the FDArrays. Here I assume that the FDArray info is correct 1427 # and complete. 1428 fdArrayIndexComp = self.dictObj.FDArray.getCompiler(strings, self) 1429 children.append(fdArrayIndexComp) 1430 children.extend(fdArrayIndexComp.getChildren(strings)) 1431 if hasattr(self.dictObj, "Private"): 1432 privComp = self.dictObj.Private.getCompiler(strings, self) 1433 children.append(privComp) 1434 children.extend(privComp.getChildren(strings)) 1435 return children 1436 1437 1438 class FontDictCompiler(DictCompiler): 1439 1440 opcodes = buildOpcodeDict(topDictOperators) 1441 1442 def getChildren(self, strings): 1443 children = [] 1444 if hasattr(self.dictObj, "Private"): 1445 privComp = self.dictObj.Private.getCompiler(strings, self) 1446 children.append(privComp) 1447 children.extend(privComp.getChildren(strings)) 1448 return children 1449 1450 1451 class PrivateDictCompiler(DictCompiler): 1452 1453 opcodes = buildOpcodeDict(privateDictOperators) 1454 1455 def setPos(self, pos, endPos): 1456 size = endPos - pos 1457 self.parent.rawDict["Private"] = size, pos 1458 self.pos = pos 1459 1460 def getChildren(self, strings): 1461 children = [] 1462 if hasattr(self.dictObj, "Subrs"): 1463 children.append(self.dictObj.Subrs.getCompiler(strings, self)) 1464 return children 1465 1466 1467 class BaseDict(object): 1468 1469 def __init__(self, strings=None, file=None, offset=None): 1470 self.rawDict = {} 1471 if DEBUG: 1472 print("loading %s at %s" % (self.__class__.__name__, offset)) 1473 self.file = file 1474 self.offset = offset 1475 self.strings = strings 1476 self.skipNames = [] 1477 1478 def decompile(self, data): 1479 if DEBUG: 1480 print(" length %s is %s" % (self.__class__.__name__, len(data))) 1481 dec = self.decompilerClass(self.strings) 1482 dec.decompile(data) 1483 self.rawDict = dec.getDict() 1484 self.postDecompile() 1485 1486 def postDecompile(self): 1487 pass 1488 1489 def getCompiler(self, strings, parent): 1490 return self.compilerClass(self, strings, parent) 1491 1492 def __getattr__(self, name): 1493 value = self.rawDict.get(name) 1494 if value is None: 1495 value = self.defaults.get(name) 1496 if value is None: 1497 raise AttributeError(name) 1498 conv = self.converters[name] 1499 value = conv.read(self, value) 1500 setattr(self, name, value) 1501 return value 1502 1503 def toXML(self, xmlWriter, progress): 1504 for name in self.order: 1505 if name in self.skipNames: 1506 continue 1507 value = getattr(self, name, None) 1508 if value is None: 1509 continue 1510 conv = self.converters[name] 1511 conv.xmlWrite(xmlWriter, name, value, progress) 1512 1513 def fromXML(self, name, attrs, content): 1514 conv = self.converters[name] 1515 value = conv.xmlRead(name, attrs, content, self) 1516 setattr(self, name, value) 1517 1518 1519 class TopDict(BaseDict): 1520 1521 defaults = buildDefaults(topDictOperators) 1522 converters = buildConverters(topDictOperators) 1523 order = buildOrder(topDictOperators) 1524 decompilerClass = TopDictDecompiler 1525 compilerClass = TopDictCompiler 1526 1527 def __init__(self, strings=None, file=None, offset=None, GlobalSubrs=None): 1528 BaseDict.__init__(self, strings, file, offset) 1529 self.GlobalSubrs = GlobalSubrs 1530 1531 def getGlyphOrder(self): 1532 return self.charset 1533 1534 def postDecompile(self): 1535 offset = self.rawDict.get("CharStrings") 1536 if offset is None: 1537 return 1538 # get the number of glyphs beforehand. 1539 self.file.seek(offset) 1540 self.numGlyphs = readCard16(self.file) 1541 1542 def toXML(self, xmlWriter, progress): 1543 if hasattr(self, "CharStrings"): 1544 self.decompileAllCharStrings(progress) 1545 if hasattr(self, "ROS"): 1546 self.skipNames = ['Encoding'] 1547 if not hasattr(self, "ROS") or not hasattr(self, "CharStrings"): 1548 # these values have default values, but I only want them to show up 1549 # in CID fonts. 1550 self.skipNames = ['CIDFontVersion', 'CIDFontRevision', 'CIDFontType', 1551 'CIDCount'] 1552 BaseDict.toXML(self, xmlWriter, progress) 1553 1554 def decompileAllCharStrings(self, progress): 1555 # XXX only when doing ttdump -i? 1556 i = 0 1557 for charString in self.CharStrings.values(): 1558 try: 1559 charString.decompile() 1560 except: 1561 print("Error in charstring ", i) 1562 import sys 1563 typ, value = sys.exc_info()[0:2] 1564 raise typ(value) 1565 if not i % 30 and progress: 1566 progress.increment(0) # update 1567 i = i + 1 1568 1569 1570 class FontDict(BaseDict): 1571 1572 defaults = buildDefaults(topDictOperators) 1573 converters = buildConverters(topDictOperators) 1574 order = buildOrder(topDictOperators) 1575 decompilerClass = None 1576 compilerClass = FontDictCompiler 1577 1578 def __init__(self, strings=None, file=None, offset=None, GlobalSubrs=None): 1579 BaseDict.__init__(self, strings, file, offset) 1580 self.GlobalSubrs = GlobalSubrs 1581 1582 def getGlyphOrder(self): 1583 return self.charset 1584 1585 def toXML(self, xmlWriter, progress): 1586 self.skipNames = ['Encoding'] 1587 BaseDict.toXML(self, xmlWriter, progress) 1588 1589 1590 1591 class PrivateDict(BaseDict): 1592 defaults = buildDefaults(privateDictOperators) 1593 converters = buildConverters(privateDictOperators) 1594 order = buildOrder(privateDictOperators) 1595 decompilerClass = PrivateDictDecompiler 1596 compilerClass = PrivateDictCompiler 1597 1598 1599 class IndexedStrings(object): 1600 1601 """SID -> string mapping.""" 1602 1603 def __init__(self, file=None): 1604 if file is None: 1605 strings = [] 1606 else: 1607 strings = [tostr(s, encoding="latin1") for s in Index(file)] 1608 self.strings = strings 1609 1610 def getCompiler(self): 1611 return IndexedStringsCompiler(self, None, None) 1612 1613 def __len__(self): 1614 return len(self.strings) 1615 1616 def __getitem__(self, SID): 1617 if SID < cffStandardStringCount: 1618 return cffStandardStrings[SID] 1619 else: 1620 return self.strings[SID - cffStandardStringCount] 1621 1622 def getSID(self, s): 1623 if not hasattr(self, "stringMapping"): 1624 self.buildStringMapping() 1625 if s in cffStandardStringMapping: 1626 SID = cffStandardStringMapping[s] 1627 elif s in self.stringMapping: 1628 SID = self.stringMapping[s] 1629 else: 1630 SID = len(self.strings) + cffStandardStringCount 1631 self.strings.append(s) 1632 self.stringMapping[s] = SID 1633 return SID 1634 1635 def getStrings(self): 1636 return self.strings 1637 1638 def buildStringMapping(self): 1639 self.stringMapping = {} 1640 for index in range(len(self.strings)): 1641 self.stringMapping[self.strings[index]] = index + cffStandardStringCount 1642 1643 1644 # The 391 Standard Strings as used in the CFF format. 1645 # from Adobe Technical None #5176, version 1.0, 18 March 1998 1646 1647 cffStandardStrings = ['.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 1648 'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright', 1649 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 1650 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 1651 'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 1652 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 1653 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 1654 'bracketright', 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c', 1655 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 1656 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 1657 'asciitilde', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 1658 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft', 1659 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 1660 'daggerdbl', 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase', 1661 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', 1662 'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 1663 'dotaccent', 'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 1664 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae', 1665 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior', 1666 'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 1667 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters', 1668 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior', 1669 'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 1670 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave', 1671 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 1672 'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 1673 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron', 1674 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla', 1675 'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex', 1676 'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis', 1677 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave', 1678 'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall', 1679 'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall', 1680 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', 1681 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 1682 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 1683 'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior', 1684 'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 1685 'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior', 1686 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', 'parenleftinferior', 1687 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 1688 'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 1689 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 1690 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall', 1691 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', 1692 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 1693 'Dieresissmall', 'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall', 1694 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', 1695 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 1696 'onethird', 'twothirds', 'zerosuperior', 'foursuperior', 'fivesuperior', 1697 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 1698 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 1699 'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 1700 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall', 1701 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', 1702 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 1703 'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 1704 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', 1705 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 1706 'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 1707 'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', '001.001', '001.002', 1708 '001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman', 1709 'Semibold' 1710 ] 1711 1712 cffStandardStringCount = 391 1713 assert len(cffStandardStrings) == cffStandardStringCount 1714 # build reverse mapping 1715 cffStandardStringMapping = {} 1716 for _i in range(cffStandardStringCount): 1717 cffStandardStringMapping[cffStandardStrings[_i]] = _i 1718 1719 cffISOAdobeStrings = [".notdef", "space", "exclam", "quotedbl", "numbersign", 1720 "dollar", "percent", "ampersand", "quoteright", "parenleft", "parenright", 1721 "asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two", 1722 "three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", 1723 "less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", 1724 "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", 1725 "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum", 1726 "underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", 1727 "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", 1728 "braceleft", "bar", "braceright", "asciitilde", "exclamdown", "cent", 1729 "sterling", "fraction", "yen", "florin", "section", "currency", "quotesingle", 1730 "quotedblleft", "guillemotleft", "guilsinglleft", "guilsinglright", "fi", "fl", 1731 "endash", "dagger", "daggerdbl", "periodcentered", "paragraph", "bullet", 1732 "quotesinglbase", "quotedblbase", "quotedblright", "guillemotright", "ellipsis", 1733 "perthousand", "questiondown", "grave", "acute", "circumflex", "tilde", 1734 "macron", "breve", "dotaccent", "dieresis", "ring", "cedilla", "hungarumlaut", 1735 "ogonek", "caron", "emdash", "AE", "ordfeminine", "Lslash", "Oslash", "OE", 1736 "ordmasculine", "ae", "dotlessi", "lslash", "oslash", "oe", "germandbls", 1737 "onesuperior", "logicalnot", "mu", "trademark", "Eth", "onehalf", "plusminus", 1738 "Thorn", "onequarter", "divide", "brokenbar", "degree", "thorn", 1739 "threequarters", "twosuperior", "registered", "minus", "eth", "multiply", 1740 "threesuperior", "copyright", "Aacute", "Acircumflex", "Adieresis", "Agrave", 1741 "Aring", "Atilde", "Ccedilla", "Eacute", "Ecircumflex", "Edieresis", "Egrave", 1742 "Iacute", "Icircumflex", "Idieresis", "Igrave", "Ntilde", "Oacute", 1743 "Ocircumflex", "Odieresis", "Ograve", "Otilde", "Scaron", "Uacute", 1744 "Ucircumflex", "Udieresis", "Ugrave", "Yacute", "Ydieresis", "Zcaron", "aacute", 1745 "acircumflex", "adieresis", "agrave", "aring", "atilde", "ccedilla", "eacute", 1746 "ecircumflex", "edieresis", "egrave", "iacute", "icircumflex", "idieresis", 1747 "igrave", "ntilde", "oacute", "ocircumflex", "odieresis", "ograve", "otilde", 1748 "scaron", "uacute", "ucircumflex", "udieresis", "ugrave", "yacute", "ydieresis", 1749 "zcaron"] 1750 1751 cffISOAdobeStringCount = 229 1752 assert len(cffISOAdobeStrings) == cffISOAdobeStringCount 1753 1754 cffIExpertStrings = [".notdef", "space", "exclamsmall", "Hungarumlautsmall", 1755 "dollaroldstyle", "dollarsuperior", "ampersandsmall", "Acutesmall", 1756 "parenleftsuperior", "parenrightsuperior", "twodotenleader", "onedotenleader", 1757 "comma", "hyphen", "period", "fraction", "zerooldstyle", "oneoldstyle", 1758 "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle", 1759 "sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", "semicolon", 1760 "commasuperior", "threequartersemdash", "periodsuperior", "questionsmall", 1761 "asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior", 1762 "lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior", 1763 "tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior", 1764 "parenrightinferior", "Circumflexsmall", "hyphensuperior", "Gravesmall", 1765 "Asmall", "Bsmall", "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall", 1766 "Ismall", "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall", 1767 "Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall", "Xsmall", 1768 "Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah", "Tildesmall", 1769 "exclamdownsmall", "centoldstyle", "Lslashsmall", "Scaronsmall", "Zcaronsmall", 1770 "Dieresissmall", "Brevesmall", "Caronsmall", "Dotaccentsmall", "Macronsmall", 1771 "figuredash", "hypheninferior", "Ogoneksmall", "Ringsmall", "Cedillasmall", 1772 "onequarter", "onehalf", "threequarters", "questiondownsmall", "oneeighth", 1773 "threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds", 1774 "zerosuperior", "onesuperior", "twosuperior", "threesuperior", "foursuperior", 1775 "fivesuperior", "sixsuperior", "sevensuperior", "eightsuperior", "ninesuperior", 1776 "zeroinferior", "oneinferior", "twoinferior", "threeinferior", "fourinferior", 1777 "fiveinferior", "sixinferior", "seveninferior", "eightinferior", "nineinferior", 1778 "centinferior", "dollarinferior", "periodinferior", "commainferior", 1779 "Agravesmall", "Aacutesmall", "Acircumflexsmall", "Atildesmall", 1780 "Adieresissmall", "Aringsmall", "AEsmall", "Ccedillasmall", "Egravesmall", 1781 "Eacutesmall", "Ecircumflexsmall", "Edieresissmall", "Igravesmall", 1782 "Iacutesmall", "Icircumflexsmall", "Idieresissmall", "Ethsmall", "Ntildesmall", 1783 "Ogravesmall", "Oacutesmall", "Ocircumflexsmall", "Otildesmall", 1784 "Odieresissmall", "OEsmall", "Oslashsmall", "Ugravesmall", "Uacutesmall", 1785 "Ucircumflexsmall", "Udieresissmall", "Yacutesmall", "Thornsmall", 1786 "Ydieresissmall"] 1787 1788 cffExpertStringCount = 166 1789 assert len(cffIExpertStrings) == cffExpertStringCount 1790 1791 cffExpertSubsetStrings = [".notdef", "space", "dollaroldstyle", 1792 "dollarsuperior", "parenleftsuperior", "parenrightsuperior", "twodotenleader", 1793 "onedotenleader", "comma", "hyphen", "period", "fraction", "zerooldstyle", 1794 "oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", 1795 "sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", 1796 "semicolon", "commasuperior", "threequartersemdash", "periodsuperior", 1797 "asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior", 1798 "lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior", 1799 "tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior", 1800 "parenrightinferior", "hyphensuperior", "colonmonetary", "onefitted", "rupiah", 1801 "centoldstyle", "figuredash", "hypheninferior", "onequarter", "onehalf", 1802 "threequarters", "oneeighth", "threeeighths", "fiveeighths", "seveneighths", 1803 "onethird", "twothirds", "zerosuperior", "onesuperior", "twosuperior", 1804 "threesuperior", "foursuperior", "fivesuperior", "sixsuperior", "sevensuperior", 1805 "eightsuperior", "ninesuperior", "zeroinferior", "oneinferior", "twoinferior", 1806 "threeinferior", "fourinferior", "fiveinferior", "sixinferior", "seveninferior", 1807 "eightinferior", "nineinferior", "centinferior", "dollarinferior", 1808 "periodinferior", "commainferior"] 1809 1810 cffExpertSubsetStringCount = 87 1811 assert len(cffExpertSubsetStrings) == cffExpertSubsetStringCount 1812