cffLib.py revision 617ec41d4c2db2011e62cadf619e2a8b07aec93e
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(object): 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=tostr(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(object): 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(object): 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(tobytes(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(object): 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 False, "unsupported FDSelect format: %s" % format 483 else: 484 # reading from XML. Make empty gidArray,, and leave format as passed in. 485 # format is 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(object): 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 is not None: 518 self.fdSelect = fdSelect 519 if fdArray is not 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(object): 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 ASCIIConverter(SimpleConverter): 677 def read(self, parent, value): 678 return tostr(value, encoding='ascii') 679 def write(self, parent, value): 680 return tobytes(value, encoding='ascii') 681 def xmlWrite(self, xmlWriter, name, value, progress): 682 xmlWriter.simpletag(name, value=tostr(value, encoding="ascii")) 683 xmlWriter.newline() 684 def xmlRead(self, name, attrs, content, parent): 685 return tobytes(attrs["value"], encoding=("ascii")) 686 687class Latin1Converter(SimpleConverter): 688 def read(self, parent, value): 689 return tostr(value, encoding='latin1') 690 def write(self, parent, value): 691 return tobytes(value, encoding='latin1') 692 def xmlWrite(self, xmlWriter, name, value, progress): 693 xmlWriter.simpletag(name, value=tostr(value, encoding="latin1")) 694 xmlWriter.newline() 695 def xmlRead(self, name, attrs, content, parent): 696 return tobytes(attrs["value"], encoding=("latin1")) 697 698 699def parseNum(s): 700 try: 701 value = int(s) 702 except: 703 value = float(s) 704 return value 705 706class NumberConverter(SimpleConverter): 707 def xmlRead(self, name, attrs, content, parent): 708 return parseNum(attrs["value"]) 709 710class ArrayConverter(SimpleConverter): 711 def xmlWrite(self, xmlWriter, name, value, progress): 712 value = " ".join(map(str, value)) 713 xmlWriter.simpletag(name, value=value) 714 xmlWriter.newline() 715 def xmlRead(self, name, attrs, content, parent): 716 values = attrs["value"].split() 717 return [parseNum(value) for value in values] 718 719class TableConverter(SimpleConverter): 720 def xmlWrite(self, xmlWriter, name, value, progress): 721 xmlWriter.begintag(name) 722 xmlWriter.newline() 723 value.toXML(xmlWriter, progress) 724 xmlWriter.endtag(name) 725 xmlWriter.newline() 726 def xmlRead(self, name, attrs, content, parent): 727 ob = self.getClass()() 728 for element in content: 729 if isinstance(element, basestring): 730 continue 731 name, attrs, content = element 732 ob.fromXML(name, attrs, content) 733 return ob 734 735class PrivateDictConverter(TableConverter): 736 def getClass(self): 737 return PrivateDict 738 def read(self, parent, value): 739 size, offset = value 740 file = parent.file 741 priv = PrivateDict(parent.strings, file, offset) 742 file.seek(offset) 743 data = file.read(size) 744 assert len(data) == size 745 priv.decompile(data) 746 return priv 747 def write(self, parent, value): 748 return (0, 0) # dummy value 749 750class SubrsConverter(TableConverter): 751 def getClass(self): 752 return SubrsIndex 753 def read(self, parent, value): 754 file = parent.file 755 file.seek(parent.offset + value) # Offset(self) 756 return SubrsIndex(file) 757 def write(self, parent, value): 758 return 0 # dummy value 759 760class CharStringsConverter(TableConverter): 761 def read(self, parent, value): 762 file = parent.file 763 charset = parent.charset 764 globalSubrs = parent.GlobalSubrs 765 if hasattr(parent, "ROS"): 766 fdSelect, fdArray = parent.FDSelect, parent.FDArray 767 private = None 768 else: 769 fdSelect, fdArray = None, None 770 private = parent.Private 771 file.seek(value) # Offset(0) 772 return CharStrings(file, charset, globalSubrs, private, fdSelect, fdArray) 773 def write(self, parent, value): 774 return 0 # dummy value 775 def xmlRead(self, name, attrs, content, parent): 776 if hasattr(parent, "ROS"): 777 # if it is a CID-keyed font, then the private Dict is extracted from the parent.FDArray 778 private, fdSelect, fdArray = None, parent.FDSelect, parent.FDArray 779 else: 780 # if it is a name-keyed font, then the private dict is in the top dict, and there is no fdArray. 781 private, fdSelect, fdArray = parent.Private, None, None 782 charStrings = CharStrings(None, None, parent.GlobalSubrs, private, fdSelect, fdArray) 783 charStrings.fromXML(name, attrs, content) 784 return charStrings 785 786class CharsetConverter(object): 787 def read(self, parent, value): 788 isCID = hasattr(parent, "ROS") 789 if value > 2: 790 numGlyphs = parent.numGlyphs 791 file = parent.file 792 file.seek(value) 793 if DEBUG: 794 print("loading charset at %s" % value) 795 format = readCard8(file) 796 if format == 0: 797 charset = parseCharset0(numGlyphs, file, parent.strings, isCID) 798 elif format == 1 or format == 2: 799 charset = parseCharset(numGlyphs, file, parent.strings, isCID, format) 800 else: 801 raise NotImplementedError 802 assert len(charset) == numGlyphs 803 if DEBUG: 804 print(" charset end at %s" % file.tell()) 805 else: # offset == 0 -> no charset data. 806 if isCID or "CharStrings" not in parent.rawDict: 807 assert value == 0 # We get here only when processing fontDicts from the FDArray of CFF-CID fonts. Only the real topDict references the chrset. 808 charset = None 809 elif value == 0: 810 charset = cffISOAdobeStrings 811 elif value == 1: 812 charset = cffIExpertStrings 813 elif value == 2: 814 charset = cffExpertSubsetStrings 815 return charset 816 817 def write(self, parent, value): 818 return 0 # dummy value 819 def xmlWrite(self, xmlWriter, name, value, progress): 820 # XXX only write charset when not in OT/TTX context, where we 821 # dump charset as a separate "GlyphOrder" table. 822 ##xmlWriter.simpletag("charset") 823 xmlWriter.comment("charset is dumped separately as the 'GlyphOrder' element") 824 xmlWriter.newline() 825 def xmlRead(self, name, attrs, content, parent): 826 if 0: 827 return safeEval(attrs["value"]) 828 829 830class CharsetCompiler(object): 831 832 def __init__(self, strings, charset, parent): 833 assert charset[0] == '.notdef' 834 isCID = hasattr(parent.dictObj, "ROS") 835 data0 = packCharset0(charset, isCID, strings) 836 data = packCharset(charset, isCID, strings) 837 if len(data) < len(data0): 838 self.data = data 839 else: 840 self.data = data0 841 self.parent = parent 842 843 def setPos(self, pos, endPos): 844 self.parent.rawDict["charset"] = pos 845 846 def getDataLength(self): 847 return len(self.data) 848 849 def toFile(self, file): 850 file.write(self.data) 851 852 853def getCIDfromName(name, strings): 854 return int(name[3:]) 855 856def getSIDfromName(name, strings): 857 return strings.getSID(name) 858 859def packCharset0(charset, isCID, strings): 860 fmt = 0 861 data = [packCard8(fmt)] 862 if isCID: 863 getNameID = getCIDfromName 864 else: 865 getNameID = getSIDfromName 866 867 for name in charset[1:]: 868 data.append(packCard16(getNameID(name,strings))) 869 return bytesjoin(data) 870 871 872def packCharset(charset, isCID, strings): 873 fmt = 1 874 ranges = [] 875 first = None 876 end = 0 877 if isCID: 878 getNameID = getCIDfromName 879 else: 880 getNameID = getSIDfromName 881 882 for name in charset[1:]: 883 SID = getNameID(name, strings) 884 if first is None: 885 first = SID 886 elif end + 1 != SID: 887 nLeft = end - first 888 if nLeft > 255: 889 fmt = 2 890 ranges.append((first, nLeft)) 891 first = SID 892 end = SID 893 nLeft = end - first 894 if nLeft > 255: 895 fmt = 2 896 ranges.append((first, nLeft)) 897 898 data = [packCard8(fmt)] 899 if fmt == 1: 900 nLeftFunc = packCard8 901 else: 902 nLeftFunc = packCard16 903 for first, nLeft in ranges: 904 data.append(packCard16(first) + nLeftFunc(nLeft)) 905 return bytesjoin(data) 906 907def parseCharset0(numGlyphs, file, strings, isCID): 908 charset = [".notdef"] 909 if isCID: 910 for i in range(numGlyphs - 1): 911 CID = readCard16(file) 912 charset.append("cid" + str(CID).zfill(5)) 913 else: 914 for i in range(numGlyphs - 1): 915 SID = readCard16(file) 916 charset.append(strings[SID]) 917 return charset 918 919def parseCharset(numGlyphs, file, strings, isCID, fmt): 920 charset = ['.notdef'] 921 count = 1 922 if fmt == 1: 923 nLeftFunc = readCard8 924 else: 925 nLeftFunc = readCard16 926 while count < numGlyphs: 927 first = readCard16(file) 928 nLeft = nLeftFunc(file) 929 if isCID: 930 for CID in range(first, first+nLeft+1): 931 charset.append("cid" + str(CID).zfill(5)) 932 else: 933 for SID in range(first, first+nLeft+1): 934 charset.append(strings[SID]) 935 count = count + nLeft + 1 936 return charset 937 938 939class EncodingCompiler(object): 940 941 def __init__(self, strings, encoding, parent): 942 assert not isinstance(encoding, basestring) 943 data0 = packEncoding0(parent.dictObj.charset, encoding, parent.strings) 944 data1 = packEncoding1(parent.dictObj.charset, encoding, parent.strings) 945 if len(data0) < len(data1): 946 self.data = data0 947 else: 948 self.data = data1 949 self.parent = parent 950 951 def setPos(self, pos, endPos): 952 self.parent.rawDict["Encoding"] = pos 953 954 def getDataLength(self): 955 return len(self.data) 956 957 def toFile(self, file): 958 file.write(self.data) 959 960 961class EncodingConverter(SimpleConverter): 962 963 def read(self, parent, value): 964 if value == 0: 965 return "StandardEncoding" 966 elif value == 1: 967 return "ExpertEncoding" 968 else: 969 assert value > 1 970 file = parent.file 971 file.seek(value) 972 if DEBUG: 973 print("loading Encoding at %s" % value) 974 fmt = readCard8(file) 975 haveSupplement = fmt & 0x80 976 if haveSupplement: 977 raise NotImplementedError("Encoding supplements are not yet supported") 978 fmt = fmt & 0x7f 979 if fmt == 0: 980 encoding = parseEncoding0(parent.charset, file, haveSupplement, 981 parent.strings) 982 elif fmt == 1: 983 encoding = parseEncoding1(parent.charset, file, haveSupplement, 984 parent.strings) 985 return encoding 986 987 def write(self, parent, value): 988 if value == "StandardEncoding": 989 return 0 990 elif value == "ExpertEncoding": 991 return 1 992 return 0 # dummy value 993 994 def xmlWrite(self, xmlWriter, name, value, progress): 995 if value in ("StandardEncoding", "ExpertEncoding"): 996 xmlWriter.simpletag(name, name=value) 997 xmlWriter.newline() 998 return 999 xmlWriter.begintag(name) 1000 xmlWriter.newline() 1001 for code in range(len(value)): 1002 glyphName = value[code] 1003 if glyphName != ".notdef": 1004 xmlWriter.simpletag("map", code=hex(code), name=glyphName) 1005 xmlWriter.newline() 1006 xmlWriter.endtag(name) 1007 xmlWriter.newline() 1008 1009 def xmlRead(self, name, attrs, content, parent): 1010 if "name" in attrs: 1011 return attrs["name"] 1012 encoding = [".notdef"] * 256 1013 for element in content: 1014 if isinstance(element, basestring): 1015 continue 1016 name, attrs, content = element 1017 code = safeEval(attrs["code"]) 1018 glyphName = attrs["name"] 1019 encoding[code] = glyphName 1020 return encoding 1021 1022 1023def parseEncoding0(charset, file, haveSupplement, strings): 1024 nCodes = readCard8(file) 1025 encoding = [".notdef"] * 256 1026 for glyphID in range(1, nCodes + 1): 1027 code = readCard8(file) 1028 if code != 0: 1029 encoding[code] = charset[glyphID] 1030 return encoding 1031 1032def parseEncoding1(charset, file, haveSupplement, strings): 1033 nRanges = readCard8(file) 1034 encoding = [".notdef"] * 256 1035 glyphID = 1 1036 for i in range(nRanges): 1037 code = readCard8(file) 1038 nLeft = readCard8(file) 1039 for glyphID in range(glyphID, glyphID + nLeft + 1): 1040 encoding[code] = charset[glyphID] 1041 code = code + 1 1042 glyphID = glyphID + 1 1043 return encoding 1044 1045def packEncoding0(charset, encoding, strings): 1046 fmt = 0 1047 m = {} 1048 for code in range(len(encoding)): 1049 name = encoding[code] 1050 if name != ".notdef": 1051 m[name] = code 1052 codes = [] 1053 for name in charset[1:]: 1054 code = m.get(name) 1055 codes.append(code) 1056 1057 while codes and codes[-1] is None: 1058 codes.pop() 1059 1060 data = [packCard8(fmt), packCard8(len(codes))] 1061 for code in codes: 1062 if code is None: 1063 code = 0 1064 data.append(packCard8(code)) 1065 return bytesjoin(data) 1066 1067def packEncoding1(charset, encoding, strings): 1068 fmt = 1 1069 m = {} 1070 for code in range(len(encoding)): 1071 name = encoding[code] 1072 if name != ".notdef": 1073 m[name] = code 1074 ranges = [] 1075 first = None 1076 end = 0 1077 for name in charset[1:]: 1078 code = m.get(name, -1) 1079 if first is None: 1080 first = code 1081 elif end + 1 != code: 1082 nLeft = end - first 1083 ranges.append((first, nLeft)) 1084 first = code 1085 end = code 1086 nLeft = end - first 1087 ranges.append((first, nLeft)) 1088 1089 # remove unencoded glyphs at the end. 1090 while ranges and ranges[-1][0] == -1: 1091 ranges.pop() 1092 1093 data = [packCard8(fmt), packCard8(len(ranges))] 1094 for first, nLeft in ranges: 1095 if first == -1: # unencoded 1096 first = 0 1097 data.append(packCard8(first) + packCard8(nLeft)) 1098 return bytesjoin(data) 1099 1100 1101class FDArrayConverter(TableConverter): 1102 1103 def read(self, parent, value): 1104 file = parent.file 1105 file.seek(value) 1106 fdArray = FDArrayIndex(file) 1107 fdArray.strings = parent.strings 1108 fdArray.GlobalSubrs = parent.GlobalSubrs 1109 return fdArray 1110 1111 def write(self, parent, value): 1112 return 0 # dummy value 1113 1114 def xmlRead(self, name, attrs, content, parent): 1115 fdArray = FDArrayIndex() 1116 for element in content: 1117 if isinstance(element, basestring): 1118 continue 1119 name, attrs, content = element 1120 fdArray.fromXML(name, attrs, content) 1121 return fdArray 1122 1123 1124class FDSelectConverter(object): 1125 1126 def read(self, parent, value): 1127 file = parent.file 1128 file.seek(value) 1129 fdSelect = FDSelect(file, parent.numGlyphs) 1130 return fdSelect 1131 1132 def write(self, parent, value): 1133 return 0 # dummy value 1134 1135 # The FDSelect glyph data is written out to XML in the charstring keys, 1136 # so we write out only the format selector 1137 def xmlWrite(self, xmlWriter, name, value, progress): 1138 xmlWriter.simpletag(name, [('format', value.format)]) 1139 xmlWriter.newline() 1140 1141 def xmlRead(self, name, attrs, content, parent): 1142 fmt = safeEval(attrs["format"]) 1143 file = None 1144 numGlyphs = None 1145 fdSelect = FDSelect(file, numGlyphs, fmt) 1146 return fdSelect 1147 1148 1149def packFDSelect0(fdSelectArray): 1150 fmt = 0 1151 data = [packCard8(fmt)] 1152 for index in fdSelectArray: 1153 data.append(packCard8(index)) 1154 return bytesjoin(data) 1155 1156 1157def packFDSelect3(fdSelectArray): 1158 fmt = 3 1159 fdRanges = [] 1160 first = None 1161 end = 0 1162 lenArray = len(fdSelectArray) 1163 lastFDIndex = -1 1164 for i in range(lenArray): 1165 fdIndex = fdSelectArray[i] 1166 if lastFDIndex != fdIndex: 1167 fdRanges.append([i, fdIndex]) 1168 lastFDIndex = fdIndex 1169 sentinelGID = i + 1 1170 1171 data = [packCard8(fmt)] 1172 data.append(packCard16( len(fdRanges) )) 1173 for fdRange in fdRanges: 1174 data.append(packCard16(fdRange[0])) 1175 data.append(packCard8(fdRange[1])) 1176 data.append(packCard16(sentinelGID)) 1177 return bytesjoin(data) 1178 1179 1180class FDSelectCompiler(object): 1181 1182 def __init__(self, fdSelect, parent): 1183 fmt = fdSelect.format 1184 fdSelectArray = fdSelect.gidArray 1185 if fmt == 0: 1186 self.data = packFDSelect0(fdSelectArray) 1187 elif fmt == 3: 1188 self.data = packFDSelect3(fdSelectArray) 1189 else: 1190 # choose smaller of the two formats 1191 data0 = packFDSelect0(fdSelectArray) 1192 data3 = packFDSelect3(fdSelectArray) 1193 if len(data0) < len(data3): 1194 self.data = data0 1195 fdSelect.format = 0 1196 else: 1197 self.data = data3 1198 fdSelect.format = 3 1199 1200 self.parent = parent 1201 1202 def setPos(self, pos, endPos): 1203 self.parent.rawDict["FDSelect"] = pos 1204 1205 def getDataLength(self): 1206 return len(self.data) 1207 1208 def toFile(self, file): 1209 file.write(self.data) 1210 1211 1212class ROSConverter(SimpleConverter): 1213 1214 def xmlWrite(self, xmlWriter, name, value, progress): 1215 registry, order, supplement = value 1216 xmlWriter.simpletag(name, [('Registry', tostr(registry)), ('Order', tostr(order)), 1217 ('Supplement', supplement)]) 1218 xmlWriter.newline() 1219 1220 def xmlRead(self, name, attrs, content, parent): 1221 return (attrs['Registry'], attrs['Order'], safeEval(attrs['Supplement'])) 1222 1223 1224 1225topDictOperators = [ 1226# opcode name argument type default converter 1227 ((12, 30), 'ROS', ('SID','SID','number'), None, ROSConverter()), 1228 ((12, 20), 'SyntheticBase', 'number', None, None), 1229 (0, 'version', 'SID', None, None), 1230 (1, 'Notice', 'SID', None, Latin1Converter()), 1231 ((12, 0), 'Copyright', 'SID', None, Latin1Converter()), 1232 (2, 'FullName', 'SID', None, None), 1233 ((12, 38), 'FontName', 'SID', None, None), 1234 (3, 'FamilyName', 'SID', None, None), 1235 (4, 'Weight', 'SID', None, None), 1236 ((12, 1), 'isFixedPitch', 'number', 0, None), 1237 ((12, 2), 'ItalicAngle', 'number', 0, None), 1238 ((12, 3), 'UnderlinePosition', 'number', None, None), 1239 ((12, 4), 'UnderlineThickness', 'number', 50, None), 1240 ((12, 5), 'PaintType', 'number', 0, None), 1241 ((12, 6), 'CharstringType', 'number', 2, None), 1242 ((12, 7), 'FontMatrix', 'array', [0.001,0,0,0.001,0,0], None), 1243 (13, 'UniqueID', 'number', None, None), 1244 (5, 'FontBBox', 'array', [0,0,0,0], None), 1245 ((12, 8), 'StrokeWidth', 'number', 0, None), 1246 (14, 'XUID', 'array', None, None), 1247 ((12, 21), 'PostScript', 'SID', None, None), 1248 ((12, 22), 'BaseFontName', 'SID', None, None), 1249 ((12, 23), 'BaseFontBlend', 'delta', None, None), 1250 ((12, 31), 'CIDFontVersion', 'number', 0, None), 1251 ((12, 32), 'CIDFontRevision', 'number', 0, None), 1252 ((12, 33), 'CIDFontType', 'number', 0, None), 1253 ((12, 34), 'CIDCount', 'number', 8720, None), 1254 (15, 'charset', 'number', 0, CharsetConverter()), 1255 ((12, 35), 'UIDBase', 'number', None, None), 1256 (16, 'Encoding', 'number', 0, EncodingConverter()), 1257 (18, 'Private', ('number','number'), None, PrivateDictConverter()), 1258 ((12, 37), 'FDSelect', 'number', None, FDSelectConverter()), 1259 ((12, 36), 'FDArray', 'number', None, FDArrayConverter()), 1260 (17, 'CharStrings', 'number', None, CharStringsConverter()), 1261] 1262 1263# Note! FDSelect and FDArray must both preceed CharStrings in the output XML build order, 1264# in order for the font to compile back from xml. 1265 1266 1267privateDictOperators = [ 1268# opcode name argument type default converter 1269 (6, 'BlueValues', 'delta', None, None), 1270 (7, 'OtherBlues', 'delta', None, None), 1271 (8, 'FamilyBlues', 'delta', None, None), 1272 (9, 'FamilyOtherBlues', 'delta', None, None), 1273 ((12, 9), 'BlueScale', 'number', 0.039625, None), 1274 ((12, 10), 'BlueShift', 'number', 7, None), 1275 ((12, 11), 'BlueFuzz', 'number', 1, None), 1276 (10, 'StdHW', 'number', None, None), 1277 (11, 'StdVW', 'number', None, None), 1278 ((12, 12), 'StemSnapH', 'delta', None, None), 1279 ((12, 13), 'StemSnapV', 'delta', None, None), 1280 ((12, 14), 'ForceBold', 'number', 0, None), 1281 ((12, 15), 'ForceBoldThreshold', 'number', None, None), # deprecated 1282 ((12, 16), 'lenIV', 'number', None, None), # deprecated 1283 ((12, 17), 'LanguageGroup', 'number', 0, None), 1284 ((12, 18), 'ExpansionFactor', 'number', 0.06, None), 1285 ((12, 19), 'initialRandomSeed', 'number', 0, None), 1286 (20, 'defaultWidthX', 'number', 0, None), 1287 (21, 'nominalWidthX', 'number', 0, None), 1288 (19, 'Subrs', 'number', None, SubrsConverter()), 1289] 1290 1291def addConverters(table): 1292 for i in range(len(table)): 1293 op, name, arg, default, conv = table[i] 1294 if conv is not None: 1295 continue 1296 if arg in ("delta", "array"): 1297 conv = ArrayConverter() 1298 elif arg == "number": 1299 conv = NumberConverter() 1300 elif arg == "SID": 1301 conv = ASCIIConverter() 1302 else: 1303 assert False 1304 table[i] = op, name, arg, default, conv 1305 1306addConverters(privateDictOperators) 1307addConverters(topDictOperators) 1308 1309 1310class TopDictDecompiler(psCharStrings.DictDecompiler): 1311 operators = buildOperatorDict(topDictOperators) 1312 1313 1314class PrivateDictDecompiler(psCharStrings.DictDecompiler): 1315 operators = buildOperatorDict(privateDictOperators) 1316 1317 1318class DictCompiler(object): 1319 1320 def __init__(self, dictObj, strings, parent): 1321 assert isinstance(strings, IndexedStrings) 1322 self.dictObj = dictObj 1323 self.strings = strings 1324 self.parent = parent 1325 rawDict = {} 1326 for name in dictObj.order: 1327 value = getattr(dictObj, name, None) 1328 if value is None: 1329 continue 1330 conv = dictObj.converters[name] 1331 value = conv.write(dictObj, value) 1332 if value == dictObj.defaults.get(name): 1333 continue 1334 rawDict[name] = value 1335 self.rawDict = rawDict 1336 1337 def setPos(self, pos, endPos): 1338 pass 1339 1340 def getDataLength(self): 1341 return len(self.compile("getDataLength")) 1342 1343 def compile(self, reason): 1344 if DEBUG: 1345 print("-- compiling %s for %s" % (self.__class__.__name__, reason)) 1346 print("in baseDict: ", self) 1347 rawDict = self.rawDict 1348 data = [] 1349 for name in self.dictObj.order: 1350 value = rawDict.get(name) 1351 if value is None: 1352 continue 1353 op, argType = self.opcodes[name] 1354 if isinstance(argType, tuple): 1355 l = len(argType) 1356 assert len(value) == l, "value doesn't match arg type" 1357 for i in range(l): 1358 arg = argType[i] 1359 v = value[i] 1360 arghandler = getattr(self, "arg_" + arg) 1361 data.append(arghandler(v)) 1362 else: 1363 arghandler = getattr(self, "arg_" + argType) 1364 data.append(arghandler(value)) 1365 data.append(op) 1366 return bytesjoin(data) 1367 1368 def toFile(self, file): 1369 file.write(self.compile("toFile")) 1370 1371 def arg_number(self, num): 1372 return encodeNumber(num) 1373 def arg_SID(self, s): 1374 return psCharStrings.encodeIntCFF(self.strings.getSID(s)) 1375 def arg_array(self, value): 1376 data = [] 1377 for num in value: 1378 data.append(encodeNumber(num)) 1379 return bytesjoin(data) 1380 def arg_delta(self, value): 1381 out = [] 1382 last = 0 1383 for v in value: 1384 out.append(v - last) 1385 last = v 1386 data = [] 1387 for num in out: 1388 data.append(encodeNumber(num)) 1389 return bytesjoin(data) 1390 1391 1392def encodeNumber(num): 1393 if isinstance(num, float): 1394 return psCharStrings.encodeFloat(num) 1395 else: 1396 return psCharStrings.encodeIntCFF(num) 1397 1398 1399class TopDictCompiler(DictCompiler): 1400 1401 opcodes = buildOpcodeDict(topDictOperators) 1402 1403 def getChildren(self, strings): 1404 children = [] 1405 if hasattr(self.dictObj, "charset") and self.dictObj.charset: 1406 children.append(CharsetCompiler(strings, self.dictObj.charset, self)) 1407 if hasattr(self.dictObj, "Encoding"): 1408 encoding = self.dictObj.Encoding 1409 if not isinstance(encoding, basestring): 1410 children.append(EncodingCompiler(strings, encoding, self)) 1411 if hasattr(self.dictObj, "FDSelect"): 1412 # I have not yet supported merging a ttx CFF-CID font, as there are interesting 1413 # issues about merging the FDArrays. Here I assume that 1414 # either the font was read from XML, and teh FDSelect indices are all 1415 # in the charstring data, or the FDSelect array is already fully defined. 1416 fdSelect = self.dictObj.FDSelect 1417 if len(fdSelect) == 0: # probably read in from XML; assume fdIndex in CharString data 1418 charStrings = self.dictObj.CharStrings 1419 for name in self.dictObj.charset: 1420 fdSelect.append(charStrings[name].fdSelectIndex) 1421 fdSelectComp = FDSelectCompiler(fdSelect, self) 1422 children.append(fdSelectComp) 1423 if hasattr(self.dictObj, "CharStrings"): 1424 items = [] 1425 charStrings = self.dictObj.CharStrings 1426 for name in self.dictObj.charset: 1427 items.append(charStrings[name]) 1428 charStringsComp = CharStringsCompiler(items, strings, self) 1429 children.append(charStringsComp) 1430 if hasattr(self.dictObj, "FDArray"): 1431 # I have not yet supported merging a ttx CFF-CID font, as there are interesting 1432 # issues about merging the FDArrays. Here I assume that the FDArray info is correct 1433 # and complete. 1434 fdArrayIndexComp = self.dictObj.FDArray.getCompiler(strings, self) 1435 children.append(fdArrayIndexComp) 1436 children.extend(fdArrayIndexComp.getChildren(strings)) 1437 if hasattr(self.dictObj, "Private"): 1438 privComp = self.dictObj.Private.getCompiler(strings, self) 1439 children.append(privComp) 1440 children.extend(privComp.getChildren(strings)) 1441 return children 1442 1443 1444class FontDictCompiler(DictCompiler): 1445 1446 opcodes = buildOpcodeDict(topDictOperators) 1447 1448 def getChildren(self, strings): 1449 children = [] 1450 if hasattr(self.dictObj, "Private"): 1451 privComp = self.dictObj.Private.getCompiler(strings, self) 1452 children.append(privComp) 1453 children.extend(privComp.getChildren(strings)) 1454 return children 1455 1456 1457class PrivateDictCompiler(DictCompiler): 1458 1459 opcodes = buildOpcodeDict(privateDictOperators) 1460 1461 def setPos(self, pos, endPos): 1462 size = endPos - pos 1463 self.parent.rawDict["Private"] = size, pos 1464 self.pos = pos 1465 1466 def getChildren(self, strings): 1467 children = [] 1468 if hasattr(self.dictObj, "Subrs"): 1469 children.append(self.dictObj.Subrs.getCompiler(strings, self)) 1470 return children 1471 1472 1473class BaseDict(object): 1474 1475 def __init__(self, strings=None, file=None, offset=None): 1476 self.rawDict = {} 1477 if DEBUG: 1478 print("loading %s at %s" % (self.__class__.__name__, offset)) 1479 self.file = file 1480 self.offset = offset 1481 self.strings = strings 1482 self.skipNames = [] 1483 1484 def decompile(self, data): 1485 if DEBUG: 1486 print(" length %s is %s" % (self.__class__.__name__, len(data))) 1487 dec = self.decompilerClass(self.strings) 1488 dec.decompile(data) 1489 self.rawDict = dec.getDict() 1490 self.postDecompile() 1491 1492 def postDecompile(self): 1493 pass 1494 1495 def getCompiler(self, strings, parent): 1496 return self.compilerClass(self, strings, parent) 1497 1498 def __getattr__(self, name): 1499 value = self.rawDict.get(name) 1500 if value is None: 1501 value = self.defaults.get(name) 1502 if value is None: 1503 raise AttributeError(name) 1504 conv = self.converters[name] 1505 value = conv.read(self, value) 1506 setattr(self, name, value) 1507 return value 1508 1509 def toXML(self, xmlWriter, progress): 1510 for name in self.order: 1511 if name in self.skipNames: 1512 continue 1513 value = getattr(self, name, None) 1514 if value is None: 1515 continue 1516 conv = self.converters[name] 1517 conv.xmlWrite(xmlWriter, name, value, progress) 1518 1519 def fromXML(self, name, attrs, content): 1520 conv = self.converters[name] 1521 value = conv.xmlRead(name, attrs, content, self) 1522 setattr(self, name, value) 1523 1524 1525class TopDict(BaseDict): 1526 1527 defaults = buildDefaults(topDictOperators) 1528 converters = buildConverters(topDictOperators) 1529 order = buildOrder(topDictOperators) 1530 decompilerClass = TopDictDecompiler 1531 compilerClass = TopDictCompiler 1532 1533 def __init__(self, strings=None, file=None, offset=None, GlobalSubrs=None): 1534 BaseDict.__init__(self, strings, file, offset) 1535 self.GlobalSubrs = GlobalSubrs 1536 1537 def getGlyphOrder(self): 1538 return self.charset 1539 1540 def postDecompile(self): 1541 offset = self.rawDict.get("CharStrings") 1542 if offset is None: 1543 return 1544 # get the number of glyphs beforehand. 1545 self.file.seek(offset) 1546 self.numGlyphs = readCard16(self.file) 1547 1548 def toXML(self, xmlWriter, progress): 1549 if hasattr(self, "CharStrings"): 1550 self.decompileAllCharStrings(progress) 1551 if hasattr(self, "ROS"): 1552 self.skipNames = ['Encoding'] 1553 if not hasattr(self, "ROS") or not hasattr(self, "CharStrings"): 1554 # these values have default values, but I only want them to show up 1555 # in CID fonts. 1556 self.skipNames = ['CIDFontVersion', 'CIDFontRevision', 'CIDFontType', 1557 'CIDCount'] 1558 BaseDict.toXML(self, xmlWriter, progress) 1559 1560 def decompileAllCharStrings(self, progress): 1561 # XXX only when doing ttdump -i? 1562 i = 0 1563 for charString in self.CharStrings.values(): 1564 try: 1565 charString.decompile() 1566 except: 1567 print("Error in charstring ", i) 1568 import sys 1569 typ, value = sys.exc_info()[0:2] 1570 raise typ(value) 1571 if not i % 30 and progress: 1572 progress.increment(0) # update 1573 i = i + 1 1574 1575 1576class FontDict(BaseDict): 1577 1578 defaults = buildDefaults(topDictOperators) 1579 converters = buildConverters(topDictOperators) 1580 order = buildOrder(topDictOperators) 1581 decompilerClass = None 1582 compilerClass = FontDictCompiler 1583 1584 def __init__(self, strings=None, file=None, offset=None, GlobalSubrs=None): 1585 BaseDict.__init__(self, strings, file, offset) 1586 self.GlobalSubrs = GlobalSubrs 1587 1588 def getGlyphOrder(self): 1589 return self.charset 1590 1591 def toXML(self, xmlWriter, progress): 1592 self.skipNames = ['Encoding'] 1593 BaseDict.toXML(self, xmlWriter, progress) 1594 1595 1596 1597class PrivateDict(BaseDict): 1598 defaults = buildDefaults(privateDictOperators) 1599 converters = buildConverters(privateDictOperators) 1600 order = buildOrder(privateDictOperators) 1601 decompilerClass = PrivateDictDecompiler 1602 compilerClass = PrivateDictCompiler 1603 1604 1605class IndexedStrings(object): 1606 1607 """SID -> string mapping.""" 1608 1609 def __init__(self, file=None): 1610 if file is None: 1611 strings = [] 1612 else: 1613 strings = [tostr(s, encoding="latin1") for s in Index(file)] 1614 self.strings = strings 1615 1616 def getCompiler(self): 1617 return IndexedStringsCompiler(self, None, None) 1618 1619 def __len__(self): 1620 return len(self.strings) 1621 1622 def __getitem__(self, SID): 1623 if SID < cffStandardStringCount: 1624 return cffStandardStrings[SID] 1625 else: 1626 return self.strings[SID - cffStandardStringCount] 1627 1628 def getSID(self, s): 1629 if not hasattr(self, "stringMapping"): 1630 self.buildStringMapping() 1631 if s in cffStandardStringMapping: 1632 SID = cffStandardStringMapping[s] 1633 elif s in self.stringMapping: 1634 SID = self.stringMapping[s] 1635 else: 1636 SID = len(self.strings) + cffStandardStringCount 1637 self.strings.append(s) 1638 self.stringMapping[s] = SID 1639 return SID 1640 1641 def getStrings(self): 1642 return self.strings 1643 1644 def buildStringMapping(self): 1645 self.stringMapping = {} 1646 for index in range(len(self.strings)): 1647 self.stringMapping[self.strings[index]] = index + cffStandardStringCount 1648 1649 1650# The 391 Standard Strings as used in the CFF format. 1651# from Adobe Technical None #5176, version 1.0, 18 March 1998 1652 1653cffStandardStrings = ['.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 1654 'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright', 1655 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 1656 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 1657 'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 1658 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 1659 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 1660 'bracketright', 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c', 1661 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 1662 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 1663 'asciitilde', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 1664 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft', 1665 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 1666 'daggerdbl', 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase', 1667 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', 1668 'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 1669 'dotaccent', 'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 1670 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae', 1671 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior', 1672 'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 1673 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters', 1674 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior', 1675 'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 1676 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave', 1677 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 1678 'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 1679 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron', 1680 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla', 1681 'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex', 1682 'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis', 1683 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave', 1684 'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall', 1685 'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall', 1686 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', 1687 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 1688 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 1689 'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior', 1690 'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 1691 'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior', 1692 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', 'parenleftinferior', 1693 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 1694 'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 1695 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 1696 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall', 1697 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', 1698 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 1699 'Dieresissmall', 'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall', 1700 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', 1701 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 1702 'onethird', 'twothirds', 'zerosuperior', 'foursuperior', 'fivesuperior', 1703 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 1704 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 1705 'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 1706 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall', 1707 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', 1708 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 1709 'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 1710 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', 1711 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 1712 'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 1713 'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', '001.001', '001.002', 1714 '001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman', 1715 'Semibold' 1716] 1717 1718cffStandardStringCount = 391 1719assert len(cffStandardStrings) == cffStandardStringCount 1720# build reverse mapping 1721cffStandardStringMapping = {} 1722for _i in range(cffStandardStringCount): 1723 cffStandardStringMapping[cffStandardStrings[_i]] = _i 1724 1725cffISOAdobeStrings = [".notdef", "space", "exclam", "quotedbl", "numbersign", 1726"dollar", "percent", "ampersand", "quoteright", "parenleft", "parenright", 1727"asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two", 1728"three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", 1729"less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", 1730"H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", 1731"X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum", 1732"underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", 1733"k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", 1734"braceleft", "bar", "braceright", "asciitilde", "exclamdown", "cent", 1735"sterling", "fraction", "yen", "florin", "section", "currency", "quotesingle", 1736"quotedblleft", "guillemotleft", "guilsinglleft", "guilsinglright", "fi", "fl", 1737"endash", "dagger", "daggerdbl", "periodcentered", "paragraph", "bullet", 1738"quotesinglbase", "quotedblbase", "quotedblright", "guillemotright", "ellipsis", 1739"perthousand", "questiondown", "grave", "acute", "circumflex", "tilde", 1740"macron", "breve", "dotaccent", "dieresis", "ring", "cedilla", "hungarumlaut", 1741"ogonek", "caron", "emdash", "AE", "ordfeminine", "Lslash", "Oslash", "OE", 1742"ordmasculine", "ae", "dotlessi", "lslash", "oslash", "oe", "germandbls", 1743"onesuperior", "logicalnot", "mu", "trademark", "Eth", "onehalf", "plusminus", 1744"Thorn", "onequarter", "divide", "brokenbar", "degree", "thorn", 1745"threequarters", "twosuperior", "registered", "minus", "eth", "multiply", 1746"threesuperior", "copyright", "Aacute", "Acircumflex", "Adieresis", "Agrave", 1747"Aring", "Atilde", "Ccedilla", "Eacute", "Ecircumflex", "Edieresis", "Egrave", 1748"Iacute", "Icircumflex", "Idieresis", "Igrave", "Ntilde", "Oacute", 1749"Ocircumflex", "Odieresis", "Ograve", "Otilde", "Scaron", "Uacute", 1750"Ucircumflex", "Udieresis", "Ugrave", "Yacute", "Ydieresis", "Zcaron", "aacute", 1751"acircumflex", "adieresis", "agrave", "aring", "atilde", "ccedilla", "eacute", 1752"ecircumflex", "edieresis", "egrave", "iacute", "icircumflex", "idieresis", 1753"igrave", "ntilde", "oacute", "ocircumflex", "odieresis", "ograve", "otilde", 1754"scaron", "uacute", "ucircumflex", "udieresis", "ugrave", "yacute", "ydieresis", 1755"zcaron"] 1756 1757cffISOAdobeStringCount = 229 1758assert len(cffISOAdobeStrings) == cffISOAdobeStringCount 1759 1760cffIExpertStrings = [".notdef", "space", "exclamsmall", "Hungarumlautsmall", 1761"dollaroldstyle", "dollarsuperior", "ampersandsmall", "Acutesmall", 1762"parenleftsuperior", "parenrightsuperior", "twodotenleader", "onedotenleader", 1763"comma", "hyphen", "period", "fraction", "zerooldstyle", "oneoldstyle", 1764"twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle", 1765"sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", "semicolon", 1766"commasuperior", "threequartersemdash", "periodsuperior", "questionsmall", 1767"asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior", 1768"lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior", 1769"tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior", 1770"parenrightinferior", "Circumflexsmall", "hyphensuperior", "Gravesmall", 1771"Asmall", "Bsmall", "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall", 1772"Ismall", "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall", 1773"Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall", "Xsmall", 1774"Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah", "Tildesmall", 1775"exclamdownsmall", "centoldstyle", "Lslashsmall", "Scaronsmall", "Zcaronsmall", 1776"Dieresissmall", "Brevesmall", "Caronsmall", "Dotaccentsmall", "Macronsmall", 1777"figuredash", "hypheninferior", "Ogoneksmall", "Ringsmall", "Cedillasmall", 1778"onequarter", "onehalf", "threequarters", "questiondownsmall", "oneeighth", 1779"threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds", 1780"zerosuperior", "onesuperior", "twosuperior", "threesuperior", "foursuperior", 1781"fivesuperior", "sixsuperior", "sevensuperior", "eightsuperior", "ninesuperior", 1782"zeroinferior", "oneinferior", "twoinferior", "threeinferior", "fourinferior", 1783"fiveinferior", "sixinferior", "seveninferior", "eightinferior", "nineinferior", 1784"centinferior", "dollarinferior", "periodinferior", "commainferior", 1785"Agravesmall", "Aacutesmall", "Acircumflexsmall", "Atildesmall", 1786"Adieresissmall", "Aringsmall", "AEsmall", "Ccedillasmall", "Egravesmall", 1787"Eacutesmall", "Ecircumflexsmall", "Edieresissmall", "Igravesmall", 1788"Iacutesmall", "Icircumflexsmall", "Idieresissmall", "Ethsmall", "Ntildesmall", 1789"Ogravesmall", "Oacutesmall", "Ocircumflexsmall", "Otildesmall", 1790"Odieresissmall", "OEsmall", "Oslashsmall", "Ugravesmall", "Uacutesmall", 1791"Ucircumflexsmall", "Udieresissmall", "Yacutesmall", "Thornsmall", 1792"Ydieresissmall"] 1793 1794cffExpertStringCount = 166 1795assert len(cffIExpertStrings) == cffExpertStringCount 1796 1797cffExpertSubsetStrings = [".notdef", "space", "dollaroldstyle", 1798"dollarsuperior", "parenleftsuperior", "parenrightsuperior", "twodotenleader", 1799"onedotenleader", "comma", "hyphen", "period", "fraction", "zerooldstyle", 1800"oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", 1801"sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", 1802"semicolon", "commasuperior", "threequartersemdash", "periodsuperior", 1803"asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior", 1804"lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior", 1805"tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior", 1806"parenrightinferior", "hyphensuperior", "colonmonetary", "onefitted", "rupiah", 1807"centoldstyle", "figuredash", "hypheninferior", "onequarter", "onehalf", 1808"threequarters", "oneeighth", "threeeighths", "fiveeighths", "seveneighths", 1809"onethird", "twothirds", "zerosuperior", "onesuperior", "twosuperior", 1810"threesuperior", "foursuperior", "fivesuperior", "sixsuperior", "sevensuperior", 1811"eightsuperior", "ninesuperior", "zeroinferior", "oneinferior", "twoinferior", 1812"threeinferior", "fourinferior", "fiveinferior", "sixinferior", "seveninferior", 1813"eightinferior", "nineinferior", "centinferior", "dollarinferior", 1814"periodinferior", "commainferior"] 1815 1816cffExpertSubsetStringCount = 87 1817assert len(cffExpertSubsetStrings) == cffExpertSubsetStringCount 1818