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