fonttools 4.55.4__cp313-cp313-musllinux_1_2_aarch64.whl → 4.61.1__cp313-cp313-musllinux_1_2_aarch64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. fontTools/__init__.py +1 -1
  2. fontTools/annotations.py +30 -0
  3. fontTools/cffLib/CFF2ToCFF.py +65 -10
  4. fontTools/cffLib/__init__.py +61 -26
  5. fontTools/cffLib/specializer.py +4 -1
  6. fontTools/cffLib/transforms.py +11 -6
  7. fontTools/config/__init__.py +15 -0
  8. fontTools/cu2qu/cu2qu.c +6567 -5579
  9. fontTools/cu2qu/cu2qu.cpython-313-aarch64-linux-musl.so +0 -0
  10. fontTools/cu2qu/cu2qu.py +36 -4
  11. fontTools/cu2qu/ufo.py +14 -0
  12. fontTools/designspaceLib/__init__.py +8 -3
  13. fontTools/designspaceLib/statNames.py +14 -7
  14. fontTools/feaLib/ast.py +24 -15
  15. fontTools/feaLib/builder.py +139 -66
  16. fontTools/feaLib/error.py +1 -1
  17. fontTools/feaLib/lexer.c +7038 -7995
  18. fontTools/feaLib/lexer.cpython-313-aarch64-linux-musl.so +0 -0
  19. fontTools/feaLib/parser.py +75 -40
  20. fontTools/feaLib/variableScalar.py +6 -1
  21. fontTools/fontBuilder.py +50 -44
  22. fontTools/merge/__init__.py +1 -1
  23. fontTools/merge/cmap.py +33 -1
  24. fontTools/merge/tables.py +12 -1
  25. fontTools/misc/bezierTools.c +14913 -17013
  26. fontTools/misc/bezierTools.cpython-313-aarch64-linux-musl.so +0 -0
  27. fontTools/misc/bezierTools.py +4 -1
  28. fontTools/misc/configTools.py +3 -1
  29. fontTools/misc/enumTools.py +23 -0
  30. fontTools/misc/etree.py +4 -27
  31. fontTools/misc/filesystem/__init__.py +68 -0
  32. fontTools/misc/filesystem/_base.py +134 -0
  33. fontTools/misc/filesystem/_copy.py +45 -0
  34. fontTools/misc/filesystem/_errors.py +54 -0
  35. fontTools/misc/filesystem/_info.py +75 -0
  36. fontTools/misc/filesystem/_osfs.py +164 -0
  37. fontTools/misc/filesystem/_path.py +67 -0
  38. fontTools/misc/filesystem/_subfs.py +92 -0
  39. fontTools/misc/filesystem/_tempfs.py +34 -0
  40. fontTools/misc/filesystem/_tools.py +34 -0
  41. fontTools/misc/filesystem/_walk.py +55 -0
  42. fontTools/misc/filesystem/_zipfs.py +204 -0
  43. fontTools/misc/fixedTools.py +1 -1
  44. fontTools/misc/loggingTools.py +1 -1
  45. fontTools/misc/psCharStrings.py +17 -2
  46. fontTools/misc/sstruct.py +2 -6
  47. fontTools/misc/symfont.py +6 -8
  48. fontTools/misc/testTools.py +5 -1
  49. fontTools/misc/textTools.py +4 -2
  50. fontTools/misc/visitor.py +32 -16
  51. fontTools/misc/xmlWriter.py +44 -8
  52. fontTools/mtiLib/__init__.py +1 -3
  53. fontTools/otlLib/builder.py +402 -155
  54. fontTools/otlLib/optimize/gpos.py +49 -63
  55. fontTools/pens/filterPen.py +218 -26
  56. fontTools/pens/momentsPen.c +5514 -5584
  57. fontTools/pens/momentsPen.cpython-313-aarch64-linux-musl.so +0 -0
  58. fontTools/pens/pointPen.py +61 -18
  59. fontTools/pens/roundingPen.py +2 -2
  60. fontTools/pens/t2CharStringPen.py +31 -11
  61. fontTools/qu2cu/qu2cu.c +6581 -6168
  62. fontTools/qu2cu/qu2cu.cpython-313-aarch64-linux-musl.so +0 -0
  63. fontTools/subset/__init__.py +283 -25
  64. fontTools/subset/svg.py +2 -3
  65. fontTools/ttLib/__init__.py +4 -0
  66. fontTools/ttLib/__main__.py +47 -8
  67. fontTools/ttLib/removeOverlaps.py +7 -5
  68. fontTools/ttLib/reorderGlyphs.py +8 -7
  69. fontTools/ttLib/sfnt.py +11 -9
  70. fontTools/ttLib/tables/D__e_b_g.py +20 -2
  71. fontTools/ttLib/tables/G_V_A_R_.py +5 -0
  72. fontTools/ttLib/tables/S__i_l_f.py +2 -2
  73. fontTools/ttLib/tables/T_S_I__0.py +14 -3
  74. fontTools/ttLib/tables/T_S_I__1.py +2 -5
  75. fontTools/ttLib/tables/T_S_I__5.py +18 -7
  76. fontTools/ttLib/tables/__init__.py +1 -0
  77. fontTools/ttLib/tables/_a_v_a_r.py +12 -3
  78. fontTools/ttLib/tables/_c_m_a_p.py +20 -7
  79. fontTools/ttLib/tables/_c_v_t.py +3 -2
  80. fontTools/ttLib/tables/_f_p_g_m.py +3 -1
  81. fontTools/ttLib/tables/_g_l_y_f.py +45 -21
  82. fontTools/ttLib/tables/_g_v_a_r.py +67 -19
  83. fontTools/ttLib/tables/_h_d_m_x.py +4 -4
  84. fontTools/ttLib/tables/_h_m_t_x.py +7 -3
  85. fontTools/ttLib/tables/_l_o_c_a.py +2 -2
  86. fontTools/ttLib/tables/_n_a_m_e.py +11 -6
  87. fontTools/ttLib/tables/_p_o_s_t.py +9 -7
  88. fontTools/ttLib/tables/otBase.py +5 -12
  89. fontTools/ttLib/tables/otConverters.py +5 -2
  90. fontTools/ttLib/tables/otData.py +1 -1
  91. fontTools/ttLib/tables/otTables.py +33 -30
  92. fontTools/ttLib/tables/otTraverse.py +2 -1
  93. fontTools/ttLib/tables/sbixStrike.py +3 -3
  94. fontTools/ttLib/ttFont.py +666 -120
  95. fontTools/ttLib/ttGlyphSet.py +0 -10
  96. fontTools/ttLib/woff2.py +10 -13
  97. fontTools/ttx.py +13 -1
  98. fontTools/ufoLib/__init__.py +300 -202
  99. fontTools/ufoLib/converters.py +103 -30
  100. fontTools/ufoLib/errors.py +8 -0
  101. fontTools/ufoLib/etree.py +1 -1
  102. fontTools/ufoLib/filenames.py +171 -106
  103. fontTools/ufoLib/glifLib.py +303 -205
  104. fontTools/ufoLib/kerning.py +98 -48
  105. fontTools/ufoLib/utils.py +46 -15
  106. fontTools/ufoLib/validators.py +121 -99
  107. fontTools/unicodedata/Blocks.py +35 -20
  108. fontTools/unicodedata/Mirrored.py +446 -0
  109. fontTools/unicodedata/ScriptExtensions.py +63 -37
  110. fontTools/unicodedata/Scripts.py +173 -152
  111. fontTools/unicodedata/__init__.py +10 -2
  112. fontTools/varLib/__init__.py +198 -109
  113. fontTools/varLib/avar/__init__.py +0 -0
  114. fontTools/varLib/avar/__main__.py +72 -0
  115. fontTools/varLib/avar/build.py +79 -0
  116. fontTools/varLib/avar/map.py +108 -0
  117. fontTools/varLib/avar/plan.py +1004 -0
  118. fontTools/varLib/{avar.py → avar/unbuild.py} +70 -59
  119. fontTools/varLib/avarPlanner.py +3 -999
  120. fontTools/varLib/featureVars.py +21 -7
  121. fontTools/varLib/hvar.py +113 -0
  122. fontTools/varLib/instancer/__init__.py +180 -65
  123. fontTools/varLib/interpolatableHelpers.py +3 -0
  124. fontTools/varLib/iup.c +7564 -6903
  125. fontTools/varLib/iup.cpython-313-aarch64-linux-musl.so +0 -0
  126. fontTools/varLib/models.py +17 -2
  127. fontTools/varLib/mutator.py +11 -0
  128. fontTools/varLib/varStore.py +10 -38
  129. fontTools/voltLib/__main__.py +206 -0
  130. fontTools/voltLib/ast.py +4 -0
  131. fontTools/voltLib/parser.py +16 -8
  132. fontTools/voltLib/voltToFea.py +347 -166
  133. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/METADATA +269 -1410
  134. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/RECORD +318 -294
  135. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/WHEEL +1 -1
  136. fonttools-4.61.1.dist-info/licenses/LICENSE.external +388 -0
  137. {fonttools-4.55.4.data → fonttools-4.61.1.data}/data/share/man/man1/ttx.1 +0 -0
  138. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/entry_points.txt +0 -0
  139. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info/licenses}/LICENSE +0 -0
  140. {fonttools-4.55.4.dist-info → fonttools-4.61.1.dist-info}/top_level.txt +0 -0
