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