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