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