cffLib.py revision 3a9fd301808f5a8991ca9ac44028d1ecb22d307f
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 1: 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 = self.keys() 562 names.sort() 563 i = 0 564 step = 10 565 numGlyphs = len(names) 566 for name in names: 567 charStr, fdSelectIndex = self.getItemAndSelector(name) 568 if charStr.needsDecompilation(): 569 raw = [("raw", 1)] 570 else: 571 raw = [] 572 if fdSelectIndex is None: 573 xmlWriter.begintag("CharString", [('name', name)] + raw) 574 else: 575 xmlWriter.begintag("CharString", 576 [('name', name), ('fdSelectIndex', fdSelectIndex)] + raw) 577 xmlWriter.newline() 578 charStr.toXML(xmlWriter) 579 xmlWriter.endtag("CharString") 580 xmlWriter.newline() 581 if not i % step and progress is not None: 582 progress.setLabel("Dumping 'CFF ' table... (%s)" % name) 583 progress.increment(step / float(numGlyphs)) 584 i = i + 1 585 586 def fromXML(self, name, attrs, content): 587 for element in content: 588 if isinstance(element, basestring): 589 continue 590 name, attrs, content = element 591 if name != "CharString": 592 continue 593 fdID = -1 594 if hasattr(self, "fdArray"): 595 fdID = safeEval(attrs["fdSelectIndex"]) 596 private = self.fdArray[fdID].Private 597 else: 598 private = self.private 599 600 glyphName = attrs["name"] 601 charString = psCharStrings.T2CharString( 602 private=private, 603 globalSubrs=self.globalSubrs) 604 charString.fromXML(name, attrs, content) 605 if fdID >= 0: 606 charString.fdSelectIndex = fdID 607 self[glyphName] = charString 608 609 610def readCard8(file): 611 return ord(file.read(1)) 612 613def readCard16(file): 614 value, = struct.unpack(">H", file.read(2)) 615 return value 616 617def writeCard8(file, value): 618 file.write(chr(value)) 619 620def writeCard16(file, value): 621 file.write(struct.pack(">H", value)) 622 623def packCard8(value): 624 return chr(value) 625 626def packCard16(value): 627 return struct.pack(">H", value) 628 629def buildOperatorDict(table): 630 d = {} 631 for op, name, arg, default, conv in table: 632 d[op] = (name, arg) 633 return d 634 635def buildOpcodeDict(table): 636 d = {} 637 for op, name, arg, default, conv in table: 638 if isinstance(op, tuple): 639 op = chr(op[0]) + chr(op[1]) 640 else: 641 op = chr(op) 642 d[name] = (op, arg) 643 return d 644 645def buildOrder(table): 646 l = [] 647 for op, name, arg, default, conv in table: 648 l.append(name) 649 return l 650 651def buildDefaults(table): 652 d = {} 653 for op, name, arg, default, conv in table: 654 if default is not None: 655 d[name] = default 656 return d 657 658def buildConverters(table): 659 d = {} 660 for op, name, arg, default, conv in table: 661 d[name] = conv 662 return d 663 664 665class SimpleConverter: 666 def read(self, parent, value): 667 return value 668 def write(self, parent, value): 669 return value 670 def xmlWrite(self, xmlWriter, name, value, progress): 671 xmlWriter.simpletag(name, value=value) 672 xmlWriter.newline() 673 def xmlRead(self, name, attrs, content, parent): 674 return attrs["value"] 675 676class Latin1Converter(SimpleConverter): 677 def xmlWrite(self, xmlWriter, name, value, progress): 678 # Store as UTF-8 679 value = unicode(value, "latin-1").encode("utf-8") 680 xmlWriter.simpletag(name, value=value) 681 xmlWriter.newline() 682 def xmlRead(self, name, attrs, content, parent): 683 s = unicode(attrs["value"], "utf-8") 684 return s.encode("latin-1") 685 686 687def parseNum(s): 688 try: 689 value = int(s) 690 except: 691 value = float(s) 692 return value 693 694class NumberConverter(SimpleConverter): 695 def xmlRead(self, name, attrs, content, parent): 696 return parseNum(attrs["value"]) 697 698class ArrayConverter(SimpleConverter): 699 def xmlWrite(self, xmlWriter, name, value, progress): 700 value = map(str, value) 701 xmlWriter.simpletag(name, value=" ".join(value)) 702 xmlWriter.newline() 703 def xmlRead(self, name, attrs, content, parent): 704 values = attrs["value"].split() 705 return map(parseNum, values) 706 707class TableConverter(SimpleConverter): 708 def xmlWrite(self, xmlWriter, name, value, progress): 709 xmlWriter.begintag(name) 710 xmlWriter.newline() 711 value.toXML(xmlWriter, progress) 712 xmlWriter.endtag(name) 713 xmlWriter.newline() 714 def xmlRead(self, name, attrs, content, parent): 715 ob = self.getClass()() 716 for element in content: 717 if isinstance(element, basestring): 718 continue 719 name, attrs, content = element 720 ob.fromXML(name, attrs, content) 721 return ob 722 723class PrivateDictConverter(TableConverter): 724 def getClass(self): 725 return PrivateDict 726 def read(self, parent, value): 727 size, offset = value 728 file = parent.file 729 priv = PrivateDict(parent.strings, file, offset) 730 file.seek(offset) 731 data = file.read(size) 732 len(data) == size 733 priv.decompile(data) 734 return priv 735 def write(self, parent, value): 736 return (0, 0) # dummy value 737 738class SubrsConverter(TableConverter): 739 def getClass(self): 740 return SubrsIndex 741 def read(self, parent, value): 742 file = parent.file 743 file.seek(parent.offset + value) # Offset(self) 744 return SubrsIndex(file) 745 def write(self, parent, value): 746 return 0 # dummy value 747 748class CharStringsConverter(TableConverter): 749 def read(self, parent, value): 750 file = parent.file 751 charset = parent.charset 752 globalSubrs = parent.GlobalSubrs 753 if hasattr(parent, "ROS"): 754 fdSelect, fdArray = parent.FDSelect, parent.FDArray 755 private = None 756 else: 757 fdSelect, fdArray = None, None 758 private = parent.Private 759 file.seek(value) # Offset(0) 760 return CharStrings(file, charset, globalSubrs, private, fdSelect, fdArray) 761 def write(self, parent, value): 762 return 0 # dummy value 763 def xmlRead(self, name, attrs, content, parent): 764 if hasattr(parent, "ROS"): 765 # if it is a CID-keyed font, then the private Dict is extracted from the parent.FDArray 766 private, fdSelect, fdArray = None, parent.FDSelect, parent.FDArray 767 else: 768 # if it is a name-keyed font, then the private dict is in the top dict, and there is no fdArray. 769 private, fdSelect, fdArray = parent.Private, None, None 770 charStrings = CharStrings(None, None, parent.GlobalSubrs, private, fdSelect, fdArray) 771 charStrings.fromXML(name, attrs, content) 772 return charStrings 773 774class CharsetConverter: 775 def read(self, parent, value): 776 isCID = hasattr(parent, "ROS") 777 if value > 2: 778 numGlyphs = parent.numGlyphs 779 file = parent.file 780 file.seek(value) 781 if DEBUG: 782 print "loading charset at %s" % value 783 format = readCard8(file) 784 if format == 0: 785 charset = parseCharset0(numGlyphs, file, parent.strings, isCID) 786 elif format == 1 or format == 2: 787 charset = parseCharset(numGlyphs, file, parent.strings, isCID, format) 788 else: 789 raise NotImplementedError 790 assert len(charset) == numGlyphs 791 if DEBUG: 792 print " charset end at %s" % file.tell() 793 else: # offset == 0 -> no charset data. 794 if isCID or "CharStrings" not in parent.rawDict: 795 assert value == 0 # We get here only when processing fontDicts from the FDArray of CFF-CID fonts. Only the real topDict references the chrset. 796 charset = None 797 elif value == 0: 798 charset = cffISOAdobeStrings 799 elif value == 1: 800 charset = cffIExpertStrings 801 elif value == 2: 802 charset = cffExpertSubsetStrings 803 return charset 804 805 def write(self, parent, value): 806 return 0 # dummy value 807 def xmlWrite(self, xmlWriter, name, value, progress): 808 # XXX only write charset when not in OT/TTX context, where we 809 # dump charset as a separate "GlyphOrder" table. 810 ##xmlWriter.simpletag("charset") 811 xmlWriter.comment("charset is dumped separately as the 'GlyphOrder' element") 812 xmlWriter.newline() 813 def xmlRead(self, name, attrs, content, parent): 814 if 0: 815 return safeEval(attrs["value"]) 816 817 818class CharsetCompiler: 819 820 def __init__(self, strings, charset, parent): 821 assert charset[0] == '.notdef' 822 isCID = hasattr(parent.dictObj, "ROS") 823 data0 = packCharset0(charset, isCID, strings) 824 data = packCharset(charset, isCID, strings) 825 if len(data) < len(data0): 826 self.data = data 827 else: 828 self.data = data0 829 self.parent = parent 830 831 def setPos(self, pos, endPos): 832 self.parent.rawDict["charset"] = pos 833 834 def getDataLength(self): 835 return len(self.data) 836 837 def toFile(self, file): 838 file.write(self.data) 839 840 841def getCIDfromName(name, strings): 842 return int(name[3:]) 843 844def getSIDfromName(name, strings): 845 return strings.getSID(name) 846 847def packCharset0(charset, isCID, strings): 848 format = 0 849 data = [packCard8(format)] 850 if isCID: 851 getNameID = getCIDfromName 852 else: 853 getNameID = getSIDfromName 854 855 for name in charset[1:]: 856 data.append(packCard16(getNameID(name,strings))) 857 return "".join(data) 858 859 860def packCharset(charset, isCID, strings): 861 format = 1 862 ranges = [] 863 first = None 864 end = 0 865 if isCID: 866 getNameID = getCIDfromName 867 else: 868 getNameID = getSIDfromName 869 870 for name in charset[1:]: 871 SID = getNameID(name, strings) 872 if first is None: 873 first = SID 874 elif end + 1 != SID: 875 nLeft = end - first 876 if nLeft > 255: 877 format = 2 878 ranges.append((first, nLeft)) 879 first = SID 880 end = SID 881 nLeft = end - first 882 if nLeft > 255: 883 format = 2 884 ranges.append((first, nLeft)) 885 886 data = [packCard8(format)] 887 if format == 1: 888 nLeftFunc = packCard8 889 else: 890 nLeftFunc = packCard16 891 for first, nLeft in ranges: 892 data.append(packCard16(first) + nLeftFunc(nLeft)) 893 return "".join(data) 894 895def parseCharset0(numGlyphs, file, strings, isCID): 896 charset = [".notdef"] 897 if isCID: 898 for i in range(numGlyphs - 1): 899 CID = readCard16(file) 900 charset.append("cid" + string.zfill(str(CID), 5) ) 901 else: 902 for i in range(numGlyphs - 1): 903 SID = readCard16(file) 904 charset.append(strings[SID]) 905 return charset 906 907def parseCharset(numGlyphs, file, strings, isCID, format): 908 charset = ['.notdef'] 909 count = 1 910 if format == 1: 911 nLeftFunc = readCard8 912 else: 913 nLeftFunc = readCard16 914 while count < numGlyphs: 915 first = readCard16(file) 916 nLeft = nLeftFunc(file) 917 if isCID: 918 for CID in range(first, first+nLeft+1): 919 charset.append("cid" + string.zfill(str(CID), 5) ) 920 else: 921 for SID in range(first, first+nLeft+1): 922 charset.append(strings[SID]) 923 count = count + nLeft + 1 924 return charset 925 926 927class EncodingCompiler: 928 929 def __init__(self, strings, encoding, parent): 930 assert not isinstance(encoding, basestring) 931 data0 = packEncoding0(parent.dictObj.charset, encoding, parent.strings) 932 data1 = packEncoding1(parent.dictObj.charset, encoding, parent.strings) 933 if len(data0) < len(data1): 934 self.data = data0 935 else: 936 self.data = data1 937 self.parent = parent 938 939 def setPos(self, pos, endPos): 940 self.parent.rawDict["Encoding"] = pos 941 942 def getDataLength(self): 943 return len(self.data) 944 945 def toFile(self, file): 946 file.write(self.data) 947 948 949class EncodingConverter(SimpleConverter): 950 951 def read(self, parent, value): 952 if value == 0: 953 return "StandardEncoding" 954 elif value == 1: 955 return "ExpertEncoding" 956 else: 957 assert value > 1 958 file = parent.file 959 file.seek(value) 960 if DEBUG: 961 print "loading Encoding at %s" % value 962 format = readCard8(file) 963 haveSupplement = format & 0x80 964 if haveSupplement: 965 raise NotImplementedError("Encoding supplements are not yet supported") 966 format = format & 0x7f 967 if format == 0: 968 encoding = parseEncoding0(parent.charset, file, haveSupplement, 969 parent.strings) 970 elif format == 1: 971 encoding = parseEncoding1(parent.charset, file, haveSupplement, 972 parent.strings) 973 return encoding 974 975 def write(self, parent, value): 976 if value == "StandardEncoding": 977 return 0 978 elif value == "ExpertEncoding": 979 return 1 980 return 0 # dummy value 981 982 def xmlWrite(self, xmlWriter, name, value, progress): 983 if value in ("StandardEncoding", "ExpertEncoding"): 984 xmlWriter.simpletag(name, name=value) 985 xmlWriter.newline() 986 return 987 xmlWriter.begintag(name) 988 xmlWriter.newline() 989 for code in range(len(value)): 990 glyphName = value[code] 991 if glyphName != ".notdef": 992 xmlWriter.simpletag("map", code=hex(code), name=glyphName) 993 xmlWriter.newline() 994 xmlWriter.endtag(name) 995 xmlWriter.newline() 996 997 def xmlRead(self, name, attrs, content, parent): 998 if "name" in attrs: 999 return attrs["name"] 1000 encoding = [".notdef"] * 256 1001 for element in content: 1002 if isinstance(element, basestring): 1003 continue 1004 name, attrs, content = element 1005 code = safeEval(attrs["code"]) 1006 glyphName = attrs["name"] 1007 encoding[code] = glyphName 1008 return encoding 1009 1010 1011def parseEncoding0(charset, file, haveSupplement, strings): 1012 nCodes = readCard8(file) 1013 encoding = [".notdef"] * 256 1014 for glyphID in range(1, nCodes + 1): 1015 code = readCard8(file) 1016 if code != 0: 1017 encoding[code] = charset[glyphID] 1018 return encoding 1019 1020def parseEncoding1(charset, file, haveSupplement, strings): 1021 nRanges = readCard8(file) 1022 encoding = [".notdef"] * 256 1023 glyphID = 1 1024 for i in range(nRanges): 1025 code = readCard8(file) 1026 nLeft = readCard8(file) 1027 for glyphID in range(glyphID, glyphID + nLeft + 1): 1028 encoding[code] = charset[glyphID] 1029 code = code + 1 1030 glyphID = glyphID + 1 1031 return encoding 1032 1033def packEncoding0(charset, encoding, strings): 1034 format = 0 1035 m = {} 1036 for code in range(len(encoding)): 1037 name = encoding[code] 1038 if name != ".notdef": 1039 m[name] = code 1040 codes = [] 1041 for name in charset[1:]: 1042 code = m.get(name) 1043 codes.append(code) 1044 1045 while codes and codes[-1] is None: 1046 codes.pop() 1047 1048 data = [packCard8(format), packCard8(len(codes))] 1049 for code in codes: 1050 if code is None: 1051 code = 0 1052 data.append(packCard8(code)) 1053 return "".join(data) 1054 1055def packEncoding1(charset, encoding, strings): 1056 format = 1 1057 m = {} 1058 for code in range(len(encoding)): 1059 name = encoding[code] 1060 if name != ".notdef": 1061 m[name] = code 1062 ranges = [] 1063 first = None 1064 end = 0 1065 for name in charset[1:]: 1066 code = m.get(name, -1) 1067 if first is None: 1068 first = code 1069 elif end + 1 != code: 1070 nLeft = end - first 1071 ranges.append((first, nLeft)) 1072 first = code 1073 end = code 1074 nLeft = end - first 1075 ranges.append((first, nLeft)) 1076 1077 # remove unencoded glyphs at the end. 1078 while ranges and ranges[-1][0] == -1: 1079 ranges.pop() 1080 1081 data = [packCard8(format), packCard8(len(ranges))] 1082 for first, nLeft in ranges: 1083 if first == -1: # unencoded 1084 first = 0 1085 data.append(packCard8(first) + packCard8(nLeft)) 1086 return "".join(data) 1087 1088 1089class FDArrayConverter(TableConverter): 1090 1091 def read(self, parent, value): 1092 file = parent.file 1093 file.seek(value) 1094 fdArray = FDArrayIndex(file) 1095 fdArray.strings = parent.strings 1096 fdArray.GlobalSubrs = parent.GlobalSubrs 1097 return fdArray 1098 1099 def write(self, parent, value): 1100 return 0 # dummy value 1101 1102 def xmlRead(self, name, attrs, content, parent): 1103 fdArray = FDArrayIndex() 1104 for element in content: 1105 if isinstance(element, basestring): 1106 continue 1107 name, attrs, content = element 1108 fdArray.fromXML(name, attrs, content) 1109 return fdArray 1110 1111 1112class FDSelectConverter: 1113 1114 def read(self, parent, value): 1115 file = parent.file 1116 file.seek(value) 1117 fdSelect = FDSelect(file, parent.numGlyphs) 1118 return fdSelect 1119 1120 def write(self, parent, value): 1121 return 0 # dummy value 1122 1123 # The FDSelect glyph data is written out to XML in the charstring keys, 1124 # so we write out only the format selector 1125 def xmlWrite(self, xmlWriter, name, value, progress): 1126 xmlWriter.simpletag(name, [('format', value.format)]) 1127 xmlWriter.newline() 1128 1129 def xmlRead(self, name, attrs, content, parent): 1130 format = safeEval(attrs["format"]) 1131 file = None 1132 numGlyphs = None 1133 fdSelect = FDSelect(file, numGlyphs, format) 1134 return fdSelect 1135 1136 1137def packFDSelect0(fdSelectArray): 1138 format = 0 1139 data = [packCard8(format)] 1140 for index in fdSelectArray: 1141 data.append(packCard8(index)) 1142 return "".join(data) 1143 1144 1145def packFDSelect3(fdSelectArray): 1146 format = 3 1147 fdRanges = [] 1148 first = None 1149 end = 0 1150 lenArray = len(fdSelectArray) 1151 lastFDIndex = -1 1152 for i in range(lenArray): 1153 fdIndex = fdSelectArray[i] 1154 if lastFDIndex != fdIndex: 1155 fdRanges.append([i, fdIndex]) 1156 lastFDIndex = fdIndex 1157 sentinelGID = i + 1 1158 1159 data = [packCard8(format)] 1160 data.append(packCard16( len(fdRanges) )) 1161 for fdRange in fdRanges: 1162 data.append(packCard16(fdRange[0])) 1163 data.append(packCard8(fdRange[1])) 1164 data.append(packCard16(sentinelGID)) 1165 return "".join(data) 1166 1167 1168class FDSelectCompiler: 1169 1170 def __init__(self, fdSelect, parent): 1171 format = fdSelect.format 1172 fdSelectArray = fdSelect.gidArray 1173 if format == 0: 1174 self.data = packFDSelect0(fdSelectArray) 1175 elif format == 3: 1176 self.data = packFDSelect3(fdSelectArray) 1177 else: 1178 # choose smaller of the two formats 1179 data0 = packFDSelect0(fdSelectArray) 1180 data3 = packFDSelect3(fdSelectArray) 1181 if len(data0) < len(data3): 1182 self.data = data0 1183 fdSelect.format = 0 1184 else: 1185 self.data = data3 1186 fdSelect.format = 3 1187 1188 self.parent = parent 1189 1190 def setPos(self, pos, endPos): 1191 self.parent.rawDict["FDSelect"] = pos 1192 1193 def getDataLength(self): 1194 return len(self.data) 1195 1196 def toFile(self, file): 1197 file.write(self.data) 1198 1199 1200class ROSConverter(SimpleConverter): 1201 1202 def xmlWrite(self, xmlWriter, name, value, progress): 1203 registry, order, supplement = value 1204 xmlWriter.simpletag(name, [('Registry', registry), ('Order', order), 1205 ('Supplement', supplement)]) 1206 xmlWriter.newline() 1207 1208 def xmlRead(self, name, attrs, content, parent): 1209 return (attrs['Registry'], attrs['Order'], safeEval(attrs['Supplement'])) 1210 1211 1212 1213topDictOperators = [ 1214# opcode name argument type default converter 1215 ((12, 30), 'ROS', ('SID','SID','number'), None, ROSConverter()), 1216 ((12, 20), 'SyntheticBase', 'number', None, None), 1217 (0, 'version', 'SID', None, None), 1218 (1, 'Notice', 'SID', None, Latin1Converter()), 1219 ((12, 0), 'Copyright', 'SID', None, Latin1Converter()), 1220 (2, 'FullName', 'SID', None, None), 1221 ((12, 38), 'FontName', 'SID', None, None), 1222 (3, 'FamilyName', 'SID', None, None), 1223 (4, 'Weight', 'SID', None, None), 1224 ((12, 1), 'isFixedPitch', 'number', 0, None), 1225 ((12, 2), 'ItalicAngle', 'number', 0, None), 1226 ((12, 3), 'UnderlinePosition', 'number', None, None), 1227 ((12, 4), 'UnderlineThickness', 'number', 50, None), 1228 ((12, 5), 'PaintType', 'number', 0, None), 1229 ((12, 6), 'CharstringType', 'number', 2, None), 1230 ((12, 7), 'FontMatrix', 'array', [0.001,0,0,0.001,0,0], None), 1231 (13, 'UniqueID', 'number', None, None), 1232 (5, 'FontBBox', 'array', [0,0,0,0], None), 1233 ((12, 8), 'StrokeWidth', 'number', 0, None), 1234 (14, 'XUID', 'array', None, None), 1235 ((12, 21), 'PostScript', 'SID', None, None), 1236 ((12, 22), 'BaseFontName', 'SID', None, None), 1237 ((12, 23), 'BaseFontBlend', 'delta', None, None), 1238 ((12, 31), 'CIDFontVersion', 'number', 0, None), 1239 ((12, 32), 'CIDFontRevision', 'number', 0, None), 1240 ((12, 33), 'CIDFontType', 'number', 0, None), 1241 ((12, 34), 'CIDCount', 'number', 8720, None), 1242 (15, 'charset', 'number', 0, CharsetConverter()), 1243 ((12, 35), 'UIDBase', 'number', None, None), 1244 (16, 'Encoding', 'number', 0, EncodingConverter()), 1245 (18, 'Private', ('number','number'), None, PrivateDictConverter()), 1246 ((12, 37), 'FDSelect', 'number', None, FDSelectConverter()), 1247 ((12, 36), 'FDArray', 'number', None, FDArrayConverter()), 1248 (17, 'CharStrings', 'number', None, CharStringsConverter()), 1249] 1250 1251# Note! FDSelect and FDArray must both preceed CharStrings in the output XML build order, 1252# in order for the font to compile back from xml. 1253 1254 1255privateDictOperators = [ 1256# opcode name argument type default converter 1257 (6, 'BlueValues', 'delta', None, None), 1258 (7, 'OtherBlues', 'delta', None, None), 1259 (8, 'FamilyBlues', 'delta', None, None), 1260 (9, 'FamilyOtherBlues', 'delta', None, None), 1261 ((12, 9), 'BlueScale', 'number', 0.039625, None), 1262 ((12, 10), 'BlueShift', 'number', 7, None), 1263 ((12, 11), 'BlueFuzz', 'number', 1, None), 1264 (10, 'StdHW', 'number', None, None), 1265 (11, 'StdVW', 'number', None, None), 1266 ((12, 12), 'StemSnapH', 'delta', None, None), 1267 ((12, 13), 'StemSnapV', 'delta', None, None), 1268 ((12, 14), 'ForceBold', 'number', 0, None), 1269 ((12, 15), 'ForceBoldThreshold', 'number', None, None), # deprecated 1270 ((12, 16), 'lenIV', 'number', None, None), # deprecated 1271 ((12, 17), 'LanguageGroup', 'number', 0, None), 1272 ((12, 18), 'ExpansionFactor', 'number', 0.06, None), 1273 ((12, 19), 'initialRandomSeed', 'number', 0, None), 1274 (20, 'defaultWidthX', 'number', 0, None), 1275 (21, 'nominalWidthX', 'number', 0, None), 1276 (19, 'Subrs', 'number', None, SubrsConverter()), 1277] 1278 1279def addConverters(table): 1280 for i in range(len(table)): 1281 op, name, arg, default, conv = table[i] 1282 if conv is not None: 1283 continue 1284 if arg in ("delta", "array"): 1285 conv = ArrayConverter() 1286 elif arg == "number": 1287 conv = NumberConverter() 1288 elif arg == "SID": 1289 conv = SimpleConverter() 1290 else: 1291 assert 0 1292 table[i] = op, name, arg, default, conv 1293 1294addConverters(privateDictOperators) 1295addConverters(topDictOperators) 1296 1297 1298class TopDictDecompiler(psCharStrings.DictDecompiler): 1299 operators = buildOperatorDict(topDictOperators) 1300 1301 1302class PrivateDictDecompiler(psCharStrings.DictDecompiler): 1303 operators = buildOperatorDict(privateDictOperators) 1304 1305 1306class DictCompiler: 1307 1308 def __init__(self, dictObj, strings, parent): 1309 assert isinstance(strings, IndexedStrings) 1310 self.dictObj = dictObj 1311 self.strings = strings 1312 self.parent = parent 1313 rawDict = {} 1314 for name in dictObj.order: 1315 value = getattr(dictObj, name, None) 1316 if value is None: 1317 continue 1318 conv = dictObj.converters[name] 1319 value = conv.write(dictObj, value) 1320 if value == dictObj.defaults.get(name): 1321 continue 1322 rawDict[name] = value 1323 self.rawDict = rawDict 1324 1325 def setPos(self, pos, endPos): 1326 pass 1327 1328 def getDataLength(self): 1329 return len(self.compile("getDataLength")) 1330 1331 def compile(self, reason): 1332 if DEBUG: 1333 print "-- compiling %s for %s" % (self.__class__.__name__, reason) 1334 print "in baseDict: ", self 1335 rawDict = self.rawDict 1336 data = [] 1337 for name in self.dictObj.order: 1338 value = rawDict.get(name) 1339 if value is None: 1340 continue 1341 op, argType = self.opcodes[name] 1342 if isinstance(argType, tuple): 1343 l = len(argType) 1344 assert len(value) == l, "value doesn't match arg type" 1345 for i in range(l): 1346 arg = argType[i] 1347 v = value[i] 1348 arghandler = getattr(self, "arg_" + arg) 1349 data.append(arghandler(v)) 1350 else: 1351 arghandler = getattr(self, "arg_" + argType) 1352 data.append(arghandler(value)) 1353 data.append(op) 1354 return "".join(data) 1355 1356 def toFile(self, file): 1357 file.write(self.compile("toFile")) 1358 1359 def arg_number(self, num): 1360 return encodeNumber(num) 1361 def arg_SID(self, s): 1362 return psCharStrings.encodeIntCFF(self.strings.getSID(s)) 1363 def arg_array(self, value): 1364 data = [] 1365 for num in value: 1366 data.append(encodeNumber(num)) 1367 return "".join(data) 1368 def arg_delta(self, value): 1369 out = [] 1370 last = 0 1371 for v in value: 1372 out.append(v - last) 1373 last = v 1374 data = [] 1375 for num in out: 1376 data.append(encodeNumber(num)) 1377 return "".join(data) 1378 1379 1380def encodeNumber(num): 1381 if isinstance(num, float): 1382 return psCharStrings.encodeFloat(num) 1383 else: 1384 return psCharStrings.encodeIntCFF(num) 1385 1386 1387class TopDictCompiler(DictCompiler): 1388 1389 opcodes = buildOpcodeDict(topDictOperators) 1390 1391 def getChildren(self, strings): 1392 children = [] 1393 if hasattr(self.dictObj, "charset") and self.dictObj.charset: 1394 children.append(CharsetCompiler(strings, self.dictObj.charset, self)) 1395 if hasattr(self.dictObj, "Encoding"): 1396 encoding = self.dictObj.Encoding 1397 if not isinstance(encoding, basestring): 1398 children.append(EncodingCompiler(strings, encoding, self)) 1399 if hasattr(self.dictObj, "FDSelect"): 1400 # I have not yet supported merging a ttx CFF-CID font, as there are interesting 1401 # issues about merging the FDArrays. Here I assume that 1402 # either the font was read from XML, and teh FDSelect indices are all 1403 # in the charstring data, or the FDSelect array is already fully defined. 1404 fdSelect = self.dictObj.FDSelect 1405 if len(fdSelect) == 0: # probably read in from XML; assume fdIndex in CharString data 1406 charStrings = self.dictObj.CharStrings 1407 for name in self.dictObj.charset: 1408 charstring = charStrings[name] 1409 fdSelect.append(charStrings[name].fdSelectIndex) 1410 fdSelectComp = FDSelectCompiler(fdSelect, self) 1411 children.append(fdSelectComp) 1412 if hasattr(self.dictObj, "CharStrings"): 1413 items = [] 1414 charStrings = self.dictObj.CharStrings 1415 for name in self.dictObj.charset: 1416 items.append(charStrings[name]) 1417 charStringsComp = CharStringsCompiler(items, strings, self) 1418 children.append(charStringsComp) 1419 if hasattr(self.dictObj, "FDArray"): 1420 # I have not yet supported merging a ttx CFF-CID font, as there are interesting 1421 # issues about merging the FDArrays. Here I assume that the FDArray info is correct 1422 # and complete. 1423 fdArrayIndexComp = self.dictObj.FDArray.getCompiler(strings, self) 1424 children.append(fdArrayIndexComp) 1425 children.extend(fdArrayIndexComp.getChildren(strings)) 1426 if hasattr(self.dictObj, "Private"): 1427 privComp = self.dictObj.Private.getCompiler(strings, self) 1428 children.append(privComp) 1429 children.extend(privComp.getChildren(strings)) 1430 return children 1431 1432 1433class FontDictCompiler(DictCompiler): 1434 1435 opcodes = buildOpcodeDict(topDictOperators) 1436 1437 def getChildren(self, strings): 1438 children = [] 1439 if hasattr(self.dictObj, "Private"): 1440 privComp = self.dictObj.Private.getCompiler(strings, self) 1441 children.append(privComp) 1442 children.extend(privComp.getChildren(strings)) 1443 return children 1444 1445 1446class PrivateDictCompiler(DictCompiler): 1447 1448 opcodes = buildOpcodeDict(privateDictOperators) 1449 1450 def setPos(self, pos, endPos): 1451 size = endPos - pos 1452 self.parent.rawDict["Private"] = size, pos 1453 self.pos = pos 1454 1455 def getChildren(self, strings): 1456 children = [] 1457 if hasattr(self.dictObj, "Subrs"): 1458 children.append(self.dictObj.Subrs.getCompiler(strings, self)) 1459 return children 1460 1461 1462class BaseDict: 1463 1464 def __init__(self, strings=None, file=None, offset=None): 1465 self.rawDict = {} 1466 if DEBUG: 1467 print "loading %s at %s" % (self.__class__.__name__, offset) 1468 self.file = file 1469 self.offset = offset 1470 self.strings = strings 1471 self.skipNames = [] 1472 1473 def decompile(self, data): 1474 if DEBUG: 1475 print " length %s is %s" % (self.__class__.__name__, len(data)) 1476 dec = self.decompilerClass(self.strings) 1477 dec.decompile(data) 1478 self.rawDict = dec.getDict() 1479 self.postDecompile() 1480 1481 def postDecompile(self): 1482 pass 1483 1484 def getCompiler(self, strings, parent): 1485 return self.compilerClass(self, strings, parent) 1486 1487 def __getattr__(self, name): 1488 value = self.rawDict.get(name) 1489 if value is None: 1490 value = self.defaults.get(name) 1491 if value is None: 1492 raise AttributeError(name) 1493 conv = self.converters[name] 1494 value = conv.read(self, value) 1495 setattr(self, name, value) 1496 return value 1497 1498 def toXML(self, xmlWriter, progress): 1499 for name in self.order: 1500 if name in self.skipNames: 1501 continue 1502 value = getattr(self, name, None) 1503 if value is None: 1504 continue 1505 conv = self.converters[name] 1506 conv.xmlWrite(xmlWriter, name, value, progress) 1507 1508 def fromXML(self, name, attrs, content): 1509 conv = self.converters[name] 1510 value = conv.xmlRead(name, attrs, content, self) 1511 setattr(self, name, value) 1512 1513 1514class TopDict(BaseDict): 1515 1516 defaults = buildDefaults(topDictOperators) 1517 converters = buildConverters(topDictOperators) 1518 order = buildOrder(topDictOperators) 1519 decompilerClass = TopDictDecompiler 1520 compilerClass = TopDictCompiler 1521 1522 def __init__(self, strings=None, file=None, offset=None, GlobalSubrs=None): 1523 BaseDict.__init__(self, strings, file, offset) 1524 self.GlobalSubrs = GlobalSubrs 1525 1526 def getGlyphOrder(self): 1527 return self.charset 1528 1529 def postDecompile(self): 1530 offset = self.rawDict.get("CharStrings") 1531 if offset is None: 1532 return 1533 # get the number of glyphs beforehand. 1534 self.file.seek(offset) 1535 self.numGlyphs = readCard16(self.file) 1536 1537 def toXML(self, xmlWriter, progress): 1538 if hasattr(self, "CharStrings"): 1539 self.decompileAllCharStrings(progress) 1540 if hasattr(self, "ROS"): 1541 self.skipNames = ['Encoding'] 1542 if not hasattr(self, "ROS") or not hasattr(self, "CharStrings"): 1543 # these values have default values, but I only want them to show up 1544 # in CID fonts. 1545 self.skipNames = ['CIDFontVersion', 'CIDFontRevision', 'CIDFontType', 1546 'CIDCount'] 1547 BaseDict.toXML(self, xmlWriter, progress) 1548 1549 def decompileAllCharStrings(self, progress): 1550 # XXX only when doing ttdump -i? 1551 i = 0 1552 for charString in self.CharStrings.values(): 1553 try: 1554 charString.decompile() 1555 except: 1556 print "Error in charstring ", i 1557 import sys 1558 type, value = sys. exc_info()[0:2] 1559 raise type(value) 1560 if not i % 30 and progress: 1561 progress.increment(0) # update 1562 i = i + 1 1563 1564 1565class FontDict(BaseDict): 1566 1567 defaults = buildDefaults(topDictOperators) 1568 converters = buildConverters(topDictOperators) 1569 order = buildOrder(topDictOperators) 1570 decompilerClass = None 1571 compilerClass = FontDictCompiler 1572 1573 def __init__(self, strings=None, file=None, offset=None, GlobalSubrs=None): 1574 BaseDict.__init__(self, strings, file, offset) 1575 self.GlobalSubrs = GlobalSubrs 1576 1577 def getGlyphOrder(self): 1578 return self.charset 1579 1580 def toXML(self, xmlWriter, progress): 1581 self.skipNames = ['Encoding'] 1582 BaseDict.toXML(self, xmlWriter, progress) 1583 1584 1585 1586class PrivateDict(BaseDict): 1587 defaults = buildDefaults(privateDictOperators) 1588 converters = buildConverters(privateDictOperators) 1589 order = buildOrder(privateDictOperators) 1590 decompilerClass = PrivateDictDecompiler 1591 compilerClass = PrivateDictCompiler 1592 1593 1594class IndexedStrings: 1595 1596 """SID -> string mapping.""" 1597 1598 def __init__(self, file=None): 1599 if file is None: 1600 strings = [] 1601 else: 1602 strings = list(Index(file)) 1603 self.strings = strings 1604 1605 def getCompiler(self): 1606 return IndexedStringsCompiler(self, None, None) 1607 1608 def __len__(self): 1609 return len(self.strings) 1610 1611 def __getitem__(self, SID): 1612 if SID < cffStandardStringCount: 1613 return cffStandardStrings[SID] 1614 else: 1615 return self.strings[SID - cffStandardStringCount] 1616 1617 def getSID(self, s): 1618 if not hasattr(self, "stringMapping"): 1619 self.buildStringMapping() 1620 if s in cffStandardStringMapping: 1621 SID = cffStandardStringMapping[s] 1622 elif s in self.stringMapping: 1623 SID = self.stringMapping[s] 1624 else: 1625 SID = len(self.strings) + cffStandardStringCount 1626 self.strings.append(s) 1627 self.stringMapping[s] = SID 1628 return SID 1629 1630 def getStrings(self): 1631 return self.strings 1632 1633 def buildStringMapping(self): 1634 self.stringMapping = {} 1635 for index in range(len(self.strings)): 1636 self.stringMapping[self.strings[index]] = index + cffStandardStringCount 1637 1638 1639# The 391 Standard Strings as used in the CFF format. 1640# from Adobe Technical None #5176, version 1.0, 18 March 1998 1641 1642cffStandardStrings = ['.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 1643 'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright', 1644 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 1645 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 1646 'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 1647 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 1648 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 1649 'bracketright', 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c', 1650 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 1651 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 1652 'asciitilde', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 1653 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft', 1654 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 1655 'daggerdbl', 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase', 1656 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', 1657 'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 1658 'dotaccent', 'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 1659 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae', 1660 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior', 1661 'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 1662 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters', 1663 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior', 1664 'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 1665 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave', 1666 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 1667 'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 1668 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron', 1669 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla', 1670 'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex', 1671 'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis', 1672 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave', 1673 'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall', 1674 'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall', 1675 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', 1676 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 1677 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 1678 'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior', 1679 'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 1680 'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior', 1681 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', 'parenleftinferior', 1682 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 1683 'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 1684 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 1685 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall', 1686 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', 1687 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 1688 'Dieresissmall', 'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall', 1689 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', 1690 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 1691 'onethird', 'twothirds', 'zerosuperior', 'foursuperior', 'fivesuperior', 1692 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 1693 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 1694 'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 1695 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall', 1696 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', 1697 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 1698 'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 1699 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', 1700 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 1701 'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 1702 'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', '001.001', '001.002', 1703 '001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman', 1704 'Semibold' 1705] 1706 1707cffStandardStringCount = 391 1708assert len(cffStandardStrings) == cffStandardStringCount 1709# build reverse mapping 1710cffStandardStringMapping = {} 1711for _i in range(cffStandardStringCount): 1712 cffStandardStringMapping[cffStandardStrings[_i]] = _i 1713 1714cffISOAdobeStrings = [".notdef", "space", "exclam", "quotedbl", "numbersign", 1715"dollar", "percent", "ampersand", "quoteright", "parenleft", "parenright", 1716"asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two", 1717"three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", 1718"less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", 1719"H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", 1720"X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum", 1721"underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", 1722"k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", 1723"braceleft", "bar", "braceright", "asciitilde", "exclamdown", "cent", 1724"sterling", "fraction", "yen", "florin", "section", "currency", "quotesingle", 1725"quotedblleft", "guillemotleft", "guilsinglleft", "guilsinglright", "fi", "fl", 1726"endash", "dagger", "daggerdbl", "periodcentered", "paragraph", "bullet", 1727"quotesinglbase", "quotedblbase", "quotedblright", "guillemotright", "ellipsis", 1728"perthousand", "questiondown", "grave", "acute", "circumflex", "tilde", 1729"macron", "breve", "dotaccent", "dieresis", "ring", "cedilla", "hungarumlaut", 1730"ogonek", "caron", "emdash", "AE", "ordfeminine", "Lslash", "Oslash", "OE", 1731"ordmasculine", "ae", "dotlessi", "lslash", "oslash", "oe", "germandbls", 1732"onesuperior", "logicalnot", "mu", "trademark", "Eth", "onehalf", "plusminus", 1733"Thorn", "onequarter", "divide", "brokenbar", "degree", "thorn", 1734"threequarters", "twosuperior", "registered", "minus", "eth", "multiply", 1735"threesuperior", "copyright", "Aacute", "Acircumflex", "Adieresis", "Agrave", 1736"Aring", "Atilde", "Ccedilla", "Eacute", "Ecircumflex", "Edieresis", "Egrave", 1737"Iacute", "Icircumflex", "Idieresis", "Igrave", "Ntilde", "Oacute", 1738"Ocircumflex", "Odieresis", "Ograve", "Otilde", "Scaron", "Uacute", 1739"Ucircumflex", "Udieresis", "Ugrave", "Yacute", "Ydieresis", "Zcaron", "aacute", 1740"acircumflex", "adieresis", "agrave", "aring", "atilde", "ccedilla", "eacute", 1741"ecircumflex", "edieresis", "egrave", "iacute", "icircumflex", "idieresis", 1742"igrave", "ntilde", "oacute", "ocircumflex", "odieresis", "ograve", "otilde", 1743"scaron", "uacute", "ucircumflex", "udieresis", "ugrave", "yacute", "ydieresis", 1744"zcaron"] 1745 1746cffISOAdobeStringCount = 229 1747assert len(cffISOAdobeStrings) == cffISOAdobeStringCount 1748 1749cffIExpertStrings = [".notdef", "space", "exclamsmall", "Hungarumlautsmall", 1750"dollaroldstyle", "dollarsuperior", "ampersandsmall", "Acutesmall", 1751"parenleftsuperior", "parenrightsuperior", "twodotenleader", "onedotenleader", 1752"comma", "hyphen", "period", "fraction", "zerooldstyle", "oneoldstyle", 1753"twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle", 1754"sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", "semicolon", 1755"commasuperior", "threequartersemdash", "periodsuperior", "questionsmall", 1756"asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior", 1757"lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior", 1758"tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior", 1759"parenrightinferior", "Circumflexsmall", "hyphensuperior", "Gravesmall", 1760"Asmall", "Bsmall", "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall", 1761"Ismall", "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall", 1762"Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall", "Xsmall", 1763"Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah", "Tildesmall", 1764"exclamdownsmall", "centoldstyle", "Lslashsmall", "Scaronsmall", "Zcaronsmall", 1765"Dieresissmall", "Brevesmall", "Caronsmall", "Dotaccentsmall", "Macronsmall", 1766"figuredash", "hypheninferior", "Ogoneksmall", "Ringsmall", "Cedillasmall", 1767"onequarter", "onehalf", "threequarters", "questiondownsmall", "oneeighth", 1768"threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds", 1769"zerosuperior", "onesuperior", "twosuperior", "threesuperior", "foursuperior", 1770"fivesuperior", "sixsuperior", "sevensuperior", "eightsuperior", "ninesuperior", 1771"zeroinferior", "oneinferior", "twoinferior", "threeinferior", "fourinferior", 1772"fiveinferior", "sixinferior", "seveninferior", "eightinferior", "nineinferior", 1773"centinferior", "dollarinferior", "periodinferior", "commainferior", 1774"Agravesmall", "Aacutesmall", "Acircumflexsmall", "Atildesmall", 1775"Adieresissmall", "Aringsmall", "AEsmall", "Ccedillasmall", "Egravesmall", 1776"Eacutesmall", "Ecircumflexsmall", "Edieresissmall", "Igravesmall", 1777"Iacutesmall", "Icircumflexsmall", "Idieresissmall", "Ethsmall", "Ntildesmall", 1778"Ogravesmall", "Oacutesmall", "Ocircumflexsmall", "Otildesmall", 1779"Odieresissmall", "OEsmall", "Oslashsmall", "Ugravesmall", "Uacutesmall", 1780"Ucircumflexsmall", "Udieresissmall", "Yacutesmall", "Thornsmall", 1781"Ydieresissmall"] 1782 1783cffExpertStringCount = 166 1784assert len(cffIExpertStrings) == cffExpertStringCount 1785 1786cffExpertSubsetStrings = [".notdef", "space", "dollaroldstyle", 1787"dollarsuperior", "parenleftsuperior", "parenrightsuperior", "twodotenleader", 1788"onedotenleader", "comma", "hyphen", "period", "fraction", "zerooldstyle", 1789"oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", 1790"sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", 1791"semicolon", "commasuperior", "threequartersemdash", "periodsuperior", 1792"asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior", 1793"lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior", 1794"tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior", 1795"parenrightinferior", "hyphensuperior", "colonmonetary", "onefitted", "rupiah", 1796"centoldstyle", "figuredash", "hypheninferior", "onequarter", "onehalf", 1797"threequarters", "oneeighth", "threeeighths", "fiveeighths", "seveneighths", 1798"onethird", "twothirds", "zerosuperior", "onesuperior", "twosuperior", 1799"threesuperior", "foursuperior", "fivesuperior", "sixsuperior", "sevensuperior", 1800"eightsuperior", "ninesuperior", "zeroinferior", "oneinferior", "twoinferior", 1801"threeinferior", "fourinferior", "fiveinferior", "sixinferior", "seveninferior", 1802"eightinferior", "nineinferior", "centinferior", "dollarinferior", 1803"periodinferior", "commainferior"] 1804 1805cffExpertSubsetStringCount = 87 1806assert len(cffExpertSubsetStrings) == cffExpertSubsetStringCount 1807