cffLib.py revision 2a9bcde369fbcdc1258c11aa1b03633bf39653b4
1"""cffLib.py -- read/write tools for Adobe CFF fonts.""" 2 3# 4# $Id: cffLib.py,v 1.34 2008-03-07 19:56:17 jvr Exp $ 5# 6 7import struct, sstruct 8import string 9from fontTools.misc import psCharStrings 10from fontTools.misc.textTools import safeEval 11 12DEBUG = 0 13 14 15cffHeaderFormat = """ 16 major: B 17 minor: B 18 hdrSize: B 19 offSize: B 20""" 21 22class CFFFontSet: 23 24 def __init__(self): 25 pass 26 27 def decompile(self, file, otFont): 28 sstruct.unpack(cffHeaderFormat, file.read(4), self) 29 assert self.major == 1 and self.minor == 0, \ 30 "unknown CFF format: %d.%d" % (self.major, self.minor) 31 32 file.seek(self.hdrSize) 33 self.fontNames = list(Index(file)) 34 self.topDictIndex = TopDictIndex(file) 35 self.strings = IndexedStrings(file) 36 self.GlobalSubrs = GlobalSubrsIndex(file) 37 self.topDictIndex.strings = self.strings 38 self.topDictIndex.GlobalSubrs = self.GlobalSubrs 39 40 def __len__(self): 41 return len(self.fontNames) 42 43 def keys(self): 44 return list(self.fontNames) 45 46 def values(self): 47 return self.topDictIndex 48 49 def __getitem__(self, name): 50 try: 51 index = self.fontNames.index(name) 52 except ValueError: 53 raise KeyError, name 54 return self.topDictIndex[index] 55 56 def compile(self, file, otFont): 57 strings = IndexedStrings() 58 writer = CFFWriter() 59 writer.add(sstruct.pack(cffHeaderFormat, self)) 60 fontNames = Index() 61 for name in self.fontNames: 62 fontNames.append(name) 63 writer.add(fontNames.getCompiler(strings, None)) 64 topCompiler = self.topDictIndex.getCompiler(strings, None) 65 writer.add(topCompiler) 66 writer.add(strings.getCompiler()) 67 writer.add(self.GlobalSubrs.getCompiler(strings, None)) 68 69 for topDict in self.topDictIndex: 70 if not hasattr(topDict, "charset") or topDict.charset is None: 71 charset = otFont.getGlyphOrder() 72 topDict.charset = charset 73 74 for child in topCompiler.getChildren(strings): 75 writer.add(child) 76 77 writer.toFile(file) 78 79 def toXML(self, xmlWriter, progress=None): 80 xmlWriter.newline() 81 for fontName in self.fontNames: 82 xmlWriter.begintag("CFFFont", name=fontName) 83 xmlWriter.newline() 84 font = self[fontName] 85 font.toXML(xmlWriter, progress) 86 xmlWriter.endtag("CFFFont") 87 xmlWriter.newline() 88 xmlWriter.newline() 89 xmlWriter.begintag("GlobalSubrs") 90 xmlWriter.newline() 91 self.GlobalSubrs.toXML(xmlWriter, progress) 92 xmlWriter.endtag("GlobalSubrs") 93 xmlWriter.newline() 94 xmlWriter.newline() 95 96 def fromXML(self, (name, attrs, content)): 97 if not hasattr(self, "GlobalSubrs"): 98 self.GlobalSubrs = GlobalSubrsIndex() 99 self.major = 1 100 self.minor = 0 101 self.hdrSize = 4 102 self.offSize = 4 # XXX ?? 103 if name == "CFFFont": 104 if not hasattr(self, "fontNames"): 105 self.fontNames = [] 106 self.topDictIndex = TopDictIndex() 107 fontName = attrs["name"] 108 topDict = TopDict(GlobalSubrs=self.GlobalSubrs) 109 topDict.charset = None # gets filled in later 110 self.fontNames.append(fontName) 111 self.topDictIndex.append(topDict) 112 for element in content: 113 if isinstance(element, basestring): 114 continue 115 topDict.fromXML(element) 116 elif name == "GlobalSubrs": 117 for element in content: 118 if isinstance(element, basestring): 119 continue 120 name, attrs, content = element 121 subr = psCharStrings.T2CharString() 122 subr.fromXML((name, attrs, content)) 123 self.GlobalSubrs.append(subr) 124 125 126class CFFWriter: 127 128 def __init__(self): 129 self.data = [] 130 131 def add(self, table): 132 self.data.append(table) 133 134 def toFile(self, file): 135 lastPosList = None 136 count = 1 137 while 1: 138 if DEBUG: 139 print "CFFWriter.toFile() iteration:", count 140 count = count + 1 141 pos = 0 142 posList = [pos] 143 for item in self.data: 144 if hasattr(item, "getDataLength"): 145 endPos = pos + item.getDataLength() 146 else: 147 endPos = pos + len(item) 148 if hasattr(item, "setPos"): 149 item.setPos(pos, endPos) 150 pos = endPos 151 posList.append(pos) 152 if posList == lastPosList: 153 break 154 lastPosList = posList 155 if DEBUG: 156 print "CFFWriter.toFile() writing to file." 157 begin = file.tell() 158 posList = [0] 159 for item in self.data: 160 if hasattr(item, "toFile"): 161 item.toFile(file) 162 else: 163 file.write(item) 164 posList.append(file.tell() - begin) 165 assert posList == lastPosList 166 167 168def calcOffSize(largestOffset): 169 if largestOffset < 0x100: 170 offSize = 1 171 elif largestOffset < 0x10000: 172 offSize = 2 173 elif largestOffset < 0x1000000: 174 offSize = 3 175 else: 176 offSize = 4 177 return offSize 178 179 180class IndexCompiler: 181 182 def __init__(self, items, strings, parent): 183 self.items = self.getItems(items, strings) 184 self.parent = parent 185 186 def getItems(self, items, strings): 187 return items 188 189 def getOffsets(self): 190 pos = 1 191 offsets = [pos] 192 for item in self.items: 193 if hasattr(item, "getDataLength"): 194 pos = pos + item.getDataLength() 195 else: 196 pos = pos + len(item) 197 offsets.append(pos) 198 return offsets 199 200 def getDataLength(self): 201 lastOffset = self.getOffsets()[-1] 202 offSize = calcOffSize(lastOffset) 203 dataLength = ( 204 2 + # count 205 1 + # offSize 206 (len(self.items) + 1) * offSize + # the offsets 207 lastOffset - 1 # size of object data 208 ) 209 return dataLength 210 211 def toFile(self, file): 212 offsets = self.getOffsets() 213 writeCard16(file, len(self.items)) 214 offSize = calcOffSize(offsets[-1]) 215 writeCard8(file, offSize) 216 offSize = -offSize 217 pack = struct.pack 218 for offset in offsets: 219 binOffset = pack(">l", offset)[offSize:] 220 assert len(binOffset) == -offSize 221 file.write(binOffset) 222 for item in self.items: 223 if hasattr(item, "toFile"): 224 item.toFile(file) 225 else: 226 file.write(item) 227 228 229class IndexedStringsCompiler(IndexCompiler): 230 231 def getItems(self, items, strings): 232 return items.strings 233 234 235class TopDictIndexCompiler(IndexCompiler): 236 237 def getItems(self, items, strings): 238 out = [] 239 for item in items: 240 out.append(item.getCompiler(strings, self)) 241 return out 242 243 def getChildren(self, strings): 244 children = [] 245 for topDict in self.items: 246 children.extend(topDict.getChildren(strings)) 247 return children 248 249 250class FDArrayIndexCompiler(IndexCompiler): 251 252 def getItems(self, items, strings): 253 out = [] 254 for item in items: 255 out.append(item.getCompiler(strings, self)) 256 return out 257 258 def getChildren(self, strings): 259 children = [] 260 for fontDict in self.items: 261 children.extend(fontDict.getChildren(strings)) 262 return children 263 264 def toFile(self, file): 265 offsets = self.getOffsets() 266 writeCard16(file, len(self.items)) 267 offSize = calcOffSize(offsets[-1]) 268 writeCard8(file, offSize) 269 offSize = -offSize 270 pack = struct.pack 271 for offset in offsets: 272 binOffset = pack(">l", offset)[offSize:] 273 assert len(binOffset) == -offSize 274 file.write(binOffset) 275 for item in self.items: 276 if hasattr(item, "toFile"): 277 item.toFile(file) 278 else: 279 file.write(item) 280 281 def setPos(self, pos, endPos): 282 self.parent.rawDict["FDArray"] = pos 283 284 285class GlobalSubrsCompiler(IndexCompiler): 286 def getItems(self, items, strings): 287 out = [] 288 for cs in items: 289 cs.compile() 290 out.append(cs.bytecode) 291 return out 292 293class SubrsCompiler(GlobalSubrsCompiler): 294 def setPos(self, pos, endPos): 295 offset = pos - self.parent.pos 296 self.parent.rawDict["Subrs"] = offset 297 298class CharStringsCompiler(GlobalSubrsCompiler): 299 def setPos(self, pos, endPos): 300 self.parent.rawDict["CharStrings"] = pos 301 302 303class Index: 304 305 """This class represents what the CFF spec calls an INDEX.""" 306 307 compilerClass = IndexCompiler 308 309 def __init__(self, file=None): 310 name = self.__class__.__name__ 311 if file is None: 312 self.items = [] 313 return 314 if DEBUG: 315 print "loading %s at %s" % (name, file.tell()) 316 self.file = file 317 count = readCard16(file) 318 self.count = count 319 self.items = [None] * count 320 if count == 0: 321 self.items = [] 322 return 323 offSize = readCard8(file) 324 if DEBUG: 325 print " index count: %s offSize: %s" % (count, offSize) 326 assert offSize <= 4, "offSize too large: %s" % offSize 327 self.offsets = offsets = [] 328 pad = '\0' * (4 - offSize) 329 for index in range(count+1): 330 chunk = file.read(offSize) 331 chunk = pad + chunk 332 offset, = struct.unpack(">L", chunk) 333 offsets.append(int(offset)) 334 self.offsetBase = file.tell() - 1 335 file.seek(self.offsetBase + offsets[-1]) # pretend we've read the whole lot 336 if DEBUG: 337 print " end of %s at %s" % (name, file.tell()) 338 339 def __len__(self): 340 return len(self.items) 341 342 def __getitem__(self, index): 343 item = self.items[index] 344 if item is not None: 345 return item 346 offset = self.offsets[index] + self.offsetBase 347 size = self.offsets[index+1] - self.offsets[index] 348 file = self.file 349 file.seek(offset) 350 data = file.read(size) 351 assert len(data) == size 352 item = self.produceItem(index, data, file, offset, size) 353 self.items[index] = item 354 return item 355 356 def produceItem(self, index, data, file, offset, size): 357 return data 358 359 def append(self, item): 360 self.items.append(item) 361 362 def getCompiler(self, strings, parent): 363 return self.compilerClass(self, strings, parent) 364 365 366class GlobalSubrsIndex(Index): 367 368 compilerClass = GlobalSubrsCompiler 369 370 def __init__(self, file=None, globalSubrs=None, private=None, fdSelect=None, fdArray=None): 371 Index.__init__(self, file) 372 self.globalSubrs = globalSubrs 373 self.private = private 374 if fdSelect: 375 self.fdSelect = fdSelect 376 if fdArray: 377 self.fdArray = fdArray 378 379 def produceItem(self, index, data, file, offset, size): 380 if self.private is not None: 381 private = self.private 382 elif hasattr(self, 'fdArray') and self.fdArray is not None: 383 private = self.fdArray[self.fdSelect[index]].Private 384 else: 385 private = None 386 return psCharStrings.T2CharString(data, private=private, globalSubrs=self.globalSubrs) 387 388 def toXML(self, xmlWriter, progress): 389 xmlWriter.comment("The 'index' attribute is only for humans; it is ignored when parsed.") 390 xmlWriter.newline() 391 for i in range(len(self)): 392 subr = self[i] 393 if subr.needsDecompilation(): 394 xmlWriter.begintag("CharString", index=i, raw=1) 395 else: 396 xmlWriter.begintag("CharString", index=i) 397 xmlWriter.newline() 398 subr.toXML(xmlWriter) 399 xmlWriter.endtag("CharString") 400 xmlWriter.newline() 401 402 def fromXML(self, (name, attrs, content)): 403 if name <> "CharString": 404 return 405 subr = psCharStrings.T2CharString() 406 subr.fromXML((name, attrs, content)) 407 self.append(subr) 408 409 def getItemAndSelector(self, index): 410 sel = None 411 if hasattr(self, 'fdSelect'): 412 sel = self.fdSelect[index] 413 return self[index], sel 414 415 416class SubrsIndex(GlobalSubrsIndex): 417 compilerClass = SubrsCompiler 418 419 420class TopDictIndex(Index): 421 422 compilerClass = TopDictIndexCompiler 423 424 def produceItem(self, index, data, file, offset, size): 425 top = TopDict(self.strings, file, offset, self.GlobalSubrs) 426 top.decompile(data) 427 return top 428 429 def toXML(self, xmlWriter, progress): 430 for i in range(len(self)): 431 xmlWriter.begintag("FontDict", index=i) 432 xmlWriter.newline() 433 self[i].toXML(xmlWriter, progress) 434 xmlWriter.endtag("FontDict") 435 xmlWriter.newline() 436 437 438class FDArrayIndex(TopDictIndex): 439 440 compilerClass = FDArrayIndexCompiler 441 442 def fromXML(self, (name, attrs, content)): 443 if name <> "FontDict": 444 return 445 fontDict = FontDict() 446 for element in content: 447 if isinstance(element, basestring): 448 continue 449 fontDict.fromXML(element) 450 self.append(fontDict) 451 452 453class FDSelect: 454 def __init__(self, file = None, numGlyphs = None, format=None): 455 if file: 456 # read data in from file 457 self.format = readCard8(file) 458 if self.format == 0: 459 from array import array 460 self.gidArray = array("B", file.read(numGlyphs)).tolist() 461 elif self.format == 3: 462 gidArray = [None] * numGlyphs 463 nRanges = readCard16(file) 464 prev = None 465 for i in range(nRanges): 466 first = readCard16(file) 467 if prev is not None: 468 for glyphID in range(prev, first): 469 gidArray[glyphID] = fd 470 prev = first 471 fd = readCard8(file) 472 if prev is not None: 473 first = readCard16(file) 474 for glyphID in range(prev, first): 475 gidArray[glyphID] = fd 476 self.gidArray = gidArray 477 else: 478 assert 0, "unsupported FDSelect format: %s" % format 479 else: 480 # reading from XML. Make empty gidArray,, and leave format as passed in. 481 # format == None will result in the smallest representation being used. 482 self.format = format 483 self.gidArray = [] 484 485 486 def __len__(self): 487 return len(self.gidArray) 488 489 def __getitem__(self, index): 490 return self.gidArray[index] 491 492 def __setitem__(self, index, fdSelectValue): 493 self.gidArray[index] = fdSelectValue 494 495 def append(self, fdSelectValue): 496 self.gidArray.append(fdSelectValue) 497 498 499class CharStrings: 500 501 def __init__(self, file, charset, globalSubrs, private, fdSelect, fdArray): 502 if file is not None: 503 self.charStringsIndex = SubrsIndex(file, globalSubrs, private, fdSelect, fdArray) 504 self.charStrings = charStrings = {} 505 for i in range(len(charset)): 506 charStrings[charset[i]] = i 507 self.charStringsAreIndexed = 1 508 else: 509 self.charStrings = {} 510 self.charStringsAreIndexed = 0 511 self.globalSubrs = globalSubrs 512 self.private = private 513 if fdSelect != None: 514 self.fdSelect = fdSelect 515 if fdArray!= None: 516 self.fdArray = fdArray 517 518 def keys(self): 519 return self.charStrings.keys() 520 521 def values(self): 522 if self.charStringsAreIndexed: 523 return self.charStringsIndex 524 else: 525 return self.charStrings.values() 526 527 def has_key(self, name): 528 return self.charStrings.has_key(name) 529 530 def __len__(self): 531 return len(self.charStrings) 532 533 def __getitem__(self, name): 534 charString = self.charStrings[name] 535 if self.charStringsAreIndexed: 536 charString = self.charStringsIndex[charString] 537 return charString 538 539 def __setitem__(self, name, charString): 540 if self.charStringsAreIndexed: 541 index = self.charStrings[name] 542 self.charStringsIndex[index] = charString 543 else: 544 self.charStrings[name] = charString 545 546 def getItemAndSelector(self, name): 547 if self.charStringsAreIndexed: 548 index = self.charStrings[name] 549 return self.charStringsIndex.getItemAndSelector(index) 550 else: 551 if hasattr(self, 'fdSelect'): 552 sel = self.fdSelect[index] 553 else: 554 raise KeyError("fdSelect array not yet defined.") 555 return self.charStrings[name], sel 556 557 def toXML(self, xmlWriter, progress): 558 names = self.keys() 559 names.sort() 560 i = 0 561 step = 10 562 numGlyphs = len(names) 563 for name in names: 564 charStr, fdSelectIndex = self.getItemAndSelector(name) 565 if charStr.needsDecompilation(): 566 raw = [("raw", 1)] 567 else: 568 raw = [] 569 if fdSelectIndex is None: 570 xmlWriter.begintag("CharString", [('name', name)] + raw) 571 else: 572 xmlWriter.begintag("CharString", 573 [('name', name), ('fdSelectIndex', fdSelectIndex)] + raw) 574 xmlWriter.newline() 575 charStr.toXML(xmlWriter) 576 xmlWriter.endtag("CharString") 577 xmlWriter.newline() 578 if not i % step and progress is not None: 579 progress.setLabel("Dumping 'CFF ' table... (%s)" % name) 580 progress.increment(step / float(numGlyphs)) 581 i = i + 1 582 583 def fromXML(self, (name, attrs, content)): 584 for element in content: 585 if isinstance(element, basestring): 586 continue 587 name, attrs, content = element 588 if name <> "CharString": 589 continue 590 fdID = -1 591 if hasattr(self, "fdArray"): 592 fdID = safeEval(attrs["fdSelectIndex"]) 593 private = self.fdArray[fdID].Private 594 else: 595 private = self.private 596 597 glyphName = attrs["name"] 598 charString = psCharStrings.T2CharString( 599 private=private, 600 globalSubrs=self.globalSubrs) 601 charString.fromXML((name, attrs, content)) 602 if fdID >= 0: 603 charString.fdSelectIndex = fdID 604 self[glyphName] = charString 605 606 607def readCard8(file): 608 return ord(file.read(1)) 609 610def readCard16(file): 611 value, = struct.unpack(">H", file.read(2)) 612 return value 613 614def writeCard8(file, value): 615 file.write(chr(value)) 616 617def writeCard16(file, value): 618 file.write(struct.pack(">H", value)) 619 620def packCard8(value): 621 return chr(value) 622 623def packCard16(value): 624 return struct.pack(">H", value) 625 626def buildOperatorDict(table): 627 d = {} 628 for op, name, arg, default, conv in table: 629 d[op] = (name, arg) 630 return d 631 632def buildOpcodeDict(table): 633 d = {} 634 for op, name, arg, default, conv in table: 635 if isinstance(op, tuple): 636 op = chr(op[0]) + chr(op[1]) 637 else: 638 op = chr(op) 639 d[name] = (op, arg) 640 return d 641 642def buildOrder(table): 643 l = [] 644 for op, name, arg, default, conv in table: 645 l.append(name) 646 return l 647 648def buildDefaults(table): 649 d = {} 650 for op, name, arg, default, conv in table: 651 if default is not None: 652 d[name] = default 653 return d 654 655def buildConverters(table): 656 d = {} 657 for op, name, arg, default, conv in table: 658 d[name] = conv 659 return d 660 661 662class SimpleConverter: 663 def read(self, parent, value): 664 return value 665 def write(self, parent, value): 666 return value 667 def xmlWrite(self, xmlWriter, name, value, progress): 668 xmlWriter.simpletag(name, value=value) 669 xmlWriter.newline() 670 def xmlRead(self, (name, attrs, content), parent): 671 return attrs["value"] 672 673class Latin1Converter(SimpleConverter): 674 def xmlRead(self, (name, attrs, content), parent): 675 s = unicode(attrs["value"], "utf-8") 676 return s.encode("latin-1") 677 678 679def parseNum(s): 680 try: 681 value = int(s) 682 except: 683 value = float(s) 684 return value 685 686class NumberConverter(SimpleConverter): 687 def xmlRead(self, (name, attrs, content), parent): 688 return parseNum(attrs["value"]) 689 690class ArrayConverter(SimpleConverter): 691 def xmlWrite(self, xmlWriter, name, value, progress): 692 value = map(str, value) 693 xmlWriter.simpletag(name, value=" ".join(value)) 694 xmlWriter.newline() 695 def xmlRead(self, (name, attrs, content), parent): 696 values = attrs["value"].split() 697 return map(parseNum, values) 698 699class TableConverter(SimpleConverter): 700 def xmlWrite(self, xmlWriter, name, value, progress): 701 xmlWriter.begintag(name) 702 xmlWriter.newline() 703 value.toXML(xmlWriter, progress) 704 xmlWriter.endtag(name) 705 xmlWriter.newline() 706 def xmlRead(self, (name, attrs, content), parent): 707 ob = self.getClass()() 708 for element in content: 709 if isinstance(element, basestring): 710 continue 711 ob.fromXML(element) 712 return ob 713 714class PrivateDictConverter(TableConverter): 715 def getClass(self): 716 return PrivateDict 717 def read(self, parent, value): 718 size, offset = value 719 file = parent.file 720 priv = PrivateDict(parent.strings, file, offset) 721 file.seek(offset) 722 data = file.read(size) 723 len(data) == size 724 priv.decompile(data) 725 return priv 726 def write(self, parent, value): 727 return (0, 0) # dummy value 728 729class SubrsConverter(TableConverter): 730 def getClass(self): 731 return SubrsIndex 732 def read(self, parent, value): 733 file = parent.file 734 file.seek(parent.offset + value) # Offset(self) 735 return SubrsIndex(file) 736 def write(self, parent, value): 737 return 0 # dummy value 738 739class CharStringsConverter(TableConverter): 740 def read(self, parent, value): 741 file = parent.file 742 charset = parent.charset 743 globalSubrs = parent.GlobalSubrs 744 if hasattr(parent, "ROS"): 745 fdSelect, fdArray = parent.FDSelect, parent.FDArray 746 private = None 747 else: 748 fdSelect, fdArray = None, None 749 private = parent.Private 750 file.seek(value) # Offset(0) 751 return CharStrings(file, charset, globalSubrs, private, fdSelect, fdArray) 752 def write(self, parent, value): 753 return 0 # dummy value 754 def xmlRead(self, (name, attrs, content), parent): 755 if hasattr(parent, "ROS"): 756 # if it is a CID-keyed font, then the private Dict is extracted from the parent.FDArray 757 private, fdSelect, fdArray = None, parent.FDSelect, parent.FDArray 758 else: 759 # if it is a name-keyed font, then the private dict is in the top dict, and there is no fdArray. 760 private, fdSelect, fdArray = parent.Private, None, None 761 charStrings = CharStrings(None, None, parent.GlobalSubrs, private, fdSelect, fdArray) 762 charStrings.fromXML((name, attrs, content)) 763 return charStrings 764 765class CharsetConverter: 766 def read(self, parent, value): 767 isCID = hasattr(parent, "ROS") 768 if value > 2: 769 numGlyphs = parent.numGlyphs 770 file = parent.file 771 file.seek(value) 772 if DEBUG: 773 print "loading charset at %s" % value 774 format = readCard8(file) 775 if format == 0: 776 charset = parseCharset0(numGlyphs, file, parent.strings, isCID) 777 elif format == 1 or format == 2: 778 charset = parseCharset(numGlyphs, file, parent.strings, isCID, format) 779 else: 780 raise NotImplementedError 781 assert len(charset) == numGlyphs 782 if DEBUG: 783 print " charset end at %s" % file.tell() 784 else: # offset == 0 -> no charset data. 785 if isCID or not parent.rawDict.has_key("CharStrings"): 786 assert value == 0 # We get here only when processing fontDicts from the FDArray of CFF-CID fonts. Only the real topDict references the chrset. 787 charset = None 788 elif value == 0: 789 charset = cffISOAdobeStrings 790 elif value == 1: 791 charset = cffIExpertStrings 792 elif value == 2: 793 charset = cffExpertSubsetStrings 794 return charset 795 796 def write(self, parent, value): 797 return 0 # dummy value 798 def xmlWrite(self, xmlWriter, name, value, progress): 799 # XXX only write charset when not in OT/TTX context, where we 800 # dump charset as a separate "GlyphOrder" table. 801 ##xmlWriter.simpletag("charset") 802 xmlWriter.comment("charset is dumped separately as the 'GlyphOrder' element") 803 xmlWriter.newline() 804 def xmlRead(self, (name, attrs, content), parent): 805 if 0: 806 return safeEval(attrs["value"]) 807 808 809class CharsetCompiler: 810 811 def __init__(self, strings, charset, parent): 812 assert charset[0] == '.notdef' 813 isCID = hasattr(parent.dictObj, "ROS") 814 data0 = packCharset0(charset, isCID, strings) 815 data = packCharset(charset, isCID, strings) 816 if len(data) < len(data0): 817 self.data = data 818 else: 819 self.data = data0 820 self.parent = parent 821 822 def setPos(self, pos, endPos): 823 self.parent.rawDict["charset"] = pos 824 825 def getDataLength(self): 826 return len(self.data) 827 828 def toFile(self, file): 829 file.write(self.data) 830 831 832def getCIDfromName(name, strings): 833 return int(name[3:]) 834 835def getSIDfromName(name, strings): 836 return strings.getSID(name) 837 838def packCharset0(charset, isCID, strings): 839 format = 0 840 data = [packCard8(format)] 841 if isCID: 842 getNameID = getCIDfromName 843 else: 844 getNameID = getSIDfromName 845 846 for name in charset[1:]: 847 data.append(packCard16(getNameID(name,strings))) 848 return "".join(data) 849 850 851def packCharset(charset, isCID, strings): 852 format = 1 853 ranges = [] 854 first = None 855 end = 0 856 if isCID: 857 getNameID = getCIDfromName 858 else: 859 getNameID = getSIDfromName 860 861 for name in charset[1:]: 862 SID = getNameID(name, strings) 863 if first is None: 864 first = SID 865 elif end + 1 <> SID: 866 nLeft = end - first 867 if nLeft > 255: 868 format = 2 869 ranges.append((first, nLeft)) 870 first = SID 871 end = SID 872 nLeft = end - first 873 if nLeft > 255: 874 format = 2 875 ranges.append((first, nLeft)) 876 877 data = [packCard8(format)] 878 if format == 1: 879 nLeftFunc = packCard8 880 else: 881 nLeftFunc = packCard16 882 for first, nLeft in ranges: 883 data.append(packCard16(first) + nLeftFunc(nLeft)) 884 return "".join(data) 885 886def parseCharset0(numGlyphs, file, strings, isCID): 887 charset = [".notdef"] 888 if isCID: 889 for i in range(numGlyphs - 1): 890 CID = readCard16(file) 891 charset.append("cid" + string.zfill(str(CID), 5) ) 892 else: 893 for i in range(numGlyphs - 1): 894 SID = readCard16(file) 895 charset.append(strings[SID]) 896 return charset 897 898def parseCharset(numGlyphs, file, strings, isCID, format): 899 charset = ['.notdef'] 900 count = 1 901 if format == 1: 902 nLeftFunc = readCard8 903 else: 904 nLeftFunc = readCard16 905 while count < numGlyphs: 906 first = readCard16(file) 907 nLeft = nLeftFunc(file) 908 if isCID: 909 for CID in range(first, first+nLeft+1): 910 charset.append("cid" + string.zfill(str(CID), 5) ) 911 else: 912 for SID in range(first, first+nLeft+1): 913 charset.append(strings[SID]) 914 count = count + nLeft + 1 915 return charset 916 917 918class EncodingCompiler: 919 920 def __init__(self, strings, encoding, parent): 921 assert not isinstance(encoding, basestring) 922 data0 = packEncoding0(parent.dictObj.charset, encoding, parent.strings) 923 data1 = packEncoding1(parent.dictObj.charset, encoding, parent.strings) 924 if len(data0) < len(data1): 925 self.data = data0 926 else: 927 self.data = data1 928 self.parent = parent 929 930 def setPos(self, pos, endPos): 931 self.parent.rawDict["Encoding"] = pos 932 933 def getDataLength(self): 934 return len(self.data) 935 936 def toFile(self, file): 937 file.write(self.data) 938 939 940class EncodingConverter(SimpleConverter): 941 942 def read(self, parent, value): 943 if value == 0: 944 return "StandardEncoding" 945 elif value == 1: 946 return "ExpertEncoding" 947 else: 948 assert value > 1 949 file = parent.file 950 file.seek(value) 951 if DEBUG: 952 print "loading Encoding at %s" % value 953 format = readCard8(file) 954 haveSupplement = format & 0x80 955 if haveSupplement: 956 raise NotImplementedError, "Encoding supplements are not yet supported" 957 format = format & 0x7f 958 if format == 0: 959 encoding = parseEncoding0(parent.charset, file, haveSupplement, 960 parent.strings) 961 elif format == 1: 962 encoding = parseEncoding1(parent.charset, file, haveSupplement, 963 parent.strings) 964 return encoding 965 966 def write(self, parent, value): 967 if value == "StandardEncoding": 968 return 0 969 elif value == "ExpertEncoding": 970 return 1 971 return 0 # dummy value 972 973 def xmlWrite(self, xmlWriter, name, value, progress): 974 if value in ("StandardEncoding", "ExpertEncoding"): 975 xmlWriter.simpletag(name, name=value) 976 xmlWriter.newline() 977 return 978 xmlWriter.begintag(name) 979 xmlWriter.newline() 980 for code in range(len(value)): 981 glyphName = value[code] 982 if glyphName != ".notdef": 983 xmlWriter.simpletag("map", code=hex(code), name=glyphName) 984 xmlWriter.newline() 985 xmlWriter.endtag(name) 986 xmlWriter.newline() 987 988 def xmlRead(self, (name, attrs, content), parent): 989 if attrs.has_key("name"): 990 return attrs["name"] 991 encoding = [".notdef"] * 256 992 for element in content: 993 if isinstance(element, basestring): 994 continue 995 name, attrs, content = element 996 code = safeEval(attrs["code"]) 997 glyphName = attrs["name"] 998 encoding[code] = glyphName 999 return encoding 1000 1001 1002def parseEncoding0(charset, file, haveSupplement, strings): 1003 nCodes = readCard8(file) 1004 encoding = [".notdef"] * 256 1005 for glyphID in range(1, nCodes + 1): 1006 code = readCard8(file) 1007 if code != 0: 1008 encoding[code] = charset[glyphID] 1009 return encoding 1010 1011def parseEncoding1(charset, file, haveSupplement, strings): 1012 nRanges = readCard8(file) 1013 encoding = [".notdef"] * 256 1014 glyphID = 1 1015 for i in range(nRanges): 1016 code = readCard8(file) 1017 nLeft = readCard8(file) 1018 for glyphID in range(glyphID, glyphID + nLeft + 1): 1019 encoding[code] = charset[glyphID] 1020 code = code + 1 1021 glyphID = glyphID + 1 1022 return encoding 1023 1024def packEncoding0(charset, encoding, strings): 1025 format = 0 1026 m = {} 1027 for code in range(len(encoding)): 1028 name = encoding[code] 1029 if name != ".notdef": 1030 m[name] = code 1031 codes = [] 1032 for name in charset[1:]: 1033 code = m.get(name) 1034 codes.append(code) 1035 1036 while codes and codes[-1] is None: 1037 codes.pop() 1038 1039 data = [packCard8(format), packCard8(len(codes))] 1040 for code in codes: 1041 if code is None: 1042 code = 0 1043 data.append(packCard8(code)) 1044 return "".join(data) 1045 1046def packEncoding1(charset, encoding, strings): 1047 format = 1 1048 m = {} 1049 for code in range(len(encoding)): 1050 name = encoding[code] 1051 if name != ".notdef": 1052 m[name] = code 1053 ranges = [] 1054 first = None 1055 end = 0 1056 for name in charset[1:]: 1057 code = m.get(name, -1) 1058 if first is None: 1059 first = code 1060 elif end + 1 <> code: 1061 nLeft = end - first 1062 ranges.append((first, nLeft)) 1063 first = code 1064 end = code 1065 nLeft = end - first 1066 ranges.append((first, nLeft)) 1067 1068 # remove unencoded glyphs at the end. 1069 while ranges and ranges[-1][0] == -1: 1070 ranges.pop() 1071 1072 data = [packCard8(format), packCard8(len(ranges))] 1073 for first, nLeft in ranges: 1074 if first == -1: # unencoded 1075 first = 0 1076 data.append(packCard8(first) + packCard8(nLeft)) 1077 return "".join(data) 1078 1079 1080class FDArrayConverter(TableConverter): 1081 1082 def read(self, parent, value): 1083 file = parent.file 1084 file.seek(value) 1085 fdArray = FDArrayIndex(file) 1086 fdArray.strings = parent.strings 1087 fdArray.GlobalSubrs = parent.GlobalSubrs 1088 return fdArray 1089 1090 def write(self, parent, value): 1091 return 0 # dummy value 1092 1093 def xmlRead(self, (name, attrs, content), parent): 1094 fdArray = FDArrayIndex() 1095 for element in content: 1096 if isinstance(element, basestring): 1097 continue 1098 fdArray.fromXML(element) 1099 return fdArray 1100 1101 1102class FDSelectConverter: 1103 1104 def read(self, parent, value): 1105 file = parent.file 1106 file.seek(value) 1107 fdSelect = FDSelect(file, parent.numGlyphs) 1108 return fdSelect 1109 1110 def write(self, parent, value): 1111 return 0 # dummy value 1112 1113 # The FDSelect glyph data is written out to XML in the charstring keys, 1114 # so we write out only the format selector 1115 def xmlWrite(self, xmlWriter, name, value, progress): 1116 xmlWriter.simpletag(name, [('format', value.format)]) 1117 xmlWriter.newline() 1118 1119 def xmlRead(self, (name, attrs, content), parent): 1120 format = safeEval(attrs["format"]) 1121 file = None 1122 numGlyphs = None 1123 fdSelect = FDSelect(file, numGlyphs, format) 1124 return fdSelect 1125 1126 1127def packFDSelect0(fdSelectArray): 1128 format = 0 1129 data = [packCard8(format)] 1130 for index in fdSelectArray: 1131 data.append(packCard8(index)) 1132 return "".join(data) 1133 1134 1135def packFDSelect3(fdSelectArray): 1136 format = 3 1137 fdRanges = [] 1138 first = None 1139 end = 0 1140 lenArray = len(fdSelectArray) 1141 lastFDIndex = -1 1142 for i in range(lenArray): 1143 fdIndex = fdSelectArray[i] 1144 if lastFDIndex != fdIndex: 1145 fdRanges.append([i, fdIndex]) 1146 lastFDIndex = fdIndex 1147 sentinelGID = i + 1 1148 1149 data = [packCard8(format)] 1150 data.append(packCard16( len(fdRanges) )) 1151 for fdRange in fdRanges: 1152 data.append(packCard16(fdRange[0])) 1153 data.append(packCard8(fdRange[1])) 1154 data.append(packCard16(sentinelGID)) 1155 return "".join(data) 1156 1157 1158class FDSelectCompiler: 1159 1160 def __init__(self, fdSelect, parent): 1161 format = fdSelect.format 1162 fdSelectArray = fdSelect.gidArray 1163 if format == 0: 1164 self.data = packFDSelect0(fdSelectArray) 1165 elif format == 3: 1166 self.data = packFDSelect3(fdSelectArray) 1167 else: 1168 # choose smaller of the two formats 1169 data0 = packFDSelect0(fdSelectArray) 1170 data3 = packFDSelect3(fdSelectArray) 1171 if len(data0) < len(data3): 1172 self.data = data0 1173 fdSelect.format = 0 1174 else: 1175 self.data = data3 1176 fdSelect.format = 3 1177 1178 self.parent = parent 1179 1180 def setPos(self, pos, endPos): 1181 self.parent.rawDict["FDSelect"] = pos 1182 1183 def getDataLength(self): 1184 return len(self.data) 1185 1186 def toFile(self, file): 1187 file.write(self.data) 1188 1189 1190class ROSConverter(SimpleConverter): 1191 1192 def xmlWrite(self, xmlWriter, name, value, progress): 1193 registry, order, supplement = value 1194 xmlWriter.simpletag(name, [('Registry', registry), ('Order', order), 1195 ('Supplement', supplement)]) 1196 xmlWriter.newline() 1197 1198 def xmlRead(self, (name, attrs, content), parent): 1199 return (attrs['Registry'], attrs['Order'], safeEval(attrs['Supplement'])) 1200 1201 1202 1203topDictOperators = [ 1204# opcode name argument type default converter 1205 ((12, 30), 'ROS', ('SID','SID','number'), None, ROSConverter()), 1206 ((12, 20), 'SyntheticBase', 'number', None, None), 1207 (0, 'version', 'SID', None, None), 1208 (1, 'Notice', 'SID', None, Latin1Converter()), 1209 ((12, 0), 'Copyright', 'SID', None, Latin1Converter()), 1210 (2, 'FullName', 'SID', None, None), 1211 ((12, 38), 'FontName', 'SID', None, None), 1212 (3, 'FamilyName', 'SID', None, None), 1213 (4, 'Weight', 'SID', None, None), 1214 ((12, 1), 'isFixedPitch', 'number', 0, None), 1215 ((12, 2), 'ItalicAngle', 'number', 0, None), 1216 ((12, 3), 'UnderlinePosition', 'number', None, None), 1217 ((12, 4), 'UnderlineThickness', 'number', 50, None), 1218 ((12, 5), 'PaintType', 'number', 0, None), 1219 ((12, 6), 'CharstringType', 'number', 2, None), 1220 ((12, 7), 'FontMatrix', 'array', [0.001,0,0,0.001,0,0], None), 1221 (13, 'UniqueID', 'number', None, None), 1222 (5, 'FontBBox', 'array', [0,0,0,0], None), 1223 ((12, 8), 'StrokeWidth', 'number', 0, None), 1224 (14, 'XUID', 'array', None, None), 1225 ((12, 21), 'PostScript', 'SID', None, None), 1226 ((12, 22), 'BaseFontName', 'SID', None, None), 1227 ((12, 23), 'BaseFontBlend', 'delta', None, None), 1228 ((12, 31), 'CIDFontVersion', 'number', 0, None), 1229 ((12, 32), 'CIDFontRevision', 'number', 0, None), 1230 ((12, 33), 'CIDFontType', 'number', 0, None), 1231 ((12, 34), 'CIDCount', 'number', 8720, None), 1232 (15, 'charset', 'number', 0, CharsetConverter()), 1233 ((12, 35), 'UIDBase', 'number', None, None), 1234 (16, 'Encoding', 'number', 0, EncodingConverter()), 1235 (18, 'Private', ('number','number'), None, PrivateDictConverter()), 1236 ((12, 37), 'FDSelect', 'number', None, FDSelectConverter()), 1237 ((12, 36), 'FDArray', 'number', None, FDArrayConverter()), 1238 (17, 'CharStrings', 'number', None, CharStringsConverter()), 1239] 1240 1241# Note! FDSelect and FDArray must both preceed CharStrings in the output XML build order, 1242# in order for the font to compile back from xml. 1243 1244 1245privateDictOperators = [ 1246# opcode name argument type default converter 1247 (6, 'BlueValues', 'delta', None, None), 1248 (7, 'OtherBlues', 'delta', None, None), 1249 (8, 'FamilyBlues', 'delta', None, None), 1250 (9, 'FamilyOtherBlues', 'delta', None, None), 1251 ((12, 9), 'BlueScale', 'number', 0.039625, None), 1252 ((12, 10), 'BlueShift', 'number', 7, None), 1253 ((12, 11), 'BlueFuzz', 'number', 1, None), 1254 (10, 'StdHW', 'number', None, None), 1255 (11, 'StdVW', 'number', None, None), 1256 ((12, 12), 'StemSnapH', 'delta', None, None), 1257 ((12, 13), 'StemSnapV', 'delta', None, None), 1258 ((12, 14), 'ForceBold', 'number', 0, None), 1259 ((12, 15), 'ForceBoldThreshold', 'number', None, None), # deprecated 1260 ((12, 16), 'lenIV', 'number', None, None), # deprecated 1261 ((12, 17), 'LanguageGroup', 'number', 0, None), 1262 ((12, 18), 'ExpansionFactor', 'number', 0.06, None), 1263 ((12, 19), 'initialRandomSeed', 'number', 0, None), 1264 (20, 'defaultWidthX', 'number', 0, None), 1265 (21, 'nominalWidthX', 'number', 0, None), 1266 (19, 'Subrs', 'number', None, SubrsConverter()), 1267] 1268 1269def addConverters(table): 1270 for i in range(len(table)): 1271 op, name, arg, default, conv = table[i] 1272 if conv is not None: 1273 continue 1274 if arg in ("delta", "array"): 1275 conv = ArrayConverter() 1276 elif arg == "number": 1277 conv = NumberConverter() 1278 elif arg == "SID": 1279 conv = SimpleConverter() 1280 else: 1281 assert 0 1282 table[i] = op, name, arg, default, conv 1283 1284addConverters(privateDictOperators) 1285addConverters(topDictOperators) 1286 1287 1288class TopDictDecompiler(psCharStrings.DictDecompiler): 1289 operators = buildOperatorDict(topDictOperators) 1290 1291 1292class PrivateDictDecompiler(psCharStrings.DictDecompiler): 1293 operators = buildOperatorDict(privateDictOperators) 1294 1295 1296class DictCompiler: 1297 1298 def __init__(self, dictObj, strings, parent): 1299 assert isinstance(strings, IndexedStrings) 1300 self.dictObj = dictObj 1301 self.strings = strings 1302 self.parent = parent 1303 rawDict = {} 1304 for name in dictObj.order: 1305 value = getattr(dictObj, name, None) 1306 if value is None: 1307 continue 1308 conv = dictObj.converters[name] 1309 value = conv.write(dictObj, value) 1310 if value == dictObj.defaults.get(name): 1311 continue 1312 rawDict[name] = value 1313 self.rawDict = rawDict 1314 1315 def setPos(self, pos, endPos): 1316 pass 1317 1318 def getDataLength(self): 1319 return len(self.compile("getDataLength")) 1320 1321 def compile(self, reason): 1322 if DEBUG: 1323 print "-- compiling %s for %s" % (self.__class__.__name__, reason) 1324 print "in baseDict: ", self 1325 rawDict = self.rawDict 1326 data = [] 1327 for name in self.dictObj.order: 1328 value = rawDict.get(name) 1329 if value is None: 1330 continue 1331 op, argType = self.opcodes[name] 1332 if isinstance(argType, tuple): 1333 l = len(argType) 1334 assert len(value) == l, "value doesn't match arg type" 1335 for i in range(l): 1336 arg = argType[i] 1337 v = value[i] 1338 arghandler = getattr(self, "arg_" + arg) 1339 data.append(arghandler(v)) 1340 else: 1341 arghandler = getattr(self, "arg_" + argType) 1342 data.append(arghandler(value)) 1343 data.append(op) 1344 return "".join(data) 1345 1346 def toFile(self, file): 1347 file.write(self.compile("toFile")) 1348 1349 def arg_number(self, num): 1350 return encodeNumber(num) 1351 def arg_SID(self, s): 1352 return psCharStrings.encodeIntCFF(self.strings.getSID(s)) 1353 def arg_array(self, value): 1354 data = [] 1355 for num in value: 1356 data.append(encodeNumber(num)) 1357 return "".join(data) 1358 def arg_delta(self, value): 1359 out = [] 1360 last = 0 1361 for v in value: 1362 out.append(v - last) 1363 last = v 1364 data = [] 1365 for num in out: 1366 data.append(encodeNumber(num)) 1367 return "".join(data) 1368 1369 1370def encodeNumber(num): 1371 if isinstance(num, float): 1372 return psCharStrings.encodeFloat(num) 1373 else: 1374 return psCharStrings.encodeIntCFF(num) 1375 1376 1377class TopDictCompiler(DictCompiler): 1378 1379 opcodes = buildOpcodeDict(topDictOperators) 1380 1381 def getChildren(self, strings): 1382 children = [] 1383 if hasattr(self.dictObj, "charset") and self.dictObj.charset: 1384 children.append(CharsetCompiler(strings, self.dictObj.charset, self)) 1385 if hasattr(self.dictObj, "Encoding"): 1386 encoding = self.dictObj.Encoding 1387 if not isinstance(encoding, basestring): 1388 children.append(EncodingCompiler(strings, encoding, self)) 1389 if hasattr(self.dictObj, "FDSelect"): 1390 # I have not yet supported merging a ttx CFF-CID font, as there are interesting 1391 # issues about merging the FDArrays. Here I assume that 1392 # either the font was read from XML, and teh FDSelect indices are all 1393 # in the charstring data, or the FDSelect array is already fully defined. 1394 fdSelect = self.dictObj.FDSelect 1395 if len(fdSelect) == 0: # probably read in from XML; assume fdIndex in CharString data 1396 charStrings = self.dictObj.CharStrings 1397 for name in self.dictObj.charset: 1398 charstring = charStrings[name] 1399 fdSelect.append(charStrings[name].fdSelectIndex) 1400 fdSelectComp = FDSelectCompiler(fdSelect, self) 1401 children.append(fdSelectComp) 1402 if hasattr(self.dictObj, "CharStrings"): 1403 items = [] 1404 charStrings = self.dictObj.CharStrings 1405 for name in self.dictObj.charset: 1406 items.append(charStrings[name]) 1407 charStringsComp = CharStringsCompiler(items, strings, self) 1408 children.append(charStringsComp) 1409 if hasattr(self.dictObj, "FDArray"): 1410 # I have not yet supported merging a ttx CFF-CID font, as there are interesting 1411 # issues about merging the FDArrays. Here I assume that the FDArray info is correct 1412 # and complete. 1413 fdArrayIndexComp = self.dictObj.FDArray.getCompiler(strings, self) 1414 children.append(fdArrayIndexComp) 1415 children.extend(fdArrayIndexComp.getChildren(strings)) 1416 if hasattr(self.dictObj, "Private"): 1417 privComp = self.dictObj.Private.getCompiler(strings, self) 1418 children.append(privComp) 1419 children.extend(privComp.getChildren(strings)) 1420 return children 1421 1422 1423class FontDictCompiler(DictCompiler): 1424 1425 opcodes = buildOpcodeDict(topDictOperators) 1426 1427 def getChildren(self, strings): 1428 children = [] 1429 if hasattr(self.dictObj, "Private"): 1430 privComp = self.dictObj.Private.getCompiler(strings, self) 1431 children.append(privComp) 1432 children.extend(privComp.getChildren(strings)) 1433 return children 1434 1435 1436class PrivateDictCompiler(DictCompiler): 1437 1438 opcodes = buildOpcodeDict(privateDictOperators) 1439 1440 def setPos(self, pos, endPos): 1441 size = endPos - pos 1442 self.parent.rawDict["Private"] = size, pos 1443 self.pos = pos 1444 1445 def getChildren(self, strings): 1446 children = [] 1447 if hasattr(self.dictObj, "Subrs"): 1448 children.append(self.dictObj.Subrs.getCompiler(strings, self)) 1449 return children 1450 1451 1452class BaseDict: 1453 1454 def __init__(self, strings=None, file=None, offset=None): 1455 self.rawDict = {} 1456 if DEBUG: 1457 print "loading %s at %s" % (self.__class__.__name__, offset) 1458 self.file = file 1459 self.offset = offset 1460 self.strings = strings 1461 self.skipNames = [] 1462 1463 def decompile(self, data): 1464 if DEBUG: 1465 print " length %s is %s" % (self.__class__.__name__, len(data)) 1466 dec = self.decompilerClass(self.strings) 1467 dec.decompile(data) 1468 self.rawDict = dec.getDict() 1469 self.postDecompile() 1470 1471 def postDecompile(self): 1472 pass 1473 1474 def getCompiler(self, strings, parent): 1475 return self.compilerClass(self, strings, parent) 1476 1477 def __getattr__(self, name): 1478 value = self.rawDict.get(name) 1479 if value is None: 1480 value = self.defaults.get(name) 1481 if value is None: 1482 raise AttributeError, name 1483 conv = self.converters[name] 1484 value = conv.read(self, value) 1485 setattr(self, name, value) 1486 return value 1487 1488 def toXML(self, xmlWriter, progress): 1489 for name in self.order: 1490 if name in self.skipNames: 1491 continue 1492 value = getattr(self, name, None) 1493 if value is None: 1494 continue 1495 conv = self.converters[name] 1496 conv.xmlWrite(xmlWriter, name, value, progress) 1497 1498 def fromXML(self, (name, attrs, content)): 1499 conv = self.converters[name] 1500 value = conv.xmlRead((name, attrs, content), self) 1501 setattr(self, name, value) 1502 1503 1504class TopDict(BaseDict): 1505 1506 defaults = buildDefaults(topDictOperators) 1507 converters = buildConverters(topDictOperators) 1508 order = buildOrder(topDictOperators) 1509 decompilerClass = TopDictDecompiler 1510 compilerClass = TopDictCompiler 1511 1512 def __init__(self, strings=None, file=None, offset=None, GlobalSubrs=None): 1513 BaseDict.__init__(self, strings, file, offset) 1514 self.GlobalSubrs = GlobalSubrs 1515 1516 def getGlyphOrder(self): 1517 return self.charset 1518 1519 def postDecompile(self): 1520 offset = self.rawDict.get("CharStrings") 1521 if offset is None: 1522 return 1523 # get the number of glyphs beforehand. 1524 self.file.seek(offset) 1525 self.numGlyphs = readCard16(self.file) 1526 1527 def toXML(self, xmlWriter, progress): 1528 if hasattr(self, "CharStrings"): 1529 self.decompileAllCharStrings(progress) 1530 if hasattr(self, "ROS"): 1531 self.skipNames = ['Encoding'] 1532 if not hasattr(self, "ROS") or not hasattr(self, "CharStrings"): 1533 # these values have default values, but I only want them to show up 1534 # in CID fonts. 1535 self.skipNames = ['CIDFontVersion', 'CIDFontRevision', 'CIDFontType', 1536 'CIDCount'] 1537 BaseDict.toXML(self, xmlWriter, progress) 1538 1539 def decompileAllCharStrings(self, progress): 1540 # XXX only when doing ttdump -i? 1541 i = 0 1542 for charString in self.CharStrings.values(): 1543 try: 1544 charString.decompile() 1545 except: 1546 print "Error in charstring ", i 1547 import sys 1548 type, value = sys. exc_info()[0:2] 1549 raise type(value) 1550 if not i % 30 and progress: 1551 progress.increment(0) # update 1552 i = i + 1 1553 1554 1555class FontDict(BaseDict): 1556 1557 defaults = buildDefaults(topDictOperators) 1558 converters = buildConverters(topDictOperators) 1559 order = buildOrder(topDictOperators) 1560 decompilerClass = None 1561 compilerClass = FontDictCompiler 1562 1563 def __init__(self, strings=None, file=None, offset=None, GlobalSubrs=None): 1564 BaseDict.__init__(self, strings, file, offset) 1565 self.GlobalSubrs = GlobalSubrs 1566 1567 def getGlyphOrder(self): 1568 return self.charset 1569 1570 def toXML(self, xmlWriter, progress): 1571 self.skipNames = ['Encoding'] 1572 BaseDict.toXML(self, xmlWriter, progress) 1573 1574 1575 1576class PrivateDict(BaseDict): 1577 defaults = buildDefaults(privateDictOperators) 1578 converters = buildConverters(privateDictOperators) 1579 order = buildOrder(privateDictOperators) 1580 decompilerClass = PrivateDictDecompiler 1581 compilerClass = PrivateDictCompiler 1582 1583 1584class IndexedStrings: 1585 1586 """SID -> string mapping.""" 1587 1588 def __init__(self, file=None): 1589 if file is None: 1590 strings = [] 1591 else: 1592 strings = list(Index(file)) 1593 self.strings = strings 1594 1595 def getCompiler(self): 1596 return IndexedStringsCompiler(self, None, None) 1597 1598 def __len__(self): 1599 return len(self.strings) 1600 1601 def __getitem__(self, SID): 1602 if SID < cffStandardStringCount: 1603 return cffStandardStrings[SID] 1604 else: 1605 return self.strings[SID - cffStandardStringCount] 1606 1607 def getSID(self, s): 1608 if not hasattr(self, "stringMapping"): 1609 self.buildStringMapping() 1610 if cffStandardStringMapping.has_key(s): 1611 SID = cffStandardStringMapping[s] 1612 elif self.stringMapping.has_key(s): 1613 SID = self.stringMapping[s] 1614 else: 1615 SID = len(self.strings) + cffStandardStringCount 1616 self.strings.append(s) 1617 self.stringMapping[s] = SID 1618 return SID 1619 1620 def getStrings(self): 1621 return self.strings 1622 1623 def buildStringMapping(self): 1624 self.stringMapping = {} 1625 for index in range(len(self.strings)): 1626 self.stringMapping[self.strings[index]] = index + cffStandardStringCount 1627 1628 1629# The 391 Standard Strings as used in the CFF format. 1630# from Adobe Technical None #5176, version 1.0, 18 March 1998 1631 1632cffStandardStrings = ['.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 1633 'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright', 1634 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 1635 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 1636 'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 1637 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 1638 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 1639 'bracketright', 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c', 1640 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 1641 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 1642 'asciitilde', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 1643 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft', 1644 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 1645 'daggerdbl', 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase', 1646 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', 1647 'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 1648 'dotaccent', 'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 1649 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae', 1650 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior', 1651 'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 1652 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters', 1653 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior', 1654 'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 1655 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave', 1656 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 1657 'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 1658 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron', 1659 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla', 1660 'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex', 1661 'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis', 1662 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave', 1663 'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall', 1664 'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall', 1665 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', 1666 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 1667 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 1668 'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior', 1669 'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 1670 'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior', 1671 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', 'parenleftinferior', 1672 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 1673 'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 1674 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 1675 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall', 1676 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', 1677 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 1678 'Dieresissmall', 'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall', 1679 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', 1680 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 1681 'onethird', 'twothirds', 'zerosuperior', 'foursuperior', 'fivesuperior', 1682 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 1683 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 1684 'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 1685 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall', 1686 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', 1687 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 1688 'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 1689 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', 1690 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 1691 'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 1692 'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', '001.001', '001.002', 1693 '001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman', 1694 'Semibold' 1695] 1696 1697cffStandardStringCount = 391 1698assert len(cffStandardStrings) == cffStandardStringCount 1699# build reverse mapping 1700cffStandardStringMapping = {} 1701for _i in range(cffStandardStringCount): 1702 cffStandardStringMapping[cffStandardStrings[_i]] = _i 1703 1704cffISOAdobeStrings = [".notdef", "space", "exclam", "quotedbl", "numbersign", 1705"dollar", "percent", "ampersand", "quoteright", "parenleft", "parenright", 1706"asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two", 1707"three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", 1708"less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", 1709"H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", 1710"X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum", 1711"underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", 1712"k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", 1713"braceleft", "bar", "braceright", "asciitilde", "exclamdown", "cent", 1714"sterling", "fraction", "yen", "florin", "section", "currency", "quotesingle", 1715"quotedblleft", "guillemotleft", "guilsinglleft", "guilsinglright", "fi", "fl", 1716"endash", "dagger", "daggerdbl", "periodcentered", "paragraph", "bullet", 1717"quotesinglbase", "quotedblbase", "quotedblright", "guillemotright", "ellipsis", 1718"perthousand", "questiondown", "grave", "acute", "circumflex", "tilde", 1719"macron", "breve", "dotaccent", "dieresis", "ring", "cedilla", "hungarumlaut", 1720"ogonek", "caron", "emdash", "AE", "ordfeminine", "Lslash", "Oslash", "OE", 1721"ordmasculine", "ae", "dotlessi", "lslash", "oslash", "oe", "germandbls", 1722"onesuperior", "logicalnot", "mu", "trademark", "Eth", "onehalf", "plusminus", 1723"Thorn", "onequarter", "divide", "brokenbar", "degree", "thorn", 1724"threequarters", "twosuperior", "registered", "minus", "eth", "multiply", 1725"threesuperior", "copyright", "Aacute", "Acircumflex", "Adieresis", "Agrave", 1726"Aring", "Atilde", "Ccedilla", "Eacute", "Ecircumflex", "Edieresis", "Egrave", 1727"Iacute", "Icircumflex", "Idieresis", "Igrave", "Ntilde", "Oacute", 1728"Ocircumflex", "Odieresis", "Ograve", "Otilde", "Scaron", "Uacute", 1729"Ucircumflex", "Udieresis", "Ugrave", "Yacute", "Ydieresis", "Zcaron", "aacute", 1730"acircumflex", "adieresis", "agrave", "aring", "atilde", "ccedilla", "eacute", 1731"ecircumflex", "edieresis", "egrave", "iacute", "icircumflex", "idieresis", 1732"igrave", "ntilde", "oacute", "ocircumflex", "odieresis", "ograve", "otilde", 1733"scaron", "uacute", "ucircumflex", "udieresis", "ugrave", "yacute", "ydieresis", 1734"zcaron"] 1735 1736cffISOAdobeStringCount = 229 1737assert len(cffISOAdobeStrings) == cffISOAdobeStringCount 1738 1739cffIExpertStrings = [".notdef", "space", "exclamsmall", "Hungarumlautsmall", 1740"dollaroldstyle", "dollarsuperior", "ampersandsmall", "Acutesmall", 1741"parenleftsuperior", "parenrightsuperior", "twodotenleader", "onedotenleader", 1742"comma", "hyphen", "period", "fraction", "zerooldstyle", "oneoldstyle", 1743"twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle", 1744"sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", "semicolon", 1745"commasuperior", "threequartersemdash", "periodsuperior", "questionsmall", 1746"asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior", 1747"lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior", 1748"tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior", 1749"parenrightinferior", "Circumflexsmall", "hyphensuperior", "Gravesmall", 1750"Asmall", "Bsmall", "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall", 1751"Ismall", "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall", 1752"Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall", "Xsmall", 1753"Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah", "Tildesmall", 1754"exclamdownsmall", "centoldstyle", "Lslashsmall", "Scaronsmall", "Zcaronsmall", 1755"Dieresissmall", "Brevesmall", "Caronsmall", "Dotaccentsmall", "Macronsmall", 1756"figuredash", "hypheninferior", "Ogoneksmall", "Ringsmall", "Cedillasmall", 1757"onequarter", "onehalf", "threequarters", "questiondownsmall", "oneeighth", 1758"threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds", 1759"zerosuperior", "onesuperior", "twosuperior", "threesuperior", "foursuperior", 1760"fivesuperior", "sixsuperior", "sevensuperior", "eightsuperior", "ninesuperior", 1761"zeroinferior", "oneinferior", "twoinferior", "threeinferior", "fourinferior", 1762"fiveinferior", "sixinferior", "seveninferior", "eightinferior", "nineinferior", 1763"centinferior", "dollarinferior", "periodinferior", "commainferior", 1764"Agravesmall", "Aacutesmall", "Acircumflexsmall", "Atildesmall", 1765"Adieresissmall", "Aringsmall", "AEsmall", "Ccedillasmall", "Egravesmall", 1766"Eacutesmall", "Ecircumflexsmall", "Edieresissmall", "Igravesmall", 1767"Iacutesmall", "Icircumflexsmall", "Idieresissmall", "Ethsmall", "Ntildesmall", 1768"Ogravesmall", "Oacutesmall", "Ocircumflexsmall", "Otildesmall", 1769"Odieresissmall", "OEsmall", "Oslashsmall", "Ugravesmall", "Uacutesmall", 1770"Ucircumflexsmall", "Udieresissmall", "Yacutesmall", "Thornsmall", 1771"Ydieresissmall"] 1772 1773cffExpertStringCount = 166 1774assert len(cffIExpertStrings) == cffExpertStringCount 1775 1776cffExpertSubsetStrings = [".notdef", "space", "dollaroldstyle", 1777"dollarsuperior", "parenleftsuperior", "parenrightsuperior", "twodotenleader", 1778"onedotenleader", "comma", "hyphen", "period", "fraction", "zerooldstyle", 1779"oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", 1780"sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", 1781"semicolon", "commasuperior", "threequartersemdash", "periodsuperior", 1782"asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior", 1783"lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior", 1784"tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior", 1785"parenrightinferior", "hyphensuperior", "colonmonetary", "onefitted", "rupiah", 1786"centoldstyle", "figuredash", "hypheninferior", "onequarter", "onehalf", 1787"threequarters", "oneeighth", "threeeighths", "fiveeighths", "seveneighths", 1788"onethird", "twothirds", "zerosuperior", "onesuperior", "twosuperior", 1789"threesuperior", "foursuperior", "fivesuperior", "sixsuperior", "sevensuperior", 1790"eightsuperior", "ninesuperior", "zeroinferior", "oneinferior", "twoinferior", 1791"threeinferior", "fourinferior", "fiveinferior", "sixinferior", "seveninferior", 1792"eightinferior", "nineinferior", "centinferior", "dollarinferior", 1793"periodinferior", "commainferior"] 1794 1795cffExpertSubsetStringCount = 87 1796assert len(cffExpertSubsetStrings) == cffExpertSubsetStringCount 1797