fontTools/__init__.py CHANGED
@@ -3,6 +3,6 @@ from fontTools.misc.loggingTools import configLogger
3
3
 
4
4
  log = logging.getLogger(__name__)
5
5
 
6
- version = __version__ = "4.55.4"
6
+ version = __version__ = "4.61.1"
7
7
 
8
8
  __all__ = ["version", "log", "configLogger"]
@@ -0,0 +1,30 @@
1
+ from __future__ import annotations
2
+ from typing import TYPE_CHECKING, Iterable, Optional, TypeVar, Union
3
+ from collections.abc import Callable, Sequence
4
+ from fontTools.misc.filesystem._base import FS
5
+ from os import PathLike
6
+ from xml.etree.ElementTree import Element as ElementTreeElement
7
+
8
+ if TYPE_CHECKING:
9
+ from fontTools.ufoLib import UFOFormatVersion
10
+ from fontTools.ufoLib.glifLib import GLIFFormatVersion
11
+ from lxml.etree import _Element as LxmlElement
12
+
13
+
14
+ T = TypeVar("T") # Generic type
15
+ K = TypeVar("K") # Generic dict key type
16
+ V = TypeVar("V") # Generic dict value type
17
+
18
+ GlyphNameToFileNameFunc = Optional[Callable[[str, set[str]], str]]
19
+ ElementType = Union[ElementTreeElement, "LxmlElement"]
20
+ FormatVersion = Union[int, tuple[int, int]]
21
+ FormatVersions = Optional[Iterable[FormatVersion]]
22
+ GLIFFormatVersionInput = Optional[Union[int, tuple[int, int], "GLIFFormatVersion"]]
23
+ UFOFormatVersionInput = Optional[Union[int, tuple[int, int], "UFOFormatVersion"]]
24
+ IntFloat = Union[int, float]
25
+ KerningPair = tuple[str, str]
26
+ KerningDict = dict[KerningPair, IntFloat]
27
+ KerningGroups = dict[str, Sequence[str]]
28
+ KerningNested = dict[str, dict[str, IntFloat]]
29
+ PathStr = Union[str, PathLike[str]]
30
+ PathOrFS = Union[PathStr, FS]
@@ -2,13 +2,17 @@
2
2
 
