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