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) 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) 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 # read data in from file. Assume position is correct. 1008 length = readCard16(self.file) 1009 self.data = self.file.read(length) 1010 globalState = {} 1011 reader = OTTableReader(self.data, globalState) 1012 self.otVarStore = ot.VarStore() 1013 self.otVarStore.decompile(reader, self.font) 1014 return self 1015 1016 def compile(self): 1017 writer = OTTableWriter() 1018 self.otVarStore.compile(writer, self.font) 1019 # Note that this omits the initial Card16 length from the CFF2 1020 # VarStore data block 1021 self.data = writer.getAllData() 1022 1023 def writeXML(self, xmlWriter, name): 1024 self.otVarStore.toXML(xmlWriter, self.font) 1025 1026 def xmlRead(self, name, attrs, content, parent): 1027 self.otVarStore = ot.VarStore() 1028 for element in content: 1029 if isinstance(element, tuple): 1030 name, attrs, content = element 1031 self.otVarStore.fromXML(name, attrs, content, self.font) 1032 else: 1033 pass 1034 return None 1035 1036 def __len__(self): 1037 return len(self.data) 1038 1039 def getNumRegions(self, vsIndex): 1040 if vsIndex is None: 1041 vsIndex = 0 1042 varData = self.otVarStore.VarData[vsIndex] 1043 numRegions = varData.VarRegionCount 1044 return numRegions 1045 1046 1047class FDSelect(object): 1048 1049 def __init__(self, file=None, numGlyphs=None, format=None): 1050 if file: 1051 # read data in from file 1052 self.format = readCard8(file) 1053 if self.format == 0: 1054 from array import array 1055 self.gidArray = array("B", file.read(numGlyphs)).tolist() 1056 elif self.format == 3: 1057 gidArray = [None] * numGlyphs 1058 nRanges = readCard16(file) 1059 fd = None 1060 prev = None 1061 for i in range(nRanges): 1062 first = readCard16(file) 1063 if prev is not None: 1064 for glyphID in range(prev, first): 1065 gidArray[glyphID] = fd 1066 prev = first 1067 fd = readCard8(file) 1068 if prev is not None: 1069 first = readCard16(file) 1070 for glyphID in range(prev, first): 1071 gidArray[glyphID] = fd 1072 self.gidArray = gidArray 1073 elif self.format == 4: 1074 gidArray = [None] * numGlyphs 1075 nRanges = readCard32(file) 1076 fd = None 1077 prev = None 1078 for i in range(nRanges): 1079 first = readCard32(file) 1080 if prev is not None: 1081 for glyphID in range(prev, first): 1082 gidArray[glyphID] = fd 1083 prev = first 1084 fd = readCard16(file) 1085 if prev is not None: 1086 first = readCard32(file) 1087 for glyphID in range(prev, first): 1088 gidArray[glyphID] = fd 1089 self.gidArray = gidArray 1090 else: 1091 assert False, "unsupported FDSelect format: %s" % format 1092 else: 1093 # reading from XML. Make empty gidArray, and leave format as passed in. 1094 # format is None will result in the smallest representation being used. 1095 self.format = format 1096 self.gidArray = [] 1097 1098 def __len__(self): 1099 return len(self.gidArray) 1100 1101 def __getitem__(self, index): 1102 return self.gidArray[index] 1103 1104 def __setitem__(self, index, fdSelectValue): 1105 self.gidArray[index] = fdSelectValue 1106 1107 def append(self, fdSelectValue): 1108 self.gidArray.append(fdSelectValue) 1109 1110 1111class CharStrings(object): 1112 """The ``CharStrings`` in the font represent the instructions for drawing 1113 each glyph. This object presents a dictionary interface to the font's 1114 CharStrings, indexed by glyph name: 1115 1116 .. code:: python 1117 1118 tt["CFF "].cff[0].CharStrings["a"] 1119 # <T2CharString (bytecode) at 103451e90> 1120 1121 See :class:`fontTools.misc.psCharStrings.T1CharString` and 1122 :class:`fontTools.misc.psCharStrings.T2CharString` for how to decompile, 1123 compile and interpret the glyph drawing instructions in the returned objects. 1124 1125 """ 1126 1127 def __init__(self, file, charset, globalSubrs, private, fdSelect, fdArray, 1128 isCFF2=None): 1129 self.globalSubrs = globalSubrs 1130 if file is not None: 1131 self.charStringsIndex = SubrsIndex( 1132 file, globalSubrs, private, fdSelect, fdArray, isCFF2=isCFF2) 1133 self.charStrings = charStrings = {} 1134 for i in range(len(charset)): 1135 charStrings[charset[i]] = i 1136 # read from OTF file: charStrings.values() are indices into 1137 # charStringsIndex. 1138 self.charStringsAreIndexed = 1 1139 else: 1140 self.charStrings = {} 1141 # read from ttx file: charStrings.values() are actual charstrings 1142 self.charStringsAreIndexed = 0 1143 self.private = private 1144 if fdSelect is not None: 1145 self.fdSelect = fdSelect 1146 if fdArray is not None: 1147 self.fdArray = fdArray 1148 1149 def keys(self): 1150 return list(self.charStrings.keys()) 1151 1152 def values(self): 1153 if self.charStringsAreIndexed: 1154 return self.charStringsIndex 1155 else: 1156 return list(self.charStrings.values()) 1157 1158 def has_key(self, name): 1159 return name in self.charStrings 1160 1161 __contains__ = has_key 1162 1163 def __len__(self): 1164 return len(self.charStrings) 1165 1166 def __getitem__(self, name): 1167 charString = self.charStrings[name] 1168 if self.charStringsAreIndexed: 1169 charString = self.charStringsIndex[charString] 1170 return charString 1171 1172 def __setitem__(self, name, charString): 1173 if self.charStringsAreIndexed: 1174 index = self.charStrings[name] 1175 self.charStringsIndex[index] = charString 1176 else: 1177 self.charStrings[name] = charString 1178 1179 def getItemAndSelector(self, name): 1180 if self.charStringsAreIndexed: 1181 index = self.charStrings[name] 1182 return self.charStringsIndex.getItemAndSelector(index) 1183 else: 1184 if hasattr(self, 'fdArray'): 1185 if hasattr(self, 'fdSelect'): 1186 sel = self.charStrings[name].fdSelectIndex 1187 else: 1188 sel = 0 1189 else: 1190 sel = None 1191 return self.charStrings[name], sel 1192 1193 def toXML(self, xmlWriter): 1194 names = sorted(self.keys()) 1195 for name in names: 1196 charStr, fdSelectIndex = self.getItemAndSelector(name) 1197 if charStr.needsDecompilation(): 1198 raw = [("raw", 1)] 1199 else: 1200 raw = [] 1201 if fdSelectIndex is None: 1202 xmlWriter.begintag("CharString", [('name', name)] + raw) 1203 else: 1204 xmlWriter.begintag( 1205 "CharString", 1206 [('name', name), ('fdSelectIndex', fdSelectIndex)] + raw) 1207 xmlWriter.newline() 1208 charStr.toXML(xmlWriter) 1209 xmlWriter.endtag("CharString") 1210 xmlWriter.newline() 1211 1212 def fromXML(self, name, attrs, content): 1213 for element in content: 1214 if isinstance(element, str): 1215 continue 1216 name, attrs, content = element 1217 if name != "CharString": 1218 continue 1219 fdID = -1 1220 if hasattr(self, "fdArray"): 1221 try: 1222 fdID = safeEval(attrs["fdSelectIndex"]) 1223 except KeyError: 1224 fdID = 0 1225 private = self.fdArray[fdID].Private 1226 else: 1227 private = self.private 1228 1229 glyphName = attrs["name"] 1230 charStringClass = psCharStrings.T2CharString 1231 charString = charStringClass( 1232 private=private, 1233 globalSubrs=self.globalSubrs) 1234 charString.fromXML(name, attrs, content) 1235 if fdID >= 0: 1236 charString.fdSelectIndex = fdID 1237 self[glyphName] = charString 1238 1239 1240def readCard8(file): 1241 return byteord(file.read(1)) 1242 1243 1244def readCard16(file): 1245 value, = struct.unpack(">H", file.read(2)) 1246 return value 1247 1248 1249def readCard32(file): 1250 value, = struct.unpack(">L", file.read(4)) 1251 return value 1252 1253 1254def writeCard8(file, value): 1255 file.write(bytechr(value)) 1256 1257 1258def writeCard16(file, value): 1259 file.write(struct.pack(">H", value)) 1260 1261 1262def writeCard32(file, value): 1263 file.write(struct.pack(">L", value)) 1264 1265 1266def packCard8(value): 1267 return bytechr(value) 1268 1269 1270def packCard16(value): 1271 return struct.pack(">H", value) 1272 1273 1274def packCard32(value): 1275 return struct.pack(">L", value) 1276 1277 1278def buildOperatorDict(table): 1279 d = {} 1280 for op, name, arg, default, conv in table: 1281 d[op] = (name, arg) 1282 return d 1283 1284 1285def buildOpcodeDict(table): 1286 d = {} 1287 for op, name, arg, default, conv in table: 1288 if isinstance(op, tuple): 1289 op = bytechr(op[0]) + bytechr(op[1]) 1290 else: 1291 op = bytechr(op) 1292 d[name] = (op, arg) 1293 return d 1294 1295 1296def buildOrder(table): 1297 l = [] 1298 for op, name, arg, default, conv in table: 1299 l.append(name) 1300 return l 1301 1302 1303def buildDefaults(table): 1304 d = {} 1305 for op, name, arg, default, conv in table: 1306 if default is not None: 1307 d[name] = default 1308 return d 1309 1310 1311def buildConverters(table): 1312 d = {} 1313 for op, name, arg, default, conv in table: 1314 d[name] = conv 1315 return d 1316 1317 1318class SimpleConverter(object): 1319 1320 def read(self, parent, value): 1321 if not hasattr(parent, "file"): 1322 return self._read(parent, value) 1323 file = parent.file 1324 pos = file.tell() 1325 try: 1326 return self._read(parent, value) 1327 finally: 1328 file.seek(pos) 1329 1330 def _read(self, parent, value): 1331 return value 1332 1333 def write(self, parent, value): 1334 return value 1335 1336 def xmlWrite(self, xmlWriter, name, value): 1337 xmlWriter.simpletag(name, value=value) 1338 xmlWriter.newline() 1339 1340 def xmlRead(self, name, attrs, content, parent): 1341 return attrs["value"] 1342 1343 1344class ASCIIConverter(SimpleConverter): 1345 1346 def _read(self, parent, value): 1347 return tostr(value, encoding='ascii') 1348 1349 def write(self, parent, value): 1350 return tobytes(value, encoding='ascii') 1351 1352 def xmlWrite(self, xmlWriter, name, value): 1353 xmlWriter.simpletag(name, value=tostr(value, encoding="ascii")) 1354 xmlWriter.newline() 1355 1356 def xmlRead(self, name, attrs, content, parent): 1357 return tobytes(attrs["value"], encoding=("ascii")) 1358 1359 1360class Latin1Converter(SimpleConverter): 1361 1362 def _read(self, parent, value): 1363 return tostr(value, encoding='latin1') 1364 1365 def write(self, parent, value): 1366 return tobytes(value, encoding='latin1') 1367 1368 def xmlWrite(self, xmlWriter, name, value): 1369 value = tostr(value, encoding="latin1") 1370 if name in ['Notice', 'Copyright']: 1371 value = re.sub(r"[\r\n]\s+", " ", value) 1372 xmlWriter.simpletag(name, value=value) 1373 xmlWriter.newline() 1374 1375 def xmlRead(self, name, attrs, content, parent): 1376 return tobytes(attrs["value"], encoding=("latin1")) 1377 1378 1379def parseNum(s): 1380 try: 1381 value = int(s) 1382 except: 1383 value = float(s) 1384 return value 1385 1386 1387def parseBlendList(s): 1388 valueList = [] 1389 for element in s: 1390 if isinstance(element, str): 1391 continue 1392 name, attrs, content = element 1393 blendList = attrs["value"].split() 1394 blendList = [eval(val) for val in blendList] 1395 valueList.append(blendList) 1396 if len(valueList) == 1: 1397 valueList = valueList[0] 1398 return valueList 1399 1400 1401class NumberConverter(SimpleConverter): 1402 def xmlWrite(self, xmlWriter, name, value): 1403 if isinstance(value, list): 1404 xmlWriter.begintag(name) 1405 xmlWriter.newline() 1406 xmlWriter.indent() 1407 blendValue = " ".join([str(val) for val in value]) 1408 xmlWriter.simpletag(kBlendDictOpName, value=blendValue) 1409 xmlWriter.newline() 1410 xmlWriter.dedent() 1411 xmlWriter.endtag(name) 1412 xmlWriter.newline() 1413 else: 1414 xmlWriter.simpletag(name, value=value) 1415 xmlWriter.newline() 1416 1417 def xmlRead(self, name, attrs, content, parent): 1418 valueString = attrs.get("value", None) 1419 if valueString is None: 1420 value = parseBlendList(content) 1421 else: 1422 value = parseNum(attrs["value"]) 1423 return value 1424 1425 1426class ArrayConverter(SimpleConverter): 1427 def xmlWrite(self, xmlWriter, name, value): 1428 if value and isinstance(value[0], list): 1429 xmlWriter.begintag(name) 1430 xmlWriter.newline() 1431 xmlWriter.indent() 1432 for valueList in value: 1433 blendValue = " ".join([str(val) for val in valueList]) 1434 xmlWriter.simpletag(kBlendDictOpName, value=blendValue) 1435 xmlWriter.newline() 1436 xmlWriter.dedent() 1437 xmlWriter.endtag(name) 1438 xmlWriter.newline() 1439 else: 1440 value = " ".join([str(val) for val in value]) 1441 xmlWriter.simpletag(name, value=value) 1442 xmlWriter.newline() 1443 1444 def xmlRead(self, name, attrs, content, parent): 1445 valueString = attrs.get("value", None) 1446 if valueString is None: 1447 valueList = parseBlendList(content) 1448 else: 1449 values = valueString.split() 1450 valueList = [parseNum(value) for value in values] 1451 return valueList 1452 1453 1454class TableConverter(SimpleConverter): 1455 1456 def xmlWrite(self, xmlWriter, name, value): 1457 xmlWriter.begintag(name) 1458 xmlWriter.newline() 1459 value.toXML(xmlWriter) 1460 xmlWriter.endtag(name) 1461 xmlWriter.newline() 1462 1463 def xmlRead(self, name, attrs, content, parent): 1464 ob = self.getClass()() 1465 for element in content: 1466 if isinstance(element, str): 1467 continue 1468 name, attrs, content = element 1469 ob.fromXML(name, attrs, content) 1470 return ob 1471 1472 1473class PrivateDictConverter(TableConverter): 1474 1475 def getClass(self): 1476 return PrivateDict 1477 1478 def _read(self, parent, value): 1479 size, offset = value 1480 file = parent.file 1481 isCFF2 = parent._isCFF2 1482 try: 1483 vstore = parent.vstore 1484 except AttributeError: 1485 vstore = None 1486 priv = PrivateDict( 1487 parent.strings, file, offset, isCFF2=isCFF2, vstore=vstore) 1488 file.seek(offset) 1489 data = file.read(size) 1490 assert len(data) == size 1491 priv.decompile(data) 1492 return priv 1493 1494 def write(self, parent, value): 1495 return (0, 0) # dummy value 1496 1497 1498class SubrsConverter(TableConverter): 1499 1500 def getClass(self): 1501 return SubrsIndex 1502 1503 def _read(self, parent, value): 1504 file = parent.file 1505 isCFF2 = parent._isCFF2 1506 file.seek(parent.offset + value) # Offset(self) 1507 return SubrsIndex(file, isCFF2=isCFF2) 1508 1509 def write(self, parent, value): 1510 return 0 # dummy value 1511 1512 1513class CharStringsConverter(TableConverter): 1514 1515 def _read(self, parent, value): 1516 file = parent.file 1517 isCFF2 = parent._isCFF2 1518 charset = parent.charset 1519 globalSubrs = parent.GlobalSubrs 1520 if hasattr(parent, "FDArray"): 1521 fdArray = parent.FDArray 1522 if hasattr(parent, "FDSelect"): 1523 fdSelect = parent.FDSelect 1524 else: 1525 fdSelect = None 1526 private = None 1527 else: 1528 fdSelect, fdArray = None, None 1529 private = parent.Private 1530 file.seek(value) # Offset(0) 1531 charStrings = CharStrings( 1532 file, charset, globalSubrs, private, fdSelect, fdArray, isCFF2=isCFF2) 1533 return charStrings 1534 1535 def write(self, parent, value): 1536 return 0 # dummy value 1537 1538 def xmlRead(self, name, attrs, content, parent): 1539 if hasattr(parent, "FDArray"): 1540 # if it is a CID-keyed font, then the private Dict is extracted from the 1541 # parent.FDArray 1542 fdArray = parent.FDArray 1543 if hasattr(parent, "FDSelect"): 1544 fdSelect = parent.FDSelect 1545 else: 1546 fdSelect = None 1547 private = None 1548 else: 1549 # if it is a name-keyed font, then the private dict is in the top dict, 1550 # and 1551 # there is no fdArray. 1552 private, fdSelect, fdArray = parent.Private, None, None 1553 charStrings = CharStrings( 1554 None, None, parent.GlobalSubrs, private, fdSelect, fdArray) 1555 charStrings.fromXML(name, attrs, content) 1556 return charStrings 1557 1558 1559class CharsetConverter(SimpleConverter): 1560 def _read(self, parent, value): 1561 isCID = hasattr(parent, "ROS") 1562 if value > 2: 1563 numGlyphs = parent.numGlyphs 1564 file = parent.file 1565 file.seek(value) 1566 log.log(DEBUG, "loading charset at %s", value) 1567 format = readCard8(file) 1568 if format == 0: 1569 charset = parseCharset0(numGlyphs, file, parent.strings, isCID) 1570 elif format == 1 or format == 2: 1571 charset = parseCharset(numGlyphs, file, parent.strings, isCID, format) 1572 else: 1573 raise NotImplementedError 1574 assert len(charset) == numGlyphs 1575 log.log(DEBUG, " charset end at %s", file.tell()) 1576 # make sure glyph names are unique 1577 allNames = {} 1578 newCharset = [] 1579 for glyphName in charset: 1580 if glyphName in allNames: 1581 # make up a new glyphName that's unique 1582 n = allNames[glyphName] 1583 while (glyphName + "#" + str(n)) in allNames: 1584 n += 1 1585 allNames[glyphName] = n + 1 1586 glyphName = glyphName + "#" + str(n) 1587 allNames[glyphName] = 1 1588 newCharset.append(glyphName) 1589 charset = newCharset 1590 else: # offset == 0 -> no charset data. 1591 if isCID or "CharStrings" not in parent.rawDict: 1592 # We get here only when processing fontDicts from the FDArray of 1593 # CFF-CID fonts. Only the real topDict references the chrset. 1594 assert value == 0 1595 charset = None 1596 elif value == 0: 1597 charset = cffISOAdobeStrings 1598 elif value == 1: 1599 charset = cffIExpertStrings 1600 elif value == 2: 1601 charset = cffExpertSubsetStrings 1602 if charset and (len(charset) != parent.numGlyphs): 1603 charset = charset[:parent.numGlyphs] 1604 return charset 1605 1606 def write(self, parent, value): 1607 return 0 # dummy value 1608 1609 def xmlWrite(self, xmlWriter, name, value): 1610 # XXX only write charset when not in OT/TTX context, where we 1611 # dump charset as a separate "GlyphOrder" table. 1612 # # xmlWriter.simpletag("charset") 1613 xmlWriter.comment("charset is dumped separately as the 'GlyphOrder' element") 1614 xmlWriter.newline() 1615 1616 def xmlRead(self, name, attrs, content, parent): 1617 pass 1618 1619 1620class CharsetCompiler(object): 1621 1622 def __init__(self, strings, charset, parent): 1623 assert charset[0] == '.notdef' 1624 isCID = hasattr(parent.dictObj, "ROS") 1625 data0 = packCharset0(charset, isCID, strings) 1626 data = packCharset(charset, isCID, strings) 1627 if len(data) < len(data0): 1628 self.data = data 1629 else: 1630 self.data = data0 1631 self.parent = parent 1632 1633 def setPos(self, pos, endPos): 1634 self.parent.rawDict["charset"] = pos 1635 1636 def getDataLength(self): 1637 return len(self.data) 1638 1639 def toFile(self, file): 1640 file.write(self.data) 1641 1642 1643def getStdCharSet(charset): 1644 # check to see if we can use a predefined charset value. 1645 predefinedCharSetVal = None 1646 predefinedCharSets = [ 1647 (cffISOAdobeStringCount, cffISOAdobeStrings, 0), 1648 (cffExpertStringCount, cffIExpertStrings, 1), 1649 (cffExpertSubsetStringCount, cffExpertSubsetStrings, 2)] 1650 lcs = len(charset) 1651 for cnt, pcs, csv in predefinedCharSets: 1652 if predefinedCharSetVal is not None: 1653 break 1654 if lcs > cnt: 1655 continue 1656 predefinedCharSetVal = csv 1657 for i in range(lcs): 1658 if charset[i] != pcs[i]: 1659 predefinedCharSetVal = None 1660 break 1661 return predefinedCharSetVal 1662 1663 1664def getCIDfromName(name, strings): 1665 return int(name[3:]) 1666 1667 1668def getSIDfromName(name, strings): 1669 return strings.getSID(name) 1670 1671 1672def packCharset0(charset, isCID, strings): 1673 fmt = 0 1674 data = [packCard8(fmt)] 1675 if isCID: 1676 getNameID = getCIDfromName 1677 else: 1678 getNameID = getSIDfromName 1679 1680 for name in charset[1:]: 1681 data.append(packCard16(getNameID(name, strings))) 1682 return bytesjoin(data) 1683 1684 1685def packCharset(charset, isCID, strings): 1686 fmt = 1 1687 ranges = [] 1688 first = None 1689 end = 0 1690 if isCID: 1691 getNameID = getCIDfromName 1692 else: 1693 getNameID = getSIDfromName 1694 1695 for name in charset[1:]: 1696 SID = getNameID(name, strings) 1697 if first is None: 1698 first = SID 1699 elif end + 1 != SID: 1700 nLeft = end - first 1701 if nLeft > 255: 1702 fmt = 2 1703 ranges.append((first, nLeft)) 1704 first = SID 1705 end = SID 1706 if end: 1707 nLeft = end - first 1708 if nLeft > 255: 1709 fmt = 2 1710 ranges.append((first, nLeft)) 1711 1712 data = [packCard8(fmt)] 1713 if fmt == 1: 1714 nLeftFunc = packCard8 1715 else: 1716 nLeftFunc = packCard16 1717 for first, nLeft in ranges: 1718 data.append(packCard16(first) + nLeftFunc(nLeft)) 1719 return bytesjoin(data) 1720 1721 1722def parseCharset0(numGlyphs, file, strings, isCID): 1723 charset = [".notdef"] 1724 if isCID: 1725 for i in range(numGlyphs - 1): 1726 CID = readCard16(file) 1727 charset.append("cid" + str(CID).zfill(5)) 1728 else: 1729 for i in range(numGlyphs - 1): 1730 SID = readCard16(file) 1731 charset.append(strings[SID]) 1732 return charset 1733 1734 1735def parseCharset(numGlyphs, file, strings, isCID, fmt): 1736 charset = ['.notdef'] 1737 count = 1 1738 if fmt == 1: 1739 nLeftFunc = readCard8 1740 else: 1741 nLeftFunc = readCard16 1742 while count < numGlyphs: 1743 first = readCard16(file) 1744 nLeft = nLeftFunc(file) 1745 if isCID: 1746 for CID in range(first, first + nLeft + 1): 1747 charset.append("cid" + str(CID).zfill(5)) 1748 else: 1749 for SID in range(first, first + nLeft + 1): 1750 charset.append(strings[SID]) 1751 count = count + nLeft + 1 1752 return charset 1753 1754 1755class EncodingCompiler(object): 1756 1757 def __init__(self, strings, encoding, parent): 1758 assert not isinstance(encoding, str) 1759 data0 = packEncoding0(parent.dictObj.charset, encoding, parent.strings) 1760 data1 = packEncoding1(parent.dictObj.charset, encoding, parent.strings) 1761 if len(data0) < len(data1): 1762 self.data = data0 1763 else: 1764 self.data = data1 1765 self.parent = parent 1766 1767 def setPos(self, pos, endPos): 1768 self.parent.rawDict["Encoding"] = pos 1769 1770 def getDataLength(self): 1771 return len(self.data) 1772 1773 def toFile(self, file): 1774 file.write(self.data) 1775 1776 1777class EncodingConverter(SimpleConverter): 1778 1779 def _read(self, parent, value): 1780 if value == 0: 1781 return "StandardEncoding" 1782 elif value == 1: 1783 return "ExpertEncoding" 1784 else: 1785 assert value > 1 1786 file = parent.file 1787 file.seek(value) 1788 log.log(DEBUG, "loading Encoding at %s", value) 1789 fmt = readCard8(file) 1790 haveSupplement = fmt & 0x80 1791 if haveSupplement: 1792 raise NotImplementedError("Encoding supplements are not yet supported") 1793 fmt = fmt & 0x7f 1794 if fmt == 0: 1795 encoding = parseEncoding0(parent.charset, file, haveSupplement, 1796 parent.strings) 1797 elif fmt == 1: 1798 encoding = parseEncoding1(parent.charset, file, haveSupplement, 1799 parent.strings) 1800 return encoding 1801 1802 def write(self, parent, value): 1803 if value == "StandardEncoding": 1804 return 0 1805 elif value == "ExpertEncoding": 1806 return 1 1807 return 0 # dummy value 1808 1809 def xmlWrite(self, xmlWriter, name, value): 1810 if value in ("StandardEncoding", "ExpertEncoding"): 1811 xmlWriter.simpletag(name, name=value) 1812 xmlWriter.newline() 1813 return 1814 xmlWriter.begintag(name) 1815 xmlWriter.newline() 1816 for code in range(len(value)): 1817 glyphName = value[code] 1818 if glyphName != ".notdef": 1819 xmlWriter.simpletag("map", code=hex(code), name=glyphName) 1820 xmlWriter.newline() 1821 xmlWriter.endtag(name) 1822 xmlWriter.newline() 1823 1824 def xmlRead(self, name, attrs, content, parent): 1825 if "name" in attrs: 1826 return attrs["name"] 1827 encoding = [".notdef"] * 256 1828 for element in content: 1829 if isinstance(element, str): 1830 continue 1831 name, attrs, content = element 1832 code = safeEval(attrs["code"]) 1833 glyphName = attrs["name"] 1834 encoding[code] = glyphName 1835 return encoding 1836 1837 1838def parseEncoding0(charset, file, haveSupplement, strings): 1839 nCodes = readCard8(file) 1840 encoding = [".notdef"] * 256 1841 for glyphID in range(1, nCodes + 1): 1842 code = readCard8(file) 1843 if code != 0: 1844 encoding[code] = charset[glyphID] 1845 return encoding 1846 1847 1848def parseEncoding1(charset, file, haveSupplement, strings): 1849 nRanges = readCard8(file) 1850 encoding = [".notdef"] * 256 1851 glyphID = 1 1852 for i in range(nRanges): 1853 code = readCard8(file) 1854 nLeft = readCard8(file) 1855 for glyphID in range(glyphID, glyphID + nLeft + 1): 1856 encoding[code] = charset[glyphID] 1857 code = code + 1 1858 glyphID = glyphID + 1 1859 return encoding 1860 1861 1862def packEncoding0(charset, encoding, strings): 1863 fmt = 0 1864 m = {} 1865 for code in range(len(encoding)): 1866 name = encoding[code] 1867 if name != ".notdef": 1868 m[name] = code 1869 codes = [] 1870 for name in charset[1:]: 1871 code = m.get(name) 1872 codes.append(code) 1873 1874 while codes and codes[-1] is None: 1875 codes.pop() 1876 1877 data = [packCard8(fmt), packCard8(len(codes))] 1878 for code in codes: 1879 if code is None: 1880 code = 0 1881 data.append(packCard8(code)) 1882 return bytesjoin(data) 1883 1884 1885def packEncoding1(charset, encoding, strings): 1886 fmt = 1 1887 m = {} 1888 for code in range(len(encoding)): 1889 name = encoding[code] 1890 if name != ".notdef": 1891 m[name] = code 1892 ranges = [] 1893 first = None 1894 end = 0 1895 for name in charset[1:]: 1896 code = m.get(name, -1) 1897 if first is None: 1898 first = code 1899 elif end + 1 != code: 1900 nLeft = end - first 1901 ranges.append((first, nLeft)) 1902 first = code 1903 end = code 1904 nLeft = end - first 1905 ranges.append((first, nLeft)) 1906 1907 # remove unencoded glyphs at the end. 1908 while ranges and ranges[-1][0] == -1: 1909 ranges.pop() 1910 1911 data = [packCard8(fmt), packCard8(len(ranges))] 1912 for first, nLeft in ranges: 1913 if first == -1: # unencoded 1914 first = 0 1915 data.append(packCard8(first) + packCard8(nLeft)) 1916 return bytesjoin(data) 1917 1918 1919class FDArrayConverter(TableConverter): 1920 1921 def _read(self, parent, value): 1922 try: 1923 vstore = parent.VarStore 1924 except AttributeError: 1925 vstore = None 1926 file = parent.file 1927 isCFF2 = parent._isCFF2 1928 file.seek(value) 1929 fdArray = FDArrayIndex(file, isCFF2=isCFF2) 1930 fdArray.vstore = vstore 1931 fdArray.strings = parent.strings 1932 fdArray.GlobalSubrs = parent.GlobalSubrs 1933 return fdArray 1934 1935 def write(self, parent, value): 1936 return 0 # dummy value 1937 1938 def xmlRead(self, name, attrs, content, parent): 1939 fdArray = FDArrayIndex() 1940 for element in content: 1941 if isinstance(element, str): 1942 continue 1943 name, attrs, content = element 1944 fdArray.fromXML(name, attrs, content) 1945 return fdArray 1946 1947 1948class FDSelectConverter(SimpleConverter): 1949 1950 def _read(self, parent, value): 1951 file = parent.file 1952 file.seek(value) 1953 fdSelect = FDSelect(file, parent.numGlyphs) 1954 return fdSelect 1955 1956 def write(self, parent, value): 1957 return 0 # dummy value 1958 1959 # The FDSelect glyph data is written out to XML in the charstring keys, 1960 # so we write out only the format selector 1961 def xmlWrite(self, xmlWriter, name, value): 1962 xmlWriter.simpletag(name, [('format', value.format)]) 1963 xmlWriter.newline() 1964 1965 def xmlRead(self, name, attrs, content, parent): 1966 fmt = safeEval(attrs["format"]) 1967 file = None 1968 numGlyphs = None 1969 fdSelect = FDSelect(file, numGlyphs, fmt) 1970 return fdSelect 1971 1972 1973class VarStoreConverter(SimpleConverter): 1974 1975 def _read(self, parent, value): 1976 file = parent.file 1977 file.seek(value) 1978 varStore = VarStoreData(file) 1979 varStore.decompile() 1980 return varStore 1981 1982 def write(self, parent, value): 1983 return 0 # dummy value 1984 1985 def xmlWrite(self, xmlWriter, name, value): 1986 value.writeXML(xmlWriter, name) 1987 1988 def xmlRead(self, name, attrs, content, parent): 1989 varStore = VarStoreData() 1990 varStore.xmlRead(name, attrs, content, parent) 1991 return varStore 1992 1993 1994def packFDSelect0(fdSelectArray): 1995 fmt = 0 1996 data = [packCard8(fmt)] 1997 for index in fdSelectArray: 1998 data.append(packCard8(index)) 1999 return bytesjoin(data) 2000 2001 2002def packFDSelect3(fdSelectArray): 2003 fmt = 3 2004 fdRanges = [] 2005 lenArray = len(fdSelectArray) 2006 lastFDIndex = -1 2007 for i in range(lenArray): 2008 fdIndex = fdSelectArray[i] 2009 if lastFDIndex != fdIndex: 2010 fdRanges.append([i, fdIndex]) 2011 lastFDIndex = fdIndex 2012 sentinelGID = i + 1 2013 2014 data = [packCard8(fmt)] 2015 data.append(packCard16(len(fdRanges))) 2016 for fdRange in fdRanges: 2017 data.append(packCard16(fdRange[0])) 2018 data.append(packCard8(fdRange[1])) 2019 data.append(packCard16(sentinelGID)) 2020 return bytesjoin(data) 2021 2022 2023def packFDSelect4(fdSelectArray): 2024 fmt = 4 2025 fdRanges = [] 2026 lenArray = len(fdSelectArray) 2027 lastFDIndex = -1 2028 for i in range(lenArray): 2029 fdIndex = fdSelectArray[i] 2030 if lastFDIndex != fdIndex: 2031 fdRanges.append([i, fdIndex]) 2032 lastFDIndex = fdIndex 2033 sentinelGID = i + 1 2034 2035 data = [packCard8(fmt)] 2036 data.append(packCard32(len(fdRanges))) 2037 for fdRange in fdRanges: 2038 data.append(packCard32(fdRange[0])) 2039 data.append(packCard16(fdRange[1])) 2040 data.append(packCard32(sentinelGID)) 2041 return bytesjoin(data) 2042 2043 2044class FDSelectCompiler(object): 2045 2046 def __init__(self, fdSelect, parent): 2047 fmt = fdSelect.format 2048 fdSelectArray = fdSelect.gidArray 2049 if fmt == 0: 2050 self.data = packFDSelect0(fdSelectArray) 2051 elif fmt == 3: 2052 self.data = packFDSelect3(fdSelectArray) 2053 elif fmt == 4: 2054 self.data = packFDSelect4(fdSelectArray) 2055 else: 2056 # choose smaller of the two formats 2057 data0 = packFDSelect0(fdSelectArray) 2058 data3 = packFDSelect3(fdSelectArray) 2059 if len(data0) < len(data3): 2060 self.data = data0 2061 fdSelect.format = 0 2062 else: 2063 self.data = data3 2064 fdSelect.format = 3 2065 2066 self.parent = parent 2067 2068 def setPos(self, pos, endPos): 2069 self.parent.rawDict["FDSelect"] = pos 2070 2071 def getDataLength(self): 2072 return len(self.data) 2073 2074 def toFile(self, file): 2075 file.write(self.data) 2076 2077 2078class VarStoreCompiler(object): 2079 2080 def __init__(self, varStoreData, parent): 2081 self.parent = parent 2082 if not varStoreData.data: 2083 varStoreData.compile() 2084 data = [ 2085 packCard16(len(varStoreData.data)), 2086 varStoreData.data 2087 ] 2088 self.data = bytesjoin(data) 2089 2090 def setPos(self, pos, endPos): 2091 self.parent.rawDict["VarStore"] = pos 2092 2093 def getDataLength(self): 2094 return len(self.data) 2095 2096 def toFile(self, file): 2097 file.write(self.data) 2098 2099 2100class ROSConverter(SimpleConverter): 2101 2102 def xmlWrite(self, xmlWriter, name, value): 2103 registry, order, supplement = value 2104 xmlWriter.simpletag( 2105 name, 2106 [ 2107 ('Registry', tostr(registry)), 2108 ('Order', tostr(order)), 2109 ('Supplement', supplement) 2110 ]) 2111 xmlWriter.newline() 2112 2113 def xmlRead(self, name, attrs, content, parent): 2114 return (attrs['Registry'], attrs['Order'], safeEval(attrs['Supplement'])) 2115 2116topDictOperators = [ 2117# opcode name argument type default converter 2118 (25, 'maxstack', 'number', None, None), 2119 ((12, 30), 'ROS', ('SID', 'SID', 'number'), None, ROSConverter()), 2120 ((12, 20), 'SyntheticBase', 'number', None, None), 2121 (0, 'version', 'SID', None, None), 2122 (1, 'Notice', 'SID', None, Latin1Converter()), 2123 ((12, 0), 'Copyright', 'SID', None, Latin1Converter()), 2124 (2, 'FullName', 'SID', None, None), 2125 ((12, 38), 'FontName', 'SID', None, None), 2126 (3, 'FamilyName', 'SID', None, None), 2127 (4, 'Weight', 'SID', None, None), 2128 ((12, 1), 'isFixedPitch', 'number', 0, None), 2129 ((12, 2), 'ItalicAngle', 'number', 0, None), 2130 ((12, 3), 'UnderlinePosition', 'number', -100, None), 2131 ((12, 4), 'UnderlineThickness', 'number', 50, None), 2132 ((12, 5), 'PaintType', 'number', 0, None), 2133 ((12, 6), 'CharstringType', 'number', 2, None), 2134 ((12, 7), 'FontMatrix', 'array', [0.001, 0, 0, 0.001, 0, 0], None), 2135 (13, 'UniqueID', 'number', None, None), 2136 (5, 'FontBBox', 'array', [0, 0, 0, 0], None), 2137 ((12, 8), 'StrokeWidth', 'number', 0, None), 2138 (14, 'XUID', 'array', None, None), 2139 ((12, 21), 'PostScript', 'SID', None, None), 2140 ((12, 22), 'BaseFontName', 'SID', None, None), 2141 ((12, 23), 'BaseFontBlend', 'delta', None, None), 2142 ((12, 31), 'CIDFontVersion', 'number', 0, None), 2143 ((12, 32), 'CIDFontRevision', 'number', 0, None), 2144 ((12, 33), 'CIDFontType', 'number', 0, None), 2145 ((12, 34), 'CIDCount', 'number', 8720, None), 2146 (15, 'charset', 'number', None, CharsetConverter()), 2147 ((12, 35), 'UIDBase', 'number', None, None), 2148 (16, 'Encoding', 'number', 0, EncodingConverter()), 2149 (18, 'Private', ('number', 'number'), None, PrivateDictConverter()), 2150 ((12, 37), 'FDSelect', 'number', None, FDSelectConverter()), 2151 ((12, 36), 'FDArray', 'number', None, FDArrayConverter()), 2152 (17, 'CharStrings', 'number', None, CharStringsConverter()), 2153 (24, 'VarStore', 'number', None, VarStoreConverter()), 2154] 2155 2156topDictOperators2 = [ 2157# opcode name argument type default converter 2158 (25, 'maxstack', 'number', None, None), 2159 ((12, 7), 'FontMatrix', 'array', [0.001, 0, 0, 0.001, 0, 0], None), 2160 ((12, 37), 'FDSelect', 'number', None, FDSelectConverter()), 2161 ((12, 36), 'FDArray', 'number', None, FDArrayConverter()), 2162 (17, 'CharStrings', 'number', None, CharStringsConverter()), 2163 (24, 'VarStore', 'number', None, VarStoreConverter()), 2164] 2165 2166# Note! FDSelect and FDArray must both preceed CharStrings in the output XML build order, 2167# in order for the font to compile back from xml. 2168 2169kBlendDictOpName = "blend" 2170blendOp = 23 2171 2172privateDictOperators = [ 2173# opcode name argument type default converter 2174 (22, "vsindex", 'number', None, None), 2175 (blendOp, kBlendDictOpName, 'blendList', None, None), # This is for reading to/from XML: it not written to CFF. 2176 (6, 'BlueValues', 'delta', None, None), 2177 (7, 'OtherBlues', 'delta', None, None), 2178 (8, 'FamilyBlues', 'delta', None, None), 2179 (9, 'FamilyOtherBlues', 'delta', None, None), 2180 ((12, 9), 'BlueScale', 'number', 0.039625, None), 2181 ((12, 10), 'BlueShift', 'number', 7, None), 2182 ((12, 11), 'BlueFuzz', 'number', 1, None), 2183 (10, 'StdHW', 'number', None, None), 2184 (11, 'StdVW', 'number', None, None), 2185 ((12, 12), 'StemSnapH', 'delta', None, None), 2186 ((12, 13), 'StemSnapV', 'delta', None, None), 2187 ((12, 14), 'ForceBold', 'number', 0, None), 2188 ((12, 15), 'ForceBoldThreshold', 'number', None, None), # deprecated 2189 ((12, 16), 'lenIV', 'number', None, None), # deprecated 2190 ((12, 17), 'LanguageGroup', 'number', 0, None), 2191 ((12, 18), 'ExpansionFactor', 'number', 0.06, None), 2192 ((12, 19), 'initialRandomSeed', 'number', 0, None), 2193 (20, 'defaultWidthX', 'number', 0, None), 2194 (21, 'nominalWidthX', 'number', 0, None), 2195 (19, 'Subrs', 'number', None, SubrsConverter()), 2196] 2197 2198privateDictOperators2 = [ 2199# opcode name argument type default converter 2200 (22, "vsindex", 'number', None, None), 2201 (blendOp, kBlendDictOpName, 'blendList', None, None), # This is for reading to/from XML: it not written to CFF. 2202 (6, 'BlueValues', 'delta', None, None), 2203 (7, 'OtherBlues', 'delta', None, None), 2204 (8, 'FamilyBlues', 'delta', None, None), 2205 (9, 'FamilyOtherBlues', 'delta', None, None), 2206 ((12, 9), 'BlueScale', 'number', 0.039625, None), 2207 ((12, 10), 'BlueShift', 'number', 7, None), 2208 ((12, 11), 'BlueFuzz', 'number', 1, None), 2209 (10, 'StdHW', 'number', None, None), 2210 (11, 'StdVW', 'number', None, None), 2211 ((12, 12), 'StemSnapH', 'delta', None, None), 2212 ((12, 13), 'StemSnapV', 'delta', None, None), 2213 ((12, 17), 'LanguageGroup', 'number', 0, None), 2214 ((12, 18), 'ExpansionFactor', 'number', 0.06, None), 2215 (19, 'Subrs', 'number', None, SubrsConverter()), 2216] 2217 2218 2219def addConverters(table): 2220 for i in range(len(table)): 2221 op, name, arg, default, conv = table[i] 2222 if conv is not None: 2223 continue 2224 if arg in ("delta", "array"): 2225 conv = ArrayConverter() 2226 elif arg == "number": 2227 conv = NumberConverter() 2228 elif arg == "SID": 2229 conv = ASCIIConverter() 2230 elif arg == 'blendList': 2231 conv = None 2232 else: 2233 assert False 2234 table[i] = op, name, arg, default, conv 2235 2236 2237addConverters(privateDictOperators) 2238addConverters(topDictOperators) 2239 2240 2241class TopDictDecompiler(psCharStrings.DictDecompiler): 2242 operators = buildOperatorDict(topDictOperators) 2243 2244 2245class PrivateDictDecompiler(psCharStrings.DictDecompiler): 2246 operators = buildOperatorDict(privateDictOperators) 2247 2248 2249class DictCompiler(object): 2250 maxBlendStack = 0 2251 2252 def __init__(self, dictObj, strings, parent, isCFF2=None): 2253 if strings: 2254 assert isinstance(strings, IndexedStrings) 2255 if isCFF2 is None and hasattr(parent, "isCFF2"): 2256 isCFF2 = parent.isCFF2 2257 assert isCFF2 is not None 2258 self.isCFF2 = isCFF2 2259 self.dictObj = dictObj 2260 self.strings = strings 2261 self.parent = parent 2262 rawDict = {} 2263 for name in dictObj.order: 2264 value = getattr(dictObj, name, None) 2265 if value is None: 2266 continue 2267 conv = dictObj.converters[name] 2268 value = conv.write(dictObj, value) 2269 if value == dictObj.defaults.get(name): 2270 continue 2271 rawDict[name] = value 2272 self.rawDict = rawDict 2273 2274 def setPos(self, pos, endPos): 2275 pass 2276 2277 def getDataLength(self): 2278 return len(self.compile("getDataLength")) 2279 2280 def compile(self, reason): 2281 log.log(DEBUG, "-- compiling %s for %s", self.__class__.__name__, reason) 2282 rawDict = self.rawDict 2283 data = [] 2284 for name in self.dictObj.order: 2285 value = rawDict.get(name) 2286 if value is None: 2287 continue 2288 op, argType = self.opcodes[name] 2289 if isinstance(argType, tuple): 2290 l = len(argType) 2291 assert len(value) == l, "value doesn't match arg type" 2292 for i in range(l): 2293 arg = argType[i] 2294 v = value[i] 2295 arghandler = getattr(self, "arg_" + arg) 2296 data.append(arghandler(v)) 2297 else: 2298 arghandler = getattr(self, "arg_" + argType) 2299 data.append(arghandler(value)) 2300 data.append(op) 2301 data = bytesjoin(data) 2302 return data 2303 2304 def toFile(self, file): 2305 data = self.compile("toFile") 2306 file.write(data) 2307 2308 def arg_number(self, num): 2309 if isinstance(num, list): 2310 data = [encodeNumber(val) for val in num] 2311 data.append(encodeNumber(1)) 2312 data.append(bytechr(blendOp)) 2313 datum = bytesjoin(data) 2314 else: 2315 datum = encodeNumber(num) 2316 return datum 2317 2318 def arg_SID(self, s): 2319 return psCharStrings.encodeIntCFF(self.strings.getSID(s)) 2320 2321 def arg_array(self, value): 2322 data = [] 2323 for num in value: 2324 data.append(self.arg_number(num)) 2325 return bytesjoin(data) 2326 2327 def arg_delta(self, value): 2328 if not value: 2329 return b"" 2330 val0 = value[0] 2331 if isinstance(val0, list): 2332 data = self.arg_delta_blend(value) 2333 else: 2334 out = [] 2335 last = 0 2336 for v in value: 2337 out.append(v - last) 2338 last = v 2339 data = [] 2340 for num in out: 2341 data.append(encodeNumber(num)) 2342 return bytesjoin(data) 2343 2344 2345 def arg_delta_blend(self, value): 2346 """A delta list with blend lists has to be *all* blend lists. 2347 2348 The value is a list is arranged as follows:: 2349 2350 [ 2351 [V0, d0..dn] 2352 [V1, d0..dn] 2353 ... 2354 [Vm, d0..dn] 2355 ] 2356 2357 ``V`` is the absolute coordinate value from the default font, and ``d0-dn`` 2358 are the delta values from the *n* regions. Each ``V`` is an absolute 2359 coordinate from the default font. 2360 2361 We want to return a list:: 2362 2363 [ 2364 [v0, v1..vm] 2365 [d0..dn] 2366 ... 2367 [d0..dn] 2368 numBlends 2369 blendOp 2370 ] 2371 2372 where each ``v`` is relative to the previous default font value. 2373 """ 2374 numMasters = len(value[0]) 2375 numBlends = len(value) 2376 numStack = (numBlends * numMasters) + 1 2377 if numStack > self.maxBlendStack: 2378 # Figure out the max number of value we can blend 2379 # and divide this list up into chunks of that size. 2380 2381 numBlendValues = int((self.maxBlendStack - 1) / numMasters) 2382 out = [] 2383 while True: 2384 numVal = min(len(value), numBlendValues) 2385 if numVal == 0: 2386 break 2387 valList = value[0:numVal] 2388 out1 = self.arg_delta_blend(valList) 2389 out.extend(out1) 2390 value = value[numVal:] 2391 else: 2392 firstList = [0] * numBlends 2393 deltaList = [None] * numBlends 2394 i = 0 2395 prevVal = 0 2396 while i < numBlends: 2397 # For PrivateDict BlueValues, the default font 2398 # values are absolute, not relative. 2399 # Must convert these back to relative coordinates 2400 # befor writing to CFF2. 2401 defaultValue = value[i][0] 2402 firstList[i] = defaultValue - prevVal 2403 prevVal = defaultValue 2404 deltaList[i] = value[i][1:] 2405 i += 1 2406 2407 relValueList = firstList 2408 for blendList in deltaList: 2409 relValueList.extend(blendList) 2410 out = [encodeNumber(val) for val in relValueList] 2411 out.append(encodeNumber(numBlends)) 2412 out.append(bytechr(blendOp)) 2413 return out 2414 2415 2416def encodeNumber(num): 2417 if isinstance(num, float): 2418 return psCharStrings.encodeFloat(num) 2419 else: 2420 return psCharStrings.encodeIntCFF(num) 2421 2422 2423class TopDictCompiler(DictCompiler): 2424 2425 opcodes = buildOpcodeDict(topDictOperators) 2426 2427 def getChildren(self, strings): 2428 isCFF2 = self.isCFF2 2429 children = [] 2430 if self.dictObj.cff2GetGlyphOrder is None: 2431 if hasattr(self.dictObj, "charset") and self.dictObj.charset: 2432 if hasattr(self.dictObj, "ROS"): # aka isCID 2433 charsetCode = None 2434 else: 2435 charsetCode = getStdCharSet(self.dictObj.charset) 2436 if charsetCode is None: 2437 children.append(CharsetCompiler(strings, self.dictObj.charset, self)) 2438 else: 2439 self.rawDict["charset"] = charsetCode 2440 if hasattr(self.dictObj, "Encoding") and self.dictObj.Encoding: 2441 encoding = self.dictObj.Encoding 2442 if not isinstance(encoding, str): 2443 children.append(EncodingCompiler(strings, encoding, self)) 2444 else: 2445 if hasattr(self.dictObj, "VarStore"): 2446 varStoreData = self.dictObj.VarStore 2447 varStoreComp = VarStoreCompiler(varStoreData, self) 2448 children.append(varStoreComp) 2449 if hasattr(self.dictObj, "FDSelect"): 2450 # I have not yet supported merging a ttx CFF-CID font, as there are 2451 # interesting issues about merging the FDArrays. Here I assume that 2452 # either the font was read from XML, and the FDSelect indices are all 2453 # in the charstring data, or the FDSelect array is already fully defined. 2454 fdSelect = self.dictObj.FDSelect 2455 # probably read in from XML; assume fdIndex in CharString data 2456 if len(fdSelect) == 0: 2457 charStrings = self.dictObj.CharStrings 2458 for name in self.dictObj.charset: 2459 fdSelect.append(charStrings[name].fdSelectIndex) 2460 fdSelectComp = FDSelectCompiler(fdSelect, self) 2461 children.append(fdSelectComp) 2462 if hasattr(self.dictObj, "CharStrings"): 2463 items = [] 2464 charStrings = self.dictObj.CharStrings 2465 for name in self.dictObj.charset: 2466 items.append(charStrings[name]) 2467 charStringsComp = CharStringsCompiler( 2468 items, strings, self, isCFF2=isCFF2) 2469 children.append(charStringsComp) 2470 if hasattr(self.dictObj, "FDArray"): 2471 # I have not yet supported merging a ttx CFF-CID font, as there are 2472 # interesting issues about merging the FDArrays. Here I assume that the 2473 # FDArray info is correct and complete. 2474 fdArrayIndexComp = self.dictObj.FDArray.getCompiler(strings, self) 2475 children.append(fdArrayIndexComp) 2476 children.extend(fdArrayIndexComp.getChildren(strings)) 2477 if hasattr(self.dictObj, "Private"): 2478 privComp = self.dictObj.Private.getCompiler(strings, self) 2479 children.append(privComp) 2480 children.extend(privComp.getChildren(strings)) 2481 return children 2482 2483 2484class FontDictCompiler(DictCompiler): 2485 opcodes = buildOpcodeDict(topDictOperators) 2486 2487 def __init__(self, dictObj, strings, parent, isCFF2=None): 2488 super(FontDictCompiler, self).__init__(dictObj, strings, parent, isCFF2=isCFF2) 2489 # 2490 # We now take some effort to detect if there were any key/value pairs 2491 # supplied that were ignored in the FontDict context, and issue a warning 2492 # for those cases. 2493 # 2494 ignoredNames = [] 2495 dictObj = self.dictObj 2496 for name in sorted(set(dictObj.converters) - set(dictObj.order)): 2497 if name in dictObj.rawDict: 2498 # The font was directly read from binary. In this 2499 # case, we want to report *all* "useless" key/value 2500 # pairs that are in the font, not just the ones that 2501 # are different from the default. 2502 ignoredNames.append(name) 2503 else: 2504 # The font was probably read from a TTX file. We only 2505 # warn about keys whos value is not the default. The 2506 # ones that have the default value will not be written 2507 # to binary anyway. 2508 default = dictObj.defaults.get(name) 2509 if default is not None: 2510 conv = dictObj.converters[name] 2511 default = conv.read(dictObj, default) 2512 if getattr(dictObj, name, None) != default: 2513 ignoredNames.append(name) 2514 if ignoredNames: 2515 log.warning( 2516 "Some CFF FDArray/FontDict keys were ignored upon compile: " + 2517 " ".join(sorted(ignoredNames))) 2518 2519 def getChildren(self, strings): 2520 children = [] 2521 if hasattr(self.dictObj, "Private"): 2522 privComp = self.dictObj.Private.getCompiler(strings, self) 2523 children.append(privComp) 2524 children.extend(privComp.getChildren(strings)) 2525 return children 2526 2527 2528class PrivateDictCompiler(DictCompiler): 2529 2530 maxBlendStack = maxStackLimit 2531 opcodes = buildOpcodeDict(privateDictOperators) 2532 2533 def setPos(self, pos, endPos): 2534 size = endPos - pos 2535 self.parent.rawDict["Private"] = size, pos 2536 self.pos = pos 2537 2538 def getChildren(self, strings): 2539 children = [] 2540 if hasattr(self.dictObj, "Subrs"): 2541 children.append(self.dictObj.Subrs.getCompiler(strings, self)) 2542 return children 2543 2544 2545class BaseDict(object): 2546 2547 def __init__(self, strings=None, file=None, offset=None, isCFF2=None): 2548 assert (isCFF2 is None) == (file is None) 2549 self.rawDict = {} 2550 self.skipNames = [] 2551 self.strings = strings 2552 if file is None: 2553 return 2554 self._isCFF2 = isCFF2 2555 self.file = file 2556 if offset is not None: 2557 log.log(DEBUG, "loading %s at %s", self.__class__.__name__, offset) 2558 self.offset = offset 2559 2560 def decompile(self, data): 2561 log.log(DEBUG, " length %s is %d", self.__class__.__name__, len(data)) 2562 dec = self.decompilerClass(self.strings, self) 2563 dec.decompile(data) 2564 self.rawDict = dec.getDict() 2565 self.postDecompile() 2566 2567 def postDecompile(self): 2568 pass 2569 2570 def getCompiler(self, strings, parent, isCFF2=None): 2571 return self.compilerClass(self, strings, parent, isCFF2=isCFF2) 2572 2573 def __getattr__(self, name): 2574 if name[:2] == name[-2:] == "__": 2575 # to make deepcopy() and pickle.load() work, we need to signal with 2576 # AttributeError that dunder methods like '__deepcopy__' or '__getstate__' 2577 # aren't implemented. For more details, see: 2578 # https://github.com/fonttools/fonttools/pull/1488 2579 raise AttributeError(name) 2580 value = self.rawDict.get(name, None) 2581 if value is None: 2582 value = self.defaults.get(name) 2583 if value is None: 2584 raise AttributeError(name) 2585 conv = self.converters[name] 2586 value = conv.read(self, value) 2587 setattr(self, name, value) 2588 return value 2589 2590 def toXML(self, xmlWriter): 2591 for name in self.order: 2592 if name in self.skipNames: 2593 continue 2594 value = getattr(self, name, None) 2595 # XXX For "charset" we never skip calling xmlWrite even if the 2596 # value is None, so we always write the following XML comment: 2597 # 2598 # <!-- charset is dumped separately as the 'GlyphOrder' element --> 2599 # 2600 # Charset is None when 'CFF ' table is imported from XML into an 2601 # empty TTFont(). By writing this comment all the time, we obtain 2602 # the same XML output whether roundtripping XML-to-XML or 2603 # dumping binary-to-XML 2604 if value is None and name != "charset": 2605 continue 2606 conv = self.converters[name] 2607 conv.xmlWrite(xmlWriter, name, value) 2608 ignoredNames = set(self.rawDict) - set(self.order) 2609 if ignoredNames: 2610 xmlWriter.comment( 2611 "some keys were ignored: %s" % " ".join(sorted(ignoredNames))) 2612 xmlWriter.newline() 2613 2614 def fromXML(self, name, attrs, content): 2615 conv = self.converters[name] 2616 value = conv.xmlRead(name, attrs, content, self) 2617 setattr(self, name, value) 2618 2619 2620class TopDict(BaseDict): 2621 """The ``TopDict`` represents the top-level dictionary holding font 2622 information. CFF2 tables contain a restricted set of top-level entries 2623 as described `here <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#7-top-dict-data>`_, 2624 but CFF tables may contain a wider range of information. This information 2625 can be accessed through attributes or through the dictionary returned 2626 through the ``rawDict`` property: 2627 2628 .. code:: python 2629 2630 font = tt["CFF "].cff[0] 2631 font.FamilyName 2632 # 'Linux Libertine O' 2633 font.rawDict["FamilyName"] 2634 # 'Linux Libertine O' 2635 2636 More information is available in the CFF file's private dictionary, accessed 2637 via the ``Private`` property: 2638 2639 .. code:: python 2640 2641 tt["CFF "].cff[0].Private.BlueValues 2642 # [-15, 0, 515, 515, 666, 666] 2643 2644 """ 2645 2646 defaults = buildDefaults(topDictOperators) 2647 converters = buildConverters(topDictOperators) 2648 compilerClass = TopDictCompiler 2649 order = buildOrder(topDictOperators) 2650 decompilerClass = TopDictDecompiler 2651 2652 def __init__(self, strings=None, file=None, offset=None, 2653 GlobalSubrs=None, cff2GetGlyphOrder=None, isCFF2=None): 2654 super(TopDict, self).__init__(strings, file, offset, isCFF2=isCFF2) 2655 self.cff2GetGlyphOrder = cff2GetGlyphOrder 2656 self.GlobalSubrs = GlobalSubrs 2657 if isCFF2: 2658 self.defaults = buildDefaults(topDictOperators2) 2659 self.charset = cff2GetGlyphOrder() 2660 self.order = buildOrder(topDictOperators2) 2661 else: 2662 self.defaults = buildDefaults(topDictOperators) 2663 self.order = buildOrder(topDictOperators) 2664 2665 def getGlyphOrder(self): 2666 """Returns a list of glyph names in the CFF font.""" 2667 return self.charset 2668 2669 def postDecompile(self): 2670 offset = self.rawDict.get("CharStrings") 2671 if offset is None: 2672 return 2673 # get the number of glyphs beforehand. 2674 self.file.seek(offset) 2675 if self._isCFF2: 2676 self.numGlyphs = readCard32(self.file) 2677 else: 2678 self.numGlyphs = readCard16(self.file) 2679 2680 def toXML(self, xmlWriter): 2681 if hasattr(self, "CharStrings"): 2682 self.decompileAllCharStrings() 2683 if hasattr(self, "ROS"): 2684 self.skipNames = ['Encoding'] 2685 if not hasattr(self, "ROS") or not hasattr(self, "CharStrings"): 2686 # these values have default values, but I only want them to show up 2687 # in CID fonts. 2688 self.skipNames = [ 2689 'CIDFontVersion', 'CIDFontRevision', 'CIDFontType', 'CIDCount'] 2690 BaseDict.toXML(self, xmlWriter) 2691 2692 def decompileAllCharStrings(self): 2693 # Make sure that all the Private Dicts have been instantiated. 2694 for i, charString in enumerate(self.CharStrings.values()): 2695 try: 2696 charString.decompile() 2697 except: 2698 log.error("Error in charstring %s", i) 2699 raise 2700 2701 def recalcFontBBox(self): 2702 fontBBox = None 2703 for charString in self.CharStrings.values(): 2704 bounds = charString.calcBounds(self.CharStrings) 2705 if bounds is not None: 2706 if fontBBox is not None: 2707 fontBBox = unionRect(fontBBox, bounds) 2708 else: 2709 fontBBox = bounds 2710 2711 if fontBBox is None: 2712 self.FontBBox = self.defaults['FontBBox'][:] 2713 else: 2714 self.FontBBox = list(intRect(fontBBox)) 2715 2716 2717class FontDict(BaseDict): 2718 # 2719 # Since fonttools used to pass a lot of fields that are not relevant in the FDArray 2720 # FontDict, there are 'ttx' files in the wild that contain all these. These got in 2721 # the ttx files because fonttools writes explicit values for all the TopDict default 2722 # values. These are not actually illegal in the context of an FDArray FontDict - you 2723 # can legally, per spec, put any arbitrary key/value pair in a FontDict - but are 2724 # useless since current major company CFF interpreters ignore anything but the set 2725 # listed in this file. So, we just silently skip them. An exception is Weight: this 2726 # is not used by any interpreter, but some foundries have asked that this be 2727 # supported in FDArray FontDicts just to preserve information about the design when 2728 # the font is being inspected. 2729 # 2730 # On top of that, there are fonts out there that contain such useless FontDict values. 2731 # 2732 # By subclassing TopDict, we *allow* all key/values from TopDict, both when reading 2733 # from binary or when reading from XML, but by overriding `order` with a limited 2734 # list of names, we ensure that only the useful names ever get exported to XML and 2735 # ever get compiled into the binary font. 2736 # 2737 # We override compilerClass so we can warn about "useless" key/value pairs, either 2738 # from the original binary font or from TTX input. 2739 # 2740 # See: 2741 # - https://github.com/fonttools/fonttools/issues/740 2742 # - https://github.com/fonttools/fonttools/issues/601 2743 # - https://github.com/adobe-type-tools/afdko/issues/137 2744 # 2745 defaults = {} 2746 converters = buildConverters(topDictOperators) 2747 compilerClass = FontDictCompiler 2748 orderCFF = ['FontName', 'FontMatrix', 'Weight', 'Private'] 2749 orderCFF2 = ['Private'] 2750 decompilerClass = TopDictDecompiler 2751 2752 def __init__(self, strings=None, file=None, offset=None, 2753 GlobalSubrs=None, isCFF2=None, vstore=None): 2754 super(FontDict, self).__init__(strings, file, offset, isCFF2=isCFF2) 2755 self.vstore = vstore 2756 self.setCFF2(isCFF2) 2757 2758 def setCFF2(self, isCFF2): 2759 # isCFF2 may be None. 2760 if isCFF2: 2761 self.order = self.orderCFF2 2762 self._isCFF2 = True 2763 else: 2764 self.order = self.orderCFF 2765 self._isCFF2 = False 2766 2767 2768class PrivateDict(BaseDict): 2769 defaults = buildDefaults(privateDictOperators) 2770 converters = buildConverters(privateDictOperators) 2771 order = buildOrder(privateDictOperators) 2772 decompilerClass = PrivateDictDecompiler 2773 compilerClass = PrivateDictCompiler 2774 2775 def __init__(self, strings=None, file=None, offset=None, isCFF2=None, 2776 vstore=None): 2777 super(PrivateDict, self).__init__(strings, file, offset, isCFF2=isCFF2) 2778 self.vstore = vstore 2779 if isCFF2: 2780 self.defaults = buildDefaults(privateDictOperators2) 2781 self.order = buildOrder(privateDictOperators2) 2782 # Provide dummy values. This avoids needing to provide 2783 # an isCFF2 state in a lot of places. 2784 self.nominalWidthX = self.defaultWidthX = None 2785 else: 2786 self.defaults = buildDefaults(privateDictOperators) 2787 self.order = buildOrder(privateDictOperators) 2788 2789 @property 2790 def in_cff2(self): 2791 return self._isCFF2 2792 2793 def getNumRegions(self, vi=None): # called from misc/psCharStrings.py 2794 # if getNumRegions is being called, we can assume that VarStore exists. 2795 if vi is None: 2796 if hasattr(self, 'vsindex'): 2797 vi = self.vsindex 2798 else: 2799 vi = 0 2800 numRegions = self.vstore.getNumRegions(vi) 2801 return numRegions 2802 2803 2804class IndexedStrings(object): 2805 2806 """SID -> string mapping.""" 2807 2808 def __init__(self, file=None): 2809 if file is None: 2810 strings = [] 2811 else: 2812 strings = [ 2813 tostr(s, encoding="latin1") 2814 for s in Index(file, isCFF2=False) 2815 ] 2816 self.strings = strings 2817 2818 def getCompiler(self): 2819 return IndexedStringsCompiler(self, None, self, isCFF2=False) 2820 2821 def __len__(self): 2822 return len(self.strings) 2823 2824 def __getitem__(self, SID): 2825 if SID < cffStandardStringCount: 2826 return cffStandardStrings[SID] 2827 else: 2828 return self.strings[SID - cffStandardStringCount] 2829 2830 def getSID(self, s): 2831 if not hasattr(self, "stringMapping"): 2832 self.buildStringMapping() 2833 s = tostr(s, encoding="latin1") 2834 if s in cffStandardStringMapping: 2835 SID = cffStandardStringMapping[s] 2836 elif s in self.stringMapping: 2837 SID = self.stringMapping[s] 2838 else: 2839 SID = len(self.strings) + cffStandardStringCount 2840 self.strings.append(s) 2841 self.stringMapping[s] = SID 2842 return SID 2843 2844 def getStrings(self): 2845 return self.strings 2846 2847 def buildStringMapping(self): 2848 self.stringMapping = {} 2849 for index in range(len(self.strings)): 2850 self.stringMapping[self.strings[index]] = index + cffStandardStringCount 2851 2852 2853# The 391 Standard Strings as used in the CFF format. 2854# from Adobe Technical None #5176, version 1.0, 18 March 1998 2855 2856cffStandardStrings = ['.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 2857 'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft', 'parenright', 2858 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 2859 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 2860 'semicolon', 'less', 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 2861 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 2862 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 2863 'bracketright', 'asciicircum', 'underscore', 'quoteleft', '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', 'braceleft', 'bar', 'braceright', 2866 'asciitilde', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 2867 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft', 2868 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 2869 'daggerdbl', 'periodcentered', 'paragraph', 'bullet', 'quotesinglbase', 2870 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', 2871 'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 2872 'dotaccent', 'dieresis', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 2873 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'ae', 2874 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior', 2875 'logicalnot', 'mu', 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 2876 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', 'threequarters', 2877 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior', 2878 'copyright', 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 2879 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', 'Edieresis', 'Egrave', 2880 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 2881 'Ocircumflex', 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 2882 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', 'Ydieresis', 'Zcaron', 2883 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla', 2884 'eacute', 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex', 2885 'idieresis', 'igrave', 'ntilde', 'oacute', 'ocircumflex', 'odieresis', 2886 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave', 2887 'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall', 2888 'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall', 2889 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', 2890 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 2891 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 2892 'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior', 2893 'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 2894 'esuperior', 'isuperior', 'lsuperior', 'msuperior', 'nsuperior', 'osuperior', 2895 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', 'parenleftinferior', 2896 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 2897 'Asmall', 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 2898 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 2899 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall', 2900 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', 2901 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 2902 'Dieresissmall', 'Brevesmall', 'Caronsmall', 'Dotaccentsmall', 'Macronsmall', 2903 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', 2904 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 2905 'onethird', 'twothirds', 'zerosuperior', 'foursuperior', 'fivesuperior', 2906 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 2907 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 2908 'sixinferior', 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 2909 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall', 2910 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', 2911 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 2912 'Edieresissmall', 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 2913 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', 2914 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 2915 'Ugravesmall', 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 2916 'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', '001.001', '001.002', 2917 '001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman', 2918 'Semibold' 2919] 2920 2921cffStandardStringCount = 391 2922assert len(cffStandardStrings) == cffStandardStringCount 2923# build reverse mapping 2924cffStandardStringMapping = {} 2925for _i in range(cffStandardStringCount): 2926 cffStandardStringMapping[cffStandardStrings[_i]] = _i 2927 2928cffISOAdobeStrings = [".notdef", "space", "exclam", "quotedbl", "numbersign", 2929"dollar", "percent", "ampersand", "quoteright", "parenleft", "parenright", 2930"asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two", 2931"three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", 2932"less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", 2933"H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", 2934"X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum", 2935"underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", 2936"k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", 2937"braceleft", "bar", "braceright", "asciitilde", "exclamdown", "cent", 2938"sterling", "fraction", "yen", "florin", "section", "currency", "quotesingle", 2939"quotedblleft", "guillemotleft", "guilsinglleft", "guilsinglright", "fi", "fl", 2940"endash", "dagger", "daggerdbl", "periodcentered", "paragraph", "bullet", 2941"quotesinglbase", "quotedblbase", "quotedblright", "guillemotright", "ellipsis", 2942"perthousand", "questiondown", "grave", "acute", "circumflex", "tilde", 2943"macron", "breve", "dotaccent", "dieresis", "ring", "cedilla", "hungarumlaut", 2944"ogonek", "caron", "emdash", "AE", "ordfeminine", "Lslash", "Oslash", "OE", 2945"ordmasculine", "ae", "dotlessi", "lslash", "oslash", "oe", "germandbls", 2946"onesuperior", "logicalnot", "mu", "trademark", "Eth", "onehalf", "plusminus", 2947"Thorn", "onequarter", "divide", "brokenbar", "degree", "thorn", 2948"threequarters", "twosuperior", "registered", "minus", "eth", "multiply", 2949"threesuperior", "copyright", "Aacute", "Acircumflex", "Adieresis", "Agrave", 2950"Aring", "Atilde", "Ccedilla", "Eacute", "Ecircumflex", "Edieresis", "Egrave", 2951"Iacute", "Icircumflex", "Idieresis", "Igrave", "Ntilde", "Oacute", 2952"Ocircumflex", "Odieresis", "Ograve", "Otilde", "Scaron", "Uacute", 2953"Ucircumflex", "Udieresis", "Ugrave", "Yacute", "Ydieresis", "Zcaron", "aacute", 2954"acircumflex", "adieresis", "agrave", "aring", "atilde", "ccedilla", "eacute", 2955"ecircumflex", "edieresis", "egrave", "iacute", "icircumflex", "idieresis", 2956"igrave", "ntilde", "oacute", "ocircumflex", "odieresis", "ograve", "otilde", 2957"scaron", "uacute", "ucircumflex", "udieresis", "ugrave", "yacute", "ydieresis", 2958"zcaron"] 2959 2960cffISOAdobeStringCount = 229 2961assert len(cffISOAdobeStrings) == cffISOAdobeStringCount 2962 2963cffIExpertStrings = [".notdef", "space", "exclamsmall", "Hungarumlautsmall", 2964"dollaroldstyle", "dollarsuperior", "ampersandsmall", "Acutesmall", 2965"parenleftsuperior", "parenrightsuperior", "twodotenleader", "onedotenleader", 2966"comma", "hyphen", "period", "fraction", "zerooldstyle", "oneoldstyle", 2967"twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle", 2968"sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", "semicolon", 2969"commasuperior", "threequartersemdash", "periodsuperior", "questionsmall", 2970"asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior", 2971"lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior", 2972"tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior", 2973"parenrightinferior", "Circumflexsmall", "hyphensuperior", "Gravesmall", 2974"Asmall", "Bsmall", "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall", 2975"Ismall", "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall", 2976"Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall", "Xsmall", 2977"Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah", "Tildesmall", 2978"exclamdownsmall", "centoldstyle", "Lslashsmall", "Scaronsmall", "Zcaronsmall", 2979"Dieresissmall", "Brevesmall", "Caronsmall", "Dotaccentsmall", "Macronsmall", 2980"figuredash", "hypheninferior", "Ogoneksmall", "Ringsmall", "Cedillasmall", 2981"onequarter", "onehalf", "threequarters", "questiondownsmall", "oneeighth", 2982"threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds", 2983"zerosuperior", "onesuperior", "twosuperior", "threesuperior", "foursuperior", 2984"fivesuperior", "sixsuperior", "sevensuperior", "eightsuperior", "ninesuperior", 2985"zeroinferior", "oneinferior", "twoinferior", "threeinferior", "fourinferior", 2986"fiveinferior", "sixinferior", "seveninferior", "eightinferior", "nineinferior", 2987"centinferior", "dollarinferior", "periodinferior", "commainferior", 2988"Agravesmall", "Aacutesmall", "Acircumflexsmall", "Atildesmall", 2989"Adieresissmall", "Aringsmall", "AEsmall", "Ccedillasmall", "Egravesmall", 2990"Eacutesmall", "Ecircumflexsmall", "Edieresissmall", "Igravesmall", 2991"Iacutesmall", "Icircumflexsmall", "Idieresissmall", "Ethsmall", "Ntildesmall", 2992"Ogravesmall", "Oacutesmall", "Ocircumflexsmall", "Otildesmall", 2993"Odieresissmall", "OEsmall", "Oslashsmall", "Ugravesmall", "Uacutesmall", 2994"Ucircumflexsmall", "Udieresissmall", "Yacutesmall", "Thornsmall", 2995"Ydieresissmall"] 2996 2997cffExpertStringCount = 166 2998assert len(cffIExpertStrings) == cffExpertStringCount 2999 3000cffExpertSubsetStrings = [".notdef", "space", "dollaroldstyle", 3001"dollarsuperior", "parenleftsuperior", "parenrightsuperior", "twodotenleader", 3002"onedotenleader", "comma", "hyphen", "period", "fraction", "zerooldstyle", 3003"oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", 3004"sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", 3005"semicolon", "commasuperior", "threequartersemdash", "periodsuperior", 3006"asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior", 3007"lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior", 3008"tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior", 3009"parenrightinferior", "hyphensuperior", "colonmonetary", "onefitted", "rupiah", 3010"centoldstyle", "figuredash", "hypheninferior", "onequarter", "onehalf", 3011"threequarters", "oneeighth", "threeeighths", "fiveeighths", "seveneighths", 3012"onethird", "twothirds", "zerosuperior", "onesuperior", "twosuperior", 3013"threesuperior", "foursuperior", "fivesuperior", "sixsuperior", "sevensuperior", 3014"eightsuperior", "ninesuperior", "zeroinferior", "oneinferior", "twoinferior", 3015"threeinferior", "fourinferior", "fiveinferior", "sixinferior", "seveninferior", 3016"eightinferior", "nineinferior", "centinferior", "dollarinferior", 3017"periodinferior", "commainferior"] 3018 3019cffExpertSubsetStringCount = 87 3020assert len(cffExpertSubsetStrings) == cffExpertSubsetStringCount 3021