cffLib.py revision b58176e5ac4d1e0b0e6a6c71c3020f5e85bd4dfe
1"""cffLib.py -- read/write tools for Adobe CFF fonts.""" 2 3# 4# $Id: cffLib.py,v 1.25 2002-05-24 11:55:37 jvr Exp $ 5# 6 7import struct, sstruct 8import string 9from types import FloatType, ListType, StringType, TupleType 10from fontTools.misc import psCharStrings 11from fontTools.misc.textTools import safeEval 12 13 14DEBUG = 0 15 16 17cffHeaderFormat = """ 18 major: B 19 minor: B 20 hdrSize: B 21 offSize: B 22""" 23 24class CFFFontSet: 25 26 def __init__(self): 27 pass 28 29 def decompile(self, file, otFont): 30 sstruct.unpack(cffHeaderFormat, file.read(4), self) 31 assert self.major == 1 and self.minor == 0, \ 32 "unknown CFF format: %d.%d" % (self.major, self.minor) 33 34 file.seek(self.hdrSize) 35 self.fontNames = list(Index(file)) 36 self.topDictIndex = TopDictIndex(file) 37 self.strings = IndexedStrings(file) 38 self.GlobalSubrs = GlobalSubrsIndex(file) 39 self.topDictIndex.strings = self.strings 40 self.topDictIndex.GlobalSubrs = self.GlobalSubrs 41 42 def __len__(self): 43 return len(self.fontNames) 44 45 def keys(self): 46 return self.fontNames[:] 47 48 def values(self): 49 return self.topDictIndex 50 51 def __getitem__(self, name): 52 try: 53 index = self.fontNames.index(name) 54 except ValueError: 55 raise KeyError, name 56 return self.topDictIndex[index] 57 58 def compile(self, file, otFont): 59 strings = IndexedStrings() 60 writer = CFFWriter() 61 writer.add(sstruct.pack(cffHeaderFormat, self)) 62 fontNames = Index() 63 for name in self.fontNames: 64 fontNames.append(name) 65 writer.add(fontNames.getCompiler(strings, None)) 66 topCompiler = self.topDictIndex.getCompiler(strings, None) 67 writer.add(topCompiler) 68 writer.add(strings.getCompiler()) 69 writer.add(self.GlobalSubrs.getCompiler(strings, None)) 70 71 for topDict in self.topDictIndex: 72 if not hasattr(topDict, "charset") or topDict.charset is None: 73 charset = otFont.getGlyphOrder() 74 topDict.charset = charset 75 76 for child in topCompiler.getChildren(strings): 77 writer.add(child) 78 79 writer.toFile(file) 80 81 def toXML(self, xmlWriter, progress=None): 82 xmlWriter.newline() 83 for fontName in self.fontNames: 84 xmlWriter.begintag("CFFFont", name=fontName) 85 xmlWriter.newline() 86 font = self[fontName] 87 font.toXML(xmlWriter, progress) 88 xmlWriter.endtag("CFFFont") 89 xmlWriter.newline() 90 xmlWriter.newline() 91 xmlWriter.begintag("GlobalSubrs") 92 xmlWriter.newline() 93 self.GlobalSubrs.toXML(xmlWriter, progress) 94 xmlWriter.endtag("GlobalSubrs") 95 xmlWriter.newline() 96 xmlWriter.newline() 97 98 def fromXML(self, (name, attrs, content)): 99 if not hasattr(self, "GlobalSubrs"): 100 self.GlobalSubrs = GlobalSubrsIndex() 101 self.major = 1 102 self.minor = 0 103 self.hdrSize = 4 104 self.offSize = 4 # XXX ?? 105 if name == "CFFFont": 106 if not hasattr(self, "fontNames"): 107 self.fontNames = [] 108 self.topDictIndex = TopDictIndex() 109 fontName = attrs["name"] 110 topDict = TopDict(GlobalSubrs=self.GlobalSubrs) 111 topDict.charset = None # gets filled in later 112 self.fontNames.append(fontName) 113 self.topDictIndex.append(topDict) 114 for element in content: 115 if isinstance(element, StringType): 116 continue 117 topDict.fromXML(element) 118 elif name == "GlobalSubrs": 119 for element in content: 120 if isinstance(element, StringType): 121 continue 122 name, attrs, content = element 123 subr = psCharStrings.T2CharString(None, subrs=None, globalSubrs=None) 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 GlobalSubrsCompiler(IndexCompiler): 253 def getItems(self, items, strings): 254 out = [] 255 for cs in items: 256 cs.compile() 257 out.append(cs.bytecode) 258 return out 259 260class SubrsCompiler(GlobalSubrsCompiler): 261 def setPos(self, pos, endPos): 262 offset = pos - self.parent.pos 263 self.parent.rawDict["Subrs"] = offset 264 265class CharStringsCompiler(GlobalSubrsCompiler): 266 def setPos(self, pos, endPos): 267 self.parent.rawDict["CharStrings"] = pos 268 269 270class Index: 271 272 """This class represents what the CFF spec calls an INDEX.""" 273 274 compilerClass = IndexCompiler 275 276 def __init__(self, file=None): 277 name = self.__class__.__name__ 278 if file is None: 279 self.items = [] 280 return 281 if DEBUG: 282 print "loading %s at %s" % (name, file.tell()) 283 self.file = file 284 count = readCard16(file) 285 self.count = count 286 self.items = [None] * count 287 if count == 0: 288 self.items = [] 289 return 290 offSize = readCard8(file) 291 if DEBUG: 292 print " index count: %s offSize: %s" % (count, offSize) 293 assert offSize <= 4, "offSize too large: %s" % offSize 294 self.offsets = offsets = [] 295 pad = '\0' * (4 - offSize) 296 for index in range(count+1): 297 chunk = file.read(offSize) 298 chunk = pad + chunk 299 offset, = struct.unpack(">L", chunk) 300 offsets.append(int(offset)) 301 self.offsetBase = file.tell() - 1 302 file.seek(self.offsetBase + offsets[-1]) # pretend we've read the whole lot 303 if DEBUG: 304 print " end of %s at %s" % (name, file.tell()) 305 306 def __len__(self): 307 return len(self.items) 308 309 def __getitem__(self, index): 310 item = self.items[index] 311 if item is not None: 312 return item 313 offset = self.offsets[index] + self.offsetBase 314 size = self.offsets[index+1] - self.offsets[index] 315 file = self.file 316 file.seek(offset) 317 data = file.read(size) 318 assert len(data) == size 319 item = self.produceItem(index, data, file, offset, size) 320 self.items[index] = item 321 return item 322 323 def produceItem(self, index, data, file, offset, size): 324 return data 325 326 def append(self, item): 327 self.items.append(item) 328 329 def getCompiler(self, strings, parent): 330 return self.compilerClass(self, strings, parent) 331 332 333class GlobalSubrsIndex(Index): 334 335 compilerClass = GlobalSubrsCompiler 336 337 def __init__(self, file=None, globalSubrs=None, private=None, fdSelect=None, fdArray=None): 338 Index.__init__(self, file) 339 self.globalSubrs = globalSubrs 340 self.private = private 341 self.fdSelect = fdSelect 342 self.fdArray = fdArray 343 344 def produceItem(self, index, data, file, offset, size): 345 if self.private is not None: 346 private = self.private 347 elif self.fdArray is not None: 348 private = self.fdArray[self.fdSelect[index]].Private 349 else: 350 private = None 351 if hasattr(private, "Subrs"): 352 subrs = private.Subrs 353 else: 354 subrs = [] 355 return psCharStrings.T2CharString(data, subrs=subrs, globalSubrs=self.globalSubrs) 356 357 def toXML(self, xmlWriter, progress): 358 fdSelect = self.fdSelect 359 xmlWriter.comment("The 'index' attribute is only for humans; " 360 "it is ignored when parsed.") 361 xmlWriter.newline() 362 for i in range(len(self)): 363 subr = self[i] 364 if subr.needsDecompilation(): 365 xmlWriter.begintag("CharString", index=i, raw=1) 366 else: 367 xmlWriter.begintag("CharString", index=i) 368 xmlWriter.newline() 369 subr.toXML(xmlWriter) 370 xmlWriter.endtag("CharString") 371 xmlWriter.newline() 372 373 def fromXML(self, (name, attrs, content)): 374 if name <> "CharString": 375 return 376 subr = psCharStrings.T2CharString(None, subrs=None, globalSubrs=None) 377 subr.fromXML((name, attrs, content)) 378 self.append(subr) 379 380 def getItemAndSelector(self, index): 381 fdSelect = self.fdSelect 382 if fdSelect is None: 383 sel = None 384 else: 385 sel = fdSelect[index] 386 return self[index], sel 387 388class SubrsIndex(GlobalSubrsIndex): 389 compilerClass = SubrsCompiler 390 391 392class TopDictIndex(Index): 393 394 compilerClass = TopDictIndexCompiler 395 396 def produceItem(self, index, data, file, offset, size): 397 top = TopDict(self.strings, file, offset, self.GlobalSubrs) 398 top.decompile(data) 399 return top 400 401 def toXML(self, xmlWriter, progress): 402 for i in range(len(self)): 403 xmlWriter.begintag("FontDict", index=i) 404 xmlWriter.newline() 405 self[i].toXML(xmlWriter, progress) 406 xmlWriter.endtag("FontDict") 407 xmlWriter.newline() 408 409 410class CharStrings: 411 412 def __init__(self, file, charset, globalSubrs, private, fdSelect, fdArray): 413 if file is not None: 414 self.charStringsIndex = SubrsIndex(file, globalSubrs, private, fdSelect, fdArray) 415 self.charStrings = charStrings = {} 416 for i in range(len(charset)): 417 charStrings[charset[i]] = i 418 self.charStringsAreIndexed = 1 419 else: 420 self.charStrings = {} 421 self.charStringsAreIndexed = 0 422 self.globalSubrs = globalSubrs 423 self.private = private 424 self.fdSelect = fdSelect 425 self.fdArray = fdArray 426 427 def keys(self): 428 return self.charStrings.keys() 429 430 def values(self): 431 if self.charStringsAreIndexed: 432 return self.charStringsIndex 433 else: 434 return self.charStrings.values() 435 436 def has_key(self, name): 437 return self.charStrings.has_key(name) 438 439 def __len__(self): 440 return len(self.charStrings) 441 442 def __getitem__(self, name): 443 charString = self.charStrings[name] 444 if self.charStringsAreIndexed: 445 charString = self.charStringsIndex[charString] 446 return charString 447 448 def __setitem__(self, name, charString): 449 if self.charStringsAreIndexed: 450 index = self.charStrings[name] 451 self.charStringsIndex[index] = charString 452 else: 453 self.charStrings[name] = charString 454 455 def getItemAndSelector(self, name): 456 if self.charStringsAreIndexed: 457 index = self.charStrings[name] 458 return self.charStringsIndex.getItemAndSelector(index) 459 else: 460 # XXX needs work for CID fonts 461 return self.charStrings[name], None 462 463 def toXML(self, xmlWriter, progress): 464 names = self.keys() 465 names.sort() 466 for name in names: 467 charStr, fdSelect = self.getItemAndSelector(name) 468 if charStr.needsDecompilation(): 469 raw = [("raw", 1)] 470 else: 471 raw = [] 472 if fdSelect is None: 473 xmlWriter.begintag("CharString", [('name', name)] + raw) 474 else: 475 xmlWriter.begintag("CharString", 476 [('name', name), ('fdSelect', fdSelect)] + raw) 477 xmlWriter.newline() 478 charStr.toXML(xmlWriter) 479 xmlWriter.endtag("CharString") 480 xmlWriter.newline() 481 482 def fromXML(self, (name, attrs, content)): 483 for element in content: 484 if isinstance(element, StringType): 485 continue 486 name, attrs, content = element 487 if name <> "CharString": 488 continue 489 glyphName = attrs["name"] 490 if hasattr(self.private, "Subrs"): 491 subrs = self.private.Subrs 492 else: 493 subrs = [] 494 globalSubrs = self.globalSubrs 495 charString = psCharStrings.T2CharString(None, subrs=subrs, globalSubrs=globalSubrs) 496 charString.fromXML((name, attrs, content)) 497 self[glyphName] = charString 498 499 500def readCard8(file): 501 return ord(file.read(1)) 502 503def readCard16(file): 504 value, = struct.unpack(">H", file.read(2)) 505 return value 506 507def writeCard8(file, value): 508 file.write(chr(value)) 509 510def writeCard16(file, value): 511 file.write(struct.pack(">H", value)) 512 513def packCard8(value): 514 return chr(value) 515 516def packCard16(value): 517 return struct.pack(">H", value) 518 519def buildOperatorDict(table): 520 d = {} 521 for op, name, arg, default, conv in table: 522 d[op] = (name, arg) 523 return d 524 525def buildOpcodeDict(table): 526 d = {} 527 for op, name, arg, default, conv in table: 528 if type(op) == TupleType: 529 op = chr(op[0]) + chr(op[1]) 530 else: 531 op = chr(op) 532 d[name] = (op, arg) 533 return d 534 535def buildOrder(table): 536 l = [] 537 for op, name, arg, default, conv in table: 538 l.append(name) 539 return l 540 541def buildDefaults(table): 542 d = {} 543 for op, name, arg, default, conv in table: 544 if default is not None: 545 d[name] = default 546 return d 547 548def buildConverters(table): 549 d = {} 550 for op, name, arg, default, conv in table: 551 d[name] = conv 552 return d 553 554 555class SimpleConverter: 556 def read(self, parent, value): 557 return value 558 def write(self, parent, value): 559 return value 560 def xmlWrite(self, xmlWriter, name, value): 561 xmlWriter.simpletag(name, value=value) 562 xmlWriter.newline() 563 def xmlRead(self, (name, attrs, content), parent): 564 return attrs["value"] 565 566def parseNum(s): 567 try: 568 value = int(s) 569 except: 570 value = float(s) 571 return value 572 573class NumberConverter(SimpleConverter): 574 def xmlRead(self, (name, attrs, content), parent): 575 return parseNum(attrs["value"]) 576 577class ArrayConverter(SimpleConverter): 578 def xmlWrite(self, xmlWriter, name, value): 579 value = map(str, value) 580 xmlWriter.simpletag(name, value=" ".join(value)) 581 xmlWriter.newline() 582 def xmlRead(self, (name, attrs, content), parent): 583 values = attrs["value"].split() 584 return map(parseNum, values) 585 586class TableConverter(SimpleConverter): 587 def xmlWrite(self, xmlWriter, name, value): 588 xmlWriter.begintag(name) 589 xmlWriter.newline() 590 value.toXML(xmlWriter, None) 591 xmlWriter.endtag(name) 592 xmlWriter.newline() 593 def xmlRead(self, (name, attrs, content), parent): 594 ob = self.getClass()() 595 for element in content: 596 if isinstance(element, StringType): 597 continue 598 ob.fromXML(element) 599 return ob 600 601class PrivateDictConverter(TableConverter): 602 def getClass(self): 603 return PrivateDict 604 def read(self, parent, value): 605 size, offset = value 606 file = parent.file 607 priv = PrivateDict(parent.strings, file, offset) 608 file.seek(offset) 609 data = file.read(size) 610 len(data) == size 611 priv.decompile(data) 612 return priv 613 def write(self, parent, value): 614 return (0, 0) # dummy value 615 616class SubrsConverter(TableConverter): 617 def getClass(self): 618 return SubrsIndex 619 def read(self, parent, value): 620 file = parent.file 621 file.seek(parent.offset + value) # Offset(self) 622 return SubrsIndex(file) 623 def write(self, parent, value): 624 return 0 # dummy value 625 626class CharStringsConverter(TableConverter): 627 def read(self, parent, value): 628 file = parent.file 629 charset = parent.charset 630 globalSubrs = parent.GlobalSubrs 631 if hasattr(parent, "ROS"): 632 fdSelect, fdArray = parent.FDSelect, parent.FDArray 633 private = None 634 else: 635 fdSelect, fdArray = None, None 636 private = parent.Private 637 file.seek(value) # Offset(0) 638 return CharStrings(file, charset, globalSubrs, private, fdSelect, fdArray) 639 def write(self, parent, value): 640 return 0 # dummy value 641 def xmlRead(self, (name, attrs, content), parent): 642 # XXX needs work for CID fonts 643 fdSelect, fdArray = None, None 644 charStrings = CharStrings(None, None, parent.GlobalSubrs, parent.Private, fdSelect, fdArray) 645 charStrings.fromXML((name, attrs, content)) 646 return charStrings 647 648class CharsetConverter: 649 def read(self, parent, value): 650 isCID = hasattr(parent, "ROS") 651 if value > 2: 652 numGlyphs = parent.numGlyphs 653 file = parent.file 654 file.seek(value) 655 if DEBUG: 656 print "loading charset at %s" % value 657 format = readCard8(file) 658 if format == 0: 659 charset =parseCharset0(numGlyphs, file, parent.strings) 660 elif format == 1 or format == 2: 661 charset = parseCharset(numGlyphs, file, parent.strings, isCID, format) 662 else: 663 raise NotImplementedError 664 assert len(charset) == numGlyphs 665 if DEBUG: 666 print " charset end at %s" % file.tell() 667 else: 668 if isCID or not hasattr(parent, "CharStrings"): 669 assert value == 0 670 charset = None 671 elif value == 0: 672 charset = ISOAdobe 673 elif value == 1: 674 charset = Expert 675 elif value == 2: 676 charset = ExpertSubset 677 # self.charset: 678 # 0: ISOAdobe (or CID font!) 679 # 1: Expert 680 # 2: ExpertSubset 681 charset = None # 682 return charset 683 def write(self, parent, value): 684 return 0 # dummy value 685 def xmlWrite(self, xmlWriter, name, value): 686 # XXX only write charset when not in OT/TTX context, where we 687 # dump charset as a separate "GlyphOrder" table. 688 ##xmlWriter.simpletag("charset") 689 xmlWriter.comment("charset is dumped separately as the 'GlyphOrder' element") 690 xmlWriter.newline() 691 def xmlRead(self, (name, attrs, content), parent): 692 if 0: 693 return safeEval(attrs["value"]) 694 695 696class CharsetCompiler: 697 698 def __init__(self, strings, charset, parent): 699 assert charset[0] == '.notdef' 700 data0 = packCharset0(charset, strings) 701 data = packCharset(charset, strings) 702 if len(data) < len(data0): 703 self.data = data 704 else: 705 self.data = data0 706 self.parent = parent 707 708 def setPos(self, pos, endPos): 709 self.parent.rawDict["charset"] = pos 710 711 def getDataLength(self): 712 return len(self.data) 713 714 def toFile(self, file): 715 file.write(self.data) 716 717 718def packCharset0(charset, strings): 719 format = 0 720 data = [packCard8(format)] 721 for name in charset[1:]: 722 data.append(packCard16(strings.getSID(name))) 723 return "".join(data) 724 725def packCharset(charset, strings): 726 format = 1 727 ranges = [] 728 first = None 729 end = 0 730 for name in charset[1:]: 731 SID = strings.getSID(name) 732 if first is None: 733 first = SID 734 elif end + 1 <> SID: 735 nLeft = end - first 736 if nLeft > 255: 737 format = 2 738 ranges.append((first, nLeft)) 739 first = SID 740 end = SID 741 nLeft = end - first 742 if nLeft > 255: 743 format = 2 744 ranges.append((first, nLeft)) 745 746 data = [packCard8(format)] 747 if format == 1: 748 nLeftFunc = packCard8 749 else: 750 nLeftFunc = packCard16 751 for first, nLeft in ranges: 752 data.append(packCard16(first) + nLeftFunc(nLeft)) 753 return "".join(data) 754 755def parseCharset0(numGlyphs, file, strings): 756 charset = [".notdef"] 757 for i in range(numGlyphs - 1): 758 SID = readCard16(file) 759 charset.append(strings[SID]) 760 return charset 761 762def parseCharset(numGlyphs, file, strings, isCID, format): 763 charset = ['.notdef'] 764 count = 1 765 if format == 1: 766 nLeftFunc = readCard8 767 else: 768 nLeftFunc = readCard16 769 while count < numGlyphs: 770 first = readCard16(file) 771 nLeft = nLeftFunc(file) 772 if isCID: 773 for CID in range(first, first+nLeft+1): 774 charset.append(CID) 775 else: 776 for SID in range(first, first+nLeft+1): 777 charset.append(strings[SID]) 778 count = count + nLeft + 1 779 return charset 780 781 782class FDArrayConverter(TableConverter): 783 def read(self, parent, value): 784 file = parent.file 785 file.seek(value) 786 fdArray = TopDictIndex(file) 787 fdArray.strings = parent.strings 788 fdArray.GlobalSubrs = parent.GlobalSubrs 789 return fdArray 790 791 792class FDSelectConverter: 793 def read(self, parent, value): 794 file = parent.file 795 file.seek(value) 796 format = readCard8(file) 797 numGlyphs = parent.numGlyphs 798 if format == 0: 799 from array import array 800 fdSelect = array("B", file.read(numGlyphs)).tolist() 801 elif format == 3: 802 fdSelect = [None] * numGlyphs 803 nRanges = readCard16(file) 804 prev = None 805 for i in range(nRanges): 806 first = readCard16(file) 807 if prev is not None: 808 for glyphID in range(prev, first): 809 fdSelect[glyphID] = fd 810 prev = first 811 fd = readCard8(file) 812 if prev is not None: 813 first = readCard16(file) 814 for glyphID in range(prev, first): 815 fdSelect[glyphID] = fd 816 else: 817 assert 0, "unsupported FDSelect format: %s" % format 818 return fdSelect 819 def xmlWrite(self, xmlWriter, name, value): 820 pass 821 822 823class ROSConverter(SimpleConverter): 824 def xmlWrite(self, xmlWriter, name, value): 825 registry, order, supplement = value 826 xmlWriter.simpletag(name, [('Registry', registry), ('Order', order), 827 ('Supplement', supplement)]) 828 xmlWriter.newline() 829 830 831topDictOperators = [ 832# opcode name argument type default converter 833 ((12, 30), 'ROS', ('SID','SID','number'), None, ROSConverter()), 834 ((12, 20), 'SyntheticBase', 'number', None, None), 835 (0, 'version', 'SID', None, None), 836 (1, 'Notice', 'SID', None, None), 837 ((12, 0), 'Copyright', 'SID', None, None), 838 (2, 'FullName', 'SID', None, None), 839 ((12, 38), 'FontName', 'SID', None, None), 840 (3, 'FamilyName', 'SID', None, None), 841 (4, 'Weight', 'SID', None, None), 842 ((12, 1), 'isFixedPitch', 'number', 0, None), 843 ((12, 2), 'ItalicAngle', 'number', 0, None), 844 ((12, 3), 'UnderlinePosition', 'number', None, None), 845 ((12, 4), 'UnderlineThickness', 'number', 50, None), 846 ((12, 5), 'PaintType', 'number', 0, None), 847 ((12, 6), 'CharstringType', 'number', 2, None), 848 ((12, 7), 'FontMatrix', 'array', [0.001,0,0,0.001,0,0], None), 849 (13, 'UniqueID', 'number', None, None), 850 (5, 'FontBBox', 'array', [0,0,0,0], None), 851 ((12, 8), 'StrokeWidth', 'number', 0, None), 852 (14, 'XUID', 'array', None, None), 853 (15, 'charset', 'number', 0, CharsetConverter()), 854 ((12, 21), 'PostScript', 'SID', None, None), 855 ((12, 22), 'BaseFontName', 'SID', None, None), 856 ((12, 23), 'BaseFontBlend', 'delta', None, None), 857 ((12, 31), 'CIDFontVersion', 'number', 0, None), 858 ((12, 32), 'CIDFontRevision', 'number', 0, None), 859 ((12, 33), 'CIDFontType', 'number', 0, None), 860 ((12, 34), 'CIDCount', 'number', 8720, None), 861 ((12, 35), 'UIDBase', 'number', None, None), 862 (16, 'Encoding', 'number', 0, None), # XXX 863 ((12, 36), 'FDArray', 'number', None, FDArrayConverter()), 864 ((12, 37), 'FDSelect', 'number', None, FDSelectConverter()), 865 (18, 'Private', ('number','number'), None, PrivateDictConverter()), 866 (17, 'CharStrings', 'number', None, CharStringsConverter()), 867] 868 869privateDictOperators = [ 870# opcode name argument type default converter 871 (6, 'BlueValues', 'delta', None, None), 872 (7, 'OtherBlues', 'delta', None, None), 873 (8, 'FamilyBlues', 'delta', None, None), 874 (9, 'FamilyOtherBlues', 'delta', None, None), 875 ((12, 9), 'BlueScale', 'number', 0.039625, None), 876 ((12, 10), 'BlueShift', 'number', 7, None), 877 ((12, 11), 'BlueFuzz', 'number', 1, None), 878 (10, 'StdHW', 'number', None, None), 879 (11, 'StdVW', 'number', None, None), 880 ((12, 12), 'StemSnapH', 'delta', None, None), 881 ((12, 13), 'StemSnapV', 'delta', None, None), 882 ((12, 14), 'ForceBold', 'number', 0, None), 883 ((12, 15), 'ForceBoldThreshold', 'number', None, None), # deprecated 884 ((12, 16), 'lenIV', 'number', None, None), # deprecated 885 ((12, 17), 'LanguageGroup', 'number', 0, None), 886 ((12, 18), 'ExpansionFactor', 'number', 0.06, None), 887 ((12, 19), 'initialRandomSeed', 'number', 0, None), 888 (20, 'defaultWidthX', 'number', 0, None), 889 (21, 'nominalWidthX', 'number', 0, None), 890 (19, 'Subrs', 'number', None, SubrsConverter()), 891] 892 893def addConverters(table): 894 for i in range(len(table)): 895 op, name, arg, default, conv = table[i] 896 if conv is not None: 897 continue 898 if arg in ("delta", "array"): 899 conv = ArrayConverter() 900 elif arg == "number": 901 conv = NumberConverter() 902 elif arg == "SID": 903 conv = SimpleConverter() 904 else: 905 assert 0 906 table[i] = op, name, arg, default, conv 907 908addConverters(privateDictOperators) 909addConverters(topDictOperators) 910 911 912class TopDictDecompiler(psCharStrings.DictDecompiler): 913 operators = buildOperatorDict(topDictOperators) 914 915 916class PrivateDictDecompiler(psCharStrings.DictDecompiler): 917 operators = buildOperatorDict(privateDictOperators) 918 919 920class DictCompiler: 921 922 def __init__(self, dictObj, strings, parent): 923 assert isinstance(strings, IndexedStrings) 924 self.dictObj = dictObj 925 self.strings = strings 926 self.parent = parent 927 rawDict = {} 928 for name in dictObj.order: 929 value = getattr(dictObj, name, None) 930 if value is None: 931 continue 932 conv = dictObj.converters[name] 933 value = conv.write(dictObj, value) 934 if value == dictObj.defaults.get(name): 935 continue 936 rawDict[name] = value 937 self.rawDict = rawDict 938 939 def setPos(self, pos, endPos): 940 pass 941 942 def getDataLength(self): 943 return len(self.compile("getDataLength")) 944 945 def compile(self, reason): 946 if DEBUG: 947 print "-- compiling %s for %s" % (self.__class__.__name__, reason) 948 rawDict = self.rawDict 949 data = [] 950 for name in self.dictObj.order: 951 value = rawDict.get(name) 952 if value is None: 953 continue 954 op, argType = self.opcodes[name] 955 if type(argType) == TupleType: 956 l = len(argType) 957 assert len(value) == l, "value doesn't match arg type" 958 for i in range(l): 959 arg = argType[l - i - 1] 960 v = value[i] 961 arghandler = getattr(self, "arg_" + arg) 962 data.append(arghandler(v)) 963 else: 964 arghandler = getattr(self, "arg_" + argType) 965 data.append(arghandler(value)) 966 data.append(op) 967 return "".join(data) 968 969 def toFile(self, file): 970 file.write(self.compile("toFile")) 971 972 def arg_number(self, num): 973 return encodeNumber(num) 974 def arg_SID(self, s): 975 return psCharStrings.encodeIntCFF(self.strings.getSID(s)) 976 def arg_array(self, value): 977 data = [] 978 for num in value: 979 data.append(encodeNumber(num)) 980 return "".join(data) 981 def arg_delta(self, value): 982 out = [] 983 last = 0 984 for v in value: 985 out.append(v - last) 986 last = v 987 data = [] 988 for num in out: 989 data.append(encodeNumber(num)) 990 return "".join(data) 991 992 993def encodeNumber(num): 994 if type(num) == FloatType: 995 return psCharStrings.encodeFloat(num) 996 else: 997 return psCharStrings.encodeIntCFF(num) 998 999 1000class TopDictCompiler(DictCompiler): 1001 1002 opcodes = buildOpcodeDict(topDictOperators) 1003 1004 def getChildren(self, strings): 1005 children = [] 1006 if hasattr(self.dictObj, "charset"): 1007 children.append(CharsetCompiler(strings, self.dictObj.charset, self)) 1008 if hasattr(self.dictObj, "CharStrings"): 1009 items = [] 1010 charStrings = self.dictObj.CharStrings 1011 for name in self.dictObj.charset: 1012 items.append(charStrings[name]) 1013 charStringsComp = CharStringsCompiler(items, strings, self) 1014 children.append(charStringsComp) 1015 if hasattr(self.dictObj, "Private"): 1016 privComp = self.dictObj.Private.getCompiler(strings, self) 1017 children.append(privComp) 1018 children.extend(privComp.getChildren(strings)) 1019 return children 1020 1021 1022class PrivateDictCompiler(DictCompiler): 1023 1024 opcodes = buildOpcodeDict(privateDictOperators) 1025 1026 def setPos(self, pos, endPos): 1027 size = endPos - pos 1028 self.parent.rawDict["Private"] = size, pos 1029 self.pos = pos 1030 1031 def getChildren(self, strings): 1032 children = [] 1033 if hasattr(self.dictObj, "Subrs"): 1034 children.append(self.dictObj.Subrs.getCompiler(strings, self)) 1035 return children 1036 1037 1038class BaseDict: 1039 1040 def __init__(self, strings=None, file=None, offset=None): 1041 self.rawDict = {} 1042 if DEBUG: 1043 print "loading %s at %s" % (self.__class__.__name__, offset) 1044 self.file = file 1045 self.offset = offset 1046 self.strings = strings 1047 self.skipNames = [] 1048 1049 def decompile(self, data): 1050 if DEBUG: 1051 print " length %s is %s" % (self.__class__.__name__, len(data)) 1052 dec = self.decompilerClass(self.strings) 1053 dec.decompile(data) 1054 self.rawDict = dec.getDict() 1055 self.postDecompile() 1056 1057 def postDecompile(self): 1058 pass 1059 1060 def getCompiler(self, strings, parent): 1061 return self.compilerClass(self, strings, parent) 1062 1063 def __getattr__(self, name): 1064 value = self.rawDict.get(name) 1065 if value is None: 1066 value = self.defaults.get(name) 1067 if value is None: 1068 raise AttributeError, name 1069 conv = self.converters[name] 1070 value = conv.read(self, value) 1071 setattr(self, name, value) 1072 return value 1073 1074 def toXML(self, xmlWriter, progress): 1075 for name in self.order: 1076 if name in self.skipNames: 1077 continue 1078 value = getattr(self, name, None) 1079 if value is None: 1080 continue 1081 conv = self.converters[name] 1082 conv.xmlWrite(xmlWriter, name, value) 1083 1084 def fromXML(self, (name, attrs, content)): 1085 conv = self.converters[name] 1086 value = conv.xmlRead((name, attrs, content), self) 1087 setattr(self, name, value) 1088 1089 1090class TopDict(BaseDict): 1091 1092 defaults = buildDefaults(topDictOperators) 1093 converters = buildConverters(topDictOperators) 1094 order = buildOrder(topDictOperators) 1095 decompilerClass = TopDictDecompiler 1096 compilerClass = TopDictCompiler 1097 1098 def __init__(self, strings=None, file=None, offset=None, GlobalSubrs=None): 1099 BaseDict.__init__(self, strings, file, offset) 1100 self.GlobalSubrs = GlobalSubrs 1101 1102 def getGlyphOrder(self): 1103 return self.charset 1104 1105 def postDecompile(self): 1106 offset = self.rawDict.get("CharStrings") 1107 if offset is None: 1108 return 1109 # get the number of glyphs beforehand. 1110 self.file.seek(offset) 1111 self.numGlyphs = readCard16(self.file) 1112 1113 def toXML(self, xmlWriter, progress): 1114 if hasattr(self, "CharStrings"): 1115 self.decompileAllCharStrings() 1116 if not hasattr(self, "ROS") or not hasattr(self, "CharStrings"): 1117 # these values have default values, but I only want them to show up 1118 # in CID fonts. 1119 self.skipNames = ['CIDFontVersion', 'CIDFontRevision', 'CIDFontType', 1120 'CIDCount'] 1121 BaseDict.toXML(self, xmlWriter, progress) 1122 1123 def decompileAllCharStrings(self): 1124 # XXX only when doing ttdump -i? 1125 for charString in self.CharStrings.values(): 1126 charString.decompile() 1127 1128 1129class PrivateDict(BaseDict): 1130 defaults = buildDefaults(privateDictOperators) 1131 converters = buildConverters(privateDictOperators) 1132 order = buildOrder(privateDictOperators) 1133 decompilerClass = PrivateDictDecompiler 1134 compilerClass = PrivateDictCompiler 1135 1136 1137class IndexedStrings: 1138 1139 """SID -> string mapping.""" 1140 1141 def __init__(self, file=None): 1142 if file is None: 1143 strings = [] 1144 else: 1145 strings = list(Index(file)) 1146 self.strings = strings 1147 1148 def getCompiler(self): 1149 return IndexedStringsCompiler(self, None, None) 1150 1151 def __len__(self): 1152 return len(self.strings) 1153 1154 def __getitem__(self, SID): 1155 if SID < cffStandardStringCount: 1156 return cffStandardStrings[SID] 1157 else: 1158 return self.strings[SID - cffStandardStringCount] 1159 1160 def getSID(self, s): 1161 if not hasattr(self, "stringMapping"): 1162 self.buildStringMapping() 1163 if cffStandardStringMapping.has_key(s): 1164 SID = cffStandardStringMapping[s] 1165 elif self.stringMapping.has_key(s): 1166 SID = self.stringMapping[s] 1167 else: 1168 SID = len(self.strings) + cffStandardStringCount 1169 self.strings.append(s) 1170 self.stringMapping[s] = SID 1171 return SID 1172 1173 def getStrings(self): 1174 return self.strings 1175 1176 def buildStringMapping(self): 1177 self.stringMapping = {} 1178 for index in range(len(self.strings)): 1179 self.stringMapping[self.strings[index]] = index + cffStandardStringCount 1180 1181 1182# The 391 Standard Strings as used in the CFF format. 1183# from Adobe Technical None #5176, version 1.0, 18 March 1998 1184 1185cffStandardStrings = ['.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 1186 'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright', 1187 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 1188 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 1189 'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 1190 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 1191 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 1192 'bracketright', 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c', 1193 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 1194 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 1195 'asciitilde', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 1196 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft', 1197 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 1198 'daggerdbl', 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase', 1199 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', 1200 'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 1201 'dotaccent', 'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 1202 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae', 1203 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior', 1204 'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 1205 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters', 1206 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior', 1207 'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 1208 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave', 1209 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 1210 'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 1211 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron', 1212 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla', 1213 'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex', 1214 'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis', 1215 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave', 1216 'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall', 1217 'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall', 1218 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', 1219 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 1220 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 1221 'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior', 1222 'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 1223 'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior', 1224 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', 'parenleftinferior', 1225 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 1226 'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 1227 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 1228 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall', 1229 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', 1230 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 1231 'Dieresissmall', 'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall', 1232 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', 1233 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 1234 'onethird', 'twothirds', 'zerosuperior', 'foursuperior', 'fivesuperior', 1235 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 1236 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 1237 'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 1238 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall', 1239 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', 1240 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 1241 'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 1242 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', 1243 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 1244 'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 1245 'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', '001.001', '001.002', 1246 '001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman', 1247 'Semibold' 1248] 1249 1250cffStandardStringCount = 391 1251assert len(cffStandardStrings) == cffStandardStringCount 1252# build reverse mapping 1253cffStandardStringMapping = {} 1254for _i in range(cffStandardStringCount): 1255 cffStandardStringMapping[cffStandardStrings[_i]] = _i 1256 1257