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