cffLib.py revision bb0beb7385d00a0f5c99895e7299a5a1307ec193
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 11from fontTools.misc.py23 import * 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 list(self.charStrings.keys()) 523 524 def values(self): 525 if self.charStringsAreIndexed: 526 return self.charStringsIndex 527 else: 528 return list(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 = value.decode("latin-1").encode("utf-8") 679 xmlWriter.simpletag(name, value=value) 680 xmlWriter.newline() 681 def xmlRead(self, name, attrs, content, parent): 682 return attrs["value"].decode("utf-8").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