1"""cffLib: read/write Adobe CFF fonts 2 3OpenType fonts with PostScript outlines contain a completely independent 4font file, Adobe's *Compact Font Format*. So dealing with OpenType fonts 5requires also dealing with CFF. This module allows you to read and write 6fonts written in the CFF format. 7 8In 2016, OpenType 1.8 introduced the `CFF2 <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2>`_ 9format which, along with other changes, extended the CFF format to deal with 10the demands of variable fonts. This module parses both original CFF and CFF2. 11 12""" 13 14from fontTools.misc import sstruct 15from fontTools.misc import psCharStrings 16from fontTools.misc.arrayTools import unionRect, intRect 17from fontTools.misc.textTools import bytechr, byteord, bytesjoin, tobytes, tostr, safeEval 18from fontTools.ttLib import TTFont 19from fontTools.ttLib.tables.otBase import OTTableWriter 20from fontTools.ttLib.tables.otBase import OTTableReader 21from fontTools.ttLib.tables import otTables as ot 22from io import BytesIO 23import struct 24import logging 25import re 26 27# mute cffLib debug messages when running ttx in verbose mode 28DEBUG = logging.DEBUG - 1 29log = logging.getLogger(__name__) 30 31cffHeaderFormat = """ 32 major: B 33 minor: B 34 hdrSize: B 35""" 36 37maxStackLimit = 513 38# maxstack operator has been deprecated. max stack is now always 513. 39 40 41class StopHintCountEvent(Exception): 42 pass 43 44 45class _DesubroutinizingT2Decompiler(psCharStrings.SimpleT2Decompiler): 46 stop_hintcount_ops = ("op_hintmask", "op_cntrmask", "op_rmoveto", "op_hmoveto", 47 "op_vmoveto") 48 49 def __init__(self, localSubrs, globalSubrs, private=None): 50 psCharStrings.SimpleT2Decompiler.__init__(self, localSubrs, globalSubrs, 51 private) 52 53 def execute(self, charString): 54 self.need_hintcount = True # until proven otherwise 55 for op_name in self.stop_hintcount_ops: 56 setattr(self, op_name, self.stop_hint_count) 57 58 if hasattr(charString, '_desubroutinized'): 59 # If a charstring has already been desubroutinized, we will still 60 # need to execute it if we need to count hints in order to 61 # compute the byte length for mask arguments, and haven't finished 62 # counting hints pairs. 63 if self.need_hintcount and self.callingStack: 64 try: 65 psCharStrings.SimpleT2Decompiler.execute(self, charString) 66 except StopHintCountEvent: 67 del self.callingStack[-1] 68 return 69 70 charString._patches = [] 71 psCharStrings.SimpleT2Decompiler.execute(self, charString) 72 desubroutinized = charString.program[:] 73 for idx, expansion in reversed(charString._patches): 74 assert idx >= 2 75 assert desubroutinized[idx - 1] in ['callsubr', 'callgsubr'], desubroutinized[idx - 1] 76 assert type(desubroutinized[idx - 2]) == int 77 if expansion[-1] == 'return': 78 expansion = expansion[:-1] 79 desubroutinized[idx-2:idx] = expansion 80 if not self.private.in_cff2: 81 if 'endchar' in desubroutinized: 82 # Cut off after first endchar 83 desubroutinized = desubroutinized[:desubroutinized.index('endchar') + 1] 84 else: 85 if not len(desubroutinized) or desubroutinized[-1] != 'return': 86 desubroutinized.append('return') 87 88 charString._desubroutinized = desubroutinized 89 del charString._patches 90 91 def op_callsubr(self, index): 92 subr = self.localSubrs[self.operandStack[-1]+self.localBias] 93 psCharStrings.SimpleT2Decompiler.op_callsubr(self, index) 94 self.processSubr(index, subr) 95 96 def op_callgsubr(self, index): 97 subr = self.globalSubrs[self.operandStack[-1]+self.globalBias] 98 psCharStrings.SimpleT2Decompiler.op_callgsubr(self, index) 99 self.processSubr(index, subr) 100 101 def stop_hint_count(self, *args): 102 self.need_hintcount = False 103 for op_name in self.stop_hintcount_ops: 104 setattr(self, op_name, None) 105 cs = self.callingStack[-1] 106 if hasattr(cs, '_desubroutinized'): 107 raise StopHintCountEvent() 108 109 def op_hintmask(self, index): 110 psCharStrings.SimpleT2Decompiler.op_hintmask(self, index) 111 if self.need_hintcount: 112 self.stop_hint_count() 113 114 def processSubr(self, index, subr): 115 cs = self.callingStack[-1] 116 if not hasattr(cs, '_desubroutinized'): 117 cs._patches.append((index, subr._desubroutinized)) 118 119 120class CFFFontSet(object): 121 """A CFF font "file" can contain more than one font, although this is 122 extremely rare (and not allowed within OpenType fonts). 123 124 This class is the entry point for parsing a CFF table. To actually 125 manipulate the data inside the CFF font, you will want to access the 126 ``CFFFontSet``'s :class:`TopDict` object. To do this, a ``CFFFontSet`` 127 object can either be treated as a dictionary (with appropriate 128 ``keys()`` and ``values()`` methods) mapping font names to :class:`TopDict` 129 objects, or as a list. 130 131 .. code:: python 132 133 from fontTools import ttLib 134 tt = ttLib.TTFont("Tests/cffLib/data/LinLibertine_RBI.otf") 135 tt["CFF "].cff 136 # <fontTools.cffLib.CFFFontSet object at 0x101e24c90> 137 tt["CFF "].cff[0] # Here's your actual font data 138 # <fontTools.cffLib.TopDict object at 0x1020f1fd0> 139 140 """ 141 142 def decompile(self, file, otFont, isCFF2=None): 143 """Parse a binary CFF file into an internal representation. ``file`` 144 should be a file handle object. ``otFont`` is the top-level 145 :py:class:`fontTools.ttLib.ttFont.TTFont` object containing this CFF file. 146 147 If ``isCFF2`` is passed and set to ``True`` or ``False``, then the 148 library makes an assertion that the CFF header is of the appropriate 149 version. 150 """ 151 152 self.otFont = otFont 153 sstruct.unpack(cffHeaderFormat, file.read(3), self) 154 if isCFF2 is not None: 155 # called from ttLib: assert 'major' as read from file matches the 156 # expected version 157 expected_major = (2 if isCFF2 else 1) 158 if self.major != expected_major: 159 raise ValueError( 160 "Invalid CFF 'major' version: expected %d, found %d" % 161 (expected_major, self.major)) 162 else: 163 # use 'major' version from file to determine if isCFF2 164 assert self.major in (1, 2), "Unknown CFF format" 165 isCFF2 = self.major == 2 166 if not isCFF2: 167 self.offSize = struct.unpack("B", file.read(1))[0] 168 file.seek(self.hdrSize) 169 self.fontNames = list(tostr(s) for s in Index(file, isCFF2=isCFF2)) 170 self.topDictIndex = TopDictIndex(file, isCFF2=isCFF2) 171 self.strings = IndexedStrings(file) 172 else: # isCFF2 173 self.topDictSize = struct.unpack(">H", file.read(2))[0] 174 file.seek(self.hdrSize) 175 self.fontNames = ["CFF2Font"] 176 cff2GetGlyphOrder = otFont.getGlyphOrder 177 # in CFF2, offsetSize is the size of the TopDict data. 178 self.topDictIndex = TopDictIndex( 179 file, cff2GetGlyphOrder, self.topDictSize, isCFF2=isCFF2) 180 self.strings = None 181 self.GlobalSubrs = GlobalSubrsIndex(file, isCFF2=isCFF2) 182 self.topDictIndex.strings = self.strings 183 self.topDictIndex.GlobalSubrs = self.GlobalSubrs 184 185 def __len__(self): 186 return len(self.fontNames) 187 188 def keys(self): 189 return list(self.fontNames) 190 191 def values(self): 192 return self.topDictIndex 193 194 def __getitem__(self, nameOrIndex): 195 """ Return TopDict instance identified by name (str) or index (int 196 or any object that implements `__index__`). 197 """ 198 if hasattr(nameOrIndex, "__index__"): 199 index = nameOrIndex.__index__() 200 elif isinstance(nameOrIndex, str): 201 name = nameOrIndex 202 try: 203 index = self.fontNames.index(name) 204 except ValueError: 205 raise KeyError(nameOrIndex) 206 else: 207 raise TypeError(nameOrIndex) 208 return self.topDictIndex[index] 209 210 def compile(self, file, otFont, isCFF2=None): 211 """Write the object back into binary representation onto the given file. 212 ``file`` should be a file handle object. ``otFont`` is the top-level 213 :py:class:`fontTools.ttLib.ttFont.TTFont` object containing this CFF file. 214 215 If ``isCFF2`` is passed and set to ``True`` or ``False``, then the 216 library makes an assertion that the CFF header is of the appropriate 217 version. 218 """ 219 self.otFont = otFont 220 if isCFF2 is not None: 221 # called from ttLib: assert 'major' value matches expected version 222 expected_major = (2 if isCFF2 else 1) 223 if self.major != expected_major: 224 raise ValueError( 225 "Invalid CFF 'major' version: expected %d, found %d" % 226 (expected_major, self.major)) 227 else: 228 # use current 'major' value to determine output format 229 assert self.major in (1, 2), "Unknown CFF format" 230 isCFF2 = self.major == 2 231 232 if otFont.recalcBBoxes and not isCFF2: 233 for topDict in self.topDictIndex: 234 topDict.recalcFontBBox() 235 236 if not isCFF2: 237 strings = IndexedStrings() 238 else: 239 strings = None 240 writer = CFFWriter(isCFF2) 241 topCompiler = self.topDictIndex.getCompiler(strings, self, isCFF2=isCFF2) 242 if isCFF2: 243 self.hdrSize = 5 244 writer.add(sstruct.pack(cffHeaderFormat, self)) 245 # Note: topDictSize will most likely change in CFFWriter.toFile(). 246 self.topDictSize = topCompiler.getDataLength() 247 writer.add(struct.pack(">H", self.topDictSize)) 248 else: 249 self.hdrSize = 4 250 self.offSize = 4 # will most likely change in CFFWriter.toFile(). 251 writer.add(sstruct.pack(cffHeaderFormat, self)) 252 writer.add(struct.pack("B", self.offSize)) 253 if not isCFF2: 254 fontNames = Index() 255 for name in self.fontNames: 256 fontNames.append(name) 257 writer.add(fontNames.getCompiler(strings, self, isCFF2=isCFF2)) 258 writer.add(topCompiler) 259 if not isCFF2: 260 writer.add(strings.getCompiler()) 261 writer.add(self.GlobalSubrs.getCompiler(strings, self, isCFF2=isCFF2)) 262 263 for topDict in self.topDictIndex: 264 if not hasattr(topDict, "charset") or topDict.charset is None: 265 charset = otFont.getGlyphOrder() 266 topDict.charset = charset 267 children = topCompiler.getChildren(strings) 268 for child in children: 269 writer.add(child) 270 271 writer.toFile(file) 272 273 def toXML(self, xmlWriter): 274 """Write the object into XML representation onto the given 275 :class:`fontTools.misc.xmlWriter.XMLWriter`. 276 277 .. code:: python 278 279 writer = xmlWriter.XMLWriter(sys.stdout) 280 tt["CFF "].cff.toXML(writer) 281 282 """ 283 284 xmlWriter.simpletag("major", value=self.major) 285 xmlWriter.newline() 286 xmlWriter.simpletag("minor", value=self.minor) 287 xmlWriter.newline() 288 for fontName in self.fontNames: 289 xmlWriter.begintag("CFFFont", name=tostr(fontName)) 290 xmlWriter.newline() 291 font = self[fontName] 292 font.toXML(xmlWriter) 293 xmlWriter.endtag("CFFFont") 294 xmlWriter.newline() 295 xmlWriter.newline() 296 xmlWriter.begintag("GlobalSubrs") 297 xmlWriter.newline() 298 self.GlobalSubrs.toXML(xmlWriter) 299 xmlWriter.endtag("GlobalSubrs") 300 xmlWriter.newline() 301 302 def fromXML(self, name, attrs, content, otFont=None): 303 """Reads data from the XML element into the ``CFFFontSet`` object.""" 304 self.otFont = otFont 305 306 # set defaults. These will be replaced if there are entries for them 307 # in the XML file. 308 if not hasattr(self, "major"): 309 self.major = 1 310 if not hasattr(self, "minor"): 311 self.minor = 0 312 313 if name == "CFFFont": 314 if self.major == 1: 315 if not hasattr(self, "offSize"): 316 # this will be recalculated when the cff is compiled. 317 self.offSize = 4 318 if not hasattr(self, "hdrSize"): 319 self.hdrSize = 4 320 if not hasattr(self, "GlobalSubrs"): 321 self.GlobalSubrs = GlobalSubrsIndex() 322 if not hasattr(self, "fontNames"): 323 self.fontNames = [] 324 self.topDictIndex = TopDictIndex() 325 fontName = attrs["name"] 326 self.fontNames.append(fontName) 327 topDict = TopDict(GlobalSubrs=self.GlobalSubrs) 328 topDict.charset = None # gets filled in later 329 elif self.major == 2: 330 if not hasattr(self, "hdrSize"): 331 self.hdrSize = 5 332 if not hasattr(self, "GlobalSubrs"): 333 self.GlobalSubrs = GlobalSubrsIndex() 334 if not hasattr(self, "fontNames"): 335 self.fontNames = ["CFF2Font"] 336 cff2GetGlyphOrder = self.otFont.getGlyphOrder 337 topDict = TopDict( 338 GlobalSubrs=self.GlobalSubrs, 339 cff2GetGlyphOrder=cff2GetGlyphOrder) 340 self.topDictIndex = TopDictIndex(None, cff2GetGlyphOrder, None) 341 self.topDictIndex.append(topDict) 342 for element in content: 343 if isinstance(element, str): 344 continue 345 name, attrs, content = element 346 topDict.fromXML(name, attrs, content) 347 348 if hasattr(topDict, "VarStore") and topDict.FDArray[0].vstore is None: 349 fdArray = topDict.FDArray 350 for fontDict in fdArray: 351 if hasattr(fontDict, "Private"): 352 fontDict.Private.vstore = topDict.VarStore 353 354 elif name == "GlobalSubrs": 355 subrCharStringClass = psCharStrings.T2CharString 356 if not hasattr(self, "GlobalSubrs"): 357 self.GlobalSubrs = GlobalSubrsIndex() 358 for element in content: 359 if isinstance(element, str): 360 continue 361 name, attrs, content = element 362 subr = subrCharStringClass() 363 subr.fromXML(name, attrs, content) 364 self.GlobalSubrs.append(subr) 365 elif name == "major": 366 self.major = int(attrs['value']) 367 elif name == "minor": 368 self.minor = int(attrs['value']) 369 370 def convertCFFToCFF2(self, otFont): 371 """Converts this object from CFF format to CFF2 format. This conversion 372 is done 'in-place'. The conversion cannot be reversed. 373 374 This assumes a decompiled CFF table. (i.e. that the object has been 375 filled via :meth:`decompile`.)""" 376 self.major = 2 377 cff2GetGlyphOrder = self.otFont.getGlyphOrder 378 topDictData = TopDictIndex(None, cff2GetGlyphOrder, None) 379 topDictData.items = self.topDictIndex.items 380 self.topDictIndex = topDictData 381 topDict = topDictData[0] 382 if hasattr(topDict, 'Private'): 383 privateDict = topDict.Private 384 else: 385 privateDict = None 386 opOrder = buildOrder(topDictOperators2) 387 topDict.order = opOrder 388 topDict.cff2GetGlyphOrder = cff2GetGlyphOrder 389 for entry in topDictOperators: 390 key = entry[1] 391 if key not in opOrder: 392 if key in topDict.rawDict: 393 del topDict.rawDict[key] 394 if hasattr(topDict, key): 395 delattr(topDict, key) 396 397 if not hasattr(topDict, "FDArray"): 398 fdArray = topDict.FDArray = FDArrayIndex() 399 fdArray.strings = None 400 fdArray.GlobalSubrs = topDict.GlobalSubrs 401 topDict.GlobalSubrs.fdArray = fdArray 402 charStrings = topDict.CharStrings 403 if charStrings.charStringsAreIndexed: 404 charStrings.charStringsIndex.fdArray = fdArray 405 else: 406 charStrings.fdArray = fdArray 407 fontDict = FontDict() 408 fontDict.setCFF2(True) 409 fdArray.append(fontDict) 410 fontDict.Private = privateDict 411 privateOpOrder = buildOrder(privateDictOperators2) 412 for entry in privateDictOperators: 413 key = entry[1] 414 if key not in privateOpOrder: 415 if key in privateDict.rawDict: 416 # print "Removing private dict", key 417 del privateDict.rawDict[key] 418 if hasattr(privateDict, key): 419 delattr(privateDict, key) 420 # print "Removing privateDict attr", key 421 else: 422 # clean up the PrivateDicts in the fdArray 423 fdArray = topDict.FDArray 424 privateOpOrder = buildOrder(privateDictOperators2) 425 for fontDict in fdArray: 426 fontDict.setCFF2(True) 427 for key in fontDict.rawDict.keys(): 428 if key not in fontDict.order: 429 del fontDict.rawDict[key] 430 if hasattr(fontDict, key): 431 delattr(fontDict, key) 432 433 privateDict = fontDict.Private 434 for entry in privateDictOperators: 435 key = entry[1] 436 if key not in privateOpOrder: 437 if key in privateDict.rawDict: 438 # print "Removing private dict", key 439 del privateDict.rawDict[key] 440 if hasattr(privateDict, key): 441 delattr(privateDict, key) 442 # print "Removing privateDict attr", key 443 # At this point, the Subrs and Charstrings are all still T2Charstring class 444 # easiest to fix this by compiling, then decompiling again 445 file = BytesIO() 446 self.compile(file, otFont, isCFF2=True) 447 file.seek(0) 448 self.decompile(file, otFont, isCFF2=True) 449 450 def desubroutinize(self): 451 for fontName in self.fontNames: 452 font = self[fontName] 453 cs = font.CharStrings 454 for g in font.charset: 455 c, _ = cs.getItemAndSelector(g) 456 c.decompile() 457 subrs = getattr(c.private, "Subrs", []) 458 decompiler = _DesubroutinizingT2Decompiler(subrs, c.globalSubrs, c.private) 459 decompiler.execute(c) 460 c.program = c._desubroutinized 461 del c._desubroutinized 462 # Delete all the local subrs 463 if hasattr(font, 'FDArray'): 464 for fd in font.FDArray: 465 pd = fd.Private 466 if hasattr(pd, 'Subrs'): 467 del pd.Subrs 468 if 'Subrs' in pd.rawDict: 469 del pd.rawDict['Subrs'] 470 else: 471 pd = font.Private 472 if hasattr(pd, 'Subrs'): 473 del pd.Subrs 474 if 'Subrs' in pd.rawDict: 475 del pd.rawDict['Subrs'] 476 # as well as the global subrs 477 self.GlobalSubrs.clear() 478 479 480class CFFWriter(object): 481 """Helper class for serializing CFF data to binary. Used by 482 :meth:`CFFFontSet.compile`.""" 483 def __init__(self, isCFF2): 484 self.data = [] 485 self.isCFF2 = isCFF2 486 487 def add(self, table): 488 self.data.append(table) 489 490 def toFile(self, file): 491 lastPosList = None 492 count = 1 493 while True: 494 log.log(DEBUG, "CFFWriter.toFile() iteration: %d", count) 495 count = count + 1 496 pos = 0 497 posList = [pos] 498 for item in self.data: 499 if hasattr(item, "getDataLength"): 500 endPos = pos + item.getDataLength() 501 if isinstance(item, TopDictIndexCompiler) and item.isCFF2: 502 self.topDictSize = item.getDataLength() 503 else: 504 endPos = pos + len(item) 505 if hasattr(item, "setPos"): 506 item.setPos(pos, endPos) 507 pos = endPos 508 posList.append(pos) 509 if posList == lastPosList: 510 break 511 lastPosList = posList 512 log.log(DEBUG, "CFFWriter.toFile() writing to file.") 513 begin = file.tell() 514 if self.isCFF2: 515 self.data[1] = struct.pack(">H", self.topDictSize) 516 else: 517 self.offSize = calcOffSize(lastPosList[-1]) 518 self.data[1] = struct.pack("B", self.offSize) 519 posList = [0] 520 for item in self.data: 521 if hasattr(item, "toFile"): 522 item.toFile(file) 523 else: 524 file.write(item) 525 posList.append(file.tell() - begin) 526 assert posList == lastPosList 527 528 529def calcOffSize(largestOffset): 530 if largestOffset < 0x100: 531 offSize = 1 532 elif largestOffset < 0x10000: 533 offSize = 2 534 elif largestOffset < 0x1000000: 535 offSize = 3 536 else: 537 offSize = 4 538 return offSize 539 540 541class IndexCompiler(object): 542 """Base class for writing CFF `INDEX data <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#5-index-data>`_ 543 to binary.""" 544 545 def __init__(self, items, strings, parent, isCFF2=None): 546 if isCFF2 is None and hasattr(parent, "isCFF2"): 547 isCFF2 = parent.isCFF2 548 assert isCFF2 is not None 549 self.isCFF2 = isCFF2 550 self.items = self.getItems(items, strings) 551 self.parent = parent 552 553 def getItems(self, items, strings): 554 return items 555 556 def getOffsets(self): 557 # An empty INDEX contains only the count field. 558 if self.items: 559 pos = 1 560 offsets = [pos] 561 for item in self.items: 562 if hasattr(item, "getDataLength"): 563 pos = pos + item.getDataLength() 564 else: 565 pos = pos + len(item) 566 offsets.append(pos) 567 else: 568 offsets = [] 569 return offsets 570 571 def getDataLength(self): 572 if self.isCFF2: 573 countSize = 4 574 else: 575 countSize = 2 576 577 if self.items: 578 lastOffset = self.getOffsets()[-1] 579 offSize = calcOffSize(lastOffset) 580 dataLength = ( 581 countSize + # count 582 1 + # offSize 583 (len(self.items) + 1) * offSize + # the offsets 584 lastOffset - 1 # size of object data 585 ) 586 else: 587 # count. For empty INDEX tables, this is the only entry. 588 dataLength = countSize 589 590 return dataLength 591 592 def toFile(self, file): 593 offsets = self.getOffsets() 594 if self.isCFF2: 595 writeCard32(file, len(self.items)) 596 else: 597 writeCard16(file, len(self.items)) 598 # An empty INDEX contains only the count field. 599 if self.items: 600 offSize = calcOffSize(offsets[-1]) 601 writeCard8(file, offSize) 602 offSize = -offSize 603 pack = struct.pack 604 for offset in offsets: 605 binOffset = pack(">l", offset)[offSize:] 606 assert len(binOffset) == -offSize 607 file.write(binOffset) 608 for item in self.items: 609 if hasattr(item, "toFile"): 610 item.toFile(file) 611 else: 612 data = tobytes(item, encoding="latin1") 613 file.write(data) 614 615 616class IndexedStringsCompiler(IndexCompiler): 617 618 def getItems(self, items, strings): 619 return items.strings 620 621 622class TopDictIndexCompiler(IndexCompiler): 623 """Helper class for writing the TopDict to binary.""" 624 625 def getItems(self, items, strings): 626 out = [] 627 for item in items: 628 out.append(item.getCompiler(strings, self)) 629 return out 630 631 def getChildren(self, strings): 632 children = [] 633 for topDict in self.items: 634 children.extend(topDict.getChildren(strings)) 635 return children 636 637 def getOffsets(self): 638 if self.isCFF2: 639 offsets = [0, self.items[0].getDataLength()] 640 return offsets 641 else: 642 return super(TopDictIndexCompiler, self).getOffsets() 643 644 def getDataLength(self): 645 if self.isCFF2: 646 dataLength = self.items[0].getDataLength() 647 return dataLength 648 else: 649 return super(TopDictIndexCompiler, self).getDataLength() 650 651 def toFile(self, file): 652 if self.isCFF2: 653 self.items[0].toFile(file) 654 else: 655 super(TopDictIndexCompiler, self).toFile(file) 656 657 658class FDArrayIndexCompiler(IndexCompiler): 659 """Helper class for writing the 660 `Font DICT INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#10-font-dict-index-font-dicts-and-fdselect>`_ 661 to binary.""" 662 663 def getItems(self, items, strings): 664 out = [] 665 for item in items: 666 out.append(item.getCompiler(strings, self)) 667 return out 668 669 def getChildren(self, strings): 670 children = [] 671 for fontDict in self.items: 672 children.extend(fontDict.getChildren(strings)) 673 return children 674 675 def toFile(self, file): 676 offsets = self.getOffsets() 677 if self.isCFF2: 678 writeCard32(file, len(self.items)) 679 else: 680 writeCard16(file, len(self.items)) 681 offSize = calcOffSize(offsets[-1]) 682 writeCard8(file, offSize) 683 offSize = -offSize 684 pack = struct.pack 685 for offset in offsets: 686 binOffset = pack(">l", offset)[offSize:] 687 assert len(binOffset) == -offSize 688 file.write(binOffset) 689 for item in self.items: 690 if hasattr(item, "toFile"): 691 item.toFile(file) 692 else: 693 file.write(item) 694 695 def setPos(self, pos, endPos): 696 self.parent.rawDict["FDArray"] = pos 697 698 699class GlobalSubrsCompiler(IndexCompiler): 700 """Helper class for writing the `global subroutine INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#9-local-and-global-subr-indexes>`_ 701 to binary.""" 702 703 def getItems(self, items, strings): 704 out = [] 705 for cs in items: 706 cs.compile(self.isCFF2) 707 out.append(cs.bytecode) 708 return out 709 710 711class SubrsCompiler(GlobalSubrsCompiler): 712 """Helper class for writing the `local subroutine INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#9-local-and-global-subr-indexes>`_ 713 to binary.""" 714 715 def setPos(self, pos, endPos): 716 offset = pos - self.parent.pos 717 self.parent.rawDict["Subrs"] = offset 718 719 720class CharStringsCompiler(GlobalSubrsCompiler): 721 """Helper class for writing the `CharStrings INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#9-local-and-global-subr-indexes>`_ 722 to binary.""" 723 def getItems(self, items, strings): 724 out = [] 725 for cs in items: 726 cs.compile(self.isCFF2) 727 out.append(cs.bytecode) 728 return out 729 730 def setPos(self, pos, endPos): 731 self.parent.rawDict["CharStrings"] = pos 732 733 734class Index(object): 735 """This class represents what the CFF spec calls an INDEX (an array of 736 variable-sized objects). `Index` items can be addressed and set using 737 Python list indexing.""" 738 739 compilerClass = IndexCompiler 740 741 def __init__(self, file=None, isCFF2=None): 742 assert (isCFF2 is None) == (file is None) 743 self.items = [] 744 name = self.__class__.__name__ 745 if file is None: 746 return 747 self._isCFF2 = isCFF2 748 log.log(DEBUG, "loading %s at %s", name, file.tell()) 749 self.file = file 750 if isCFF2: 751 count = readCard32(file) 752 else: 753 count = readCard16(file) 754 if count == 0: 755 return 756 self.items = [None] * count 757 offSize = readCard8(file) 758 log.log(DEBUG, " index count: %s offSize: %s", count, offSize) 759 assert offSize <= 4, "offSize too large: %s" % offSize 760 self.offsets = offsets = [] 761 pad = b'\0' * (4 - offSize) 762 for index in range(count + 1): 763 chunk = file.read(offSize) 764 chunk = pad + chunk 765 offset, = struct.unpack(">L", chunk) 766 offsets.append(int(offset)) 767 self.offsetBase = file.tell() - 1 768 file.seek(self.offsetBase + offsets[-1]) # pretend we've read the whole lot 769 log.log(DEBUG, " end of %s at %s", name, file.tell()) 770 771 def __len__(self): 772 return len(self.items) 773 774 def __getitem__(self, index): 775 item = self.items[index] 776 if item is not None: 777 return item 778 offset = self.offsets[index] + self.offsetBase 779 size = self.offsets[index + 1] - self.offsets[index] 780 file = self.file 781 file.seek(offset) 782 data = file.read(size) 783 assert len(data) == size 784 item = self.produceItem(index, data, file, offset) 785 self.items[index] = item 786 return item 787 788 def __setitem__(self, index, item): 789 self.items[index] = item 790 791 def produceItem(self, index, data, file, offset): 792 return data 793 794 def append(self, item): 795 """Add an item to an INDEX.""" 796 self.items.append(item) 797 798 def getCompiler(self, strings, parent, isCFF2=None): 799 return self.compilerClass(self, strings, parent, isCFF2=isCFF2) 800 801 def clear(self): 802 """Empty the INDEX.""" 803 del self.items[:] 804 805 806class GlobalSubrsIndex(Index): 807 """This index contains all the global subroutines in the font. A global 808 subroutine is a set of ``CharString`` data which is accessible to any 809 glyph in the font, and are used to store repeated instructions - for 810 example, components may be encoded as global subroutines, but so could 811 hinting instructions. 812 813 Remember that when interpreting a ``callgsubr`` instruction (or indeed 814 a ``callsubr`` instruction) that you will need to add the "subroutine 815 number bias" to number given: 816 817 .. code:: python 818 819 tt = ttLib.TTFont("Almendra-Bold.otf") 820 u = tt["CFF "].cff[0].CharStrings["udieresis"] 821 u.decompile() 822 823 u.toXML(XMLWriter(sys.stdout)) 824 # <some stuff> 825 # -64 callgsubr <-- Subroutine which implements the dieresis mark 826 # <other stuff> 827 828 tt["CFF "].cff[0].GlobalSubrs[-64] # <-- WRONG 829 # <T2CharString (bytecode) at 103451d10> 830 831 tt["CFF "].cff[0].GlobalSubrs[-64 + 107] # <-- RIGHT 832 # <T2CharString (source) at 103451390> 833 834 ("The bias applied depends on the number of subrs (gsubrs). If the number of 835 subrs (gsubrs) is less than 1240, the bias is 107. Otherwise if it is less 836 than 33900, it is 1131; otherwise it is 32768.", 837 `Subroutine Operators <https://docs.microsoft.com/en-us/typography/opentype/otspec180/cff2charstr#section4.4>`) 838 """ 839 840 compilerClass = GlobalSubrsCompiler 841 subrClass = psCharStrings.T2CharString 842 charStringClass = psCharStrings.T2CharString 843 844 def __init__(self, file=None, globalSubrs=None, private=None, 845 fdSelect=None, fdArray=None, isCFF2=None): 846 super(GlobalSubrsIndex, self).__init__(file, isCFF2=isCFF2) 847 self.globalSubrs = globalSubrs 848 self.private = private 849 if fdSelect: 850 self.fdSelect = fdSelect 851 if fdArray: 852 self.fdArray = fdArray 853 854 def produceItem(self, index, data, file, offset): 855 if self.private is not None: 856 private = self.private 857 elif hasattr(self, 'fdArray') and self.fdArray is not None: 858 if hasattr(self, 'fdSelect') and self.fdSelect is not None: 859 fdIndex = self.fdSelect[index] 860 else: 861 fdIndex = 0 862 private = self.fdArray[fdIndex].Private 863 else: 864 private = None 865 return self.subrClass(data, private=private, globalSubrs=self.globalSubrs) 866 867 def toXML(self, xmlWriter): 868 """Write the subroutines index into XML representation onto the given 869 :class:`fontTools.misc.xmlWriter.XMLWriter`. 870 871 .. code:: python 872 873 writer = xmlWriter.XMLWriter(sys.stdout) 874 tt["CFF "].cff[0].GlobalSubrs.toXML(writer) 875 876 """ 877 xmlWriter.comment( 878 "The 'index' attribute is only for humans; " 879 "it is ignored when parsed.") 880 xmlWriter.newline() 881 for i in range(len(self)): 882 subr = self[i] 883 if subr.needsDecompilation(): 884 xmlWriter.begintag("CharString", index=i, raw=1) 885 else: 886 xmlWriter.begintag("CharString", index=i) 887 xmlWriter.newline() 888 subr.toXML(xmlWriter) 889 xmlWriter.endtag("CharString") 890 xmlWriter.newline() 891 892 def fromXML(self, name, attrs, content): 893 if name != "CharString": 894 return 895 subr = self.subrClass() 896 subr.fromXML(name, attrs, content) 897 self.append(subr) 898 899 def getItemAndSelector(self, index): 900 sel = None 901 if hasattr(self, 'fdSelect'): 902 sel = self.fdSelect[index] 903 return self[index], sel 904 905 906class SubrsIndex(GlobalSubrsIndex): 907 """This index contains a glyph's local subroutines. A local subroutine is a 908 private set of ``CharString`` data which is accessible only to the glyph to 909 which the index is attached.""" 910 911 compilerClass = SubrsCompiler 912 913 914class TopDictIndex(Index): 915 """This index represents the array of ``TopDict`` structures in the font 916 (again, usually only one entry is present). Hence the following calls are 917 equivalent: 918 919 .. code:: python 920 921 tt["CFF "].cff[0] 922 # <fontTools.cffLib.TopDict object at 0x102ed6e50> 923 tt["CFF "].cff.topDictIndex[0] 924 # <fontTools.cffLib.TopDict object at 0x102ed6e50> 925 926 """ 927 928 compilerClass = TopDictIndexCompiler 929 930 def __init__(self, file=None, cff2GetGlyphOrder=None, topSize=0, 931 isCFF2=None): 932 assert (isCFF2 is None) == (file is None) 933 self.cff2GetGlyphOrder = cff2GetGlyphOrder 934 if file is not None and isCFF2: 935 self._isCFF2 = isCFF2 936 self.items = [] 937 name = self.__class__.__name__ 938 log.log(DEBUG, "loading %s at %s", name, file.tell()) 939 self.file = file 940 count = 1 941 self.items = [None] * count 942 self.offsets = [0, topSize] 943 self.offsetBase = file.tell() 944 # pretend we've read the whole lot 945 file.seek(self.offsetBase + topSize) 946 log.log(DEBUG, " end of %s at %s", name, file.tell()) 947 else: 948 super(TopDictIndex, self).__init__(file, isCFF2=isCFF2) 949 950 def produceItem(self, index, data, file, offset): 951 top = TopDict( 952 self.strings, file, offset, self.GlobalSubrs, 953 self.cff2GetGlyphOrder, isCFF2=self._isCFF2) 954 top.decompile(data) 955 return top 956 957 def toXML(self, xmlWriter): 958 for i in range(len(self)): 959 xmlWriter.begintag("FontDict", index=i) 960 xmlWriter.newline() 961 self[i].toXML(xmlWriter) 962 xmlWriter.endtag("FontDict") 963 xmlWriter.newline() 964 965 966class FDArrayIndex(Index): 967 968 compilerClass = FDArrayIndexCompiler 969 970 def toXML(self, xmlWriter): 971 for i in range(len(self)): 972 xmlWriter.begintag("FontDict", index=i) 973 xmlWriter.newline() 974 self[i].toXML(xmlWriter) 975 xmlWriter.endtag("FontDict") 976 xmlWriter.newline() 977 978 def produceItem(self, index, data, file, offset): 979 fontDict = FontDict( 980 self.strings, file, offset, self.GlobalSubrs, isCFF2=self._isCFF2, 981 vstore=self.vstore) 982 fontDict.decompile(data) 983 return fontDict 984 985 def fromXML(self, name, attrs, content): 986 if name != "FontDict": 987 return 988 fontDict = FontDict() 989 for element in content: 990 if isinstance(element, str): 991 continue 992 name, attrs, content = element 993 fontDict.fromXML(name, attrs, content) 994 self.append(fontDict) 995 996 997class VarStoreData(object): 998 999 def __init__(self, file=None, otVarStore=None): 1000 self.file = file 1001 self.data = None 1002 self.otVarStore = otVarStore 1003 self.font = TTFont() # dummy font for the decompile function. 1004 1005 def decompile(self): 1006 if self.file: 1007 class GlobalState(object): 1008 def __init__(self, tableType, cachingStats): 1009 self.tableType = tableType 1010 self.cachingStats = cachingStats 1011 globalState = GlobalState(tableType="VarStore", cachingStats={}) 1012 # read data in from file. Assume position is correct. 1013 length = readCard16(self.file) 1014 self.data = self.file.read(length) 1015 globalState = {} 1016 reader = OTTableReader(self.data, globalState) 1017 self.otVarStore = ot.VarStore() 1018 self.otVarStore.decompile(reader, self.font) 1019 return self 1020 1021 def compile(self): 1022 writer = OTTableWriter() 1023 self.otVarStore.compile(writer, self.font) 1024 # Note that this omits the initial Card16 length from the CFF2 1025 # VarStore data block 1026 self.data = writer.getAllData() 1027 1028 def writeXML(self, xmlWriter, name): 1029 self.otVarStore.toXML(xmlWriter, self.font) 1030 1031 def xmlRead(self, name, attrs, content, parent): 1032 self.otVarStore = ot.VarStore() 1033 for element in content: 1034 if isinstance(element, tuple): 1035 name, attrs, content = element 1036 self.otVarStore.fromXML(name, attrs, content, self.font) 1037 else: 1038 pass 1039 return None 1040 1041 def __len__(self): 1042 return len(self.data) 1043 1044 def getNumRegions(self, vsIndex): 1045 varData = self.otVarStore.VarData[vsIndex] 1046 numRegions = varData.VarRegionCount 1047 return numRegions 1048 1049 1050class FDSelect(object): 1051 1052 def __init__(self, file=None, numGlyphs=None, format=None): 1053 if file: 1054 # read data in from file 1055 self.format = readCard8(file) 1056 if self.format == 0: 1057 from array import array 1058 self.gidArray = array("B", file.read(numGlyphs)).tolist() 1059 elif self.format == 3: 1060 gidArray = [None] * numGlyphs 1061 nRanges = readCard16(file) 1062 fd = None 1063 prev = None 1064 for i in range(nRanges): 1065 first = readCard16(file) 1066 if prev is not None: 1067 for glyphID in range(prev, first): 1068 gidArray[glyphID] = fd 1069 prev = first 1070 fd = readCard8(file) 1071 if prev is not None: 1072 first = readCard16(file) 1073 for glyphID in range(prev, first): 1074 gidArray[glyphID] = fd 1075 self.gidArray = gidArray 1076 elif self.format == 4: 1077 gidArray = [None] * numGlyphs 1078 nRanges = readCard32(file) 1079 fd = None 1080 prev = None 1081 for i in range(nRanges): 1082 first = readCard32(file) 1083 if prev is not None: 1084 for glyphID in range(prev, first): 1085 gidArray[glyphID] = fd 1086 prev = first 1087 fd = readCard16(file) 1088 if prev is not None: 1089 first = readCard32(file) 1090 for glyphID in range(prev, first): 1091 gidArray[glyphID] = fd 1092 self.gidArray = gidArray 1093 else: 1094 assert False, "unsupported FDSelect format: %s" % format 1095 else: 1096 # reading from XML. Make empty gidArray, and leave format as passed in. 1097 # format is None will result in the smallest representation being used. 1098 self.format = format 1099 self.gidArray = [] 1100 1101 def __len__(self): 1102 return len(self.gidArray) 1103 1104 def __getitem__(self, index): 1105 return self.gidArray[index] 1106 1107 def __setitem__(self, index, fdSelectValue): 1108 self.gidArray[index] = fdSelectValue 1109 1110 def append(self, fdSelectValue): 1111 self.gidArray.append(fdSelectValue) 1112 1113 1114class CharStrings(object): 1115 """The ``CharStrings`` in the font represent the instructions for drawing 1116 each glyph. This object presents a dictionary interface to the font's 1117 CharStrings, indexed by glyph name: 1118 1119 .. code:: python 1120 1121 tt["CFF "].cff[0].CharStrings["a"] 1122 # <T2CharString (bytecode) at 103451e90> 1123 1124 See :class:`fontTools.misc.psCharStrings.T1CharString` and 1125 :class:`fontTools.misc.psCharStrings.T2CharString` for how to decompile, 1126 compile and interpret the glyph drawing instructions in the returned objects. 1127 1128 """ 1129 1130 def __init__(self, file, charset, globalSubrs, private, fdSelect, fdArray, 1131 isCFF2=None): 1132 self.globalSubrs = globalSubrs 1133 if file is not None: 1134 self.charStringsIndex = SubrsIndex( 1135 file, globalSubrs, private, fdSelect, fdArray, isCFF2=isCFF2) 1136 self.charStrings = charStrings = {} 1137 for i in range(len(charset)): 1138 charStrings[charset[i]] = i 1139 # read from OTF file: charStrings.values() are indices into 1140 # charStringsIndex. 1141 self.charStringsAreIndexed = 1 1142 else: 1143 self.charStrings = {} 1144 # read from ttx file: charStrings.values() are actual charstrings 1145 self.charStringsAreIndexed = 0 1146 self.private = private 1147 if fdSelect is not None: 1148 self.fdSelect = fdSelect 1149 if fdArray is not None: 1150 self.fdArray = fdArray 1151 1152 def keys(self): 1153 return list(self.charStrings.keys()) 1154 1155 def values(self): 1156 if self.charStringsAreIndexed: 1157 return self.charStringsIndex 1158 else: 1159 return list(self.charStrings.values()) 1160 1161 def has_key(self, name): 1162 return name in self.charStrings 1163 1164 __contains__ = has_key 1165 1166 def __len__(self): 1167 return len(self.charStrings) 1168 1169 def __getitem__(self, name): 1170 charString = self.charStrings[name] 1171 if self.charStringsAreIndexed: 1172 charString = self.charStringsIndex[charString] 1173 return charString 1174 1175 def __setitem__(self, name, charString): 1176 if self.charStringsAreIndexed: 1177 index = self.charStrings[name] 1178 self.charStringsIndex[index] = charString 1179 else: 1180 self.charStrings[name] = charString 1181 1182 def getItemAndSelector(self, name): 1183 if self.charStringsAreIndexed: 1184 index = self.charStrings[name] 1185 return self.charStringsIndex.getItemAndSelector(index) 1186 else: 1187 if hasattr(self, 'fdArray'): 1188 if hasattr(self, 'fdSelect'): 1189 sel = self.charStrings[name].fdSelectIndex 1190 else: 1191 sel = 0 1192 else: 1193 sel = None 1194 return self.charStrings[name], sel 1195 1196 def toXML(self, xmlWriter): 1197 names = sorted(self.keys()) 1198 for name in names: 1199 charStr, fdSelectIndex = self.getItemAndSelector(name) 1200 if charStr.needsDecompilation(): 1201 raw = [("raw", 1)] 1202 else: 1203 raw = [] 1204 if fdSelectIndex is None: 1205 xmlWriter.begintag("CharString", [('name', name)] + raw) 1206 else: 1207 xmlWriter.begintag( 1208 "CharString", 1209 [('name', name), ('fdSelectIndex', fdSelectIndex)] + raw) 1210 xmlWriter.newline() 1211 charStr.toXML(xmlWriter) 1212 xmlWriter.endtag("CharString") 1213 xmlWriter.newline() 1214 1215 def fromXML(self, name, attrs, content): 1216 for element in content: 1217 if isinstance(element, str): 1218 continue 1219 name, attrs, content = element 1220 if name != "CharString": 1221 continue 1222 fdID = -1 1223 if hasattr(self, "fdArray"): 1224 try: 1225 fdID = safeEval(attrs["fdSelectIndex"]) 1226 except KeyError: 1227 fdID = 0 1228 private = self.fdArray[fdID].Private 1229 else: 1230 private = self.private 1231 1232 glyphName = attrs["name"] 1233 charStringClass = psCharStrings.T2CharString 1234 charString = charStringClass( 1235 private=private, 1236 globalSubrs=self.globalSubrs) 1237 charString.fromXML(name, attrs, content) 1238 if fdID >= 0: 1239 charString.fdSelectIndex = fdID 1240 self[glyphName] = charString 1241 1242 1243def readCard8(file): 1244 return byteord(file.read(1)) 1245 1246 1247def readCard16(file): 1248 value, = struct.unpack(">H", file.read(2)) 1249 return value 1250 1251 1252def readCard32(file): 1253 value, = struct.unpack(">L", file.read(4)) 1254 return value 1255 1256 1257def writeCard8(file, value): 1258 file.write(bytechr(value)) 1259 1260 1261def writeCard16(file, value): 1262 file.write(struct.pack(">H", value)) 1263 1264 1265def writeCard32(file, value): 1266 file.write(struct.pack(">L", value)) 1267 1268 1269def packCard8(value): 1270 return bytechr(value) 1271 1272 1273def packCard16(value): 1274 return struct.pack(">H", value) 1275 1276 1277def packCard32(value): 1278 return struct.pack(">L", value) 1279 1280 1281def buildOperatorDict(table): 1282 d = {} 1283 for op, name, arg, default, conv in table: 1284 d[op] = (name, arg) 1285 return d 1286 1287 1288def buildOpcodeDict(table): 1289 d = {} 1290 for op, name, arg, default, conv in table: 1291 if isinstance(op, tuple): 1292 op = bytechr(op[0]) + bytechr(op[1]) 1293 else: 1294 op = bytechr(op) 1295 d[name] = (op, arg) 1296 return d 1297 1298 1299def buildOrder(table): 1300 l = [] 1301 for op, name, arg, default, conv in table: 1302 l.append(name) 1303 return l 1304 1305 1306def buildDefaults(table): 1307 d = {} 1308 for op, name, arg, default, conv in table: 1309 if default is not None: 1310 d[name] = default 1311 return d 1312 1313 1314def buildConverters(table): 1315 d = {} 1316 for op, name, arg, default, conv in table: 1317 d[name] = conv 1318 return d 1319 1320 1321class SimpleConverter(object): 1322 1323 def read(self, parent, value): 1324 if not hasattr(parent, "file"): 1325 return self._read(parent, value) 1326 file = parent.file 1327 pos = file.tell() 1328 try: 1329 return self._read(parent, value) 1330 finally: 1331 file.seek(pos) 1332 1333 def _read(self, parent, value): 1334 return value 1335 1336 def write(self, parent, value): 1337 return value 1338 1339 def xmlWrite(self, xmlWriter, name, value): 1340 xmlWriter.simpletag(name, value=value) 1341 xmlWriter.newline() 1342 1343 def xmlRead(self, name, attrs, content, parent): 1344 return attrs["value"] 1345 1346 1347class ASCIIConverter(SimpleConverter): 1348 1349 def _read(self, parent, value): 1350 return tostr(value, encoding='ascii') 1351 1352 def write(self, parent, value): 1353 return tobytes(value, encoding='ascii') 1354 1355 def xmlWrite(self, xmlWriter, name, value): 1356 xmlWriter.simpletag(name, value=tostr(value, encoding="ascii")) 1357 xmlWriter.newline() 1358 1359 def xmlRead(self, name, attrs, content, parent): 1360 return tobytes(attrs["value"], encoding=("ascii")) 1361 1362 1363class Latin1Converter(SimpleConverter): 1364 1365 def _read(self, parent, value): 1366 return tostr(value, encoding='latin1') 1367 1368 def write(self, parent, value): 1369 return tobytes(value, encoding='latin1') 1370 1371 def xmlWrite(self, xmlWriter, name, value): 1372 value = tostr(value, encoding="latin1") 1373 if name in ['Notice', 'Copyright']: 1374 value = re.sub(r"[\r\n]\s+", " ", value) 1375 xmlWriter.simpletag(name, value=value) 1376 xmlWriter.newline() 1377 1378 def xmlRead(self, name, attrs, content, parent): 1379 return tobytes(attrs["value"], encoding=("latin1")) 1380 1381 1382def parseNum(s): 1383 try: 1384 value = int(s) 1385 except: 1386 value = float(s) 1387 return value 1388 1389 1390def parseBlendList(s): 1391 valueList = [] 1392 for element in s: 1393 if isinstance(element, str): 1394 continue 1395 name, attrs, content = element 1396 blendList = attrs["value"].split() 1397 blendList = [eval(val) for val in blendList] 1398 valueList.append(blendList) 1399 if len(valueList) == 1: 1400 valueList = valueList[0] 1401 return valueList 1402 1403 1404class NumberConverter(SimpleConverter): 1405 def xmlWrite(self, xmlWriter, name, value): 1406 if isinstance(value, list): 1407 xmlWriter.begintag(name) 1408 xmlWriter.newline() 1409 xmlWriter.indent() 1410 blendValue = " ".join([str(val) for val in value]) 1411 xmlWriter.simpletag(kBlendDictOpName, value=blendValue) 1412 xmlWriter.newline() 1413 xmlWriter.dedent() 1414 xmlWriter.endtag(name) 1415 xmlWriter.newline() 1416 else: 1417 xmlWriter.simpletag(name, value=value) 1418 xmlWriter.newline() 1419 1420 def xmlRead(self, name, attrs, content, parent): 1421 valueString = attrs.get("value", None) 1422 if valueString is None: 1423 value = parseBlendList(content) 1424 else: 1425 value = parseNum(attrs["value"]) 1426 return value 1427 1428 1429class ArrayConverter(SimpleConverter): 1430 def xmlWrite(self, xmlWriter, name, value): 1431 if value and isinstance(value[0], list): 1432 xmlWriter.begintag(name) 1433 xmlWriter.newline() 1434 xmlWriter.indent() 1435 for valueList in value: 1436 blendValue = " ".join([str(val) for val in valueList]) 1437 xmlWriter.simpletag(kBlendDictOpName, value=blendValue) 1438 xmlWriter.newline() 1439 xmlWriter.dedent() 1440 xmlWriter.endtag(name) 1441 xmlWriter.newline() 1442 else: 1443 value = " ".join([str(val) for val in value]) 1444 xmlWriter.simpletag(name, value=value) 1445 xmlWriter.newline() 1446 1447 def xmlRead(self, name, attrs, content, parent): 1448 valueString = attrs.get("value", None) 1449 if valueString is None: 1450 valueList = parseBlendList(content) 1451 else: 1452 values = valueString.split() 1453 valueList = [parseNum(value) for value in values] 1454 return valueList 1455 1456 1457class TableConverter(SimpleConverter): 1458 1459 def xmlWrite(self, xmlWriter, name, value): 1460 xmlWriter.begintag(name) 1461 xmlWriter.newline() 1462 value.toXML(xmlWriter) 1463 xmlWriter.endtag(name) 1464 xmlWriter.newline() 1465 1466 def xmlRead(self, name, attrs, content, parent): 1467 ob = self.getClass()() 1468 for element in content: 1469 if isinstance(element, str): 1470 continue 1471 name, attrs, content = element 1472 ob.fromXML(name, attrs, content) 1473 return ob 1474 1475 1476class PrivateDictConverter(TableConverter): 1477 1478 def getClass(self): 1479 return PrivateDict 1480 1481 def _read(self, parent, value): 1482 size, offset = value 1483 file = parent.file 1484 isCFF2 = parent._isCFF2 1485 try: 1486 vstore = parent.vstore 1487 except AttributeError: 1488 vstore = None 1489 priv = PrivateDict( 1490 parent.strings, file, offset, isCFF2=isCFF2, vstore=vstore) 1491 file.seek(offset) 1492 data = file.read(size) 1493 assert len(data) == size 1494 priv.decompile(data) 1495 return priv 1496 1497 def write(self, parent, value): 1498 return (0, 0) # dummy value 1499 1500 1501class SubrsConverter(TableConverter): 1502 1503 def getClass(self): 1504 return SubrsIndex 1505 1506 def _read(self, parent, value): 1507 file = parent.file 1508 isCFF2 = parent._isCFF2 1509 file.seek(parent.offset + value) # Offset(self) 1510 return SubrsIndex(file, isCFF2=isCFF2) 1511 1512 def write(self, parent, value): 1513 return 0 # dummy value 1514 1515 1516class CharStringsConverter(TableConverter): 1517 1518 def _read(self, parent, value): 1519 file = parent.file 1520 isCFF2 = parent._isCFF2 1521 charset = parent.charset 1522 globalSubrs = parent.GlobalSubrs 1523 if hasattr(parent, "FDArray"): 1524 fdArray = parent.FDArray 1525 if hasattr(parent, "FDSelect"): 1526 fdSelect = parent.FDSelect 1527 else: 1528 fdSelect = None 1529 private = None 1530 else: 1531 fdSelect, fdArray = None, None 1532 private = parent.Private 1533 file.seek(value) # Offset(0) 1534 charStrings = CharStrings( 1535 file, charset, globalSubrs, private, fdSelect, fdArray, isCFF2=isCFF2) 1536 return charStrings 1537 1538 def write(self, parent, value): 1539 return 0 # dummy value 1540 1541 def xmlRead(self, name, attrs, content, parent): 1542 if hasattr(parent, "FDArray"): 1543 # if it is a CID-keyed font, then the private Dict is extracted from the 1544 # parent.FDArray 1545 fdArray = parent.FDArray 1546 if hasattr(parent, "FDSelect"): 1547 fdSelect = parent.FDSelect 1548 else: 1549 fdSelect = None 1550 private = None 1551 else: 1552 # if it is a name-keyed font, then the private dict is in the top dict, 1553 # and 1554 # there is no fdArray. 1555 private, fdSelect, fdArray = parent.Private, None, None 1556 charStrings = CharStrings( 1557 None, None, parent.GlobalSubrs, private, fdSelect, fdArray) 1558 charStrings.fromXML(name, attrs, content) 1559 return charStrings 1560 1561 1562class CharsetConverter(SimpleConverter): 1563 def _read(self, parent, value): 1564 isCID = hasattr(parent, "ROS") 1565 if value > 2: 1566 numGlyphs = parent.numGlyphs 1567 file = parent.file 1568 file.seek(value) 1569 log.log(DEBUG, "loading charset at %s", value) 1570 format = readCard8(file) 1571 if format == 0: 1572 charset = parseCharset0(numGlyphs, file, parent.strings, isCID) 1573 elif format == 1 or format == 2: 1574 charset = parseCharset(numGlyphs, file, parent.strings, isCID, format) 1575 else: 1576 raise NotImplementedError 1577 assert len(charset) == numGlyphs 1578 log.log(DEBUG, " charset end at %s", file.tell()) 1579 # make sure glyph names are unique 1580 allNames = {} 1581 newCharset = [] 1582 for glyphName in charset: 1583 if glyphName in allNames: 1584 # make up a new glyphName that's unique 1585 n = allNames[glyphName] 1586 while (glyphName + "#" + str(n)) in allNames: 1587 n += 1 1588 allNames[glyphName] = n + 1 1589 glyphName = glyphName + "#" + str(n) 1590 allNames[glyphName] = 1 1591 newCharset.append(glyphName) 1592 charset = newCharset 1593 else: # offset == 0 -> no charset data. 1594 if isCID or "CharStrings" not in parent.rawDict: 1595 # We get here only when processing fontDicts from the FDArray of 1596 # CFF-CID fonts. Only the real topDict references the chrset. 1597 assert value == 0 1598 charset = None 1599 elif value == 0: 1600 charset = cffISOAdobeStrings 1601 elif value == 1: 1602 charset = cffIExpertStrings 1603 elif value == 2: 1604 charset = cffExpertSubsetStrings 1605 if charset and (len(charset) != parent.numGlyphs): 1606 charset = charset[:parent.numGlyphs] 1607 return charset 1608 1609 def write(self, parent, value): 1610 return 0 # dummy value 1611 1612 def xmlWrite(self, xmlWriter, name, value): 1613 # XXX only write charset when not in OT/TTX context, where we 1614 # dump charset as a separate "GlyphOrder" table. 1615 # # xmlWriter.simpletag("charset") 1616 xmlWriter.comment("charset is dumped separately as the 'GlyphOrder' element") 1617 xmlWriter.newline() 1618 1619 def xmlRead(self, name, attrs, content, parent): 1620 pass 1621 1622 1623class CharsetCompiler(object): 1624 1625 def __init__(self, strings, charset, parent): 1626 assert charset[0] == '.notdef' 1627 isCID = hasattr(parent.dictObj, "ROS") 1628 data0 = packCharset0(charset, isCID, strings) 1629 data = packCharset(charset, isCID, strings) 1630 if len(data) < len(data0): 1631 self.data = data 1632 else: 1633 self.data = data0 1634 self.parent = parent 1635 1636 def setPos(self, pos, endPos): 1637 self.parent.rawDict["charset"] = pos 1638 1639 def getDataLength(self): 1640 return len(self.data) 1641 1642 def toFile(self, file): 1643 file.write(self.data) 1644 1645 1646def getStdCharSet(charset): 1647 # check to see if we can use a predefined charset value. 1648 predefinedCharSetVal = None 1649 predefinedCharSets = [ 1650 (cffISOAdobeStringCount, cffISOAdobeStrings, 0), 1651 (cffExpertStringCount, cffIExpertStrings, 1), 1652 (cffExpertSubsetStringCount, cffExpertSubsetStrings, 2)] 1653 lcs = len(charset) 1654 for cnt, pcs, csv in predefinedCharSets: 1655 if predefinedCharSetVal is not None: 1656 break 1657 if lcs > cnt: 1658 continue 1659 predefinedCharSetVal = csv 1660 for i in range(lcs): 1661 if charset[i] != pcs[i]: 1662 predefinedCharSetVal = None 1663 break 1664 return predefinedCharSetVal 1665 1666 1667def getCIDfromName(name, strings): 1668 return int(name[3:]) 1669 1670 1671def getSIDfromName(name, strings): 1672 return strings.getSID(name) 1673 1674 1675def packCharset0(charset, isCID, strings): 1676 fmt = 0 1677 data = [packCard8(fmt)] 1678 if isCID: 1679 getNameID = getCIDfromName 1680 else: 1681 getNameID = getSIDfromName 1682 1683 for name in charset[1:]: 1684 data.append(packCard16(getNameID(name, strings))) 1685 return bytesjoin(data) 1686 1687 1688def packCharset(charset, isCID, strings): 1689 fmt = 1 1690 ranges = [] 1691 first = None 1692 end = 0 1693 if isCID: 1694 getNameID = getCIDfromName 1695 else: 1696 getNameID = getSIDfromName 1697 1698 for name in charset[1:]: 1699 SID = getNameID(name, strings) 1700 if first is None: 1701 first = SID 1702 elif end + 1 != SID: 1703 nLeft = end - first 1704 if nLeft > 255: 1705 fmt = 2 1706 ranges.append((first, nLeft)) 1707 first = SID 1708 end = SID 1709 if end: 1710 nLeft = end - first 1711 if nLeft > 255: 1712 fmt = 2 1713 ranges.append((first, nLeft)) 1714 1715 data = [packCard8(fmt)] 1716 if fmt == 1: 1717 nLeftFunc = packCard8 1718 else: 1719 nLeftFunc = packCard16 1720 for first, nLeft in ranges: 1721 data.append(packCard16(first) + nLeftFunc(nLeft)) 1722 return bytesjoin(data) 1723 1724 1725def parseCharset0(numGlyphs, file, strings, isCID): 1726 charset = [".notdef"] 1727 if isCID: 1728 for i in range(numGlyphs - 1): 1729 CID = readCard16(file) 1730 charset.append("cid" + str(CID).zfill(5)) 1731 else: 1732 for i in range(numGlyphs - 1): 1733 SID = readCard16(file) 1734 charset.append(strings[SID]) 1735 return charset 1736 1737 1738def parseCharset(numGlyphs, file, strings, isCID, fmt): 1739 charset = ['.notdef'] 1740 count = 1 1741 if fmt == 1: 1742 nLeftFunc = readCard8 1743 else: 1744 nLeftFunc = readCard16 1745 while count < numGlyphs: 1746 first = readCard16(file) 1747 nLeft = nLeftFunc(file) 1748 if isCID: 1749 for CID in range(first, first + nLeft + 1): 1750 charset.append("cid" + str(CID).zfill(5)) 1751 else: 1752 for SID in range(first, first + nLeft + 1): 1753 charset.append(strings[SID]) 1754 count = count + nLeft + 1 1755 return charset 1756 1757 1758class EncodingCompiler(object): 1759 1760 def __init__(self, strings, encoding, parent): 1761 assert not isinstance(encoding, str) 1762 data0 = packEncoding0(parent.dictObj.charset, encoding, parent.strings) 1763 data1 = packEncoding1(parent.dictObj.charset, encoding, parent.strings) 1764 if len(data0) < len(data1): 1765 self.data = data0 1766 else: 1767 self.data = data1 1768 self.parent = parent 1769 1770 def setPos(self, pos, endPos): 1771 self.parent.rawDict["Encoding"] = pos 1772 1773 def getDataLength(self): 1774 return len(self.data) 1775 1776 def toFile(self, file): 1777 file.write(self.data) 1778 1779 1780class EncodingConverter(SimpleConverter): 1781 1782 def _read(self, parent, value): 1783 if value == 0: 1784 return "StandardEncoding" 1785 elif value == 1: 1786 return "ExpertEncoding" 1787 else: 1788 assert value > 1 1789 file = parent.file 1790 file.seek(value) 1791 log.log(DEBUG, "loading Encoding at %s", value) 1792 fmt = readCard8(file) 1793 haveSupplement = fmt & 0x80 1794 if haveSupplement: 1795 raise NotImplementedError("Encoding supplements are not yet supported") 1796 fmt = fmt & 0x7f 1797 if fmt == 0: 1798 encoding = parseEncoding0(parent.charset, file, haveSupplement, 1799 parent.strings) 1800 elif fmt == 1: 1801 encoding = parseEncoding1(parent.charset, file, haveSupplement, 1802 parent.strings) 1803 return encoding 1804 1805 def write(self, parent, value): 1806 if value == "StandardEncoding": 1807 return 0 1808 elif value == "ExpertEncoding": 1809 return 1 1810 return 0 # dummy value 1811 1812 def xmlWrite(self, xmlWriter, name, value): 1813 if value in ("StandardEncoding", "ExpertEncoding"): 1814 xmlWriter.simpletag(name, name=value) 1815 xmlWriter.newline() 1816 return 1817 xmlWriter.begintag(name) 1818 xmlWriter.newline() 1819 for code in range(len(value)): 1820 glyphName = value[code] 1821 if glyphName != ".notdef": 1822 xmlWriter.simpletag("map", code=hex(code), name=glyphName) 1823 xmlWriter.newline() 1824 xmlWriter.endtag(name) 1825 xmlWriter.newline() 1826 1827 def xmlRead(self, name, attrs, content, parent): 1828 if "name" in attrs: 1829 return attrs["name"] 1830 encoding = [".notdef"] * 256 1831 for element in content: 1832 if isinstance(element, str): 1833 continue 1834 name, attrs, content = element 1835 code = safeEval(attrs["code"]) 1836 glyphName = attrs["name"] 1837 encoding[code] = glyphName 1838 return encoding 1839 1840 1841def parseEncoding0(charset, file, haveSupplement, strings): 1842 nCodes = readCard8(file) 1843 encoding = [".notdef"] * 256 1844 for glyphID in range(1, nCodes + 1): 1845 code = readCard8(file) 1846 if code != 0: 1847 encoding[code] = charset[glyphID] 1848 return encoding 1849 1850 1851def parseEncoding1(charset, file, haveSupplement, strings): 1852 nRanges = readCard8(file) 1853 encoding = [".notdef"] * 256 1854 glyphID = 1 1855 for i in range(nRanges): 1856 code = readCard8(file) 1857 nLeft = readCard8(file) 1858 for glyphID in range(glyphID, glyphID + nLeft + 1): 1859 encoding[code] = charset[glyphID] 1860 code = code + 1 1861 glyphID = glyphID + 1 1862 return encoding 1863 1864 1865def packEncoding0(charset, encoding, strings): 1866 fmt = 0 1867 m = {} 1868 for code in range(len(encoding)): 1869 name = encoding[code] 1870 if name != ".notdef": 1871 m[name] = code 1872 codes = [] 1873 for name in charset[1:]: 1874 code = m.get(name) 1875 codes.append(code) 1876 1877 while codes and codes[-1] is None: 1878 codes.pop() 1879 1880 data = [packCard8(fmt), packCard8(len(codes))] 1881 for code in codes: 1882 if code is None: 1883 code = 0 1884 data.append(packCard8(code)) 1885 return bytesjoin(data) 1886 1887 1888def packEncoding1(charset, encoding, strings): 1889 fmt = 1 1890 m = {} 1891 for code in range(len(encoding)): 1892 name = encoding[code] 1893 if name != ".notdef": 1894 m[name] = code 1895 ranges = [] 1896 first = None 1897 end = 0 1898 for name in charset[1:]: 1899 code = m.get(name, -1) 1900 if first is None: 1901 first = code 1902 elif end + 1 != code: 1903 nLeft = end - first 1904 ranges.append((first, nLeft)) 1905 first = code 1906 end = code 1907 nLeft = end - first 1908 ranges.append((first, nLeft)) 1909 1910 # remove unencoded glyphs at the end. 1911 while ranges and ranges[-1][0] == -1: 1912 ranges.pop() 1913 1914 data = [packCard8(fmt), packCard8(len(ranges))] 1915 for first, nLeft in ranges: 1916 if first == -1: # unencoded 1917 first = 0 1918 data.append(packCard8(first) + packCard8(nLeft)) 1919 return bytesjoin(data) 1920 1921 1922class FDArrayConverter(TableConverter): 1923 1924 def _read(self, parent, value): 1925 try: 1926 vstore = parent.VarStore 1927 except AttributeError: 1928 vstore = None 1929 file = parent.file 1930 isCFF2 = parent._isCFF2 1931 file.seek(value) 1932 fdArray = FDArrayIndex(file, isCFF2=isCFF2) 1933 fdArray.vstore = vstore 1934 fdArray.strings = parent.strings 1935 fdArray.GlobalSubrs = parent.GlobalSubrs 1936 return fdArray 1937 1938 def write(self, parent, value): 1939 return 0 # dummy value 1940 1941 def xmlRead(self, name, attrs, content, parent): 1942 fdArray = FDArrayIndex() 1943 for element in content: 1944 if isinstance(element, str): 1945 continue 1946 name, attrs, content = element 1947 fdArray.fromXML(name, attrs, content) 1948 return fdArray 1949 1950 1951class FDSelectConverter(SimpleConverter): 1952 1953 def _read(self, parent, value): 1954 file = parent.file 1955 file.seek(value) 1956 fdSelect = FDSelect(file, parent.numGlyphs) 1957 return fdSelect 1958 1959 def write(self, parent, value): 1960 return 0 # dummy value 1961 1962 # The FDSelect glyph data is written out to XML in the charstring keys, 1963 # so we write out only the format selector 1964 def xmlWrite(self, xmlWriter, name, value): 1965 xmlWriter.simpletag(name, [('format', value.format)]) 1966 xmlWriter.newline() 1967 1968 def xmlRead(self, name, attrs, content, parent): 1969 fmt = safeEval(attrs["format"]) 1970 file = None 1971 numGlyphs = None 1972 fdSelect = FDSelect(file, numGlyphs, fmt) 1973 return fdSelect 1974 1975 1976class VarStoreConverter(SimpleConverter): 1977 1978 def _read(self, parent, value): 1979 file = parent.file 1980 file.seek(value) 1981 varStore = VarStoreData(file) 1982 varStore.decompile() 1983 return varStore 1984 1985 def write(self, parent, value): 1986 return 0 # dummy value 1987 1988 def xmlWrite(self, xmlWriter, name, value): 1989 value.writeXML(xmlWriter, name) 1990 1991 def xmlRead(self, name, attrs, content, parent): 1992 varStore = VarStoreData() 1993 varStore.xmlRead(name, attrs, content, parent) 1994 return varStore 1995 1996 1997def packFDSelect0(fdSelectArray): 1998 fmt = 0 1999 data = [packCard8(fmt)] 2000 for index in fdSelectArray: 2001 data.append(packCard8(index)) 2002 return bytesjoin(data) 2003 2004 2005def packFDSelect3(fdSelectArray): 2006 fmt = 3 2007 fdRanges = [] 2008 lenArray = len(fdSelectArray) 2009 lastFDIndex = -1 2010 for i in range(lenArray): 2011 fdIndex = fdSelectArray[i] 2012 if lastFDIndex != fdIndex: 2013 fdRanges.append([i, fdIndex]) 2014 lastFDIndex = fdIndex 2015 sentinelGID = i + 1 2016 2017 data = [packCard8(fmt)] 2018 data.append(packCard16(len(fdRanges))) 2019 for fdRange in fdRanges: 2020 data.append(packCard16(fdRange[0])) 2021 data.append(packCard8(fdRange[1])) 2022 data.append(packCard16(sentinelGID)) 2023 return bytesjoin(data) 2024 2025 2026def packFDSelect4(fdSelectArray): 2027 fmt = 4 2028 fdRanges = [] 2029 lenArray = len(fdSelectArray) 2030 lastFDIndex = -1 2031 for i in range(lenArray): 2032 fdIndex = fdSelectArray[i] 2033 if lastFDIndex != fdIndex: 2034 fdRanges.append([i, fdIndex]) 2035 lastFDIndex = fdIndex 2036 sentinelGID = i + 1 2037 2038 data = [packCard8(fmt)] 2039 data.append(packCard32(len(fdRanges))) 2040 for fdRange in fdRanges: 2041 data.append(packCard32(fdRange[0])) 2042 data.append(packCard16(fdRange[1])) 2043 data.append(packCard32(sentinelGID)) 2044 return bytesjoin(data) 2045 2046 2047class FDSelectCompiler(object): 2048 2049 def __init__(self, fdSelect, parent): 2050 fmt = fdSelect.format 2051 fdSelectArray = fdSelect.gidArray 2052 if fmt == 0: 2053 self.data = packFDSelect0(fdSelectArray) 2054 elif fmt == 3: 2055 self.data = packFDSelect3(fdSelectArray) 2056 elif fmt == 4: 2057 self.data = packFDSelect4(fdSelectArray) 2058 else: 2059 # choose smaller of the two formats 2060 data0 = packFDSelect0(fdSelectArray) 2061 data3 = packFDSelect3(fdSelectArray) 2062 if len(data0) < len(data3): 2063 self.data = data0 2064 fdSelect.format = 0 2065 else: 2066 self.data = data3 2067 fdSelect.format = 3 2068 2069 self.parent = parent 2070 2071 def setPos(self, pos, endPos): 2072 self.parent.rawDict["FDSelect"] = pos 2073 2074 def getDataLength(self): 2075 return len(self.data) 2076 2077 def toFile(self, file): 2078 file.write(self.data) 2079 2080 2081class VarStoreCompiler(object): 2082 2083 def __init__(self, varStoreData, parent): 2084 self.parent = parent 2085 if not varStoreData.data: 2086 varStoreData.compile() 2087 data = [ 2088 packCard16(len(varStoreData.data)), 2089 varStoreData.data 2090 ] 2091 self.data = bytesjoin(data) 2092 2093 def setPos(self, pos, endPos): 2094 self.parent.rawDict["VarStore"] = pos 2095 2096 def getDataLength(self): 2097 return len(self.data) 2098 2099 def toFile(self, file): 2100 file.write(self.data) 2101 2102 2103class ROSConverter(SimpleConverter): 2104 2105 def xmlWrite(self, xmlWriter, name, value): 2106 registry, order, supplement = value 2107 xmlWriter.simpletag( 2108 name, 2109 [ 2110 ('Registry', tostr(registry)), 2111 ('Order', tostr(order)), 2112 ('Supplement', supplement) 2113 ]) 2114 xmlWriter.newline() 2115 2116 def xmlRead(self, name, attrs, content, parent): 2117 return (attrs['Registry'], attrs['Order'], safeEval(attrs['Supplement'])) 2118 2119topDictOperators = [ 2120# opcode name argument type default converter 2121 (25, 'maxstack', 'number', None, None), 2122 ((12, 30), 'ROS', ('SID', 'SID', 'number'), None, ROSConverter()), 2123 ((12, 20), 'SyntheticBase', 'number', None, None), 2124 (0, 'version', 'SID', None, None), 2125 (1, 'Notice', 'SID', None, Latin1Converter()), 2126 ((12, 0), 'Copyright', 'SID', None, Latin1Converter()), 2127 (2, 'FullName', 'SID', None, None), 2128 ((12, 38), 'FontName', 'SID', None, None), 2129 (3, 'FamilyName', 'SID', None, None), 2130 (4, 'Weight', 'SID', None, None), 2131 ((12, 1), 'isFixedPitch', 'number', 0, None), 2132 ((12, 2), 'ItalicAngle', 'number', 0, None), 2133 ((12, 3), 'UnderlinePosition', 'number', -100, None), 2134 ((12, 4), 'UnderlineThickness', 'number', 50, None), 2135 ((12, 5), 'PaintType', 'number', 0, None), 2136 ((12, 6), 'CharstringType', 'number', 2, None), 2137 ((12, 7), 'FontMatrix', 'array', [0.001, 0, 0, 0.001, 0, 0], None), 2138 (13, 'UniqueID', 'number', None, None), 2139 (5, 'FontBBox', 'array', [0, 0, 0, 0], None), 2140 ((12, 8), 'StrokeWidth', 'number', 0, None), 2141 (14, 'XUID', 'array', None, None), 2142 ((12, 21), 'PostScript', 'SID', None, None), 2143 ((12, 22), 'BaseFontName', 'SID', None, None), 2144 ((12, 23), 'BaseFontBlend', 'delta', None, None), 2145 ((12, 31), 'CIDFontVersion', 'number', 0, None), 2146 ((12, 32), 'CIDFontRevision', 'number', 0, None), 2147 ((12, 33), 'CIDFontType', 'number', 0, None), 2148 ((12, 34), 'CIDCount', 'number', 8720, None), 2149 (15, 'charset', 'number', None, CharsetConverter()), 2150 ((12, 35), 'UIDBase', 'number', None, None), 2151 (16, 'Encoding', 'number', 0, EncodingConverter()), 2152 (18, 'Private', ('number', 'number'), None, PrivateDictConverter()), 2153 ((12, 37), 'FDSelect', 'number', None, FDSelectConverter()), 2154 ((12, 36), 'FDArray', 'number', None, FDArrayConverter()), 2155 (17, 'CharStrings', 'number', None, CharStringsConverter()), 2156 (24, 'VarStore', 'number', None, VarStoreConverter()), 2157] 2158 2159topDictOperators2 = [ 2160# opcode name argument type default converter 2161 (25, 'maxstack', 'number', None, None), 2162 ((12, 7), 'FontMatrix', 'array', [0.001, 0, 0, 0.001, 0, 0], None), 2163 ((12, 37), 'FDSelect', 'number', None, FDSelectConverter()), 2164 ((12, 36), 'FDArray', 'number', None, FDArrayConverter()), 2165 (17, 'CharStrings', 'number', None, CharStringsConverter()), 2166 (24, 'VarStore', 'number', None, VarStoreConverter()), 2167] 2168 2169# Note! FDSelect and FDArray must both preceed CharStrings in the output XML build order, 2170# in order for the font to compile back from xml. 2171 2172kBlendDictOpName = "blend" 2173blendOp = 23 2174 2175privateDictOperators = [ 2176# opcode name argument type default converter 2177 (22, "vsindex", 'number', None, None), 2178 (blendOp, kBlendDictOpName, 'blendList', None, None), # This is for reading to/from XML: it not written to CFF. 2179 (6, 'BlueValues', 'delta', None, None), 2180 (7, 'OtherBlues', 'delta', None, None), 2181 (8, 'FamilyBlues', 'delta', None, None), 2182 (9, 'FamilyOtherBlues', 'delta', None, None), 2183 ((12, 9), 'BlueScale', 'number', 0.039625, None), 2184 ((12, 10), 'BlueShift', 'number', 7, None), 2185 ((12, 11), 'BlueFuzz', 'number', 1, None), 2186 (10, 'StdHW', 'number', None, None), 2187 (11, 'StdVW', 'number', None, None), 2188 ((12, 12), 'StemSnapH', 'delta', None, None), 2189 ((12, 13), 'StemSnapV', 'delta', None, None), 2190 ((12, 14), 'ForceBold', 'number', 0, None), 2191 ((12, 15), 'ForceBoldThreshold', 'number', None, None), # deprecated 2192 ((12, 16), 'lenIV', 'number', None, None), # deprecated 2193 ((12, 17), 'LanguageGroup', 'number', 0, None), 2194 ((12, 18), 'ExpansionFactor', 'number', 0.06, None), 2195 ((12, 19), 'initialRandomSeed', 'number', 0, None), 2196 (20, 'defaultWidthX', 'number', 0, None), 2197 (21, 'nominalWidthX', 'number', 0, None), 2198 (19, 'Subrs', 'number', None, SubrsConverter()), 2199] 2200 2201privateDictOperators2 = [ 2202# opcode name argument type default converter 2203 (22, "vsindex", 'number', None, None), 2204 (blendOp, kBlendDictOpName, 'blendList', None, None), # This is for reading to/from XML: it not written to CFF. 2205 (6, 'BlueValues', 'delta', None, None), 2206 (7, 'OtherBlues', 'delta', None, None), 2207 (8, 'FamilyBlues', 'delta', None, None), 2208 (9, 'FamilyOtherBlues', 'delta', None, None), 2209 ((12, 9), 'BlueScale', 'number', 0.039625, None), 2210 ((12, 10), 'BlueShift', 'number', 7, None), 2211 ((12, 11), 'BlueFuzz', 'number', 1, None), 2212 (10, 'StdHW', 'number', None, None), 2213 (11, 'StdVW', 'number', None, None), 2214 ((12, 12), 'StemSnapH', 'delta', None, None), 2215 ((12, 13), 'StemSnapV', 'delta', None, None), 2216 ((12, 17), 'LanguageGroup', 'number', 0, None), 2217 ((12, 18), 'ExpansionFactor', 'number', 0.06, None), 2218 (19, 'Subrs', 'number', None, SubrsConverter()), 2219] 2220 2221 2222def addConverters(table): 2223 for i in range(len(table)): 2224 op, name, arg, default, conv = table[i] 2225 if conv is not None: 2226 continue 2227 if arg in ("delta", "array"): 2228 conv = ArrayConverter() 2229 elif arg == "number": 2230 conv = NumberConverter() 2231 elif arg == "SID": 2232 conv = ASCIIConverter() 2233 elif arg == 'blendList': 2234 conv = None 2235 else: 2236 assert False 2237 table[i] = op, name, arg, default, conv 2238 2239 2240addConverters(privateDictOperators) 2241addConverters(topDictOperators) 2242 2243 2244class TopDictDecompiler(psCharStrings.DictDecompiler): 2245 operators = buildOperatorDict(topDictOperators) 2246 2247 2248class PrivateDictDecompiler(psCharStrings.DictDecompiler): 2249 operators = buildOperatorDict(privateDictOperators) 2250 2251 2252class DictCompiler(object): 2253 maxBlendStack = 0 2254 2255 def __init__(self, dictObj, strings, parent, isCFF2=None): 2256 if strings: 2257 assert isinstance(strings, IndexedStrings) 2258 if isCFF2 is None and hasattr(parent, "isCFF2"): 2259 isCFF2 = parent.isCFF2 2260 assert isCFF2 is not None 2261 self.isCFF2 = isCFF2 2262 self.dictObj = dictObj 2263 self.strings = strings 2264 self.parent = parent 2265 rawDict = {} 2266 for name in dictObj.order: 2267 value = getattr(dictObj, name, None) 2268 if value is None: 2269 continue 2270 conv = dictObj.converters[name] 2271 value = conv.write(dictObj, value) 2272 if value == dictObj.defaults.get(name): 2273 continue 2274 rawDict[name] = value 2275 self.rawDict = rawDict 2276 2277 def setPos(self, pos, endPos): 2278 pass 2279 2280 def getDataLength(self): 2281 return len(self.compile("getDataLength")) 2282 2283 def compile(self, reason): 2284 log.log(DEBUG, "-- compiling %s for %s", self.__class__.__name__, reason) 2285 rawDict = self.rawDict 2286 data = [] 2287 for name in self.dictObj.order: 2288 value = rawDict.get(name) 2289 if value is None: 2290 continue 2291 op, argType = self.opcodes[name] 2292 if isinstance(argType, tuple): 2293 l = len(argType) 2294 assert len(value) == l, "value doesn't match arg type" 2295 for i in range(l): 2296 arg = argType[i] 2297 v = value[i] 2298 arghandler = getattr(self, "arg_" + arg) 2299 data.append(arghandler(v)) 2300 else: 2301 arghandler = getattr(self, "arg_" + argType) 2302 data.append(arghandler(value)) 2303 data.append(op) 2304 data = bytesjoin(data) 2305 return data 2306 2307 def toFile(self, file): 2308 data = self.compile("toFile") 2309 file.write(data) 2310 2311 def arg_number(self, num): 2312 if isinstance(num, list): 2313 data = [encodeNumber(val) for val in num] 2314 data.append(encodeNumber(1)) 2315 data.append(bytechr(blendOp)) 2316 datum = bytesjoin(data) 2317 else: 2318 datum = encodeNumber(num) 2319 return datum 2320 2321 def arg_SID(self, s): 2322 return psCharStrings.encodeIntCFF(self.strings.getSID(s)) 2323 2324 def arg_array(self, value): 2325 data = [] 2326 for num in value: 2327 data.append(self.arg_number(num)) 2328 return bytesjoin(data) 2329 2330 def arg_delta(self, value): 2331 if not value: 2332 return b"" 2333 val0 = value[0] 2334 if isinstance(val0, list): 2335 data = self.arg_delta_blend(value) 2336 else: 2337 out = [] 2338 last = 0 2339 for v in value: 2340 out.append(v - last) 2341 last = v 2342 data = [] 2343 for num in out: 2344 data.append(encodeNumber(num)) 2345 return bytesjoin(data) 2346 2347 2348 def arg_delta_blend(self, value): 2349 """A delta list with blend lists has to be *all* blend lists. 2350 2351 The value is a list is arranged as follows:: 2352 2353 [ 2354 [V0, d0..dn] 2355 [V1, d0..dn] 2356 ... 2357 [Vm, d0..dn] 2358 ] 2359 2360 ``V`` is the absolute coordinate value from the default font, and ``d0-dn`` 2361 are the delta values from the *n* regions. Each ``V`` is an absolute 2362 coordinate from the default font. 2363 2364 We want to return a list:: 2365 2366 [ 2367 [v0, v1..vm] 2368 [d0..dn] 2369 ... 2370 [d0..dn] 2371 numBlends 2372 blendOp 2373 ] 2374 2375 where each ``v`` is relative to the previous default font value. 2376 """ 2377 numMasters = len(value[0]) 2378 numBlends = len(value) 2379 numStack = (numBlends * numMasters) + 1 2380 if numStack > self.maxBlendStack: 2381 # Figure out the max number of value we can blend 2382 # and divide this list up into chunks of that size. 2383 2384 numBlendValues = int((self.maxBlendStack - 1) / numMasters) 2385 out = [] 2386 while True: 2387 numVal = min(len(value), numBlendValues) 2388 if numVal == 0: 2389 break 2390 valList = value[0:numVal] 2391 out1 = self.arg_delta_blend(valList) 2392 out.extend(out1) 2393 value = value[numVal:] 2394 else: 2395 firstList = [0] * numBlends 2396 deltaList = [None] * numBlends 2397 i = 0 2398 prevVal = 0 2399 while i < numBlends: 2400 # For PrivateDict BlueValues, the default font 2401 # values are absolute, not relative. 2402 # Must convert these back to relative coordinates 2403 # befor writing to CFF2. 2404 defaultValue = value[i][0] 2405 firstList[i] = defaultValue - prevVal 2406 prevVal = defaultValue 2407 deltaList[i] = value[i][1:] 2408 i += 1 2409 2410 relValueList = firstList 2411 for blendList in deltaList: 2412 relValueList.extend(blendList) 2413 out = [encodeNumber(val) for val in relValueList] 2414 out.append(encodeNumber(numBlends)) 2415 out.append(bytechr(blendOp)) 2416 return out 2417 2418 2419def encodeNumber(num): 2420 if isinstance(num, float): 2421 return psCharStrings.encodeFloat(num) 2422 else: 2423 return psCharStrings.encodeIntCFF(num) 2424 2425 2426class TopDictCompiler(DictCompiler): 2427 2428 opcodes = buildOpcodeDict(topDictOperators) 2429 2430 def getChildren(self, strings): 2431 isCFF2 = self.isCFF2 2432 children = [] 2433 if self.dictObj.cff2GetGlyphOrder is None: 2434 if hasattr(self.dictObj, "charset") and self.dictObj.charset: 2435 if hasattr(self.dictObj, "ROS"): # aka isCID 2436 charsetCode = None 2437 else: 2438 charsetCode = getStdCharSet(self.dictObj.charset) 2439 if charsetCode is None: 2440 children.append(CharsetCompiler(strings, self.dictObj.charset, self)) 2441 else: 2442 self.rawDict["charset"] = charsetCode 2443 if hasattr(self.dictObj, "Encoding") and self.dictObj.Encoding: 2444 encoding = self.dictObj.Encoding 2445 if not isinstance(encoding, str): 2446 children.append(EncodingCompiler(strings, encoding, self)) 2447 else: 2448 if hasattr(self.dictObj, "VarStore"): 2449 varStoreData = self.dictObj.VarStore 2450 varStoreComp = VarStoreCompiler(varStoreData, self) 2451 children.append(varStoreComp) 2452 if hasattr(self.dictObj, "FDSelect"): 2453 # I have not yet supported merging a ttx CFF-CID font, as there are 2454 # interesting issues about merging the FDArrays. Here I assume that 2455 # either the font was read from XML, and the FDSelect indices are all 2456 # in the charstring data, or the FDSelect array is already fully defined. 2457 fdSelect = self.dictObj.FDSelect 2458 # probably read in from XML; assume fdIndex in CharString data 2459 if len(fdSelect) == 0: 2460 charStrings = self.dictObj.CharStrings 2461 for name in self.dictObj.charset: 2462 fdSelect.append(charStrings[name].fdSelectIndex) 2463 fdSelectComp = FDSelectCompiler(fdSelect, self) 2464 children.append(fdSelectComp) 2465 if hasattr(self.dictObj, "CharStrings"): 2466 items = [] 2467 charStrings = self.dictObj.CharStrings 2468 for name in self.dictObj.charset: 2469 items.append(charStrings[name]) 2470 charStringsComp = CharStringsCompiler( 2471 items, strings, self, isCFF2=isCFF2) 2472 children.append(charStringsComp) 2473 if hasattr(self.dictObj, "FDArray"): 2474 # I have not yet supported merging a ttx CFF-CID font, as there are 2475 # interesting issues about merging the FDArrays. Here I assume that the 2476 # FDArray info is correct and complete. 2477 fdArrayIndexComp = self.dictObj.FDArray.getCompiler(strings, self) 2478 children.append(fdArrayIndexComp) 2479 children.extend(fdArrayIndexComp.getChildren(strings)) 2480 if hasattr(self.dictObj, "Private"): 2481 privComp = self.dictObj.Private.getCompiler(strings, self) 2482 children.append(privComp) 2483 children.extend(privComp.getChildren(strings)) 2484 return children 2485 2486 2487class FontDictCompiler(DictCompiler): 2488 opcodes = buildOpcodeDict(topDictOperators) 2489 2490 def __init__(self, dictObj, strings, parent, isCFF2=None): 2491 super(FontDictCompiler, self).__init__(dictObj, strings, parent, isCFF2=isCFF2) 2492 # 2493 # We now take some effort to detect if there were any key/value pairs 2494 # supplied that were ignored in the FontDict context, and issue a warning 2495 # for those cases. 2496 # 2497 ignoredNames = [] 2498 dictObj = self.dictObj 2499 for name in sorted(set(dictObj.converters) - set(dictObj.order)): 2500 if name in dictObj.rawDict: 2501 # The font was directly read from binary. In this 2502 # case, we want to report *all* "useless" key/value 2503 # pairs that are in the font, not just the ones that 2504 # are different from the default. 2505 ignoredNames.append(name) 2506 else: 2507 # The font was probably read from a TTX file. We only 2508 # warn about keys whos value is not the default. The 2509 # ones that have the default value will not be written 2510 # to binary anyway. 2511 default = dictObj.defaults.get(name) 2512 if default is not None: 2513 conv = dictObj.converters[name] 2514 default = conv.read(dictObj, default) 2515 if getattr(dictObj, name, None) != default: 2516 ignoredNames.append(name) 2517 if ignoredNames: 2518 log.warning( 2519 "Some CFF FDArray/FontDict keys were ignored upon compile: " + 2520 " ".join(sorted(ignoredNames))) 2521 2522 def getChildren(self, strings): 2523 children = [] 2524 if hasattr(self.dictObj, "Private"): 2525 privComp = self.dictObj.Private.getCompiler(strings, self) 2526 children.append(privComp) 2527 children.extend(privComp.getChildren(strings)) 2528 return children 2529 2530 2531class PrivateDictCompiler(DictCompiler): 2532 2533 maxBlendStack = maxStackLimit 2534 opcodes = buildOpcodeDict(privateDictOperators) 2535 2536 def setPos(self, pos, endPos): 2537 size = endPos - pos 2538 self.parent.rawDict["Private"] = size, pos 2539 self.pos = pos 2540 2541 def getChildren(self, strings): 2542 children = [] 2543 if hasattr(self.dictObj, "Subrs"): 2544 children.append(self.dictObj.Subrs.getCompiler(strings, self)) 2545 return children 2546 2547 2548class BaseDict(object): 2549 2550 def __init__(self, strings=None, file=None, offset=None, isCFF2=None): 2551 assert (isCFF2 is None) == (file is None) 2552 self.rawDict = {} 2553 self.skipNames = [] 2554 self.strings = strings 2555 if file is None: 2556 return 2557 self._isCFF2 = isCFF2 2558 self.file = file 2559 if offset is not None: 2560 log.log(DEBUG, "loading %s at %s", self.__class__.__name__, offset) 2561 self.offset = offset 2562 2563 def decompile(self, data): 2564 log.log(DEBUG, " length %s is %d", self.__class__.__name__, len(data)) 2565 dec = self.decompilerClass(self.strings, self) 2566 dec.decompile(data) 2567 self.rawDict = dec.getDict() 2568 self.postDecompile() 2569 2570 def postDecompile(self): 2571 pass 2572 2573 def getCompiler(self, strings, parent, isCFF2=None): 2574 return self.compilerClass(self, strings, parent, isCFF2=isCFF2) 2575 2576 def __getattr__(self, name): 2577 if name[:2] == name[-2:] == "__": 2578 # to make deepcopy() and pickle.load() work, we need to signal with 2579 # AttributeError that dunder methods like '__deepcopy__' or '__getstate__' 2580 # aren't implemented. For more details, see: 2581 # https://github.com/fonttools/fonttools/pull/1488 2582 raise AttributeError(name) 2583 value = self.rawDict.get(name, None) 2584 if value is None: 2585 value = self.defaults.get(name) 2586 if value is None: 2587 raise AttributeError(name) 2588 conv = self.converters[name] 2589 value = conv.read(self, value) 2590 setattr(self, name, value) 2591 return value 2592 2593 def toXML(self, xmlWriter): 2594 for name in self.order: 2595 if name in self.skipNames: 2596 continue 2597 value = getattr(self, name, None) 2598 # XXX For "charset" we never skip calling xmlWrite even if the 2599 # value is None, so we always write the following XML comment: 2600 # 2601 # <!-- charset is dumped separately as the 'GlyphOrder' element --> 2602 # 2603 # Charset is None when 'CFF ' table is imported from XML into an 2604 # empty TTFont(). By writing this comment all the time, we obtain 2605 # the same XML output whether roundtripping XML-to-XML or 2606 # dumping binary-to-XML 2607 if value is None and name != "charset": 2608 continue 2609 conv = self.converters[name] 2610 conv.xmlWrite(xmlWriter, name, value) 2611 ignoredNames = set(self.rawDict) - set(self.order) 2612 if ignoredNames: 2613 xmlWriter.comment( 2614 "some keys were ignored: %s" % " ".join(sorted(ignoredNames))) 2615 xmlWriter.newline() 2616 2617 def fromXML(self, name, attrs, content): 2618 conv = self.converters[name] 2619 value = conv.xmlRead(name, attrs, content, self) 2620 setattr(self, name, value) 2621 2622 2623class TopDict(BaseDict): 2624 """The ``TopDict`` represents the top-level dictionary holding font 2625 information. CFF2 tables contain a restricted set of top-level entries 2626 as described `here <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#7-top-dict-data>`_, 2627 but CFF tables may contain a wider range of information. This information 2628 can be accessed through attributes or through the dictionary returned 2629 through the ``rawDict`` property: 2630 2631 .. code:: python 2632 2633 font = tt["CFF "].cff[0] 2634 font.FamilyName 2635 # 'Linux Libertine O' 2636 font.rawDict["FamilyName"] 2637 # 'Linux Libertine O' 2638 2639 More information is available in the CFF file's private dictionary, accessed 2640 via the ``Private`` property: 2641 2642 .. code:: python 2643 2644 tt["CFF "].cff[0].Private.BlueValues 2645 # [-15, 0, 515, 515, 666, 666] 2646 2647 """ 2648 2649 defaults = buildDefaults(topDictOperators) 2650 converters = buildConverters(topDictOperators) 2651 compilerClass = TopDictCompiler 2652 order = buildOrder(topDictOperators) 2653 decompilerClass = TopDictDecompiler 2654 2655 def __init__(self, strings=None, file=None, offset=None, 2656 GlobalSubrs=None, cff2GetGlyphOrder=None, isCFF2=None): 2657 super(TopDict, self).__init__(strings, file, offset, isCFF2=isCFF2) 2658 self.cff2GetGlyphOrder = cff2GetGlyphOrder 2659 self.GlobalSubrs = GlobalSubrs 2660 if isCFF2: 2661 self.defaults = buildDefaults(topDictOperators2) 2662 self.charset = cff2GetGlyphOrder() 2663 self.order = buildOrder(topDictOperators2) 2664 else: 2665 self.defaults = buildDefaults(topDictOperators) 2666 self.order = buildOrder(topDictOperators) 2667 2668 def getGlyphOrder(self): 2669 """Returns a list of glyph names in the CFF font.""" 2670 return self.charset 2671 2672 def postDecompile(self): 2673 offset = self.rawDict.get("CharStrings") 2674 if offset is None: 2675 return 2676 # get the number of glyphs beforehand. 2677 self.file.seek(offset) 2678 if self._isCFF2: 2679 self.numGlyphs = readCard32(self.file) 2680 else: 2681 self.numGlyphs = readCard16(self.file) 2682 2683 def toXML(self, xmlWriter): 2684 if hasattr(self, "CharStrings"): 2685 self.decompileAllCharStrings() 2686 if hasattr(self, "ROS"): 2687 self.skipNames = ['Encoding'] 2688 if not hasattr(self, "ROS") or not hasattr(self, "CharStrings"): 2689 # these values have default values, but I only want them to show up 2690 # in CID fonts. 2691 self.skipNames = [ 2692 'CIDFontVersion', 'CIDFontRevision', 'CIDFontType', 'CIDCount'] 2693 BaseDict.toXML(self, xmlWriter) 2694 2695 def decompileAllCharStrings(self): 2696 # Make sure that all the Private Dicts have been instantiated. 2697 for i, charString in enumerate(self.CharStrings.values()): 2698 try: 2699 charString.decompile() 2700 except: 2701 log.error("Error in charstring %s", i) 2702 raise 2703 2704 def recalcFontBBox(self): 2705 fontBBox = None 2706 for charString in self.CharStrings.values(): 2707 bounds = charString.calcBounds(self.CharStrings) 2708 if bounds is not None: 2709 if fontBBox is not None: 2710 fontBBox = unionRect(fontBBox, bounds) 2711 else: 2712 fontBBox = bounds 2713 2714 if fontBBox is None: 2715 self.FontBBox = self.defaults['FontBBox'][:] 2716 else: 2717 self.FontBBox = list(intRect(fontBBox)) 2718 2719 2720class FontDict(BaseDict): 2721 # 2722 # Since fonttools used to pass a lot of fields that are not relevant in the FDArray 2723 # FontDict, there are 'ttx' files in the wild that contain all these. These got in 2724 # the ttx files because fonttools writes explicit values for all the TopDict default 2725 # values. These are not actually illegal in the context of an FDArray FontDict - you 2726 # can legally, per spec, put any arbitrary key/value pair in a FontDict - but are 2727 # useless since current major company CFF interpreters ignore anything but the set 2728 # listed in this file. So, we just silently skip them. An exception is Weight: this 2729 # is not used by any interpreter, but some foundries have asked that this be 2730 # supported in FDArray FontDicts just to preserve information about the design when 2731 # the font is being inspected. 2732 # 2733 # On top of that, there are fonts out there that contain such useless FontDict values. 2734 # 2735 # By subclassing TopDict, we *allow* all key/values from TopDict, both when reading 2736 # from binary or when reading from XML, but by overriding `order` with a limited 2737 # list of names, we ensure that only the useful names ever get exported to XML and 2738 # ever get compiled into the binary font. 2739 # 2740 # We override compilerClass so we can warn about "useless" key/value pairs, either 2741 # from the original binary font or from TTX input. 2742 # 2743 # See: 2744 # - https://github.com/fonttools/fonttools/issues/740 2745 # - https://github.com/fonttools/fonttools/issues/601 2746 # - https://github.com/adobe-type-tools/afdko/issues/137 2747 # 2748 defaults = {} 2749 converters = buildConverters(topDictOperators) 2750 compilerClass = FontDictCompiler 2751 orderCFF = ['FontName', 'FontMatrix', 'Weight', 'Private'] 2752 orderCFF2 = ['Private'] 2753 decompilerClass = TopDictDecompiler 2754 2755 def __init__(self, strings=None, file=None, offset=None, 2756 GlobalSubrs=None, isCFF2=None, vstore=None): 2757 super(FontDict, self).__init__(strings, file, offset, isCFF2=isCFF2) 2758 self.vstore = vstore 2759 self.setCFF2(isCFF2) 2760 2761 def setCFF2(self, isCFF2): 2762 # isCFF2 may be None. 2763 if isCFF2: 2764 self.order = self.orderCFF2 2765 self._isCFF2 = True 2766 else: 2767 self.order = self.orderCFF 2768 self._isCFF2 = False 2769 2770 2771class PrivateDict(BaseDict): 2772 defaults = buildDefaults(privateDictOperators) 2773 converters = buildConverters(privateDictOperators) 2774 order = buildOrder(privateDictOperators) 2775 decompilerClass = PrivateDictDecompiler 2776 compilerClass = PrivateDictCompiler 2777 2778 def __init__(self, strings=None, file=None, offset=None, isCFF2=None, 2779 vstore=None): 2780 super(PrivateDict, self).__init__(strings, file, offset, isCFF2=isCFF2) 2781 self.vstore = vstore 2782 if isCFF2: 2783 self.defaults = buildDefaults(privateDictOperators2) 2784 self.order = buildOrder(privateDictOperators2) 2785 # Provide dummy values. This avoids needing to provide 2786 # an isCFF2 state in a lot of places. 2787 self.nominalWidthX = self.defaultWidthX = None 2788 else: 2789 self.defaults = buildDefaults(privateDictOperators) 2790 self.order = buildOrder(privateDictOperators) 2791 2792 @property 2793 def in_cff2(self): 2794 return self._isCFF2 2795 2796 def getNumRegions(self, vi=None): # called from misc/psCharStrings.py 2797 # if getNumRegions is being called, we can assume that VarStore exists. 2798 if vi is None: 2799 if hasattr(self, 'vsindex'): 2800 vi = self.vsindex 2801 else: 2802 vi = 0 2803 numRegions = self.vstore.getNumRegions(vi) 2804 return numRegions 2805 2806 2807class IndexedStrings(object): 2808 2809 """SID -> string mapping.""" 2810 2811 def __init__(self, file=None): 2812 if file is None: 2813 strings = [] 2814 else: 2815 strings = [ 2816 tostr(s, encoding="latin1") 2817 for s in Index(file, isCFF2=False) 2818 ] 2819 self.strings = strings 2820 2821 def getCompiler(self): 2822 return IndexedStringsCompiler(self, None, self, isCFF2=False) 2823 2824 def __len__(self): 2825 return len(self.strings) 2826 2827 def __getitem__(self, SID): 2828 if SID < cffStandardStringCount: 2829 return cffStandardStrings[SID] 2830 else: 2831 return self.strings[SID - cffStandardStringCount] 2832 2833 def getSID(self, s): 2834 if not hasattr(self, "stringMapping"): 2835 self.buildStringMapping() 2836 s = tostr(s, encoding="latin1") 2837 if s in cffStandardStringMapping: 2838 SID = cffStandardStringMapping[s] 2839 elif s in self.stringMapping: 2840 SID = self.stringMapping[s] 2841 else: 2842 SID = len(self.strings) + cffStandardStringCount 2843 self.strings.append(s) 2844 self.stringMapping[s] = SID 2845 return SID 2846 2847 def getStrings(self): 2848 return self.strings 2849 2850 def buildStringMapping(self): 2851 self.stringMapping = {} 2852 for index in range(len(self.strings)): 2853 self.stringMapping[self.strings[index]] = index + cffStandardStringCount 2854 2855 2856# The 391 Standard Strings as used in the CFF format. 2857# from Adobe Technical None #5176, version 1.0, 18 March 1998 2858 2859cffStandardStrings = ['.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 2860 'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright', 2861 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 2862 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 2863 'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 2864 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 2865 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 2866 'bracketright', 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c', 2867 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 2868 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 2869 'asciitilde', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 2870 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft', 2871 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 2872 'daggerdbl', 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase', 2873 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', 2874 'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 2875 'dotaccent', 'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 2876 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae', 2877 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior', 2878 'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 2879 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters', 2880 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior', 2881 'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 2882 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave', 2883 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 2884 'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 2885 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron', 2886 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla', 2887 'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex', 2888 'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis', 2889 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave', 2890 'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall', 2891 'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall', 2892 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', 2893 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 2894 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 2895 'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior', 2896 'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 2897 'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior', 2898 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', 'parenleftinferior', 2899 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 2900 'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 2901 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 2902 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall', 2903 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', 2904 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 2905 'Dieresissmall', 'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall', 2906 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', 2907 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 2908 'onethird', 'twothirds', 'zerosuperior', 'foursuperior', 'fivesuperior', 2909 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 2910 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 2911 'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 2912 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall', 2913 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', 2914 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 2915 'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 2916 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', 2917 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 2918 'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 2919 'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', '001.001', '001.002', 2920 '001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman', 2921 'Semibold' 2922] 2923 2924cffStandardStringCount = 391 2925assert len(cffStandardStrings) == cffStandardStringCount 2926# build reverse mapping 2927cffStandardStringMapping = {} 2928for _i in range(cffStandardStringCount): 2929 cffStandardStringMapping[cffStandardStrings[_i]] = _i 2930 2931cffISOAdobeStrings = [".notdef", "space", "exclam", "quotedbl", "numbersign", 2932"dollar", "percent", "ampersand", "quoteright", "parenleft", "parenright", 2933"asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two", 2934"three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", 2935"less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", 2936"H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", 2937"X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum", 2938"underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", 2939"k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", 2940"braceleft", "bar", "braceright", "asciitilde", "exclamdown", "cent", 2941"sterling", "fraction", "yen", "florin", "section", "currency", "quotesingle", 2942"quotedblleft", "guillemotleft", "guilsinglleft", "guilsinglright", "fi", "fl", 2943"endash", "dagger", "daggerdbl", "periodcentered", "paragraph", "bullet", 2944"quotesinglbase", "quotedblbase", "quotedblright", "guillemotright", "ellipsis", 2945"perthousand", "questiondown", "grave", "acute", "circumflex", "tilde", 2946"macron", "breve", "dotaccent", "dieresis", "ring", "cedilla", "hungarumlaut", 2947"ogonek", "caron", "emdash", "AE", "ordfeminine", "Lslash", "Oslash", "OE", 2948"ordmasculine", "ae", "dotlessi", "lslash", "oslash", "oe", "germandbls", 2949"onesuperior", "logicalnot", "mu", "trademark", "Eth", "onehalf", "plusminus", 2950"Thorn", "onequarter", "divide", "brokenbar", "degree", "thorn", 2951"threequarters", "twosuperior", "registered", "minus", "eth", "multiply", 2952"threesuperior", "copyright", "Aacute", "Acircumflex", "Adieresis", "Agrave", 2953"Aring", "Atilde", "Ccedilla", "Eacute", "Ecircumflex", "Edieresis", "Egrave", 2954"Iacute", "Icircumflex", "Idieresis", "Igrave", "Ntilde", "Oacute", 2955"Ocircumflex", "Odieresis", "Ograve", "Otilde", "Scaron", "Uacute", 2956"Ucircumflex", "Udieresis", "Ugrave", "Yacute", "Ydieresis", "Zcaron", "aacute", 2957"acircumflex", "adieresis", "agrave", "aring", "atilde", "ccedilla", "eacute", 2958"ecircumflex", "edieresis", "egrave", "iacute", "icircumflex", "idieresis", 2959"igrave", "ntilde", "oacute", "ocircumflex", "odieresis", "ograve", "otilde", 2960"scaron", "uacute", "ucircumflex", "udieresis", "ugrave", "yacute", "ydieresis", 2961"zcaron"] 2962 2963cffISOAdobeStringCount = 229 2964assert len(cffISOAdobeStrings) == cffISOAdobeStringCount 2965 2966cffIExpertStrings = [".notdef", "space", "exclamsmall", "Hungarumlautsmall", 2967"dollaroldstyle", "dollarsuperior", "ampersandsmall", "Acutesmall", 2968"parenleftsuperior", "parenrightsuperior", "twodotenleader", "onedotenleader", 2969"comma", "hyphen", "period", "fraction", "zerooldstyle", "oneoldstyle", 2970"twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle", 2971"sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", "semicolon", 2972"commasuperior", "threequartersemdash", "periodsuperior", "questionsmall", 2973"asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior", 2974"lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior", 2975"tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior", 2976"parenrightinferior", "Circumflexsmall", "hyphensuperior", "Gravesmall", 2977"Asmall", "Bsmall", "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall", 2978"Ismall", "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall", 2979"Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall", "Xsmall", 2980"Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah", "Tildesmall", 2981"exclamdownsmall", "centoldstyle", "Lslashsmall", "Scaronsmall", "Zcaronsmall", 2982"Dieresissmall", "Brevesmall", "Caronsmall", "Dotaccentsmall", "Macronsmall", 2983"figuredash", "hypheninferior", "Ogoneksmall", "Ringsmall", "Cedillasmall", 2984"onequarter", "onehalf", "threequarters", "questiondownsmall", "oneeighth", 2985"threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds", 2986"zerosuperior", "onesuperior", "twosuperior", "threesuperior", "foursuperior", 2987"fivesuperior", "sixsuperior", "sevensuperior", "eightsuperior", "ninesuperior", 2988"zeroinferior", "oneinferior", "twoinferior", "threeinferior", "fourinferior", 2989"fiveinferior", "sixinferior", "seveninferior", "eightinferior", "nineinferior", 2990"centinferior", "dollarinferior", "periodinferior", "commainferior", 2991"Agravesmall", "Aacutesmall", "Acircumflexsmall", "Atildesmall", 2992"Adieresissmall", "Aringsmall", "AEsmall", "Ccedillasmall", "Egravesmall", 2993"Eacutesmall", "Ecircumflexsmall", "Edieresissmall", "Igravesmall", 2994"Iacutesmall", "Icircumflexsmall", "Idieresissmall", "Ethsmall", "Ntildesmall", 2995"Ogravesmall", "Oacutesmall", "Ocircumflexsmall", "Otildesmall", 2996"Odieresissmall", "OEsmall", "Oslashsmall", "Ugravesmall", "Uacutesmall", 2997"Ucircumflexsmall", "Udieresissmall", "Yacutesmall", "Thornsmall", 2998"Ydieresissmall"] 2999 3000cffExpertStringCount = 166 3001assert len(cffIExpertStrings) == cffExpertStringCount 3002 3003cffExpertSubsetStrings = [".notdef", "space", "dollaroldstyle", 3004"dollarsuperior", "parenleftsuperior", "parenrightsuperior", "twodotenleader", 3005"onedotenleader", "comma", "hyphen", "period", "fraction", "zerooldstyle", 3006"oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", 3007"sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", 3008"semicolon", "commasuperior", "threequartersemdash", "periodsuperior", 3009"asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior", 3010"lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior", 3011"tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior", 3012"parenrightinferior", "hyphensuperior", "colonmonetary", "onefitted", "rupiah", 3013"centoldstyle", "figuredash", "hypheninferior", "onequarter", "onehalf", 3014"threequarters", "oneeighth", "threeeighths", "fiveeighths", "seveneighths", 3015"onethird", "twothirds", "zerosuperior", "onesuperior", "twosuperior", 3016"threesuperior", "foursuperior", "fivesuperior", "sixsuperior", "sevensuperior", 3017"eightsuperior", "ninesuperior", "zeroinferior", "oneinferior", "twoinferior", 3018"threeinferior", "fourinferior", "fiveinferior", "sixinferior", "seveninferior", 3019"eightinferior", "nineinferior", "centinferior", "dollarinferior", 3020"periodinferior", "commainferior"] 3021 3022cffExpertSubsetStringCount = 87 3023assert len(cffExpertSubsetStrings) == cffExpertSubsetStringCount 3024