3
3
  from fontTools.ttLib import TTFont, newTable
4
4
  from fontTools.misc.cliTools import makeOutputFileName
5
+ from fontTools.misc.psCharStrings import T2StackUseExtractor
5
6
  from fontTools.cffLib import (
6
7
  TopDictIndex,
7
8
  buildOrder,
8
9
  buildDefaults,
9
10
  topDictOperators,
10
11
  privateDictOperators,
12
+ FDSelect,
11
13
  )
14
+ from .transforms import desubroutinizeCharString
15
+ from .specializer import specializeProgram
12
16
  from .width import optimizeWidths
13
17
  from collections import defaultdict
14
18
  import logging
@@ -27,7 +31,7 @@ def _convertCFF2ToCFF(cff, otFont):
27
31
  The CFF2 font cannot be variable. (TODO Accept those and convert to the
28
32
  default instance?)
29
33
 
30
- This assumes a decompiled CFF table. (i.e. that the object has been
34
+ This assumes a decompiled CFF2 table. (i.e. that the object has been
31
35
  filled via :meth:`decompile` and e.g. not loaded from XML.)"""
32
36
 
33
37
  cff.major = 1
@@ -51,9 +55,14 @@ def _convertCFF2ToCFF(cff, otFont):
51
55
  if hasattr(topDict, key):
52
56
  delattr(topDict, key)
53
57
 
54
- fdArray = topDict.FDArray
55
58
  charStrings = topDict.CharStrings
56
59
 
60
+ fdArray = topDict.FDArray
61
+ if not hasattr(topDict, "FDSelect"):
62
+ # FDSelect is optional in CFF2, but required in CFF.
63
+ fdSelect = topDict.FDSelect = FDSelect()
64
+ fdSelect.gidArray = [0] * len(charStrings.charStrings)
65
+
57
66
  defaults = buildDefaults(privateDictOperators)
58
67
  order = buildOrder(privateDictOperators)
59
68
  for fd in fdArray:
@@ -69,6 +78,7 @@ def _convertCFF2ToCFF(cff, otFont):
69
78
  if hasattr(privateDict, key):
70
79
  delattr(privateDict, key)
71
80
 
81
+ # Add ending operators
72
82
  for cs in charStrings.values():
73
83
  cs.decompile()
74
84
  cs.program.append("endchar")
@@ -100,23 +110,43 @@ def _convertCFF2ToCFF(cff, otFont):
100
110
  if width != private.defaultWidthX:
101
111
  cs.program.insert(0, width - private.nominalWidthX)
102
112
 
113
+ # Handle stack use since stack-depth is lower in CFF than in CFF2.
114
+ for glyphName in charStrings.keys():
115
+ cs, fdIndex = charStrings.getItemAndSelector(glyphName)
116
+ if fdIndex is None:
117
+ fdIndex = 0
118
+ private = fdArray[fdIndex].Private
119
+ extractor = T2StackUseExtractor(
120
+ getattr(private, "Subrs", []), cff.GlobalSubrs, private=private
121
+ )
122
+ stackUse = extractor.execute(cs)
123
+ if stackUse > 48: # CFF stack depth is 48
124
+ desubroutinizeCharString(cs)
125
+ cs.program = specializeProgram(cs.program)
126
+
127
+ # Unused subroutines are still in CFF2 (ie. lacking 'return' operator)
128
+ # because they were not decompiled when we added the 'return'.
129
+ # Moreover, some used subroutines may have become unused after the
130
+ # stack-use fixup. So we remove all unused subroutines now.
131
+ cff.remove_unused_subroutines()
132
+
103
133
  mapping = {
104
- name: ("cid" + str(n) if n else ".notdef")
134
+ name: ("cid" + str(n).zfill(5) if n else ".notdef")
105
135
  for n, name in enumerate(topDict.charset)
106
136
  }
107
137
  topDict.charset = [
108
- "cid" + str(n) if n else ".notdef" for n in range(len(topDict.charset))
138
+ "cid" + str(n).zfill(5) if n else ".notdef" for n in range(len(topDict.charset))
109
139
  ]
110
140
  charStrings.charStrings = {
111
141
  mapping[name]: v for name, v in charStrings.charStrings.items()
112
142
  }
113
143
 
114
- # I'm not sure why the following is *not* necessary. And it breaks
115
- # the output if I add it.
116
- # topDict.ROS = ("Adobe", "Identity", 0)
144
+ topDict.ROS = ("Adobe", "Identity", 0)
117
145
 
118
146
 
119
147
  def convertCFF2ToCFF(font, *, updatePostTable=True):
148
+ if "CFF2" not in font:
149
+ raise ValueError("Input font does not contain a CFF2 table.")
120
150
  cff = font["CFF2"].cff
121
151
  _convertCFF2ToCFF(cff, font)
122
152
  del font["CFF2"]
@@ -131,7 +161,7 @@ def convertCFF2ToCFF(font, *, updatePostTable=True):
131
161
 
132
162
 
133
163
  def main(args=None):
134
- """Convert CFF OTF font to CFF2 OTF font"""
164
+ """Convert CFF2 OTF font to CFF OTF font"""
135
165
  if args is None:
136
166
  import sys
137
167
 
@@ -140,8 +170,8 @@ def main(args=None):
140
170
  import argparse
141
171
 
142
172
  parser = argparse.ArgumentParser(
143
- "fonttools cffLib.CFFToCFF2",
144
- description="Upgrade a CFF font to CFF2.",
173
+ "fonttools cffLib.CFF2ToCFF",
174
+ description="Convert a non-variable CFF2 font to CFF.",
145
175
  )
146
176
  parser.add_argument(
147
177
  "input", metavar="INPUT.ttf", help="Input OTF file with CFF table."
@@ -159,6 +189,16 @@ def main(args=None):
159
189
  action="store_false",
160
190
  help="Don't set the output font's timestamp to the current time.",
161
191
  )
192
+ parser.add_argument(
193
+ "--remove-overlaps",
194
+ action="store_true",
195
+ help="Merge overlapping contours and components. Requires skia-pathops",
196
+ )
197
+ parser.add_argument(
198
+ "--ignore-overlap-errors",
199
+ action="store_true",
200
+ help="Don't crash if the remove-overlaps operation fails for some glyphs.",
201
+ )
162
202
  loggingGroup = parser.add_mutually_exclusive_group(required=False)
163
203
  loggingGroup.add_argument(
164
204
  "-v", "--verbose", action="store_true", help="Run more verbosely."
@@ -190,6 +230,21 @@ def main(args=None):
190
230
 
191
231
  convertCFF2ToCFF(font)
192
232
 
233
+ if options.remove_overlaps:
234
+ from fontTools.ttLib.removeOverlaps import removeOverlaps
235
+ from io import BytesIO
236
+
237
+ log.debug("Removing overlaps")
238
+
239
+ stream = BytesIO()
240
+ font.save(stream)
241
+ stream.seek(0)
242
+ font = TTFont(stream, recalcTimestamp=False, recalcBBoxes=False)
243
+ removeOverlaps(
244
+ font,
245
+ ignoreErrors=options.ignore_overlap_errors,
246
+ )
247
+
193
248
  log.info(
194
249
  "Saving %s",
195
250
  outfile,
@@ -1464,10 +1464,11 @@ class CharsetConverter(SimpleConverter):
1464
1464
  if glyphName in allNames:
1465
1465
  # make up a new glyphName that's unique
1466
1466
  n = allNames[glyphName]
1467
- while (glyphName + "#" + str(n)) in allNames:
1467
+ names = set(allNames) | set(charset)
1468
+ while (glyphName + "." + str(n)) in names:
1468
1469
  n += 1
1469
1470
  allNames[glyphName] = n + 1
1470
- glyphName = glyphName + "#" + str(n)
1471
+ glyphName = glyphName + "." + str(n)
1471
1472
  allNames[glyphName] = 1
1472
1473
  newCharset.append(glyphName)
1473
1474
  charset = newCharset
@@ -1663,25 +1664,26 @@ class EncodingConverter(SimpleConverter):
1663
1664
  return "StandardEncoding"
1664
1665
  elif value == 1:
1665
1666
  return "ExpertEncoding"
1667
+ # custom encoding at offset `value`
1668
+ assert value > 1
1669
+ file = parent.file
1670
+ file.seek(value)
1671
+ log.log(DEBUG, "loading Encoding at %s", value)
1672
+ fmt = readCard8(file)
1673
+ haveSupplement = bool(fmt & 0x80)
1674
+ fmt = fmt & 0x7F
1675
+
1676
+ if fmt == 0:
1677
+ encoding = parseEncoding0(parent.charset, file)
1678
+ elif fmt == 1:
1679
+ encoding = parseEncoding1(parent.charset, file)
1666
1680
  else:
1667
- assert value > 1
1668
- file = parent.file
1669
- file.seek(value)
1670
- log.log(DEBUG, "loading Encoding at %s", value)
1671
- fmt = readCard8(file)
1672
- haveSupplement = fmt & 0x80
1673
- if haveSupplement:
1674
- raise NotImplementedError("Encoding supplements are not yet supported")
1675
- fmt = fmt & 0x7F
1676
- if fmt == 0:
1677
- encoding = parseEncoding0(
1678
- parent.charset, file, haveSupplement, parent.strings
1679
- )
1680
- elif fmt == 1:
1681
- encoding = parseEncoding1(
1682
- parent.charset, file, haveSupplement, parent.strings
1683
- )
1684
- return encoding
1681
+ raise ValueError(f"Unknown Encoding format: {fmt}")
1682
+
1683
+ if haveSupplement:
1684
+ parseEncodingSupplement(file, encoding, parent.strings)
1685
+
1686
+ return encoding
1685
1687
 
1686
1688
  def write(self, parent, value):
1687
1689
  if value == "StandardEncoding":
@@ -1719,27 +1721,60 @@ class EncodingConverter(SimpleConverter):
1719
1721
  return encoding
1720
1722
 
1721
1723
 
1722
- def parseEncoding0(charset, file, haveSupplement, strings):
1724
+ def readSID(file):
1725
+ """Read a String ID (SID) — 2-byte unsigned integer."""
1726
+ data = file.read(2)
1727
+ if len(data) != 2:
1728
+ raise EOFError("Unexpected end of file while reading SID")
1729
+ return struct.unpack(">H", data)[0] # big-endian uint16
1730
+
1731
+
1732
+ def parseEncodingSupplement(file, encoding, strings):
1733
+ """
1734
+ Parse the CFF Encoding supplement data:
1735
+ - nSups: number of supplementary mappings
1736
+ - each mapping: (code, SID) pair
1737
+ and apply them to the `encoding` list in place.
1738
+ """
1739
+ nSups = readCard8(file)
1740
+ for _ in range(nSups):
1741
+ code = readCard8(file)
1742
+ sid = readSID(file)
1743
+ name = strings[sid]
1744
+ encoding[code] = name
1745
+
1746
+
1747
+ def parseEncoding0(charset, file):
1748
+ """
1749
+ Format 0: simple list of codes.
1750
+ After reading the base table, optionally parse the supplement.
1751
+ """
1723
1752
  nCodes = readCard8(file)
1724
1753
  encoding = [".notdef"] * 256
1725
1754
  for glyphID in range(1, nCodes + 1):
1726
1755
  code = readCard8(file)
1727
1756
  if code != 0:
1728
1757
  encoding[code] = charset[glyphID]
1758
+
1729
1759
  return encoding
1730
1760
 
1731
1761
 
1732
- def parseEncoding1(charset, file, haveSupplement, strings):
1762
+ def parseEncoding1(charset, file):
1763
+ """
1764
+ Format 1: range-based encoding.
1765
+ After reading the base ranges, optionally parse the supplement.
1766
+ """
1733
1767
  nRanges = readCard8(file)
1734
1768
  encoding = [".notdef"] * 256
1735
1769
  glyphID = 1
1736
- for i in range(nRanges):
1770
+ for _ in range(nRanges):
1737
1771
  code = readCard8(file)
1738
1772
  nLeft = readCard8(file)
1739
- for glyphID in range(glyphID, glyphID + nLeft + 1):
1773
+ for _ in range(nLeft + 1):
1740
1774
  encoding[code] = charset[glyphID]
1741
- code = code + 1
1742
- glyphID = glyphID + 1
1775
+ code += 1
1776
+ glyphID += 1
1777
+
1743
1778
  return encoding
1744
1779
 
1745
1780
 
@@ -580,7 +580,10 @@ def specializeCommands(
580
580
  for i in range(len(commands) - 1, 0, -1):
581
581
  if "rmoveto" == commands[i][0] == commands[i - 1][0]:
582
582
  v1, v2 = commands[i - 1][1], commands[i][1]
583
- commands[i - 1] = ("rmoveto", [v1[0] + v2[0], v1[1] + v2[1]])
583
+ commands[i - 1] = (
584
+ "rmoveto",
585
+ [_addArgs(v1[0], v2[0]), _addArgs(v1[1], v2[1])],
586
+ )
584
587
  del commands[i]
585
588
 
586
589
  # 2. Specialize rmoveto/rlineto/rrcurveto operators into horizontal/vertical variants.
@@ -94,17 +94,22 @@ class _DesubroutinizingT2Decompiler(SimpleT2Decompiler):
94
94
  cs._patches.append((index, subr._desubroutinized))
95
95
 
96
96
 
97
+ def desubroutinizeCharString(cs):
98
+ """Desubroutinize a charstring in-place."""
99
+ cs.decompile()
100
+ subrs = getattr(cs.private, "Subrs", [])
101
+ decompiler = _DesubroutinizingT2Decompiler(subrs, cs.globalSubrs, cs.private)
102
+ decompiler.execute(cs)
103
+ cs.program = cs._desubroutinized
104
+ del cs._desubroutinized
105
+
106
+
97
107
  def desubroutinize(cff):
98
108
  for fontName in cff.fontNames:
99
109
  font = cff[fontName]
100
110
  cs = font.CharStrings
101
111
  for c in cs.values():
102
- c.decompile()
103
- subrs = getattr(c.private, "Subrs", [])
104
- decompiler = _DesubroutinizingT2Decompiler(subrs, c.globalSubrs, c.private)
105
- decompiler.execute(c)
106
- c.program = c._desubroutinized
107
- del c._desubroutinized
112
+ desubroutinizeCharString(c)
108
113
  # Delete all the local subrs
109
114
  if hasattr(font, "FDArray"):
110
115
  for fd in font.FDArray:
@@ -73,3 +73,18 @@ Config.register_option(
73
73
  parse=Option.parse_optional_bool,
74
74
  validate=Option.validate_optional_bool,
75
75
  )
76
+
77
+ Config.register_option(
78
+ name="fontTools.ttLib:OPTIMIZE_FONT_SPEED",
79
+ help=dedent(
80
+ """\
81
+ Enable optimizations that prioritize speed over file size. This
82
+ mainly affects how glyf table and gvar / VARC tables are compiled.
83
+ The produced fonts will be larger, but rendering performance will
84
+ be improved with HarfBuzz and other text layout engines.
85
+ """
86
+ ),
87
+ default=False,
88
+ parse=Option.parse_optional_bool,
89
+ validate=Option.validate_optional_bool,
90
+ )