Coverage for /usr/lib/python3/dist-packages/fontTools/cffLib/__init__.py: 20%
1893 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1"""cffLib: read/write Adobe CFF fonts
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.
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.
12"""
14from fontTools.misc import sstruct
15from fontTools.misc import psCharStrings
16from fontTools.misc.arrayTools import unionRect, intRect
17from fontTools.misc.textTools import (
18 bytechr,
19 byteord,
20 bytesjoin,
21 tobytes,
22 tostr,
23 safeEval,
24)
25from fontTools.ttLib import TTFont
26from fontTools.ttLib.tables.otBase import OTTableWriter
27from fontTools.ttLib.tables.otBase import OTTableReader
28from fontTools.ttLib.tables import otTables as ot
29from io import BytesIO
30import struct
31import logging
32import re
34# mute cffLib debug messages when running ttx in verbose mode
35DEBUG = logging.DEBUG - 1
36log = logging.getLogger(__name__)
38cffHeaderFormat = """
39 major: B
40 minor: B
41 hdrSize: B
42"""
44maxStackLimit = 513
45# maxstack operator has been deprecated. max stack is now always 513.
48class StopHintCountEvent(Exception):
49 pass
52class _DesubroutinizingT2Decompiler(psCharStrings.SimpleT2Decompiler):
53 stop_hintcount_ops = (
54 "op_hintmask",
55 "op_cntrmask",
56 "op_rmoveto",
57 "op_hmoveto",
58 "op_vmoveto",
59 )
61 def __init__(self, localSubrs, globalSubrs, private=None):
62 psCharStrings.SimpleT2Decompiler.__init__(
63 self, localSubrs, globalSubrs, private
64 )
66 def execute(self, charString):
67 self.need_hintcount = True # until proven otherwise
68 for op_name in self.stop_hintcount_ops:
69 setattr(self, op_name, self.stop_hint_count)
71 if hasattr(charString, "_desubroutinized"):
72 # If a charstring has already been desubroutinized, we will still
73 # need to execute it if we need to count hints in order to
74 # compute the byte length for mask arguments, and haven't finished
75 # counting hints pairs.
76 if self.need_hintcount and self.callingStack:
77 try:
78 psCharStrings.SimpleT2Decompiler.execute(self, charString)
79 except StopHintCountEvent:
80 del self.callingStack[-1]
81 return
83 charString._patches = []
84 psCharStrings.SimpleT2Decompiler.execute(self, charString)
85 desubroutinized = charString.program[:]
86 for idx, expansion in reversed(charString._patches):
87 assert idx >= 2
88 assert desubroutinized[idx - 1] in [
89 "callsubr",
90 "callgsubr",
91 ], desubroutinized[idx - 1]
92 assert type(desubroutinized[idx - 2]) == int
93 if expansion[-1] == "return":
94 expansion = expansion[:-1]
95 desubroutinized[idx - 2 : idx] = expansion
96 if not self.private.in_cff2:
97 if "endchar" in desubroutinized:
98 # Cut off after first endchar
99 desubroutinized = desubroutinized[
100 : desubroutinized.index("endchar") + 1
101 ]
102 else:
103 if not len(desubroutinized) or desubroutinized[-1] != "return":
104 desubroutinized.append("return")
106 charString._desubroutinized = desubroutinized
107 del charString._patches
109 def op_callsubr(self, index):
110 subr = self.localSubrs[self.operandStack[-1] + self.localBias]
111 psCharStrings.SimpleT2Decompiler.op_callsubr(self, index)
112 self.processSubr(index, subr)
114 def op_callgsubr(self, index):
115 subr = self.globalSubrs[self.operandStack[-1] + self.globalBias]
116 psCharStrings.SimpleT2Decompiler.op_callgsubr(self, index)
117 self.processSubr(index, subr)
119 def stop_hint_count(self, *args):
120 self.need_hintcount = False
121 for op_name in self.stop_hintcount_ops:
122 setattr(self, op_name, None)
123 cs = self.callingStack[-1]
124 if hasattr(cs, "_desubroutinized"):
125 raise StopHintCountEvent()
127 def op_hintmask(self, index):
128 psCharStrings.SimpleT2Decompiler.op_hintmask(self, index)
129 if self.need_hintcount:
130 self.stop_hint_count()
132 def processSubr(self, index, subr):
133 cs = self.callingStack[-1]
134 if not hasattr(cs, "_desubroutinized"):
135 cs._patches.append((index, subr._desubroutinized))
138class CFFFontSet(object):
139 """A CFF font "file" can contain more than one font, although this is
140 extremely rare (and not allowed within OpenType fonts).
142 This class is the entry point for parsing a CFF table. To actually
143 manipulate the data inside the CFF font, you will want to access the
144 ``CFFFontSet``'s :class:`TopDict` object. To do this, a ``CFFFontSet``
145 object can either be treated as a dictionary (with appropriate
146 ``keys()`` and ``values()`` methods) mapping font names to :class:`TopDict`
147 objects, or as a list.
149 .. code:: python
151 from fontTools import ttLib
152 tt = ttLib.TTFont("Tests/cffLib/data/LinLibertine_RBI.otf")
153 tt["CFF "].cff
154 # <fontTools.cffLib.CFFFontSet object at 0x101e24c90>
155 tt["CFF "].cff[0] # Here's your actual font data
156 # <fontTools.cffLib.TopDict object at 0x1020f1fd0>
158 """
160 def decompile(self, file, otFont, isCFF2=None):
161 """Parse a binary CFF file into an internal representation. ``file``
162 should be a file handle object. ``otFont`` is the top-level
163 :py:class:`fontTools.ttLib.ttFont.TTFont` object containing this CFF file.
165 If ``isCFF2`` is passed and set to ``True`` or ``False``, then the
166 library makes an assertion that the CFF header is of the appropriate
167 version.
168 """
170 self.otFont = otFont
171 sstruct.unpack(cffHeaderFormat, file.read(3), self)
172 if isCFF2 is not None:
173 # called from ttLib: assert 'major' as read from file matches the
174 # expected version
175 expected_major = 2 if isCFF2 else 1
176 if self.major != expected_major:
177 raise ValueError(
178 "Invalid CFF 'major' version: expected %d, found %d"
179 % (expected_major, self.major)
180 )
181 else:
182 # use 'major' version from file to determine if isCFF2
183 assert self.major in (1, 2), "Unknown CFF format"
184 isCFF2 = self.major == 2
185 if not isCFF2:
186 self.offSize = struct.unpack("B", file.read(1))[0]
187 file.seek(self.hdrSize)
188 self.fontNames = list(tostr(s) for s in Index(file, isCFF2=isCFF2))
189 self.topDictIndex = TopDictIndex(file, isCFF2=isCFF2)
190 self.strings = IndexedStrings(file)
191 else: # isCFF2
192 self.topDictSize = struct.unpack(">H", file.read(2))[0]
193 file.seek(self.hdrSize)
194 self.fontNames = ["CFF2Font"]
195 cff2GetGlyphOrder = otFont.getGlyphOrder
196 # in CFF2, offsetSize is the size of the TopDict data.
197 self.topDictIndex = TopDictIndex(
198 file, cff2GetGlyphOrder, self.topDictSize, isCFF2=isCFF2
199 )
200 self.strings = None
201 self.GlobalSubrs = GlobalSubrsIndex(file, isCFF2=isCFF2)
202 self.topDictIndex.strings = self.strings
203 self.topDictIndex.GlobalSubrs = self.GlobalSubrs
205 def __len__(self):
206 return len(self.fontNames)
208 def keys(self):
209 return list(self.fontNames)
211 def values(self):
212 return self.topDictIndex
214 def __getitem__(self, nameOrIndex):
215 """Return TopDict instance identified by name (str) or index (int
216 or any object that implements `__index__`).
217 """
218 if hasattr(nameOrIndex, "__index__"):
219 index = nameOrIndex.__index__()
220 elif isinstance(nameOrIndex, str):
221 name = nameOrIndex
222 try:
223 index = self.fontNames.index(name)
224 except ValueError:
225 raise KeyError(nameOrIndex)
226 else:
227 raise TypeError(nameOrIndex)
228 return self.topDictIndex[index]
230 def compile(self, file, otFont, isCFF2=None):
231 """Write the object back into binary representation onto the given file.
232 ``file`` should be a file handle object. ``otFont`` is the top-level
233 :py:class:`fontTools.ttLib.ttFont.TTFont` object containing this CFF file.
235 If ``isCFF2`` is passed and set to ``True`` or ``False``, then the
236 library makes an assertion that the CFF header is of the appropriate
237 version.
238 """
239 self.otFont = otFont
240 if isCFF2 is not None:
241 # called from ttLib: assert 'major' value matches expected version
242 expected_major = 2 if isCFF2 else 1
243 if self.major != expected_major:
244 raise ValueError(
245 "Invalid CFF 'major' version: expected %d, found %d"
246 % (expected_major, self.major)
247 )
248 else:
249 # use current 'major' value to determine output format
250 assert self.major in (1, 2), "Unknown CFF format"
251 isCFF2 = self.major == 2
253 if otFont.recalcBBoxes and not isCFF2:
254 for topDict in self.topDictIndex:
255 topDict.recalcFontBBox()
257 if not isCFF2:
258 strings = IndexedStrings()
259 else:
260 strings = None
261 writer = CFFWriter(isCFF2)
262 topCompiler = self.topDictIndex.getCompiler(strings, self, isCFF2=isCFF2)
263 if isCFF2:
264 self.hdrSize = 5
265 writer.add(sstruct.pack(cffHeaderFormat, self))
266 # Note: topDictSize will most likely change in CFFWriter.toFile().
267 self.topDictSize = topCompiler.getDataLength()
268 writer.add(struct.pack(">H", self.topDictSize))
269 else:
270 self.hdrSize = 4
271 self.offSize = 4 # will most likely change in CFFWriter.toFile().
272 writer.add(sstruct.pack(cffHeaderFormat, self))
273 writer.add(struct.pack("B", self.offSize))
274 if not isCFF2:
275 fontNames = Index()
276 for name in self.fontNames:
277 fontNames.append(name)
278 writer.add(fontNames.getCompiler(strings, self, isCFF2=isCFF2))
279 writer.add(topCompiler)
280 if not isCFF2:
281 writer.add(strings.getCompiler())
282 writer.add(self.GlobalSubrs.getCompiler(strings, self, isCFF2=isCFF2))
284 for topDict in self.topDictIndex:
285 if not hasattr(topDict, "charset") or topDict.charset is None:
286 charset = otFont.getGlyphOrder()
287 topDict.charset = charset
288 children = topCompiler.getChildren(strings)
289 for child in children:
290 writer.add(child)
292 writer.toFile(file)
294 def toXML(self, xmlWriter):
295 """Write the object into XML representation onto the given
296 :class:`fontTools.misc.xmlWriter.XMLWriter`.
298 .. code:: python
300 writer = xmlWriter.XMLWriter(sys.stdout)
301 tt["CFF "].cff.toXML(writer)
303 """
305 xmlWriter.simpletag("major", value=self.major)
306 xmlWriter.newline()
307 xmlWriter.simpletag("minor", value=self.minor)
308 xmlWriter.newline()
309 for fontName in self.fontNames:
310 xmlWriter.begintag("CFFFont", name=tostr(fontName))
311 xmlWriter.newline()
312 font = self[fontName]
313 font.toXML(xmlWriter)
314 xmlWriter.endtag("CFFFont")
315 xmlWriter.newline()
316 xmlWriter.newline()
317 xmlWriter.begintag("GlobalSubrs")
318 xmlWriter.newline()
319 self.GlobalSubrs.toXML(xmlWriter)
320 xmlWriter.endtag("GlobalSubrs")
321 xmlWriter.newline()
323 def fromXML(self, name, attrs, content, otFont=None):
324 """Reads data from the XML element into the ``CFFFontSet`` object."""
325 self.otFont = otFont
327 # set defaults. These will be replaced if there are entries for them
328 # in the XML file.
329 if not hasattr(self, "major"):
330 self.major = 1
331 if not hasattr(self, "minor"):
332 self.minor = 0
334 if name == "CFFFont":
335 if self.major == 1:
336 if not hasattr(self, "offSize"):
337 # this will be recalculated when the cff is compiled.
338 self.offSize = 4
339 if not hasattr(self, "hdrSize"):
340 self.hdrSize = 4
341 if not hasattr(self, "GlobalSubrs"):
342 self.GlobalSubrs = GlobalSubrsIndex()
343 if not hasattr(self, "fontNames"):
344 self.fontNames = []
345 self.topDictIndex = TopDictIndex()
346 fontName = attrs["name"]
347 self.fontNames.append(fontName)
348 topDict = TopDict(GlobalSubrs=self.GlobalSubrs)
349 topDict.charset = None # gets filled in later
350 elif self.major == 2:
351 if not hasattr(self, "hdrSize"):
352 self.hdrSize = 5
353 if not hasattr(self, "GlobalSubrs"):
354 self.GlobalSubrs = GlobalSubrsIndex()
355 if not hasattr(self, "fontNames"):
356 self.fontNames = ["CFF2Font"]
357 cff2GetGlyphOrder = self.otFont.getGlyphOrder
358 topDict = TopDict(
359 GlobalSubrs=self.GlobalSubrs, cff2GetGlyphOrder=cff2GetGlyphOrder
360 )
361 self.topDictIndex = TopDictIndex(None, cff2GetGlyphOrder)
362 self.topDictIndex.append(topDict)
363 for element in content:
364 if isinstance(element, str):
365 continue
366 name, attrs, content = element
367 topDict.fromXML(name, attrs, content)
369 if hasattr(topDict, "VarStore") and topDict.FDArray[0].vstore is None:
370 fdArray = topDict.FDArray
371 for fontDict in fdArray:
372 if hasattr(fontDict, "Private"):
373 fontDict.Private.vstore = topDict.VarStore
375 elif name == "GlobalSubrs":
376 subrCharStringClass = psCharStrings.T2CharString
377 if not hasattr(self, "GlobalSubrs"):
378 self.GlobalSubrs = GlobalSubrsIndex()
379 for element in content:
380 if isinstance(element, str):
381 continue
382 name, attrs, content = element
383 subr = subrCharStringClass()
384 subr.fromXML(name, attrs, content)
385 self.GlobalSubrs.append(subr)
386 elif name == "major":
387 self.major = int(attrs["value"])
388 elif name == "minor":
389 self.minor = int(attrs["value"])
391 def convertCFFToCFF2(self, otFont):
392 """Converts this object from CFF format to CFF2 format. This conversion
393 is done 'in-place'. The conversion cannot be reversed.
395 This assumes a decompiled CFF table. (i.e. that the object has been
396 filled via :meth:`decompile`.)"""
397 self.major = 2
398 cff2GetGlyphOrder = self.otFont.getGlyphOrder
399 topDictData = TopDictIndex(None, cff2GetGlyphOrder)
400 topDictData.items = self.topDictIndex.items
401 self.topDictIndex = topDictData
402 topDict = topDictData[0]
403 if hasattr(topDict, "Private"):
404 privateDict = topDict.Private
405 else:
406 privateDict = None
407 opOrder = buildOrder(topDictOperators2)
408 topDict.order = opOrder
409 topDict.cff2GetGlyphOrder = cff2GetGlyphOrder
410 for entry in topDictOperators:
411 key = entry[1]
412 if key not in opOrder:
413 if key in topDict.rawDict:
414 del topDict.rawDict[key]
415 if hasattr(topDict, key):
416 delattr(topDict, key)
418 if not hasattr(topDict, "FDArray"):
419 fdArray = topDict.FDArray = FDArrayIndex()
420 fdArray.strings = None
421 fdArray.GlobalSubrs = topDict.GlobalSubrs
422 topDict.GlobalSubrs.fdArray = fdArray
423 charStrings = topDict.CharStrings
424 if charStrings.charStringsAreIndexed:
425 charStrings.charStringsIndex.fdArray = fdArray
426 else:
427 charStrings.fdArray = fdArray
428 fontDict = FontDict()
429 fontDict.setCFF2(True)
430 fdArray.append(fontDict)
431 fontDict.Private = privateDict
432 privateOpOrder = buildOrder(privateDictOperators2)
433 for entry in privateDictOperators:
434 key = entry[1]
435 if key not in privateOpOrder:
436 if key in privateDict.rawDict:
437 # print "Removing private dict", key
438 del privateDict.rawDict[key]
439 if hasattr(privateDict, key):
440 delattr(privateDict, key)
441 # print "Removing privateDict attr", key
442 else:
443 # clean up the PrivateDicts in the fdArray
444 fdArray = topDict.FDArray
445 privateOpOrder = buildOrder(privateDictOperators2)
446 for fontDict in fdArray:
447 fontDict.setCFF2(True)
448 for key in fontDict.rawDict.keys():
449 if key not in fontDict.order:
450 del fontDict.rawDict[key]
451 if hasattr(fontDict, key):
452 delattr(fontDict, key)
454 privateDict = fontDict.Private
455 for entry in privateDictOperators:
456 key = entry[1]
457 if key not in privateOpOrder:
458 if key in privateDict.rawDict:
459 # print "Removing private dict", key
460 del privateDict.rawDict[key]
461 if hasattr(privateDict, key):
462 delattr(privateDict, key)
463 # print "Removing privateDict attr", key
464 # At this point, the Subrs and Charstrings are all still T2Charstring class
465 # easiest to fix this by compiling, then decompiling again
466 file = BytesIO()
467 self.compile(file, otFont, isCFF2=True)
468 file.seek(0)
469 self.decompile(file, otFont, isCFF2=True)
471 def desubroutinize(self):
472 for fontName in self.fontNames:
473 font = self[fontName]
474 cs = font.CharStrings
475 for g in font.charset:
476 c, _ = cs.getItemAndSelector(g)
477 c.decompile()
478 subrs = getattr(c.private, "Subrs", [])
479 decompiler = _DesubroutinizingT2Decompiler(
480 subrs, c.globalSubrs, c.private
481 )
482 decompiler.execute(c)
483 c.program = c._desubroutinized
484 del c._desubroutinized
485 # Delete all the local subrs
486 if hasattr(font, "FDArray"):
487 for fd in font.FDArray:
488 pd = fd.Private
489 if hasattr(pd, "Subrs"):
490 del pd.Subrs
491 if "Subrs" in pd.rawDict:
492 del pd.rawDict["Subrs"]
493 else:
494 pd = font.Private
495 if hasattr(pd, "Subrs"):
496 del pd.Subrs
497 if "Subrs" in pd.rawDict:
498 del pd.rawDict["Subrs"]
499 # as well as the global subrs
500 self.GlobalSubrs.clear()
503class CFFWriter(object):
504 """Helper class for serializing CFF data to binary. Used by
505 :meth:`CFFFontSet.compile`."""
507 def __init__(self, isCFF2):
508 self.data = []
509 self.isCFF2 = isCFF2
511 def add(self, table):
512 self.data.append(table)
514 def toFile(self, file):
515 lastPosList = None
516 count = 1
517 while True:
518 log.log(DEBUG, "CFFWriter.toFile() iteration: %d", count)
519 count = count + 1
520 pos = 0
521 posList = [pos]
522 for item in self.data:
523 if hasattr(item, "getDataLength"):
524 endPos = pos + item.getDataLength()
525 if isinstance(item, TopDictIndexCompiler) and item.isCFF2:
526 self.topDictSize = item.getDataLength()
527 else:
528 endPos = pos + len(item)
529 if hasattr(item, "setPos"):
530 item.setPos(pos, endPos)
531 pos = endPos
532 posList.append(pos)
533 if posList == lastPosList:
534 break
535 lastPosList = posList
536 log.log(DEBUG, "CFFWriter.toFile() writing to file.")
537 begin = file.tell()
538 if self.isCFF2:
539 self.data[1] = struct.pack(">H", self.topDictSize)
540 else:
541 self.offSize = calcOffSize(lastPosList[-1])
542 self.data[1] = struct.pack("B", self.offSize)
543 posList = [0]
544 for item in self.data:
545 if hasattr(item, "toFile"):
546 item.toFile(file)
547 else:
548 file.write(item)
549 posList.append(file.tell() - begin)
550 assert posList == lastPosList
553def calcOffSize(largestOffset):
554 if largestOffset < 0x100:
555 offSize = 1
556 elif largestOffset < 0x10000:
557 offSize = 2
558 elif largestOffset < 0x1000000:
559 offSize = 3
560 else:
561 offSize = 4
562 return offSize
565class IndexCompiler(object):
566 """Base class for writing CFF `INDEX data <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#5-index-data>`_
567 to binary."""
569 def __init__(self, items, strings, parent, isCFF2=None):
570 if isCFF2 is None and hasattr(parent, "isCFF2"):
571 isCFF2 = parent.isCFF2
572 assert isCFF2 is not None
573 self.isCFF2 = isCFF2
574 self.items = self.getItems(items, strings)
575 self.parent = parent
577 def getItems(self, items, strings):
578 return items
580 def getOffsets(self):
581 # An empty INDEX contains only the count field.
582 if self.items:
583 pos = 1
584 offsets = [pos]
585 for item in self.items:
586 if hasattr(item, "getDataLength"):
587 pos = pos + item.getDataLength()
588 else:
589 pos = pos + len(item)
590 offsets.append(pos)
591 else:
592 offsets = []
593 return offsets
595 def getDataLength(self):
596 if self.isCFF2:
597 countSize = 4
598 else:
599 countSize = 2
601 if self.items:
602 lastOffset = self.getOffsets()[-1]
603 offSize = calcOffSize(lastOffset)
604 dataLength = (
605 countSize
606 + 1 # count
607 + (len(self.items) + 1) * offSize # offSize
608 + lastOffset # the offsets
609 - 1 # size of object data
610 )
611 else:
612 # count. For empty INDEX tables, this is the only entry.
613 dataLength = countSize
615 return dataLength
617 def toFile(self, file):
618 offsets = self.getOffsets()
619 if self.isCFF2:
620 writeCard32(file, len(self.items))
621 else:
622 writeCard16(file, len(self.items))
623 # An empty INDEX contains only the count field.
624 if self.items:
625 offSize = calcOffSize(offsets[-1])
626 writeCard8(file, offSize)
627 offSize = -offSize
628 pack = struct.pack
629 for offset in offsets:
630 binOffset = pack(">l", offset)[offSize:]
631 assert len(binOffset) == -offSize
632 file.write(binOffset)
633 for item in self.items:
634 if hasattr(item, "toFile"):
635 item.toFile(file)
636 else:
637 data = tobytes(item, encoding="latin1")
638 file.write(data)
641class IndexedStringsCompiler(IndexCompiler):
642 def getItems(self, items, strings):
643 return items.strings
646class TopDictIndexCompiler(IndexCompiler):
647 """Helper class for writing the TopDict to binary."""
649 def getItems(self, items, strings):
650 out = []
651 for item in items:
652 out.append(item.getCompiler(strings, self))
653 return out
655 def getChildren(self, strings):
656 children = []
657 for topDict in self.items:
658 children.extend(topDict.getChildren(strings))
659 return children
661 def getOffsets(self):
662 if self.isCFF2:
663 offsets = [0, self.items[0].getDataLength()]
664 return offsets
665 else:
666 return super(TopDictIndexCompiler, self).getOffsets()
668 def getDataLength(self):
669 if self.isCFF2:
670 dataLength = self.items[0].getDataLength()
671 return dataLength
672 else:
673 return super(TopDictIndexCompiler, self).getDataLength()
675 def toFile(self, file):
676 if self.isCFF2:
677 self.items[0].toFile(file)
678 else:
679 super(TopDictIndexCompiler, self).toFile(file)
682class FDArrayIndexCompiler(IndexCompiler):
683 """Helper class for writing the
684 `Font DICT INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#10-font-dict-index-font-dicts-and-fdselect>`_
685 to binary."""
687 def getItems(self, items, strings):
688 out = []
689 for item in items:
690 out.append(item.getCompiler(strings, self))
691 return out
693 def getChildren(self, strings):
694 children = []
695 for fontDict in self.items:
696 children.extend(fontDict.getChildren(strings))
697 return children
699 def toFile(self, file):
700 offsets = self.getOffsets()
701 if self.isCFF2:
702 writeCard32(file, len(self.items))
703 else:
704 writeCard16(file, len(self.items))
705 offSize = calcOffSize(offsets[-1])
706 writeCard8(file, offSize)
707 offSize = -offSize
708 pack = struct.pack
709 for offset in offsets:
710 binOffset = pack(">l", offset)[offSize:]
711 assert len(binOffset) == -offSize
712 file.write(binOffset)
713 for item in self.items:
714 if hasattr(item, "toFile"):
715 item.toFile(file)
716 else:
717 file.write(item)
719 def setPos(self, pos, endPos):
720 self.parent.rawDict["FDArray"] = pos
723class GlobalSubrsCompiler(IndexCompiler):
724 """Helper class for writing the `global subroutine INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#9-local-and-global-subr-indexes>`_
725 to binary."""
727 def getItems(self, items, strings):
728 out = []
729 for cs in items:
730 cs.compile(self.isCFF2)
731 out.append(cs.bytecode)
732 return out
735class SubrsCompiler(GlobalSubrsCompiler):
736 """Helper class for writing the `local subroutine INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#9-local-and-global-subr-indexes>`_
737 to binary."""
739 def setPos(self, pos, endPos):
740 offset = pos - self.parent.pos
741 self.parent.rawDict["Subrs"] = offset
744class CharStringsCompiler(GlobalSubrsCompiler):
745 """Helper class for writing the `CharStrings INDEX <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#9-local-and-global-subr-indexes>`_
746 to binary."""
748 def getItems(self, items, strings):
749 out = []
750 for cs in items:
751 cs.compile(self.isCFF2)
752 out.append(cs.bytecode)
753 return out
755 def setPos(self, pos, endPos):
756 self.parent.rawDict["CharStrings"] = pos
759class Index(object):
760 """This class represents what the CFF spec calls an INDEX (an array of
761 variable-sized objects). `Index` items can be addressed and set using
762 Python list indexing."""
764 compilerClass = IndexCompiler
766 def __init__(self, file=None, isCFF2=None):
767 assert (isCFF2 is None) == (file is None)
768 self.items = []
769 name = self.__class__.__name__
770 if file is None:
771 return
772 self._isCFF2 = isCFF2
773 log.log(DEBUG, "loading %s at %s", name, file.tell())
774 self.file = file
775 if isCFF2:
776 count = readCard32(file)
777 else:
778 count = readCard16(file)
779 if count == 0:
780 return
781 self.items = [None] * count
782 offSize = readCard8(file)
783 log.log(DEBUG, " index count: %s offSize: %s", count, offSize)
784 assert offSize <= 4, "offSize too large: %s" % offSize
785 self.offsets = offsets = []
786 pad = b"\0" * (4 - offSize)
787 for index in range(count + 1):
788 chunk = file.read(offSize)
789 chunk = pad + chunk
790 (offset,) = struct.unpack(">L", chunk)
791 offsets.append(int(offset))
792 self.offsetBase = file.tell() - 1
793 file.seek(self.offsetBase + offsets[-1]) # pretend we've read the whole lot
794 log.log(DEBUG, " end of %s at %s", name, file.tell())
796 def __len__(self):
797 return len(self.items)
799 def __getitem__(self, index):
800 item = self.items[index]
801 if item is not None:
802 return item
803 offset = self.offsets[index] + self.offsetBase
804 size = self.offsets[index + 1] - self.offsets[index]
805 file = self.file
806 file.seek(offset)
807 data = file.read(size)
808 assert len(data) == size
809 item = self.produceItem(index, data, file, offset)
810 self.items[index] = item
811 return item
813 def __setitem__(self, index, item):
814 self.items[index] = item
816 def produceItem(self, index, data, file, offset):
817 return data
819 def append(self, item):
820 """Add an item to an INDEX."""
821 self.items.append(item)
823 def getCompiler(self, strings, parent, isCFF2=None):
824 return self.compilerClass(self, strings, parent, isCFF2=isCFF2)
826 def clear(self):
827 """Empty the INDEX."""
828 del self.items[:]
831class GlobalSubrsIndex(Index):
832 """This index contains all the global subroutines in the font. A global
833 subroutine is a set of ``CharString`` data which is accessible to any
834 glyph in the font, and are used to store repeated instructions - for
835 example, components may be encoded as global subroutines, but so could
836 hinting instructions.
838 Remember that when interpreting a ``callgsubr`` instruction (or indeed
839 a ``callsubr`` instruction) that you will need to add the "subroutine
840 number bias" to number given:
842 .. code:: python
844 tt = ttLib.TTFont("Almendra-Bold.otf")
845 u = tt["CFF "].cff[0].CharStrings["udieresis"]
846 u.decompile()
848 u.toXML(XMLWriter(sys.stdout))
849 # <some stuff>
850 # -64 callgsubr <-- Subroutine which implements the dieresis mark
851 # <other stuff>
853 tt["CFF "].cff[0].GlobalSubrs[-64] # <-- WRONG
854 # <T2CharString (bytecode) at 103451d10>
856 tt["CFF "].cff[0].GlobalSubrs[-64 + 107] # <-- RIGHT
857 # <T2CharString (source) at 103451390>
859 ("The bias applied depends on the number of subrs (gsubrs). If the number of
860 subrs (gsubrs) is less than 1240, the bias is 107. Otherwise if it is less
861 than 33900, it is 1131; otherwise it is 32768.",
862 `Subroutine Operators <https://docs.microsoft.com/en-us/typography/opentype/otspec180/cff2charstr#section4.4>`)
863 """
865 compilerClass = GlobalSubrsCompiler
866 subrClass = psCharStrings.T2CharString
867 charStringClass = psCharStrings.T2CharString
869 def __init__(
870 self,
871 file=None,
872 globalSubrs=None,
873 private=None,
874 fdSelect=None,
875 fdArray=None,
876 isCFF2=None,
877 ):
878 super(GlobalSubrsIndex, self).__init__(file, isCFF2=isCFF2)
879 self.globalSubrs = globalSubrs
880 self.private = private
881 if fdSelect:
882 self.fdSelect = fdSelect
883 if fdArray:
884 self.fdArray = fdArray
886 def produceItem(self, index, data, file, offset):
887 if self.private is not None:
888 private = self.private
889 elif hasattr(self, "fdArray") and self.fdArray is not None:
890 if hasattr(self, "fdSelect") and self.fdSelect is not None:
891 fdIndex = self.fdSelect[index]
892 else:
893 fdIndex = 0
894 private = self.fdArray[fdIndex].Private
895 else:
896 private = None
897 return self.subrClass(data, private=private, globalSubrs=self.globalSubrs)
899 def toXML(self, xmlWriter):
900 """Write the subroutines index into XML representation onto the given
901 :class:`fontTools.misc.xmlWriter.XMLWriter`.
903 .. code:: python
905 writer = xmlWriter.XMLWriter(sys.stdout)
906 tt["CFF "].cff[0].GlobalSubrs.toXML(writer)
908 """
909 xmlWriter.comment(
910 "The 'index' attribute is only for humans; " "it is ignored when parsed."
911 )
912 xmlWriter.newline()
913 for i in range(len(self)):
914 subr = self[i]
915 if subr.needsDecompilation():
916 xmlWriter.begintag("CharString", index=i, raw=1)
917 else:
918 xmlWriter.begintag("CharString", index=i)
919 xmlWriter.newline()
920 subr.toXML(xmlWriter)
921 xmlWriter.endtag("CharString")
922 xmlWriter.newline()
924 def fromXML(self, name, attrs, content):
925 if name != "CharString":
926 return
927 subr = self.subrClass()
928 subr.fromXML(name, attrs, content)
929 self.append(subr)
931 def getItemAndSelector(self, index):
932 sel = None
933 if hasattr(self, "fdSelect"):
934 sel = self.fdSelect[index]
935 return self[index], sel
938class SubrsIndex(GlobalSubrsIndex):
939 """This index contains a glyph's local subroutines. A local subroutine is a
940 private set of ``CharString`` data which is accessible only to the glyph to
941 which the index is attached."""
943 compilerClass = SubrsCompiler
946class TopDictIndex(Index):
947 """This index represents the array of ``TopDict`` structures in the font
948 (again, usually only one entry is present). Hence the following calls are
949 equivalent:
951 .. code:: python
953 tt["CFF "].cff[0]
954 # <fontTools.cffLib.TopDict object at 0x102ed6e50>
955 tt["CFF "].cff.topDictIndex[0]
956 # <fontTools.cffLib.TopDict object at 0x102ed6e50>
958 """
960 compilerClass = TopDictIndexCompiler
962 def __init__(self, file=None, cff2GetGlyphOrder=None, topSize=0, isCFF2=None):
963 assert (isCFF2 is None) == (file is None)
964 self.cff2GetGlyphOrder = cff2GetGlyphOrder
965 if file is not None and isCFF2:
966 self._isCFF2 = isCFF2
967 self.items = []
968 name = self.__class__.__name__
969 log.log(DEBUG, "loading %s at %s", name, file.tell())
970 self.file = file
971 count = 1
972 self.items = [None] * count
973 self.offsets = [0, topSize]
974 self.offsetBase = file.tell()
975 # pretend we've read the whole lot
976 file.seek(self.offsetBase + topSize)
977 log.log(DEBUG, " end of %s at %s", name, file.tell())
978 else:
979 super(TopDictIndex, self).__init__(file, isCFF2=isCFF2)
981 def produceItem(self, index, data, file, offset):
982 top = TopDict(
983 self.strings,
984 file,
985 offset,
986 self.GlobalSubrs,
987 self.cff2GetGlyphOrder,
988 isCFF2=self._isCFF2,
989 )
990 top.decompile(data)
991 return top
993 def toXML(self, xmlWriter):
994 for i in range(len(self)):
995 xmlWriter.begintag("FontDict", index=i)
996 xmlWriter.newline()
997 self[i].toXML(xmlWriter)
998 xmlWriter.endtag("FontDict")
999 xmlWriter.newline()
1002class FDArrayIndex(Index):
1003 compilerClass = FDArrayIndexCompiler
1005 def toXML(self, xmlWriter):
1006 for i in range(len(self)):
1007 xmlWriter.begintag("FontDict", index=i)
1008 xmlWriter.newline()
1009 self[i].toXML(xmlWriter)
1010 xmlWriter.endtag("FontDict")
1011 xmlWriter.newline()
1013 def produceItem(self, index, data, file, offset):
1014 fontDict = FontDict(
1015 self.strings,
1016 file,
1017 offset,
1018 self.GlobalSubrs,
1019 isCFF2=self._isCFF2,
1020 vstore=self.vstore,
1021 )
1022 fontDict.decompile(data)
1023 return fontDict
1025 def fromXML(self, name, attrs, content):
1026 if name != "FontDict":
1027 return
1028 fontDict = FontDict()
1029 for element in content:
1030 if isinstance(element, str):
1031 continue
1032 name, attrs, content = element
1033 fontDict.fromXML(name, attrs, content)
1034 self.append(fontDict)
1037class VarStoreData(object):
1038 def __init__(self, file=None, otVarStore=None):
1039 self.file = file
1040 self.data = None
1041 self.otVarStore = otVarStore
1042 self.font = TTFont() # dummy font for the decompile function.
1044 def decompile(self):
1045 if self.file:
1046 # read data in from file. Assume position is correct.
1047 length = readCard16(self.file)
1048 self.data = self.file.read(length)
1049 globalState = {}
1050 reader = OTTableReader(self.data, globalState)
1051 self.otVarStore = ot.VarStore()
1052 self.otVarStore.decompile(reader, self.font)
1053 return self
1055 def compile(self):
1056 writer = OTTableWriter()
1057 self.otVarStore.compile(writer, self.font)
1058 # Note that this omits the initial Card16 length from the CFF2
1059 # VarStore data block
1060 self.data = writer.getAllData()
1062 def writeXML(self, xmlWriter, name):
1063 self.otVarStore.toXML(xmlWriter, self.font)
1065 def xmlRead(self, name, attrs, content, parent):
1066 self.otVarStore = ot.VarStore()
1067 for element in content:
1068 if isinstance(element, tuple):
1069 name, attrs, content = element
1070 self.otVarStore.fromXML(name, attrs, content, self.font)
1071 else:
1072 pass
1073 return None
1075 def __len__(self):
1076 return len(self.data)
1078 def getNumRegions(self, vsIndex):
1079 if vsIndex is None:
1080 vsIndex = 0
1081 varData = self.otVarStore.VarData[vsIndex]
1082 numRegions = varData.VarRegionCount
1083 return numRegions
1086class FDSelect(object):
1087 def __init__(self, file=None, numGlyphs=None, format=None):
1088 if file:
1089 # read data in from file
1090 self.format = readCard8(file)
1091 if self.format == 0:
1092 from array import array
1094 self.gidArray = array("B", file.read(numGlyphs)).tolist()
1095 elif self.format == 3:
1096 gidArray = [None] * numGlyphs
1097 nRanges = readCard16(file)
1098 fd = None
1099 prev = None
1100 for i in range(nRanges):
1101 first = readCard16(file)
1102 if prev is not None:
1103 for glyphID in range(prev, first):
1104 gidArray[glyphID] = fd
1105 prev = first
1106 fd = readCard8(file)
1107 if prev is not None:
1108 first = readCard16(file)
1109 for glyphID in range(prev, first):
1110 gidArray[glyphID] = fd
1111 self.gidArray = gidArray
1112 elif self.format == 4:
1113 gidArray = [None] * numGlyphs
1114 nRanges = readCard32(file)
1115 fd = None
1116 prev = None
1117 for i in range(nRanges):
1118 first = readCard32(file)
1119 if prev is not None:
1120 for glyphID in range(prev, first):
1121 gidArray[glyphID] = fd
1122 prev = first
1123 fd = readCard16(file)
1124 if prev is not None:
1125 first = readCard32(file)
1126 for glyphID in range(prev, first):
1127 gidArray[glyphID] = fd
1128 self.gidArray = gidArray
1129 else:
1130 assert False, "unsupported FDSelect format: %s" % format
1131 else:
1132 # reading from XML. Make empty gidArray, and leave format as passed in.
1133 # format is None will result in the smallest representation being used.
1134 self.format = format
1135 self.gidArray = []
1137 def __len__(self):
1138 return len(self.gidArray)
1140 def __getitem__(self, index):
1141 return self.gidArray[index]
1143 def __setitem__(self, index, fdSelectValue):
1144 self.gidArray[index] = fdSelectValue
1146 def append(self, fdSelectValue):
1147 self.gidArray.append(fdSelectValue)
1150class CharStrings(object):
1151 """The ``CharStrings`` in the font represent the instructions for drawing
1152 each glyph. This object presents a dictionary interface to the font's
1153 CharStrings, indexed by glyph name:
1155 .. code:: python
1157 tt["CFF "].cff[0].CharStrings["a"]
1158 # <T2CharString (bytecode) at 103451e90>
1160 See :class:`fontTools.misc.psCharStrings.T1CharString` and
1161 :class:`fontTools.misc.psCharStrings.T2CharString` for how to decompile,
1162 compile and interpret the glyph drawing instructions in the returned objects.
1164 """
1166 def __init__(
1167 self,
1168 file,
1169 charset,
1170 globalSubrs,
1171 private,
1172 fdSelect,
1173 fdArray,
1174 isCFF2=None,
1175 varStore=None,
1176 ):
1177 self.globalSubrs = globalSubrs
1178 self.varStore = varStore
1179 if file is not None:
1180 self.charStringsIndex = SubrsIndex(
1181 file, globalSubrs, private, fdSelect, fdArray, isCFF2=isCFF2
1182 )
1183 self.charStrings = charStrings = {}
1184 for i in range(len(charset)):
1185 charStrings[charset[i]] = i
1186 # read from OTF file: charStrings.values() are indices into
1187 # charStringsIndex.
1188 self.charStringsAreIndexed = 1
1189 else:
1190 self.charStrings = {}
1191 # read from ttx file: charStrings.values() are actual charstrings
1192 self.charStringsAreIndexed = 0
1193 self.private = private
1194 if fdSelect is not None:
1195 self.fdSelect = fdSelect
1196 if fdArray is not None:
1197 self.fdArray = fdArray
1199 def keys(self):
1200 return list(self.charStrings.keys())
1202 def values(self):
1203 if self.charStringsAreIndexed:
1204 return self.charStringsIndex
1205 else:
1206 return list(self.charStrings.values())
1208 def has_key(self, name):
1209 return name in self.charStrings
1211 __contains__ = has_key
1213 def __len__(self):
1214 return len(self.charStrings)
1216 def __getitem__(self, name):
1217 charString = self.charStrings[name]
1218 if self.charStringsAreIndexed:
1219 charString = self.charStringsIndex[charString]
1220 return charString
1222 def __setitem__(self, name, charString):
1223 if self.charStringsAreIndexed:
1224 index = self.charStrings[name]
1225 self.charStringsIndex[index] = charString
1226 else:
1227 self.charStrings[name] = charString
1229 def getItemAndSelector(self, name):
1230 if self.charStringsAreIndexed:
1231 index = self.charStrings[name]
1232 return self.charStringsIndex.getItemAndSelector(index)
1233 else:
1234 if hasattr(self, "fdArray"):
1235 if hasattr(self, "fdSelect"):
1236 sel = self.charStrings[name].fdSelectIndex
1237 else:
1238 sel = 0
1239 else:
1240 sel = None
1241 return self.charStrings[name], sel
1243 def toXML(self, xmlWriter):
1244 names = sorted(self.keys())
1245 for name in names:
1246 charStr, fdSelectIndex = self.getItemAndSelector(name)
1247 if charStr.needsDecompilation():
1248 raw = [("raw", 1)]
1249 else:
1250 raw = []
1251 if fdSelectIndex is None:
1252 xmlWriter.begintag("CharString", [("name", name)] + raw)
1253 else:
1254 xmlWriter.begintag(
1255 "CharString",
1256 [("name", name), ("fdSelectIndex", fdSelectIndex)] + raw,
1257 )
1258 xmlWriter.newline()
1259 charStr.toXML(xmlWriter)
1260 xmlWriter.endtag("CharString")
1261 xmlWriter.newline()
1263 def fromXML(self, name, attrs, content):
1264 for element in content:
1265 if isinstance(element, str):
1266 continue
1267 name, attrs, content = element
1268 if name != "CharString":
1269 continue
1270 fdID = -1
1271 if hasattr(self, "fdArray"):
1272 try:
1273 fdID = safeEval(attrs["fdSelectIndex"])
1274 except KeyError:
1275 fdID = 0
1276 private = self.fdArray[fdID].Private
1277 else:
1278 private = self.private
1280 glyphName = attrs["name"]
1281 charStringClass = psCharStrings.T2CharString
1282 charString = charStringClass(private=private, globalSubrs=self.globalSubrs)
1283 charString.fromXML(name, attrs, content)
1284 if fdID >= 0:
1285 charString.fdSelectIndex = fdID
1286 self[glyphName] = charString
1289def readCard8(file):
1290 return byteord(file.read(1))
1293def readCard16(file):
1294 (value,) = struct.unpack(">H", file.read(2))
1295 return value
1298def readCard32(file):
1299 (value,) = struct.unpack(">L", file.read(4))
1300 return value
1303def writeCard8(file, value):
1304 file.write(bytechr(value))
1307def writeCard16(file, value):
1308 file.write(struct.pack(">H", value))
1311def writeCard32(file, value):
1312 file.write(struct.pack(">L", value))
1315def packCard8(value):
1316 return bytechr(value)
1319def packCard16(value):
1320 return struct.pack(">H", value)
1323def packCard32(value):
1324 return struct.pack(">L", value)
1327def buildOperatorDict(table):
1328 d = {}
1329 for op, name, arg, default, conv in table:
1330 d[op] = (name, arg)
1331 return d
1334def buildOpcodeDict(table):
1335 d = {}
1336 for op, name, arg, default, conv in table:
1337 if isinstance(op, tuple):
1338 op = bytechr(op[0]) + bytechr(op[1])
1339 else:
1340 op = bytechr(op)
1341 d[name] = (op, arg)
1342 return d
1345def buildOrder(table):
1346 l = []
1347 for op, name, arg, default, conv in table:
1348 l.append(name)
1349 return l
1352def buildDefaults(table):
1353 d = {}
1354 for op, name, arg, default, conv in table:
1355 if default is not None:
1356 d[name] = default
1357 return d
1360def buildConverters(table):
1361 d = {}
1362 for op, name, arg, default, conv in table:
1363 d[name] = conv
1364 return d
1367class SimpleConverter(object):
1368 def read(self, parent, value):
1369 if not hasattr(parent, "file"):
1370 return self._read(parent, value)
1371 file = parent.file
1372 pos = file.tell()
1373 try:
1374 return self._read(parent, value)
1375 finally:
1376 file.seek(pos)
1378 def _read(self, parent, value):
1379 return value
1381 def write(self, parent, value):
1382 return value
1384 def xmlWrite(self, xmlWriter, name, value):
1385 xmlWriter.simpletag(name, value=value)
1386 xmlWriter.newline()
1388 def xmlRead(self, name, attrs, content, parent):
1389 return attrs["value"]
1392class ASCIIConverter(SimpleConverter):
1393 def _read(self, parent, value):
1394 return tostr(value, encoding="ascii")
1396 def write(self, parent, value):
1397 return tobytes(value, encoding="ascii")
1399 def xmlWrite(self, xmlWriter, name, value):
1400 xmlWriter.simpletag(name, value=tostr(value, encoding="ascii"))
1401 xmlWriter.newline()
1403 def xmlRead(self, name, attrs, content, parent):
1404 return tobytes(attrs["value"], encoding=("ascii"))
1407class Latin1Converter(SimpleConverter):
1408 def _read(self, parent, value):
1409 return tostr(value, encoding="latin1")
1411 def write(self, parent, value):
1412 return tobytes(value, encoding="latin1")
1414 def xmlWrite(self, xmlWriter, name, value):
1415 value = tostr(value, encoding="latin1")
1416 if name in ["Notice", "Copyright"]:
1417 value = re.sub(r"[\r\n]\s+", " ", value)
1418 xmlWriter.simpletag(name, value=value)
1419 xmlWriter.newline()
1421 def xmlRead(self, name, attrs, content, parent):
1422 return tobytes(attrs["value"], encoding=("latin1"))
1425def parseNum(s):
1426 try:
1427 value = int(s)
1428 except:
1429 value = float(s)
1430 return value
1433def parseBlendList(s):
1434 valueList = []
1435 for element in s:
1436 if isinstance(element, str):
1437 continue
1438 name, attrs, content = element
1439 blendList = attrs["value"].split()
1440 blendList = [eval(val) for val in blendList]
1441 valueList.append(blendList)
1442 if len(valueList) == 1:
1443 valueList = valueList[0]
1444 return valueList
1447class NumberConverter(SimpleConverter):
1448 def xmlWrite(self, xmlWriter, name, value):
1449 if isinstance(value, list):
1450 xmlWriter.begintag(name)
1451 xmlWriter.newline()
1452 xmlWriter.indent()
1453 blendValue = " ".join([str(val) for val in value])
1454 xmlWriter.simpletag(kBlendDictOpName, value=blendValue)
1455 xmlWriter.newline()
1456 xmlWriter.dedent()
1457 xmlWriter.endtag(name)
1458 xmlWriter.newline()
1459 else:
1460 xmlWriter.simpletag(name, value=value)
1461 xmlWriter.newline()
1463 def xmlRead(self, name, attrs, content, parent):
1464 valueString = attrs.get("value", None)
1465 if valueString is None:
1466 value = parseBlendList(content)
1467 else:
1468 value = parseNum(attrs["value"])
1469 return value
1472class ArrayConverter(SimpleConverter):
1473 def xmlWrite(self, xmlWriter, name, value):
1474 if value and isinstance(value[0], list):
1475 xmlWriter.begintag(name)
1476 xmlWriter.newline()
1477 xmlWriter.indent()
1478 for valueList in value:
1479 blendValue = " ".join([str(val) for val in valueList])
1480 xmlWriter.simpletag(kBlendDictOpName, value=blendValue)
1481 xmlWriter.newline()
1482 xmlWriter.dedent()
1483 xmlWriter.endtag(name)
1484 xmlWriter.newline()
1485 else:
1486 value = " ".join([str(val) for val in value])
1487 xmlWriter.simpletag(name, value=value)
1488 xmlWriter.newline()
1490 def xmlRead(self, name, attrs, content, parent):
1491 valueString = attrs.get("value", None)
1492 if valueString is None:
1493 valueList = parseBlendList(content)
1494 else:
1495 values = valueString.split()
1496 valueList = [parseNum(value) for value in values]
1497 return valueList
1500class TableConverter(SimpleConverter):
1501 def xmlWrite(self, xmlWriter, name, value):
1502 xmlWriter.begintag(name)
1503 xmlWriter.newline()
1504 value.toXML(xmlWriter)
1505 xmlWriter.endtag(name)
1506 xmlWriter.newline()
1508 def xmlRead(self, name, attrs, content, parent):
1509 ob = self.getClass()()
1510 for element in content:
1511 if isinstance(element, str):
1512 continue
1513 name, attrs, content = element
1514 ob.fromXML(name, attrs, content)
1515 return ob
1518class PrivateDictConverter(TableConverter):
1519 def getClass(self):
1520 return PrivateDict
1522 def _read(self, parent, value):
1523 size, offset = value
1524 file = parent.file
1525 isCFF2 = parent._isCFF2
1526 try:
1527 vstore = parent.vstore
1528 except AttributeError:
1529 vstore = None
1530 priv = PrivateDict(parent.strings, file, offset, isCFF2=isCFF2, vstore=vstore)
1531 file.seek(offset)
1532 data = file.read(size)
1533 assert len(data) == size
1534 priv.decompile(data)
1535 return priv
1537 def write(self, parent, value):
1538 return (0, 0) # dummy value
1541class SubrsConverter(TableConverter):
1542 def getClass(self):
1543 return SubrsIndex
1545 def _read(self, parent, value):
1546 file = parent.file
1547 isCFF2 = parent._isCFF2
1548 file.seek(parent.offset + value) # Offset(self)
1549 return SubrsIndex(file, isCFF2=isCFF2)
1551 def write(self, parent, value):
1552 return 0 # dummy value
1555class CharStringsConverter(TableConverter):
1556 def _read(self, parent, value):
1557 file = parent.file
1558 isCFF2 = parent._isCFF2
1559 charset = parent.charset
1560 varStore = getattr(parent, "VarStore", None)
1561 globalSubrs = parent.GlobalSubrs
1562 if hasattr(parent, "FDArray"):
1563 fdArray = parent.FDArray
1564 if hasattr(parent, "FDSelect"):
1565 fdSelect = parent.FDSelect
1566 else:
1567 fdSelect = None
1568 private = None
1569 else:
1570 fdSelect, fdArray = None, None
1571 private = parent.Private
1572 file.seek(value) # Offset(0)
1573 charStrings = CharStrings(
1574 file,
1575 charset,
1576 globalSubrs,
1577 private,
1578 fdSelect,
1579 fdArray,
1580 isCFF2=isCFF2,
1581 varStore=varStore,
1582 )
1583 return charStrings
1585 def write(self, parent, value):
1586 return 0 # dummy value
1588 def xmlRead(self, name, attrs, content, parent):
1589 if hasattr(parent, "FDArray"):
1590 # if it is a CID-keyed font, then the private Dict is extracted from the
1591 # parent.FDArray
1592 fdArray = parent.FDArray
1593 if hasattr(parent, "FDSelect"):
1594 fdSelect = parent.FDSelect
1595 else:
1596 fdSelect = None
1597 private = None
1598 else:
1599 # if it is a name-keyed font, then the private dict is in the top dict,
1600 # and
1601 # there is no fdArray.
1602 private, fdSelect, fdArray = parent.Private, None, None
1603 charStrings = CharStrings(
1604 None,
1605 None,
1606 parent.GlobalSubrs,
1607 private,
1608 fdSelect,
1609 fdArray,
1610 varStore=getattr(parent, "VarStore", None),
1611 )
1612 charStrings.fromXML(name, attrs, content)
1613 return charStrings
1616class CharsetConverter(SimpleConverter):
1617 def _read(self, parent, value):
1618 isCID = hasattr(parent, "ROS")
1619 if value > 2:
1620 numGlyphs = parent.numGlyphs
1621 file = parent.file
1622 file.seek(value)
1623 log.log(DEBUG, "loading charset at %s", value)
1624 format = readCard8(file)
1625 if format == 0:
1626 charset = parseCharset0(numGlyphs, file, parent.strings, isCID)
1627 elif format == 1 or format == 2:
1628 charset = parseCharset(numGlyphs, file, parent.strings, isCID, format)
1629 else:
1630 raise NotImplementedError
1631 assert len(charset) == numGlyphs
1632 log.log(DEBUG, " charset end at %s", file.tell())
1633 # make sure glyph names are unique
1634 allNames = {}
1635 newCharset = []
1636 for glyphName in charset:
1637 if glyphName in allNames:
1638 # make up a new glyphName that's unique
1639 n = allNames[glyphName]
1640 while (glyphName + "#" + str(n)) in allNames:
1641 n += 1
1642 allNames[glyphName] = n + 1
1643 glyphName = glyphName + "#" + str(n)
1644 allNames[glyphName] = 1
1645 newCharset.append(glyphName)
1646 charset = newCharset
1647 else: # offset == 0 -> no charset data.
1648 if isCID or "CharStrings" not in parent.rawDict:
1649 # We get here only when processing fontDicts from the FDArray of
1650 # CFF-CID fonts. Only the real topDict references the chrset.
1651 assert value == 0
1652 charset = None
1653 elif value == 0:
1654 charset = cffISOAdobeStrings
1655 elif value == 1:
1656 charset = cffIExpertStrings
1657 elif value == 2:
1658 charset = cffExpertSubsetStrings
1659 if charset and (len(charset) != parent.numGlyphs):
1660 charset = charset[: parent.numGlyphs]
1661 return charset
1663 def write(self, parent, value):
1664 return 0 # dummy value
1666 def xmlWrite(self, xmlWriter, name, value):
1667 # XXX only write charset when not in OT/TTX context, where we
1668 # dump charset as a separate "GlyphOrder" table.
1669 # # xmlWriter.simpletag("charset")
1670 xmlWriter.comment("charset is dumped separately as the 'GlyphOrder' element")
1671 xmlWriter.newline()
1673 def xmlRead(self, name, attrs, content, parent):
1674 pass
1677class CharsetCompiler(object):
1678 def __init__(self, strings, charset, parent):
1679 assert charset[0] == ".notdef"
1680 isCID = hasattr(parent.dictObj, "ROS")
1681 data0 = packCharset0(charset, isCID, strings)
1682 data = packCharset(charset, isCID, strings)
1683 if len(data) < len(data0):
1684 self.data = data
1685 else:
1686 self.data = data0
1687 self.parent = parent
1689 def setPos(self, pos, endPos):
1690 self.parent.rawDict["charset"] = pos
1692 def getDataLength(self):
1693 return len(self.data)
1695 def toFile(self, file):
1696 file.write(self.data)
1699def getStdCharSet(charset):
1700 # check to see if we can use a predefined charset value.
1701 predefinedCharSetVal = None
1702 predefinedCharSets = [
1703 (cffISOAdobeStringCount, cffISOAdobeStrings, 0),
1704 (cffExpertStringCount, cffIExpertStrings, 1),
1705 (cffExpertSubsetStringCount, cffExpertSubsetStrings, 2),
1706 ]
1707 lcs = len(charset)
1708 for cnt, pcs, csv in predefinedCharSets:
1709 if predefinedCharSetVal is not None:
1710 break
1711 if lcs > cnt:
1712 continue
1713 predefinedCharSetVal = csv
1714 for i in range(lcs):
1715 if charset[i] != pcs[i]:
1716 predefinedCharSetVal = None
1717 break
1718 return predefinedCharSetVal
1721def getCIDfromName(name, strings):
1722 return int(name[3:])
1725def getSIDfromName(name, strings):
1726 return strings.getSID(name)
1729def packCharset0(charset, isCID, strings):
1730 fmt = 0
1731 data = [packCard8(fmt)]
1732 if isCID:
1733 getNameID = getCIDfromName
1734 else:
1735 getNameID = getSIDfromName
1737 for name in charset[1:]:
1738 data.append(packCard16(getNameID(name, strings)))
1739 return bytesjoin(data)
1742def packCharset(charset, isCID, strings):
1743 fmt = 1
1744 ranges = []
1745 first = None
1746 end = 0
1747 if isCID:
1748 getNameID = getCIDfromName
1749 else:
1750 getNameID = getSIDfromName
1752 for name in charset[1:]:
1753 SID = getNameID(name, strings)
1754 if first is None:
1755 first = SID
1756 elif end + 1 != SID:
1757 nLeft = end - first
1758 if nLeft > 255:
1759 fmt = 2
1760 ranges.append((first, nLeft))
1761 first = SID
1762 end = SID
1763 if end:
1764 nLeft = end - first
1765 if nLeft > 255:
1766 fmt = 2
1767 ranges.append((first, nLeft))
1769 data = [packCard8(fmt)]
1770 if fmt == 1:
1771 nLeftFunc = packCard8
1772 else:
1773 nLeftFunc = packCard16
1774 for first, nLeft in ranges:
1775 data.append(packCard16(first) + nLeftFunc(nLeft))
1776 return bytesjoin(data)
1779def parseCharset0(numGlyphs, file, strings, isCID):
1780 charset = [".notdef"]
1781 if isCID:
1782 for i in range(numGlyphs - 1):
1783 CID = readCard16(file)
1784 charset.append("cid" + str(CID).zfill(5))
1785 else:
1786 for i in range(numGlyphs - 1):
1787 SID = readCard16(file)
1788 charset.append(strings[SID])
1789 return charset
1792def parseCharset(numGlyphs, file, strings, isCID, fmt):
1793 charset = [".notdef"]
1794 count = 1
1795 if fmt == 1:
1796 nLeftFunc = readCard8
1797 else:
1798 nLeftFunc = readCard16
1799 while count < numGlyphs:
1800 first = readCard16(file)
1801 nLeft = nLeftFunc(file)
1802 if isCID:
1803 for CID in range(first, first + nLeft + 1):
1804 charset.append("cid" + str(CID).zfill(5))
1805 else:
1806 for SID in range(first, first + nLeft + 1):
1807 charset.append(strings[SID])
1808 count = count + nLeft + 1
1809 return charset
1812class EncodingCompiler(object):
1813 def __init__(self, strings, encoding, parent):
1814 assert not isinstance(encoding, str)
1815 data0 = packEncoding0(parent.dictObj.charset, encoding, parent.strings)
1816 data1 = packEncoding1(parent.dictObj.charset, encoding, parent.strings)
1817 if len(data0) < len(data1):
1818 self.data = data0
1819 else:
1820 self.data = data1
1821 self.parent = parent
1823 def setPos(self, pos, endPos):
1824 self.parent.rawDict["Encoding"] = pos
1826 def getDataLength(self):
1827 return len(self.data)
1829 def toFile(self, file):
1830 file.write(self.data)
1833class EncodingConverter(SimpleConverter):
1834 def _read(self, parent, value):
1835 if value == 0:
1836 return "StandardEncoding"
1837 elif value == 1:
1838 return "ExpertEncoding"
1839 else:
1840 assert value > 1
1841 file = parent.file
1842 file.seek(value)
1843 log.log(DEBUG, "loading Encoding at %s", value)
1844 fmt = readCard8(file)
1845 haveSupplement = fmt & 0x80
1846 if haveSupplement:
1847 raise NotImplementedError("Encoding supplements are not yet supported")
1848 fmt = fmt & 0x7F
1849 if fmt == 0:
1850 encoding = parseEncoding0(
1851 parent.charset, file, haveSupplement, parent.strings
1852 )
1853 elif fmt == 1:
1854 encoding = parseEncoding1(
1855 parent.charset, file, haveSupplement, parent.strings
1856 )
1857 return encoding
1859 def write(self, parent, value):
1860 if value == "StandardEncoding":
1861 return 0
1862 elif value == "ExpertEncoding":
1863 return 1
1864 return 0 # dummy value
1866 def xmlWrite(self, xmlWriter, name, value):
1867 if value in ("StandardEncoding", "ExpertEncoding"):
1868 xmlWriter.simpletag(name, name=value)
1869 xmlWriter.newline()
1870 return
1871 xmlWriter.begintag(name)
1872 xmlWriter.newline()
1873 for code in range(len(value)):
1874 glyphName = value[code]
1875 if glyphName != ".notdef":
1876 xmlWriter.simpletag("map", code=hex(code), name=glyphName)
1877 xmlWriter.newline()
1878 xmlWriter.endtag(name)
1879 xmlWriter.newline()
1881 def xmlRead(self, name, attrs, content, parent):
1882 if "name" in attrs:
1883 return attrs["name"]
1884 encoding = [".notdef"] * 256
1885 for element in content:
1886 if isinstance(element, str):
1887 continue
1888 name, attrs, content = element
1889 code = safeEval(attrs["code"])
1890 glyphName = attrs["name"]
1891 encoding[code] = glyphName
1892 return encoding
1895def parseEncoding0(charset, file, haveSupplement, strings):
1896 nCodes = readCard8(file)
1897 encoding = [".notdef"] * 256
1898 for glyphID in range(1, nCodes + 1):
1899 code = readCard8(file)
1900 if code != 0:
1901 encoding[code] = charset[glyphID]
1902 return encoding
1905def parseEncoding1(charset, file, haveSupplement, strings):
1906 nRanges = readCard8(file)
1907 encoding = [".notdef"] * 256
1908 glyphID = 1
1909 for i in range(nRanges):
1910 code = readCard8(file)
1911 nLeft = readCard8(file)
1912 for glyphID in range(glyphID, glyphID + nLeft + 1):
1913 encoding[code] = charset[glyphID]
1914 code = code + 1
1915 glyphID = glyphID + 1
1916 return encoding
1919def packEncoding0(charset, encoding, strings):
1920 fmt = 0
1921 m = {}
1922 for code in range(len(encoding)):
1923 name = encoding[code]
1924 if name != ".notdef":
1925 m[name] = code
1926 codes = []
1927 for name in charset[1:]:
1928 code = m.get(name)
1929 codes.append(code)
1931 while codes and codes[-1] is None:
1932 codes.pop()
1934 data = [packCard8(fmt), packCard8(len(codes))]
1935 for code in codes:
1936 if code is None:
1937 code = 0
1938 data.append(packCard8(code))
1939 return bytesjoin(data)
1942def packEncoding1(charset, encoding, strings):
1943 fmt = 1
1944 m = {}
1945 for code in range(len(encoding)):
1946 name = encoding[code]
1947 if name != ".notdef":
1948 m[name] = code
1949 ranges = []
1950 first = None
1951 end = 0
1952 for name in charset[1:]:
1953 code = m.get(name, -1)
1954 if first is None:
1955 first = code
1956 elif end + 1 != code:
1957 nLeft = end - first
1958 ranges.append((first, nLeft))
1959 first = code
1960 end = code
1961 nLeft = end - first
1962 ranges.append((first, nLeft))
1964 # remove unencoded glyphs at the end.
1965 while ranges and ranges[-1][0] == -1:
1966 ranges.pop()
1968 data = [packCard8(fmt), packCard8(len(ranges))]
1969 for first, nLeft in ranges:
1970 if first == -1: # unencoded
1971 first = 0
1972 data.append(packCard8(first) + packCard8(nLeft))
1973 return bytesjoin(data)
1976class FDArrayConverter(TableConverter):
1977 def _read(self, parent, value):
1978 try:
1979 vstore = parent.VarStore
1980 except AttributeError:
1981 vstore = None
1982 file = parent.file
1983 isCFF2 = parent._isCFF2
1984 file.seek(value)
1985 fdArray = FDArrayIndex(file, isCFF2=isCFF2)
1986 fdArray.vstore = vstore
1987 fdArray.strings = parent.strings
1988 fdArray.GlobalSubrs = parent.GlobalSubrs
1989 return fdArray
1991 def write(self, parent, value):
1992 return 0 # dummy value
1994 def xmlRead(self, name, attrs, content, parent):
1995 fdArray = FDArrayIndex()
1996 for element in content:
1997 if isinstance(element, str):
1998 continue
1999 name, attrs, content = element
2000 fdArray.fromXML(name, attrs, content)
2001 return fdArray
2004class FDSelectConverter(SimpleConverter):
2005 def _read(self, parent, value):
2006 file = parent.file
2007 file.seek(value)
2008 fdSelect = FDSelect(file, parent.numGlyphs)
2009 return fdSelect
2011 def write(self, parent, value):
2012 return 0 # dummy value
2014 # The FDSelect glyph data is written out to XML in the charstring keys,
2015 # so we write out only the format selector
2016 def xmlWrite(self, xmlWriter, name, value):
2017 xmlWriter.simpletag(name, [("format", value.format)])
2018 xmlWriter.newline()
2020 def xmlRead(self, name, attrs, content, parent):
2021 fmt = safeEval(attrs["format"])
2022 file = None
2023 numGlyphs = None
2024 fdSelect = FDSelect(file, numGlyphs, fmt)
2025 return fdSelect
2028class VarStoreConverter(SimpleConverter):
2029 def _read(self, parent, value):
2030 file = parent.file
2031 file.seek(value)
2032 varStore = VarStoreData(file)
2033 varStore.decompile()
2034 return varStore
2036 def write(self, parent, value):
2037 return 0 # dummy value
2039 def xmlWrite(self, xmlWriter, name, value):
2040 value.writeXML(xmlWriter, name)
2042 def xmlRead(self, name, attrs, content, parent):
2043 varStore = VarStoreData()
2044 varStore.xmlRead(name, attrs, content, parent)
2045 return varStore
2048def packFDSelect0(fdSelectArray):
2049 fmt = 0
2050 data = [packCard8(fmt)]
2051 for index in fdSelectArray:
2052 data.append(packCard8(index))
2053 return bytesjoin(data)
2056def packFDSelect3(fdSelectArray):
2057 fmt = 3
2058 fdRanges = []
2059 lenArray = len(fdSelectArray)
2060 lastFDIndex = -1
2061 for i in range(lenArray):
2062 fdIndex = fdSelectArray[i]
2063 if lastFDIndex != fdIndex:
2064 fdRanges.append([i, fdIndex])
2065 lastFDIndex = fdIndex
2066 sentinelGID = i + 1
2068 data = [packCard8(fmt)]
2069 data.append(packCard16(len(fdRanges)))
2070 for fdRange in fdRanges:
2071 data.append(packCard16(fdRange[0]))
2072 data.append(packCard8(fdRange[1]))
2073 data.append(packCard16(sentinelGID))
2074 return bytesjoin(data)
2077def packFDSelect4(fdSelectArray):
2078 fmt = 4
2079 fdRanges = []
2080 lenArray = len(fdSelectArray)
2081 lastFDIndex = -1
2082 for i in range(lenArray):
2083 fdIndex = fdSelectArray[i]
2084 if lastFDIndex != fdIndex:
2085 fdRanges.append([i, fdIndex])
2086 lastFDIndex = fdIndex
2087 sentinelGID = i + 1
2089 data = [packCard8(fmt)]
2090 data.append(packCard32(len(fdRanges)))
2091 for fdRange in fdRanges:
2092 data.append(packCard32(fdRange[0]))
2093 data.append(packCard16(fdRange[1]))
2094 data.append(packCard32(sentinelGID))
2095 return bytesjoin(data)
2098class FDSelectCompiler(object):
2099 def __init__(self, fdSelect, parent):
2100 fmt = fdSelect.format
2101 fdSelectArray = fdSelect.gidArray
2102 if fmt == 0:
2103 self.data = packFDSelect0(fdSelectArray)
2104 elif fmt == 3:
2105 self.data = packFDSelect3(fdSelectArray)
2106 elif fmt == 4:
2107 self.data = packFDSelect4(fdSelectArray)
2108 else:
2109 # choose smaller of the two formats
2110 data0 = packFDSelect0(fdSelectArray)
2111 data3 = packFDSelect3(fdSelectArray)
2112 if len(data0) < len(data3):
2113 self.data = data0
2114 fdSelect.format = 0
2115 else:
2116 self.data = data3
2117 fdSelect.format = 3
2119 self.parent = parent
2121 def setPos(self, pos, endPos):
2122 self.parent.rawDict["FDSelect"] = pos
2124 def getDataLength(self):
2125 return len(self.data)
2127 def toFile(self, file):
2128 file.write(self.data)
2131class VarStoreCompiler(object):
2132 def __init__(self, varStoreData, parent):
2133 self.parent = parent
2134 if not varStoreData.data:
2135 varStoreData.compile()
2136 data = [packCard16(len(varStoreData.data)), varStoreData.data]
2137 self.data = bytesjoin(data)
2139 def setPos(self, pos, endPos):
2140 self.parent.rawDict["VarStore"] = pos
2142 def getDataLength(self):
2143 return len(self.data)
2145 def toFile(self, file):
2146 file.write(self.data)
2149class ROSConverter(SimpleConverter):
2150 def xmlWrite(self, xmlWriter, name, value):
2151 registry, order, supplement = value
2152 xmlWriter.simpletag(
2153 name,
2154 [
2155 ("Registry", tostr(registry)),
2156 ("Order", tostr(order)),
2157 ("Supplement", supplement),
2158 ],
2159 )
2160 xmlWriter.newline()
2162 def xmlRead(self, name, attrs, content, parent):
2163 return (attrs["Registry"], attrs["Order"], safeEval(attrs["Supplement"]))
2166topDictOperators = [
2167 # opcode name argument type default converter
2168 (25, "maxstack", "number", None, None),
2169 ((12, 30), "ROS", ("SID", "SID", "number"), None, ROSConverter()),
2170 ((12, 20), "SyntheticBase", "number", None, None),
2171 (0, "version", "SID", None, None),
2172 (1, "Notice", "SID", None, Latin1Converter()),
2173 ((12, 0), "Copyright", "SID", None, Latin1Converter()),
2174 (2, "FullName", "SID", None, Latin1Converter()),
2175 ((12, 38), "FontName", "SID", None, Latin1Converter()),
2176 (3, "FamilyName", "SID", None, Latin1Converter()),
2177 (4, "Weight", "SID", None, None),
2178 ((12, 1), "isFixedPitch", "number", 0, None),
2179 ((12, 2), "ItalicAngle", "number", 0, None),
2180 ((12, 3), "UnderlinePosition", "number", -100, None),
2181 ((12, 4), "UnderlineThickness", "number", 50, None),
2182 ((12, 5), "PaintType", "number", 0, None),
2183 ((12, 6), "CharstringType", "number", 2, None),
2184 ((12, 7), "FontMatrix", "array", [0.001, 0, 0, 0.001, 0, 0], None),
2185 (13, "UniqueID", "number", None, None),
2186 (5, "FontBBox", "array", [0, 0, 0, 0], None),
2187 ((12, 8), "StrokeWidth", "number", 0, None),
2188 (14, "XUID", "array", None, None),
2189 ((12, 21), "PostScript", "SID", None, None),
2190 ((12, 22), "BaseFontName", "SID", None, None),
2191 ((12, 23), "BaseFontBlend", "delta", None, None),
2192 ((12, 31), "CIDFontVersion", "number", 0, None),
2193 ((12, 32), "CIDFontRevision", "number", 0, None),
2194 ((12, 33), "CIDFontType", "number", 0, None),
2195 ((12, 34), "CIDCount", "number", 8720, None),
2196 (15, "charset", "number", None, CharsetConverter()),
2197 ((12, 35), "UIDBase", "number", None, None),
2198 (16, "Encoding", "number", 0, EncodingConverter()),
2199 (18, "Private", ("number", "number"), None, PrivateDictConverter()),
2200 ((12, 37), "FDSelect", "number", None, FDSelectConverter()),
2201 ((12, 36), "FDArray", "number", None, FDArrayConverter()),
2202 (17, "CharStrings", "number", None, CharStringsConverter()),
2203 (24, "VarStore", "number", None, VarStoreConverter()),
2204]
2206topDictOperators2 = [
2207 # opcode name argument type default converter
2208 (25, "maxstack", "number", None, None),
2209 ((12, 7), "FontMatrix", "array", [0.001, 0, 0, 0.001, 0, 0], None),
2210 ((12, 37), "FDSelect", "number", None, FDSelectConverter()),
2211 ((12, 36), "FDArray", "number", None, FDArrayConverter()),
2212 (17, "CharStrings", "number", None, CharStringsConverter()),
2213 (24, "VarStore", "number", None, VarStoreConverter()),
2214]
2216# Note! FDSelect and FDArray must both preceed CharStrings in the output XML build order,
2217# in order for the font to compile back from xml.
2219kBlendDictOpName = "blend"
2220blendOp = 23
2222privateDictOperators = [
2223 # opcode name argument type default converter
2224 (22, "vsindex", "number", None, None),
2225 (
2226 blendOp,
2227 kBlendDictOpName,
2228 "blendList",
2229 None,
2230 None,
2231 ), # This is for reading to/from XML: it not written to CFF.
2232 (6, "BlueValues", "delta", None, None),
2233 (7, "OtherBlues", "delta", None, None),
2234 (8, "FamilyBlues", "delta", None, None),
2235 (9, "FamilyOtherBlues", "delta", None, None),
2236 ((12, 9), "BlueScale", "number", 0.039625, None),
2237 ((12, 10), "BlueShift", "number", 7, None),
2238 ((12, 11), "BlueFuzz", "number", 1, None),
2239 (10, "StdHW", "number", None, None),
2240 (11, "StdVW", "number", None, None),
2241 ((12, 12), "StemSnapH", "delta", None, None),
2242 ((12, 13), "StemSnapV", "delta", None, None),
2243 ((12, 14), "ForceBold", "number", 0, None),
2244 ((12, 15), "ForceBoldThreshold", "number", None, None), # deprecated
2245 ((12, 16), "lenIV", "number", None, None), # deprecated
2246 ((12, 17), "LanguageGroup", "number", 0, None),
2247 ((12, 18), "ExpansionFactor", "number", 0.06, None),
2248 ((12, 19), "initialRandomSeed", "number", 0, None),
2249 (20, "defaultWidthX", "number", 0, None),
2250 (21, "nominalWidthX", "number", 0, None),
2251 (19, "Subrs", "number", None, SubrsConverter()),
2252]
2254privateDictOperators2 = [
2255 # opcode name argument type default converter
2256 (22, "vsindex", "number", None, None),
2257 (
2258 blendOp,
2259 kBlendDictOpName,
2260 "blendList",
2261 None,
2262 None,
2263 ), # This is for reading to/from XML: it not written to CFF.
2264 (6, "BlueValues", "delta", None, None),
2265 (7, "OtherBlues", "delta", None, None),
2266 (8, "FamilyBlues", "delta", None, None),
2267 (9, "FamilyOtherBlues", "delta", None, None),
2268 ((12, 9), "BlueScale", "number", 0.039625, None),
2269 ((12, 10), "BlueShift", "number", 7, None),
2270 ((12, 11), "BlueFuzz", "number", 1, None),
2271 (10, "StdHW", "number", None, None),
2272 (11, "StdVW", "number", None, None),
2273 ((12, 12), "StemSnapH", "delta", None, None),
2274 ((12, 13), "StemSnapV", "delta", None, None),
2275 ((12, 17), "LanguageGroup", "number", 0, None),
2276 ((12, 18), "ExpansionFactor", "number", 0.06, None),
2277 (19, "Subrs", "number", None, SubrsConverter()),
2278]
2281def addConverters(table):
2282 for i in range(len(table)):
2283 op, name, arg, default, conv = table[i]
2284 if conv is not None:
2285 continue
2286 if arg in ("delta", "array"):
2287 conv = ArrayConverter()
2288 elif arg == "number":
2289 conv = NumberConverter()
2290 elif arg == "SID":
2291 conv = ASCIIConverter()
2292 elif arg == "blendList":
2293 conv = None
2294 else:
2295 assert False
2296 table[i] = op, name, arg, default, conv
2299addConverters(privateDictOperators)
2300addConverters(topDictOperators)
2303class TopDictDecompiler(psCharStrings.DictDecompiler):
2304 operators = buildOperatorDict(topDictOperators)
2307class PrivateDictDecompiler(psCharStrings.DictDecompiler):
2308 operators = buildOperatorDict(privateDictOperators)
2311class DictCompiler(object):
2312 maxBlendStack = 0
2314 def __init__(self, dictObj, strings, parent, isCFF2=None):
2315 if strings:
2316 assert isinstance(strings, IndexedStrings)
2317 if isCFF2 is None and hasattr(parent, "isCFF2"):
2318 isCFF2 = parent.isCFF2
2319 assert isCFF2 is not None
2320 self.isCFF2 = isCFF2
2321 self.dictObj = dictObj
2322 self.strings = strings
2323 self.parent = parent
2324 rawDict = {}
2325 for name in dictObj.order:
2326 value = getattr(dictObj, name, None)
2327 if value is None:
2328 continue
2329 conv = dictObj.converters[name]
2330 value = conv.write(dictObj, value)
2331 if value == dictObj.defaults.get(name):
2332 continue
2333 rawDict[name] = value
2334 self.rawDict = rawDict
2336 def setPos(self, pos, endPos):
2337 pass
2339 def getDataLength(self):
2340 return len(self.compile("getDataLength"))
2342 def compile(self, reason):
2343 log.log(DEBUG, "-- compiling %s for %s", self.__class__.__name__, reason)
2344 rawDict = self.rawDict
2345 data = []
2346 for name in self.dictObj.order:
2347 value = rawDict.get(name)
2348 if value is None:
2349 continue
2350 op, argType = self.opcodes[name]
2351 if isinstance(argType, tuple):
2352 l = len(argType)
2353 assert len(value) == l, "value doesn't match arg type"
2354 for i in range(l):
2355 arg = argType[i]
2356 v = value[i]
2357 arghandler = getattr(self, "arg_" + arg)
2358 data.append(arghandler(v))
2359 else:
2360 arghandler = getattr(self, "arg_" + argType)
2361 data.append(arghandler(value))
2362 data.append(op)
2363 data = bytesjoin(data)
2364 return data
2366 def toFile(self, file):
2367 data = self.compile("toFile")
2368 file.write(data)
2370 def arg_number(self, num):
2371 if isinstance(num, list):
2372 data = [encodeNumber(val) for val in num]
2373 data.append(encodeNumber(1))
2374 data.append(bytechr(blendOp))
2375 datum = bytesjoin(data)
2376 else:
2377 datum = encodeNumber(num)
2378 return datum
2380 def arg_SID(self, s):
2381 return psCharStrings.encodeIntCFF(self.strings.getSID(s))
2383 def arg_array(self, value):
2384 data = []
2385 for num in value:
2386 data.append(self.arg_number(num))
2387 return bytesjoin(data)
2389 def arg_delta(self, value):
2390 if not value:
2391 return b""
2392 val0 = value[0]
2393 if isinstance(val0, list):
2394 data = self.arg_delta_blend(value)
2395 else:
2396 out = []
2397 last = 0
2398 for v in value:
2399 out.append(v - last)
2400 last = v
2401 data = []
2402 for num in out:
2403 data.append(encodeNumber(num))
2404 return bytesjoin(data)
2406 def arg_delta_blend(self, value):
2407 """A delta list with blend lists has to be *all* blend lists.
2409 The value is a list is arranged as follows::
2411 [
2412 [V0, d0..dn]
2413 [V1, d0..dn]
2414 ...
2415 [Vm, d0..dn]
2416 ]
2418 ``V`` is the absolute coordinate value from the default font, and ``d0-dn``
2419 are the delta values from the *n* regions. Each ``V`` is an absolute
2420 coordinate from the default font.
2422 We want to return a list::
2424 [
2425 [v0, v1..vm]
2426 [d0..dn]
2427 ...
2428 [d0..dn]
2429 numBlends
2430 blendOp
2431 ]
2433 where each ``v`` is relative to the previous default font value.
2434 """
2435 numMasters = len(value[0])
2436 numBlends = len(value)
2437 numStack = (numBlends * numMasters) + 1
2438 if numStack > self.maxBlendStack:
2439 # Figure out the max number of value we can blend
2440 # and divide this list up into chunks of that size.
2442 numBlendValues = int((self.maxBlendStack - 1) / numMasters)
2443 out = []
2444 while True:
2445 numVal = min(len(value), numBlendValues)
2446 if numVal == 0:
2447 break
2448 valList = value[0:numVal]
2449 out1 = self.arg_delta_blend(valList)
2450 out.extend(out1)
2451 value = value[numVal:]
2452 else:
2453 firstList = [0] * numBlends
2454 deltaList = [None] * numBlends
2455 i = 0
2456 prevVal = 0
2457 while i < numBlends:
2458 # For PrivateDict BlueValues, the default font
2459 # values are absolute, not relative.
2460 # Must convert these back to relative coordinates
2461 # befor writing to CFF2.
2462 defaultValue = value[i][0]
2463 firstList[i] = defaultValue - prevVal
2464 prevVal = defaultValue
2465 deltaList[i] = value[i][1:]
2466 i += 1
2468 relValueList = firstList
2469 for blendList in deltaList:
2470 relValueList.extend(blendList)
2471 out = [encodeNumber(val) for val in relValueList]
2472 out.append(encodeNumber(numBlends))
2473 out.append(bytechr(blendOp))
2474 return out
2477def encodeNumber(num):
2478 if isinstance(num, float):
2479 return psCharStrings.encodeFloat(num)
2480 else:
2481 return psCharStrings.encodeIntCFF(num)
2484class TopDictCompiler(DictCompiler):
2485 opcodes = buildOpcodeDict(topDictOperators)
2487 def getChildren(self, strings):
2488 isCFF2 = self.isCFF2
2489 children = []
2490 if self.dictObj.cff2GetGlyphOrder is None:
2491 if hasattr(self.dictObj, "charset") and self.dictObj.charset:
2492 if hasattr(self.dictObj, "ROS"): # aka isCID
2493 charsetCode = None
2494 else:
2495 charsetCode = getStdCharSet(self.dictObj.charset)
2496 if charsetCode is None:
2497 children.append(
2498 CharsetCompiler(strings, self.dictObj.charset, self)
2499 )
2500 else:
2501 self.rawDict["charset"] = charsetCode
2502 if hasattr(self.dictObj, "Encoding") and self.dictObj.Encoding:
2503 encoding = self.dictObj.Encoding
2504 if not isinstance(encoding, str):
2505 children.append(EncodingCompiler(strings, encoding, self))
2506 else:
2507 if hasattr(self.dictObj, "VarStore"):
2508 varStoreData = self.dictObj.VarStore
2509 varStoreComp = VarStoreCompiler(varStoreData, self)
2510 children.append(varStoreComp)
2511 if hasattr(self.dictObj, "FDSelect"):
2512 # I have not yet supported merging a ttx CFF-CID font, as there are
2513 # interesting issues about merging the FDArrays. Here I assume that
2514 # either the font was read from XML, and the FDSelect indices are all
2515 # in the charstring data, or the FDSelect array is already fully defined.
2516 fdSelect = self.dictObj.FDSelect
2517 # probably read in from XML; assume fdIndex in CharString data
2518 if len(fdSelect) == 0:
2519 charStrings = self.dictObj.CharStrings
2520 for name in self.dictObj.charset:
2521 fdSelect.append(charStrings[name].fdSelectIndex)
2522 fdSelectComp = FDSelectCompiler(fdSelect, self)
2523 children.append(fdSelectComp)
2524 if hasattr(self.dictObj, "CharStrings"):
2525 items = []
2526 charStrings = self.dictObj.CharStrings
2527 for name in self.dictObj.charset:
2528 items.append(charStrings[name])
2529 charStringsComp = CharStringsCompiler(items, strings, self, isCFF2=isCFF2)
2530 children.append(charStringsComp)
2531 if hasattr(self.dictObj, "FDArray"):
2532 # I have not yet supported merging a ttx CFF-CID font, as there are
2533 # interesting issues about merging the FDArrays. Here I assume that the
2534 # FDArray info is correct and complete.
2535 fdArrayIndexComp = self.dictObj.FDArray.getCompiler(strings, self)
2536 children.append(fdArrayIndexComp)
2537 children.extend(fdArrayIndexComp.getChildren(strings))
2538 if hasattr(self.dictObj, "Private"):
2539 privComp = self.dictObj.Private.getCompiler(strings, self)
2540 children.append(privComp)
2541 children.extend(privComp.getChildren(strings))
2542 return children
2545class FontDictCompiler(DictCompiler):
2546 opcodes = buildOpcodeDict(topDictOperators)
2548 def __init__(self, dictObj, strings, parent, isCFF2=None):
2549 super(FontDictCompiler, self).__init__(dictObj, strings, parent, isCFF2=isCFF2)
2550 #
2551 # We now take some effort to detect if there were any key/value pairs
2552 # supplied that were ignored in the FontDict context, and issue a warning
2553 # for those cases.
2554 #
2555 ignoredNames = []
2556 dictObj = self.dictObj
2557 for name in sorted(set(dictObj.converters) - set(dictObj.order)):
2558 if name in dictObj.rawDict:
2559 # The font was directly read from binary. In this
2560 # case, we want to report *all* "useless" key/value
2561 # pairs that are in the font, not just the ones that
2562 # are different from the default.
2563 ignoredNames.append(name)
2564 else:
2565 # The font was probably read from a TTX file. We only
2566 # warn about keys whos value is not the default. The
2567 # ones that have the default value will not be written
2568 # to binary anyway.
2569 default = dictObj.defaults.get(name)
2570 if default is not None:
2571 conv = dictObj.converters[name]
2572 default = conv.read(dictObj, default)
2573 if getattr(dictObj, name, None) != default:
2574 ignoredNames.append(name)
2575 if ignoredNames:
2576 log.warning(
2577 "Some CFF FDArray/FontDict keys were ignored upon compile: "
2578 + " ".join(sorted(ignoredNames))
2579 )
2581 def getChildren(self, strings):
2582 children = []
2583 if hasattr(self.dictObj, "Private"):
2584 privComp = self.dictObj.Private.getCompiler(strings, self)
2585 children.append(privComp)
2586 children.extend(privComp.getChildren(strings))
2587 return children
2590class PrivateDictCompiler(DictCompiler):
2591 maxBlendStack = maxStackLimit
2592 opcodes = buildOpcodeDict(privateDictOperators)
2594 def setPos(self, pos, endPos):
2595 size = endPos - pos
2596 self.parent.rawDict["Private"] = size, pos
2597 self.pos = pos
2599 def getChildren(self, strings):
2600 children = []
2601 if hasattr(self.dictObj, "Subrs"):
2602 children.append(self.dictObj.Subrs.getCompiler(strings, self))
2603 return children
2606class BaseDict(object):
2607 def __init__(self, strings=None, file=None, offset=None, isCFF2=None):
2608 assert (isCFF2 is None) == (file is None)
2609 self.rawDict = {}
2610 self.skipNames = []
2611 self.strings = strings
2612 if file is None:
2613 return
2614 self._isCFF2 = isCFF2
2615 self.file = file
2616 if offset is not None:
2617 log.log(DEBUG, "loading %s at %s", self.__class__.__name__, offset)
2618 self.offset = offset
2620 def decompile(self, data):
2621 log.log(DEBUG, " length %s is %d", self.__class__.__name__, len(data))
2622 dec = self.decompilerClass(self.strings, self)
2623 dec.decompile(data)
2624 self.rawDict = dec.getDict()
2625 self.postDecompile()
2627 def postDecompile(self):
2628 pass
2630 def getCompiler(self, strings, parent, isCFF2=None):
2631 return self.compilerClass(self, strings, parent, isCFF2=isCFF2)
2633 def __getattr__(self, name):
2634 if name[:2] == name[-2:] == "__":
2635 # to make deepcopy() and pickle.load() work, we need to signal with
2636 # AttributeError that dunder methods like '__deepcopy__' or '__getstate__'
2637 # aren't implemented. For more details, see:
2638 # https://github.com/fonttools/fonttools/pull/1488
2639 raise AttributeError(name)
2640 value = self.rawDict.get(name, None)
2641 if value is None:
2642 value = self.defaults.get(name)
2643 if value is None:
2644 raise AttributeError(name)
2645 conv = self.converters[name]
2646 value = conv.read(self, value)
2647 setattr(self, name, value)
2648 return value
2650 def toXML(self, xmlWriter):
2651 for name in self.order:
2652 if name in self.skipNames:
2653 continue
2654 value = getattr(self, name, None)
2655 # XXX For "charset" we never skip calling xmlWrite even if the
2656 # value is None, so we always write the following XML comment:
2657 #
2658 # <!-- charset is dumped separately as the 'GlyphOrder' element -->
2659 #
2660 # Charset is None when 'CFF ' table is imported from XML into an
2661 # empty TTFont(). By writing this comment all the time, we obtain
2662 # the same XML output whether roundtripping XML-to-XML or
2663 # dumping binary-to-XML
2664 if value is None and name != "charset":
2665 continue
2666 conv = self.converters[name]
2667 conv.xmlWrite(xmlWriter, name, value)
2668 ignoredNames = set(self.rawDict) - set(self.order)
2669 if ignoredNames:
2670 xmlWriter.comment(
2671 "some keys were ignored: %s" % " ".join(sorted(ignoredNames))
2672 )
2673 xmlWriter.newline()
2675 def fromXML(self, name, attrs, content):
2676 conv = self.converters[name]
2677 value = conv.xmlRead(name, attrs, content, self)
2678 setattr(self, name, value)
2681class TopDict(BaseDict):
2682 """The ``TopDict`` represents the top-level dictionary holding font
2683 information. CFF2 tables contain a restricted set of top-level entries
2684 as described `here <https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#7-top-dict-data>`_,
2685 but CFF tables may contain a wider range of information. This information
2686 can be accessed through attributes or through the dictionary returned
2687 through the ``rawDict`` property:
2689 .. code:: python
2691 font = tt["CFF "].cff[0]
2692 font.FamilyName
2693 # 'Linux Libertine O'
2694 font.rawDict["FamilyName"]
2695 # 'Linux Libertine O'
2697 More information is available in the CFF file's private dictionary, accessed
2698 via the ``Private`` property:
2700 .. code:: python
2702 tt["CFF "].cff[0].Private.BlueValues
2703 # [-15, 0, 515, 515, 666, 666]
2705 """
2707 defaults = buildDefaults(topDictOperators)
2708 converters = buildConverters(topDictOperators)
2709 compilerClass = TopDictCompiler
2710 order = buildOrder(topDictOperators)
2711 decompilerClass = TopDictDecompiler
2713 def __init__(
2714 self,
2715 strings=None,
2716 file=None,
2717 offset=None,
2718 GlobalSubrs=None,
2719 cff2GetGlyphOrder=None,
2720 isCFF2=None,
2721 ):
2722 super(TopDict, self).__init__(strings, file, offset, isCFF2=isCFF2)
2723 self.cff2GetGlyphOrder = cff2GetGlyphOrder
2724 self.GlobalSubrs = GlobalSubrs
2725 if isCFF2:
2726 self.defaults = buildDefaults(topDictOperators2)
2727 self.charset = cff2GetGlyphOrder()
2728 self.order = buildOrder(topDictOperators2)
2729 else:
2730 self.defaults = buildDefaults(topDictOperators)
2731 self.order = buildOrder(topDictOperators)
2733 def getGlyphOrder(self):
2734 """Returns a list of glyph names in the CFF font."""
2735 return self.charset
2737 def postDecompile(self):
2738 offset = self.rawDict.get("CharStrings")
2739 if offset is None:
2740 return
2741 # get the number of glyphs beforehand.
2742 self.file.seek(offset)
2743 if self._isCFF2:
2744 self.numGlyphs = readCard32(self.file)
2745 else:
2746 self.numGlyphs = readCard16(self.file)
2748 def toXML(self, xmlWriter):
2749 if hasattr(self, "CharStrings"):
2750 self.decompileAllCharStrings()
2751 if hasattr(self, "ROS"):
2752 self.skipNames = ["Encoding"]
2753 if not hasattr(self, "ROS") or not hasattr(self, "CharStrings"):
2754 # these values have default values, but I only want them to show up
2755 # in CID fonts.
2756 self.skipNames = [
2757 "CIDFontVersion",
2758 "CIDFontRevision",
2759 "CIDFontType",
2760 "CIDCount",
2761 ]
2762 BaseDict.toXML(self, xmlWriter)
2764 def decompileAllCharStrings(self):
2765 # Make sure that all the Private Dicts have been instantiated.
2766 for i, charString in enumerate(self.CharStrings.values()):
2767 try:
2768 charString.decompile()
2769 except:
2770 log.error("Error in charstring %s", i)
2771 raise
2773 def recalcFontBBox(self):
2774 fontBBox = None
2775 for charString in self.CharStrings.values():
2776 bounds = charString.calcBounds(self.CharStrings)
2777 if bounds is not None:
2778 if fontBBox is not None:
2779 fontBBox = unionRect(fontBBox, bounds)
2780 else:
2781 fontBBox = bounds
2783 if fontBBox is None:
2784 self.FontBBox = self.defaults["FontBBox"][:]
2785 else:
2786 self.FontBBox = list(intRect(fontBBox))
2789class FontDict(BaseDict):
2790 #
2791 # Since fonttools used to pass a lot of fields that are not relevant in the FDArray
2792 # FontDict, there are 'ttx' files in the wild that contain all these. These got in
2793 # the ttx files because fonttools writes explicit values for all the TopDict default
2794 # values. These are not actually illegal in the context of an FDArray FontDict - you
2795 # can legally, per spec, put any arbitrary key/value pair in a FontDict - but are
2796 # useless since current major company CFF interpreters ignore anything but the set
2797 # listed in this file. So, we just silently skip them. An exception is Weight: this
2798 # is not used by any interpreter, but some foundries have asked that this be
2799 # supported in FDArray FontDicts just to preserve information about the design when
2800 # the font is being inspected.
2801 #
2802 # On top of that, there are fonts out there that contain such useless FontDict values.
2803 #
2804 # By subclassing TopDict, we *allow* all key/values from TopDict, both when reading
2805 # from binary or when reading from XML, but by overriding `order` with a limited
2806 # list of names, we ensure that only the useful names ever get exported to XML and
2807 # ever get compiled into the binary font.
2808 #
2809 # We override compilerClass so we can warn about "useless" key/value pairs, either
2810 # from the original binary font or from TTX input.
2811 #
2812 # See:
2813 # - https://github.com/fonttools/fonttools/issues/740
2814 # - https://github.com/fonttools/fonttools/issues/601
2815 # - https://github.com/adobe-type-tools/afdko/issues/137
2816 #
2817 defaults = {}
2818 converters = buildConverters(topDictOperators)
2819 compilerClass = FontDictCompiler
2820 orderCFF = ["FontName", "FontMatrix", "Weight", "Private"]
2821 orderCFF2 = ["Private"]
2822 decompilerClass = TopDictDecompiler
2824 def __init__(
2825 self,
2826 strings=None,
2827 file=None,
2828 offset=None,
2829 GlobalSubrs=None,
2830 isCFF2=None,
2831 vstore=None,
2832 ):
2833 super(FontDict, self).__init__(strings, file, offset, isCFF2=isCFF2)
2834 self.vstore = vstore
2835 self.setCFF2(isCFF2)
2837 def setCFF2(self, isCFF2):
2838 # isCFF2 may be None.
2839 if isCFF2:
2840 self.order = self.orderCFF2
2841 self._isCFF2 = True
2842 else:
2843 self.order = self.orderCFF
2844 self._isCFF2 = False
2847class PrivateDict(BaseDict):
2848 defaults = buildDefaults(privateDictOperators)
2849 converters = buildConverters(privateDictOperators)
2850 order = buildOrder(privateDictOperators)
2851 decompilerClass = PrivateDictDecompiler
2852 compilerClass = PrivateDictCompiler
2854 def __init__(self, strings=None, file=None, offset=None, isCFF2=None, vstore=None):
2855 super(PrivateDict, self).__init__(strings, file, offset, isCFF2=isCFF2)
2856 self.vstore = vstore
2857 if isCFF2:
2858 self.defaults = buildDefaults(privateDictOperators2)
2859 self.order = buildOrder(privateDictOperators2)
2860 # Provide dummy values. This avoids needing to provide
2861 # an isCFF2 state in a lot of places.
2862 self.nominalWidthX = self.defaultWidthX = None
2863 else:
2864 self.defaults = buildDefaults(privateDictOperators)
2865 self.order = buildOrder(privateDictOperators)
2867 @property
2868 def in_cff2(self):
2869 return self._isCFF2
2871 def getNumRegions(self, vi=None): # called from misc/psCharStrings.py
2872 # if getNumRegions is being called, we can assume that VarStore exists.
2873 if vi is None:
2874 if hasattr(self, "vsindex"):
2875 vi = self.vsindex
2876 else:
2877 vi = 0
2878 numRegions = self.vstore.getNumRegions(vi)
2879 return numRegions
2882class IndexedStrings(object):
2884 """SID -> string mapping."""
2886 def __init__(self, file=None):
2887 if file is None:
2888 strings = []
2889 else:
2890 strings = [tostr(s, encoding="latin1") for s in Index(file, isCFF2=False)]
2891 self.strings = strings
2893 def getCompiler(self):
2894 return IndexedStringsCompiler(self, None, self, isCFF2=False)
2896 def __len__(self):
2897 return len(self.strings)
2899 def __getitem__(self, SID):
2900 if SID < cffStandardStringCount:
2901 return cffStandardStrings[SID]
2902 else:
2903 return self.strings[SID - cffStandardStringCount]
2905 def getSID(self, s):
2906 if not hasattr(self, "stringMapping"):
2907 self.buildStringMapping()
2908 s = tostr(s, encoding="latin1")
2909 if s in cffStandardStringMapping:
2910 SID = cffStandardStringMapping[s]
2911 elif s in self.stringMapping:
2912 SID = self.stringMapping[s]
2913 else:
2914 SID = len(self.strings) + cffStandardStringCount
2915 self.strings.append(s)
2916 self.stringMapping[s] = SID
2917 return SID
2919 def getStrings(self):
2920 return self.strings
2922 def buildStringMapping(self):
2923 self.stringMapping = {}
2924 for index in range(len(self.strings)):
2925 self.stringMapping[self.strings[index]] = index + cffStandardStringCount
2928# The 391 Standard Strings as used in the CFF format.
2929# from Adobe Technical None #5176, version 1.0, 18 March 1998
2931cffStandardStrings = [
2932 ".notdef",
2933 "space",
2934 "exclam",
2935 "quotedbl",
2936 "numbersign",
2937 "dollar",
2938 "percent",
2939 "ampersand",
2940 "quoteright",
2941 "parenleft",
2942 "parenright",
2943 "asterisk",
2944 "plus",
2945 "comma",
2946 "hyphen",
2947 "period",
2948 "slash",
2949 "zero",
2950 "one",
2951 "two",
2952 "three",
2953 "four",
2954 "five",
2955 "six",
2956 "seven",
2957 "eight",
2958 "nine",
2959 "colon",
2960 "semicolon",
2961 "less",
2962 "equal",
2963 "greater",
2964 "question",
2965 "at",
2966 "A",
2967 "B",
2968 "C",
2969 "D",
2970 "E",
2971 "F",
2972 "G",
2973 "H",
2974 "I",
2975 "J",
2976 "K",
2977 "L",
2978 "M",
2979 "N",
2980 "O",
2981 "P",
2982 "Q",
2983 "R",
2984 "S",
2985 "T",
2986 "U",
2987 "V",
2988 "W",
2989 "X",
2990 "Y",
2991 "Z",
2992 "bracketleft",
2993 "backslash",
2994 "bracketright",
2995 "asciicircum",
2996 "underscore",
2997 "quoteleft",
2998 "a",
2999 "b",
3000 "c",
3001 "d",
3002 "e",
3003 "f",
3004 "g",
3005 "h",
3006 "i",
3007 "j",
3008 "k",
3009 "l",
3010 "m",
3011 "n",
3012 "o",
3013 "p",
3014 "q",
3015 "r",
3016 "s",
3017 "t",
3018 "u",
3019 "v",
3020 "w",
3021 "x",
3022 "y",
3023 "z",
3024 "braceleft",
3025 "bar",
3026 "braceright",
3027 "asciitilde",
3028 "exclamdown",
3029 "cent",
3030 "sterling",
3031 "fraction",
3032 "yen",
3033 "florin",
3034 "section",
3035 "currency",
3036 "quotesingle",
3037 "quotedblleft",
3038 "guillemotleft",
3039 "guilsinglleft",
3040 "guilsinglright",
3041 "fi",
3042 "fl",
3043 "endash",
3044 "dagger",
3045 "daggerdbl",
3046 "periodcentered",
3047 "paragraph",
3048 "bullet",
3049 "quotesinglbase",
3050 "quotedblbase",
3051 "quotedblright",
3052 "guillemotright",
3053 "ellipsis",
3054 "perthousand",
3055 "questiondown",
3056 "grave",
3057 "acute",
3058 "circumflex",
3059 "tilde",
3060 "macron",
3061 "breve",
3062 "dotaccent",
3063 "dieresis",
3064 "ring",
3065 "cedilla",
3066 "hungarumlaut",
3067 "ogonek",
3068 "caron",
3069 "emdash",
3070 "AE",
3071 "ordfeminine",
3072 "Lslash",
3073 "Oslash",
3074 "OE",
3075 "ordmasculine",
3076 "ae",
3077 "dotlessi",
3078 "lslash",
3079 "oslash",
3080 "oe",
3081 "germandbls",
3082 "onesuperior",
3083 "logicalnot",
3084 "mu",
3085 "trademark",
3086 "Eth",
3087 "onehalf",
3088 "plusminus",
3089 "Thorn",
3090 "onequarter",
3091 "divide",
3092 "brokenbar",
3093 "degree",
3094 "thorn",
3095 "threequarters",
3096 "twosuperior",
3097 "registered",
3098 "minus",
3099 "eth",
3100 "multiply",
3101 "threesuperior",
3102 "copyright",
3103 "Aacute",
3104 "Acircumflex",
3105 "Adieresis",
3106 "Agrave",
3107 "Aring",
3108 "Atilde",
3109 "Ccedilla",
3110 "Eacute",
3111 "Ecircumflex",
3112 "Edieresis",
3113 "Egrave",
3114 "Iacute",
3115 "Icircumflex",
3116 "Idieresis",
3117 "Igrave",
3118 "Ntilde",
3119 "Oacute",
3120 "Ocircumflex",
3121 "Odieresis",
3122 "Ograve",
3123 "Otilde",
3124 "Scaron",
3125 "Uacute",
3126 "Ucircumflex",
3127 "Udieresis",
3128 "Ugrave",
3129 "Yacute",
3130 "Ydieresis",
3131 "Zcaron",
3132 "aacute",
3133 "acircumflex",
3134 "adieresis",
3135 "agrave",
3136 "aring",
3137 "atilde",
3138 "ccedilla",
3139 "eacute",
3140 "ecircumflex",
3141 "edieresis",
3142 "egrave",
3143 "iacute",
3144 "icircumflex",
3145 "idieresis",
3146 "igrave",
3147 "ntilde",
3148 "oacute",
3149 "ocircumflex",
3150 "odieresis",
3151 "ograve",
3152 "otilde",
3153 "scaron",
3154 "uacute",
3155 "ucircumflex",
3156 "udieresis",
3157 "ugrave",
3158 "yacute",
3159 "ydieresis",
3160 "zcaron",
3161 "exclamsmall",
3162 "Hungarumlautsmall",
3163 "dollaroldstyle",
3164 "dollarsuperior",
3165 "ampersandsmall",
3166 "Acutesmall",
3167 "parenleftsuperior",
3168 "parenrightsuperior",
3169 "twodotenleader",
3170 "onedotenleader",
3171 "zerooldstyle",
3172 "oneoldstyle",
3173 "twooldstyle",
3174 "threeoldstyle",
3175 "fouroldstyle",
3176 "fiveoldstyle",
3177 "sixoldstyle",
3178 "sevenoldstyle",
3179 "eightoldstyle",
3180 "nineoldstyle",
3181 "commasuperior",
3182 "threequartersemdash",
3183 "periodsuperior",
3184 "questionsmall",
3185 "asuperior",
3186 "bsuperior",
3187 "centsuperior",
3188 "dsuperior",
3189 "esuperior",
3190 "isuperior",
3191 "lsuperior",
3192 "msuperior",
3193 "nsuperior",
3194 "osuperior",
3195 "rsuperior",
3196 "ssuperior",
3197 "tsuperior",
3198 "ff",
3199 "ffi",
3200 "ffl",
3201 "parenleftinferior",
3202 "parenrightinferior",
3203 "Circumflexsmall",
3204 "hyphensuperior",
3205 "Gravesmall",
3206 "Asmall",
3207 "Bsmall",
3208 "Csmall",
3209 "Dsmall",
3210 "Esmall",
3211 "Fsmall",
3212 "Gsmall",
3213 "Hsmall",
3214 "Ismall",
3215 "Jsmall",
3216 "Ksmall",
3217 "Lsmall",
3218 "Msmall",
3219 "Nsmall",
3220 "Osmall",
3221 "Psmall",
3222 "Qsmall",
3223 "Rsmall",
3224 "Ssmall",
3225 "Tsmall",
3226 "Usmall",
3227 "Vsmall",
3228 "Wsmall",
3229 "Xsmall",
3230 "Ysmall",
3231 "Zsmall",
3232 "colonmonetary",
3233 "onefitted",
3234 "rupiah",
3235 "Tildesmall",
3236 "exclamdownsmall",
3237 "centoldstyle",
3238 "Lslashsmall",
3239 "Scaronsmall",
3240 "Zcaronsmall",
3241 "Dieresissmall",
3242 "Brevesmall",
3243 "Caronsmall",
3244 "Dotaccentsmall",
3245 "Macronsmall",
3246 "figuredash",
3247 "hypheninferior",
3248 "Ogoneksmall",
3249 "Ringsmall",
3250 "Cedillasmall",
3251 "questiondownsmall",
3252 "oneeighth",
3253 "threeeighths",
3254 "fiveeighths",
3255 "seveneighths",
3256 "onethird",
3257 "twothirds",
3258 "zerosuperior",
3259 "foursuperior",
3260 "fivesuperior",
3261 "sixsuperior",
3262 "sevensuperior",
3263 "eightsuperior",
3264 "ninesuperior",
3265 "zeroinferior",
3266 "oneinferior",
3267 "twoinferior",
3268 "threeinferior",
3269 "fourinferior",
3270 "fiveinferior",
3271 "sixinferior",
3272 "seveninferior",
3273 "eightinferior",
3274 "nineinferior",
3275 "centinferior",
3276 "dollarinferior",
3277 "periodinferior",
3278 "commainferior",
3279 "Agravesmall",
3280 "Aacutesmall",
3281 "Acircumflexsmall",
3282 "Atildesmall",
3283 "Adieresissmall",
3284 "Aringsmall",
3285 "AEsmall",
3286 "Ccedillasmall",
3287 "Egravesmall",
3288 "Eacutesmall",
3289 "Ecircumflexsmall",
3290 "Edieresissmall",
3291 "Igravesmall",
3292 "Iacutesmall",
3293 "Icircumflexsmall",
3294 "Idieresissmall",
3295 "Ethsmall",
3296 "Ntildesmall",
3297 "Ogravesmall",
3298 "Oacutesmall",
3299 "Ocircumflexsmall",
3300 "Otildesmall",
3301 "Odieresissmall",
3302 "OEsmall",
3303 "Oslashsmall",
3304 "Ugravesmall",
3305 "Uacutesmall",
3306 "Ucircumflexsmall",
3307 "Udieresissmall",
3308 "Yacutesmall",
3309 "Thornsmall",
3310 "Ydieresissmall",
3311 "001.000",
3312 "001.001",
3313 "001.002",
3314 "001.003",
3315 "Black",
3316 "Bold",
3317 "Book",
3318 "Light",
3319 "Medium",
3320 "Regular",
3321 "Roman",
3322 "Semibold",
3323]
3325cffStandardStringCount = 391
3326assert len(cffStandardStrings) == cffStandardStringCount
3327# build reverse mapping
3328cffStandardStringMapping = {}
3329for _i in range(cffStandardStringCount):
3330 cffStandardStringMapping[cffStandardStrings[_i]] = _i
3332cffISOAdobeStrings = [
3333 ".notdef",
3334 "space",
3335 "exclam",
3336 "quotedbl",
3337 "numbersign",
3338 "dollar",
3339 "percent",
3340 "ampersand",
3341 "quoteright",
3342 "parenleft",
3343 "parenright",
3344 "asterisk",
3345 "plus",
3346 "comma",
3347 "hyphen",
3348 "period",
3349 "slash",
3350 "zero",
3351 "one",
3352 "two",
3353 "three",
3354 "four",
3355 "five",
3356 "six",
3357 "seven",
3358 "eight",
3359 "nine",
3360 "colon",
3361 "semicolon",
3362 "less",
3363 "equal",
3364 "greater",
3365 "question",
3366 "at",
3367 "A",
3368 "B",
3369 "C",
3370 "D",
3371 "E",
3372 "F",
3373 "G",
3374 "H",
3375 "I",
3376 "J",
3377 "K",
3378 "L",
3379 "M",
3380 "N",
3381 "O",
3382 "P",
3383 "Q",
3384 "R",
3385 "S",
3386 "T",
3387 "U",
3388 "V",
3389 "W",
3390 "X",
3391 "Y",
3392 "Z",
3393 "bracketleft",
3394 "backslash",
3395 "bracketright",
3396 "asciicircum",
3397 "underscore",
3398 "quoteleft",
3399 "a",
3400 "b",
3401 "c",
3402 "d",
3403 "e",
3404 "f",
3405 "g",
3406 "h",
3407 "i",
3408 "j",
3409 "k",
3410 "l",
3411 "m",
3412 "n",
3413 "o",
3414 "p",
3415 "q",
3416 "r",
3417 "s",
3418 "t",
3419 "u",
3420 "v",
3421 "w",
3422 "x",
3423 "y",
3424 "z",
3425 "braceleft",
3426 "bar",
3427 "braceright",
3428 "asciitilde",
3429 "exclamdown",
3430 "cent",
3431 "sterling",
3432 "fraction",
3433 "yen",
3434 "florin",
3435 "section",
3436 "currency",
3437 "quotesingle",
3438 "quotedblleft",
3439 "guillemotleft",
3440 "guilsinglleft",
3441 "guilsinglright",
3442 "fi",
3443 "fl",
3444 "endash",
3445 "dagger",
3446 "daggerdbl",
3447 "periodcentered",
3448 "paragraph",
3449 "bullet",
3450 "quotesinglbase",
3451 "quotedblbase",
3452 "quotedblright",
3453 "guillemotright",
3454 "ellipsis",
3455 "perthousand",
3456 "questiondown",
3457 "grave",
3458 "acute",
3459 "circumflex",
3460 "tilde",
3461 "macron",
3462 "breve",
3463 "dotaccent",
3464 "dieresis",
3465 "ring",
3466 "cedilla",
3467 "hungarumlaut",
3468 "ogonek",
3469 "caron",
3470 "emdash",
3471 "AE",
3472 "ordfeminine",
3473 "Lslash",
3474 "Oslash",
3475 "OE",
3476 "ordmasculine",
3477 "ae",
3478 "dotlessi",
3479 "lslash",
3480 "oslash",
3481 "oe",
3482 "germandbls",
3483 "onesuperior",
3484 "logicalnot",
3485 "mu",
3486 "trademark",
3487 "Eth",
3488 "onehalf",
3489 "plusminus",
3490 "Thorn",
3491 "onequarter",
3492 "divide",
3493 "brokenbar",
3494 "degree",
3495 "thorn",
3496 "threequarters",
3497 "twosuperior",
3498 "registered",
3499 "minus",
3500 "eth",
3501 "multiply",
3502 "threesuperior",
3503 "copyright",
3504 "Aacute",
3505 "Acircumflex",
3506 "Adieresis",
3507 "Agrave",
3508 "Aring",
3509 "Atilde",
3510 "Ccedilla",
3511 "Eacute",
3512 "Ecircumflex",
3513 "Edieresis",
3514 "Egrave",
3515 "Iacute",
3516 "Icircumflex",
3517 "Idieresis",
3518 "Igrave",
3519 "Ntilde",
3520 "Oacute",
3521 "Ocircumflex",
3522 "Odieresis",
3523 "Ograve",
3524 "Otilde",
3525 "Scaron",
3526 "Uacute",
3527 "Ucircumflex",
3528 "Udieresis",
3529 "Ugrave",
3530 "Yacute",
3531 "Ydieresis",
3532 "Zcaron",
3533 "aacute",
3534 "acircumflex",
3535 "adieresis",
3536 "agrave",
3537 "aring",
3538 "atilde",
3539 "ccedilla",
3540 "eacute",
3541 "ecircumflex",
3542 "edieresis",
3543 "egrave",
3544 "iacute",
3545 "icircumflex",
3546 "idieresis",
3547 "igrave",
3548 "ntilde",
3549 "oacute",
3550 "ocircumflex",
3551 "odieresis",
3552 "ograve",
3553 "otilde",
3554 "scaron",
3555 "uacute",
3556 "ucircumflex",
3557 "udieresis",
3558 "ugrave",
3559 "yacute",
3560 "ydieresis",
3561 "zcaron",
3562]
3564cffISOAdobeStringCount = 229
3565assert len(cffISOAdobeStrings) == cffISOAdobeStringCount
3567cffIExpertStrings = [
3568 ".notdef",
3569 "space",
3570 "exclamsmall",
3571 "Hungarumlautsmall",
3572 "dollaroldstyle",
3573 "dollarsuperior",
3574 "ampersandsmall",
3575 "Acutesmall",
3576 "parenleftsuperior",
3577 "parenrightsuperior",
3578 "twodotenleader",
3579 "onedotenleader",
3580 "comma",
3581 "hyphen",
3582 "period",
3583 "fraction",
3584 "zerooldstyle",
3585 "oneoldstyle",
3586 "twooldstyle",
3587 "threeoldstyle",
3588 "fouroldstyle",
3589 "fiveoldstyle",
3590 "sixoldstyle",
3591 "sevenoldstyle",
3592 "eightoldstyle",
3593 "nineoldstyle",
3594 "colon",
3595 "semicolon",
3596 "commasuperior",
3597 "threequartersemdash",
3598 "periodsuperior",
3599 "questionsmall",
3600 "asuperior",
3601 "bsuperior",
3602 "centsuperior",
3603 "dsuperior",
3604 "esuperior",
3605 "isuperior",
3606 "lsuperior",
3607 "msuperior",
3608 "nsuperior",
3609 "osuperior",
3610 "rsuperior",
3611 "ssuperior",
3612 "tsuperior",
3613 "ff",
3614 "fi",
3615 "fl",
3616 "ffi",
3617 "ffl",
3618 "parenleftinferior",
3619 "parenrightinferior",
3620 "Circumflexsmall",
3621 "hyphensuperior",
3622 "Gravesmall",
3623 "Asmall",
3624 "Bsmall",
3625 "Csmall",
3626 "Dsmall",
3627 "Esmall",
3628 "Fsmall",
3629 "Gsmall",
3630 "Hsmall",
3631 "Ismall",
3632 "Jsmall",
3633 "Ksmall",
3634 "Lsmall",
3635 "Msmall",
3636 "Nsmall",
3637 "Osmall",
3638 "Psmall",
3639 "Qsmall",
3640 "Rsmall",
3641 "Ssmall",
3642 "Tsmall",
3643 "Usmall",
3644 "Vsmall",
3645 "Wsmall",
3646 "Xsmall",
3647 "Ysmall",
3648 "Zsmall",
3649 "colonmonetary",
3650 "onefitted",
3651 "rupiah",
3652 "Tildesmall",
3653 "exclamdownsmall",
3654 "centoldstyle",
3655 "Lslashsmall",
3656 "Scaronsmall",
3657 "Zcaronsmall",
3658 "Dieresissmall",
3659 "Brevesmall",
3660 "Caronsmall",
3661 "Dotaccentsmall",
3662 "Macronsmall",
3663 "figuredash",
3664 "hypheninferior",
3665 "Ogoneksmall",
3666 "Ringsmall",
3667 "Cedillasmall",
3668 "onequarter",
3669 "onehalf",
3670 "threequarters",
3671 "questiondownsmall",
3672 "oneeighth",
3673 "threeeighths",
3674 "fiveeighths",
3675 "seveneighths",
3676 "onethird",
3677 "twothirds",
3678 "zerosuperior",
3679 "onesuperior",
3680 "twosuperior",
3681 "threesuperior",
3682 "foursuperior",
3683 "fivesuperior",
3684 "sixsuperior",
3685 "sevensuperior",
3686 "eightsuperior",
3687 "ninesuperior",
3688 "zeroinferior",
3689 "oneinferior",
3690 "twoinferior",
3691 "threeinferior",
3692 "fourinferior",
3693 "fiveinferior",
3694 "sixinferior",
3695 "seveninferior",
3696 "eightinferior",
3697 "nineinferior",
3698 "centinferior",
3699 "dollarinferior",
3700 "periodinferior",
3701 "commainferior",
3702 "Agravesmall",
3703 "Aacutesmall",
3704 "Acircumflexsmall",
3705 "Atildesmall",
3706 "Adieresissmall",
3707 "Aringsmall",
3708 "AEsmall",
3709 "Ccedillasmall",
3710 "Egravesmall",
3711 "Eacutesmall",
3712 "Ecircumflexsmall",
3713 "Edieresissmall",
3714 "Igravesmall",
3715 "Iacutesmall",
3716 "Icircumflexsmall",
3717 "Idieresissmall",
3718 "Ethsmall",
3719 "Ntildesmall",
3720 "Ogravesmall",
3721 "Oacutesmall",
3722 "Ocircumflexsmall",
3723 "Otildesmall",
3724 "Odieresissmall",
3725 "OEsmall",
3726 "Oslashsmall",
3727 "Ugravesmall",
3728 "Uacutesmall",
3729 "Ucircumflexsmall",
3730 "Udieresissmall",
3731 "Yacutesmall",
3732 "Thornsmall",
3733 "Ydieresissmall",
3734]
3736cffExpertStringCount = 166
3737assert len(cffIExpertStrings) == cffExpertStringCount
3739cffExpertSubsetStrings = [
3740 ".notdef",
3741 "space",
3742 "dollaroldstyle",
3743 "dollarsuperior",
3744 "parenleftsuperior",
3745 "parenrightsuperior",
3746 "twodotenleader",
3747 "onedotenleader",
3748 "comma",
3749 "hyphen",
3750 "period",
3751 "fraction",
3752 "zerooldstyle",
3753 "oneoldstyle",
3754 "twooldstyle",
3755 "threeoldstyle",
3756 "fouroldstyle",
3757 "fiveoldstyle",
3758 "sixoldstyle",
3759 "sevenoldstyle",
3760 "eightoldstyle",
3761 "nineoldstyle",
3762 "colon",
3763 "semicolon",
3764 "commasuperior",
3765 "threequartersemdash",
3766 "periodsuperior",
3767 "asuperior",
3768 "bsuperior",
3769 "centsuperior",
3770 "dsuperior",
3771 "esuperior",
3772 "isuperior",
3773 "lsuperior",
3774 "msuperior",
3775 "nsuperior",
3776 "osuperior",
3777 "rsuperior",
3778 "ssuperior",
3779 "tsuperior",
3780 "ff",
3781 "fi",
3782 "fl",
3783 "ffi",
3784 "ffl",
3785 "parenleftinferior",
3786 "parenrightinferior",
3787 "hyphensuperior",
3788 "colonmonetary",
3789 "onefitted",
3790 "rupiah",
3791 "centoldstyle",
3792 "figuredash",
3793 "hypheninferior",
3794 "onequarter",
3795 "onehalf",
3796 "threequarters",
3797 "oneeighth",
3798 "threeeighths",
3799 "fiveeighths",
3800 "seveneighths",
3801 "onethird",
3802 "twothirds",
3803 "zerosuperior",
3804 "onesuperior",
3805 "twosuperior",
3806 "threesuperior",
3807 "foursuperior",
3808 "fivesuperior",
3809 "sixsuperior",
3810 "sevensuperior",
3811 "eightsuperior",
3812 "ninesuperior",
3813 "zeroinferior",
3814 "oneinferior",
3815 "twoinferior",
3816 "threeinferior",
3817 "fourinferior",
3818 "fiveinferior",
3819 "sixinferior",
3820 "seveninferior",
3821 "eightinferior",
3822 "nineinferior",
3823 "centinferior",
3824 "dollarinferior",
3825 "periodinferior",
3826 "commainferior",
3827]
3829cffExpertSubsetStringCount = 87
3830assert len(cffExpertSubsetStrings) == cffExpertSubsetStringCount