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