fonttools 4.56.0__cp313-cp313-win_amd64.whl → 4.58.0__cp313-cp313-win_amd64.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.

Potentially problematic release.


This version of fonttools might be problematic. Click here for more details.

Files changed (71) hide show
  1. fontTools/__init__.py +1 -1
  2. fontTools/cffLib/__init__.py +61 -26
  3. fontTools/config/__init__.py +15 -0
  4. fontTools/cu2qu/cu2qu.c +15345 -14829
  5. fontTools/cu2qu/cu2qu.cp313-win_amd64.pyd +0 -0
  6. fontTools/designspaceLib/statNames.py +14 -7
  7. fontTools/feaLib/ast.py +92 -13
  8. fontTools/feaLib/builder.py +52 -13
  9. fontTools/feaLib/lexer.c +17143 -17986
  10. fontTools/feaLib/lexer.cp313-win_amd64.pyd +0 -0
  11. fontTools/feaLib/parser.py +59 -39
  12. fontTools/fontBuilder.py +6 -0
  13. fontTools/misc/bezierTools.c +39936 -41831
  14. fontTools/misc/bezierTools.cp313-win_amd64.pyd +0 -0
  15. fontTools/misc/etree.py +4 -27
  16. fontTools/misc/testTools.py +2 -1
  17. fontTools/mtiLib/__init__.py +0 -2
  18. fontTools/otlLib/builder.py +195 -145
  19. fontTools/otlLib/optimize/gpos.py +49 -63
  20. fontTools/pens/momentsPen.c +13266 -13448
  21. fontTools/pens/momentsPen.cp313-win_amd64.pyd +0 -0
  22. fontTools/pens/pointPen.py +21 -12
  23. fontTools/qu2cu/qu2cu.c +16538 -16269
  24. fontTools/qu2cu/qu2cu.cp313-win_amd64.pyd +0 -0
  25. fontTools/subset/__init__.py +11 -0
  26. fontTools/ttLib/__init__.py +4 -0
  27. fontTools/ttLib/__main__.py +47 -8
  28. fontTools/ttLib/tables/D__e_b_g.py +20 -2
  29. fontTools/ttLib/tables/G_V_A_R_.py +5 -0
  30. fontTools/ttLib/tables/T_S_I__0.py +14 -3
  31. fontTools/ttLib/tables/T_S_I__5.py +16 -5
  32. fontTools/ttLib/tables/__init__.py +1 -0
  33. fontTools/ttLib/tables/_c_m_a_p.py +19 -6
  34. fontTools/ttLib/tables/_c_v_t.py +2 -0
  35. fontTools/ttLib/tables/_f_p_g_m.py +3 -1
  36. fontTools/ttLib/tables/_g_l_y_f.py +11 -10
  37. fontTools/ttLib/tables/_g_v_a_r.py +62 -17
  38. fontTools/ttLib/tables/_p_o_s_t.py +5 -2
  39. fontTools/ttLib/tables/otBase.py +1 -0
  40. fontTools/ttLib/tables/otConverters.py +5 -2
  41. fontTools/ttLib/tables/otTables.py +5 -1
  42. fontTools/ttLib/ttFont.py +3 -5
  43. fontTools/ttLib/ttGlyphSet.py +0 -10
  44. fontTools/ttx.py +13 -1
  45. fontTools/ufoLib/__init__.py +2 -2
  46. fontTools/ufoLib/converters.py +89 -25
  47. fontTools/ufoLib/errors.py +8 -0
  48. fontTools/ufoLib/etree.py +1 -1
  49. fontTools/ufoLib/filenames.py +155 -100
  50. fontTools/ufoLib/glifLib.py +9 -2
  51. fontTools/ufoLib/kerning.py +66 -36
  52. fontTools/ufoLib/utils.py +5 -2
  53. fontTools/unicodedata/Mirrored.py +446 -0
  54. fontTools/unicodedata/__init__.py +6 -2
  55. fontTools/varLib/__init__.py +94 -89
  56. fontTools/varLib/hvar.py +113 -0
  57. fontTools/varLib/iup.c +19630 -19154
  58. fontTools/varLib/iup.cp313-win_amd64.pyd +0 -0
  59. fontTools/varLib/varStore.py +1 -1
  60. fontTools/voltLib/__main__.py +206 -0
  61. fontTools/voltLib/ast.py +4 -0
  62. fontTools/voltLib/parser.py +16 -8
  63. fontTools/voltLib/voltToFea.py +347 -166
  64. {fonttools-4.56.0.dist-info → fonttools-4.58.0.dist-info}/METADATA +60 -12
  65. {fonttools-4.56.0.dist-info → fonttools-4.58.0.dist-info}/RECORD +71 -66
  66. {fonttools-4.56.0.dist-info → fonttools-4.58.0.dist-info}/WHEEL +1 -1
  67. fonttools-4.58.0.dist-info/licenses/LICENSE.external +359 -0
  68. {fonttools-4.56.0.data → fonttools-4.58.0.data}/data/share/man/man1/ttx.1 +0 -0
  69. {fonttools-4.56.0.dist-info → fonttools-4.58.0.dist-info}/entry_points.txt +0 -0
  70. {fonttools-4.56.0.dist-info → fonttools-4.58.0.dist-info/licenses}/LICENSE +0 -0
  71. {fonttools-4.56.0.dist-info → fonttools-4.58.0.dist-info}/top_level.txt +0 -0
Binary file
@@ -16,6 +16,7 @@ from fontTools.subset.cff import *
16
16
  from fontTools.subset.svg import *
17
17
  from fontTools.varLib import varStore, multiVarStore # For monkey-patching
18
18
  from fontTools.ttLib.tables._n_a_m_e import NameRecordVisitor
19
+ from fontTools.unicodedata import mirrored
19
20
  import sys
20
21
  import struct
21
22
  import array
@@ -2870,6 +2871,15 @@ def prune_post_subset(self, font, options):
2870
2871
  def closure_glyphs(self, s):
2871
2872
  tables = [t for t in self.tables if t.isUnicode()]
2872
2873
 
2874
+ # Closure unicodes, which for now is pulling in bidi mirrored variants
2875
+ if s.options.bidi_closure:
2876
+ additional_unicodes = set()
2877
+ for u in s.unicodes_requested:
2878
+ mirror_u = mirrored(u)
2879
+ if mirror_u is not None:
2880
+ additional_unicodes.add(mirror_u)
2881
+ s.unicodes_requested.update(additional_unicodes)
2882
+
2873
2883
  # Close glyphs
2874
2884
  for table in tables:
2875
2885
  if table.format == 14:
@@ -3191,6 +3201,7 @@ class Options(object):
3191
3201
  self.font_number = -1
3192
3202
  self.pretty_svg = False
3193
3203
  self.lazy = True
3204
+ self.bidi_closure = True
3194
3205
 
3195
3206
  self.set(**kwargs)
3196
3207
 
@@ -1,5 +1,6 @@
1
1
  """fontTools.ttLib -- a package for dealing with TrueType fonts."""
2
2
 
3
+ from fontTools.config import OPTIONS
3
4
  from fontTools.misc.loggingTools import deprecateFunction
4
5
  import logging
5
6
 
@@ -7,6 +8,9 @@ import logging
7
8
  log = logging.getLogger(__name__)
8
9
 
9
10
 
11
+ OPTIMIZE_FONT_SPEED = OPTIONS["fontTools.ttLib:OPTIMIZE_FONT_SPEED"]
12
+
13
+
10
14
  class TTLibError(Exception):
11
15
  pass
12
16
 
@@ -1,5 +1,5 @@
1
1
  import sys
2
- from fontTools.ttLib import TTLibError, TTLibFileIsCollectionError
2
+ from fontTools.ttLib import OPTIMIZE_FONT_SPEED, TTLibError, TTLibFileIsCollectionError
3
3
  from fontTools.ttLib.ttFont import *
4
4
  from fontTools.ttLib.ttCollection import TTCollection
5
5
 
@@ -51,7 +51,7 @@ def main(args=None):
51
51
  )
52
52
  parser.add_argument("font", metavar="font", nargs="*", help="Font file.")
53
53
  parser.add_argument(
54
- "-t", "--table", metavar="table", nargs="*", help="Tables to decompile."
54
+ "-t", "--table", metavar="table", action="append", help="Tables to decompile."
55
55
  )
56
56
  parser.add_argument(
57
57
  "-o", "--output", metavar="FILE", default=None, help="Output file."
@@ -71,27 +71,66 @@ def main(args=None):
71
71
  default=None,
72
72
  help="Flavor of output font. 'woff' or 'woff2'.",
73
73
  )
74
+ parser.add_argument(
75
+ "--no-recalc-timestamp",
76
+ dest="recalcTimestamp",
77
+ action="store_false",
78
+ help="Keep the original font 'modified' timestamp.",
79
+ )
80
+ parser.add_argument(
81
+ "-b",
82
+ dest="recalcBBoxes",
83
+ action="store_false",
84
+ help="Don't recalc glyph bounding boxes: use the values in the original font.",
85
+ )
86
+ parser.add_argument(
87
+ "--optimize-font-speed",
88
+ action="store_true",
89
+ help=(
90
+ "Enable optimizations that prioritize speed over file size. This "
91
+ "mainly affects how glyf table and gvar / VARC tables are compiled."
92
+ ),
93
+ )
74
94
  options = parser.parse_args(args)
75
95
 
76
96
  fontNumber = int(options.y) if options.y is not None else None
77
97
  outFile = options.output
78
98
  lazy = options.lazy
79
99
  flavor = options.flavor
80
- tables = options.table if options.table is not None else ["*"]
100
+ tables = options.table
101
+ recalcBBoxes = options.recalcBBoxes
102
+ recalcTimestamp = options.recalcTimestamp
103
+ optimizeFontSpeed = options.optimize_font_speed
81
104
 
82
105
  fonts = []
83
106
  for f in options.font:
84
107
  try:
85
- font = TTFont(f, fontNumber=fontNumber, lazy=lazy)
108
+ font = TTFont(
109
+ f,
110
+ recalcBBoxes=recalcBBoxes,
111
+ recalcTimestamp=recalcTimestamp,
112
+ fontNumber=fontNumber,
113
+ lazy=lazy,
114
+ )
115
+ if optimizeFontSpeed:
116
+ font.cfg[OPTIMIZE_FONT_SPEED] = optimizeFontSpeed
86
117
  fonts.append(font)
87
118
  except TTLibFileIsCollectionError:
88
119
  collection = TTCollection(f, lazy=lazy)
89
120
  fonts.extend(collection.fonts)
90
121
 
91
- if lazy is False:
92
- for font in fonts:
93
- for table in tables if "*" not in tables else font.keys():
94
- font[table] # Decompiles
122
+ if tables is None:
123
+ if lazy is False:
124
+ tables = ["*"]
125
+ elif optimizeFontSpeed:
126
+ tables = {"glyf", "gvar", "VARC"}.intersection(font.keys())
127
+ else:
128
+ tables = []
129
+ for font in fonts:
130
+ if "GlyphOrder" in tables:
131
+ font.getGlyphOrder()
132
+ for table in tables if "*" not in tables else font.keys():
133
+ font[table] # Decompiles
95
134
 
96
135
  if outFile is not None:
97
136
  if len(fonts) == 1:
@@ -1,9 +1,15 @@
1
1
  import json
2
+ from textwrap import indent
2
3
 
3
4
  from . import DefaultTable
5
+ from fontTools.misc.textTools import tostr
4
6
 
5
7
 
6
8
  class table_D__e_b_g(DefaultTable.DefaultTable):
9
+ def __init__(self, tag=None):
10
+ DefaultTable.DefaultTable.__init__(self, tag)
11
+ self.data = {}
12
+
7
13
  def decompile(self, data, ttFont):
8
14
  self.data = json.loads(data)
9
15
 
@@ -11,7 +17,19 @@ class table_D__e_b_g(DefaultTable.DefaultTable):
11
17
  return json.dumps(self.data).encode("utf-8")
12
18
 
13
19
  def toXML(self, writer, ttFont):
14
- writer.writecdata(json.dumps(self.data, indent=2))
20
+ # make sure json indentation inside CDATA block matches XMLWriter's
21
+ data = json.dumps(self.data, indent=len(writer.indentwhite))
22
+ prefix = tostr(writer.indentwhite) * (writer.indentlevel + 1)
23
+ # but don't indent the first json line so it's adjacent to `<![CDATA[{`
24
+ cdata = indent(data, prefix, lambda ln: ln != "{\n")
25
+
26
+ writer.begintag("json")
27
+ writer.newline()
28
+ writer.writecdata(cdata)
29
+ writer.newline()
30
+ writer.endtag("json")
31
+ writer.newline()
15
32
 
16
33
  def fromXML(self, name, attrs, content, ttFont):
17
- self.data = json.loads(content)
34
+ if name == "json":
35
+ self.data = json.loads("".join(content))
@@ -0,0 +1,5 @@
1
+ from ._g_v_a_r import table__g_v_a_r
2
+
3
+
4
+ class table_G_V_A_R_(table__g_v_a_r):
5
+ gid_size = 3
@@ -1,4 +1,4 @@
1
- """ TSI{0,1,2,3,5} are private tables used by Microsoft Visual TrueType (VTT)
1
+ """TSI{0,1,2,3,5} are private tables used by Microsoft Visual TrueType (VTT)
2
2
  tool to store its hinting source data.
3
3
 
4
4
  TSI0 is the index table containing the lengths and offsets for the glyph
@@ -8,9 +8,13 @@ in the TSI1 table.
8
8
  See also https://learn.microsoft.com/en-us/typography/tools/vtt/tsi-tables
9
9
  """
10
10
 
11
- from . import DefaultTable
11
+ import logging
12
12
  import struct
13
13
 
14
+ from . import DefaultTable
15
+
16
+ log = logging.getLogger(__name__)
17
+
14
18
  tsi0Format = ">HHL"
15
19
 
16
20
 
@@ -25,7 +29,14 @@ class table_T_S_I__0(DefaultTable.DefaultTable):
25
29
  numGlyphs = ttFont["maxp"].numGlyphs
26
30
  indices = []
27
31
  size = struct.calcsize(tsi0Format)
28
- for i in range(numGlyphs + 5):
32
+ numEntries = len(data) // size
33
+ if numEntries != numGlyphs + 5:
34
+ diff = numEntries - numGlyphs - 5
35
+ log.warning(
36
+ "Number of glyphPrograms differs from the number of glyphs in the font "
37
+ f"by {abs(diff)} ({numEntries - 5} programs vs. {numGlyphs} glyphs)."
38
+ )
39
+ for _ in range(numEntries):
29
40
  glyphID, textLength, textOffset = fixlongs(
30
41
  *struct.unpack(tsi0Format, data[:size])
31
42
  )
@@ -1,4 +1,4 @@
1
- """ TSI{0,1,2,3,5} are private tables used by Microsoft Visual TrueType (VTT)
1
+ """TSI{0,1,2,3,5} are private tables used by Microsoft Visual TrueType (VTT)
2
2
  tool to store its hinting source data.
3
3
 
4
4
  TSI5 contains the VTT character groups.
@@ -6,22 +6,33 @@ TSI5 contains the VTT character groups.
6
6
  See also https://learn.microsoft.com/en-us/typography/tools/vtt/tsi-tables
7
7
  """
8
8
 
9
+ import array
10
+ import logging
11
+ import sys
12
+
9
13
  from fontTools.misc.textTools import safeEval
14
+
10
15
  from . import DefaultTable
11
- import sys
12
- import array
16
+
17
+ log = logging.getLogger(__name__)
13
18
 
14
19
 
15
20
  class table_T_S_I__5(DefaultTable.DefaultTable):
16
21
  def decompile(self, data, ttFont):
17
22
  numGlyphs = ttFont["maxp"].numGlyphs
18
- assert len(data) == 2 * numGlyphs
19
23
  a = array.array("H")
20
24
  a.frombytes(data)
21
25
  if sys.byteorder != "big":
22
26
  a.byteswap()
23
27
  self.glyphGrouping = {}
24
- for i in range(numGlyphs):
28
+ numEntries = len(data) // 2
29
+ if numEntries != numGlyphs:
30
+ diff = numEntries - numGlyphs
31
+ log.warning(
32
+ "Number of entries differs from the number of glyphs in the font "
33
+ f"by {abs(diff)} ({numEntries} entries vs. {numGlyphs} glyphs)."
34
+ )
35
+ for i in range(numEntries):
25
36
  self.glyphGrouping[ttFont.getGlyphName(i)] = a[i]
26
37
 
27
38
  def compile(self, ttFont):
@@ -23,6 +23,7 @@ def _moduleFinderHint():
23
23
  from . import G_P_K_G_
24
24
  from . import G_P_O_S_
25
25
  from . import G_S_U_B_
26
+ from . import G_V_A_R_
26
27
  from . import G__l_a_t
27
28
  from . import G__l_o_c
28
29
  from . import H_V_A_R_
@@ -141,6 +141,17 @@ class table__c_m_a_p(DefaultTable.DefaultTable):
141
141
  result.setdefault(name, set()).add(codepoint)
142
142
  return result
143
143
 
144
+ def buildReversedMin(self):
145
+ result = {}
146
+ for subtable in self.tables:
147
+ if subtable.isUnicode():
148
+ for codepoint, name in subtable.cmap.items():
149
+ if name in result:
150
+ result[name] = min(result[name], codepoint)
151
+ else:
152
+ result[name] = codepoint
153
+ return result
154
+
144
155
  def decompile(self, data, ttFont):
145
156
  tableVersion, numSubTables = struct.unpack(">HH", data[:4])
146
157
  self.tableVersion = int(tableVersion)
@@ -1162,13 +1173,15 @@ class cmap_format_12_or_13(CmapSubtable):
1162
1173
  charCodes = []
1163
1174
  gids = []
1164
1175
  pos = 0
1176
+ groups = array.array("I", data[: self.nGroups * 12])
1177
+ if sys.byteorder != "big":
1178
+ groups.byteswap()
1165
1179
  for i in range(self.nGroups):
1166
- startCharCode, endCharCode, glyphID = struct.unpack(
1167
- ">LLL", data[pos : pos + 12]
1168
- )
1169
- pos += 12
1180
+ startCharCode = groups[i * 3]
1181
+ endCharCode = groups[i * 3 + 1]
1182
+ glyphID = groups[i * 3 + 2]
1170
1183
  lenGroup = 1 + endCharCode - startCharCode
1171
- charCodes.extend(list(range(startCharCode, endCharCode + 1)))
1184
+ charCodes.extend(range(startCharCode, endCharCode + 1))
1172
1185
  gids.extend(self._computeGIDs(glyphID, lenGroup))
1173
1186
  self.data = data = None
1174
1187
  self.cmap = _make_map(self.ttFont, charCodes, gids)
@@ -1299,7 +1312,7 @@ class cmap_format_12(cmap_format_12_or_13):
1299
1312
  cmap_format_12_or_13.__init__(self, format)
1300
1313
 
1301
1314
  def _computeGIDs(self, startingGlyph, numberOfGlyphs):
1302
- return list(range(startingGlyph, startingGlyph + numberOfGlyphs))
1315
+ return range(startingGlyph, startingGlyph + numberOfGlyphs)
1303
1316
 
1304
1317
  def _IsInSameRun(self, glyphID, lastGlyphID, charCode, lastCharCode):
1305
1318
  return (glyphID == 1 + lastGlyphID) and (charCode == 1 + lastCharCode)
@@ -21,6 +21,8 @@ class table__c_v_t(DefaultTable.DefaultTable):
21
21
  self.values = values
22
22
 
23
23
  def compile(self, ttFont):
24
+ if not hasattr(self, "values"):
25
+ return b""
24
26
  values = self.values[:]
25
27
  if sys.byteorder != "big":
26
28
  values.byteswap()
@@ -20,7 +20,9 @@ class table__f_p_g_m(DefaultTable.DefaultTable):
20
20
  self.program = program
21
21
 
22
22
  def compile(self, ttFont):
23
- return self.program.getBytecode()
23
+ if hasattr(self, "program"):
24
+ return self.program.getBytecode()
25
+ return b""
24
26
 
25
27
  def toXML(self, writer, ttFont):
26
28
  self.program.toXML(writer, ttFont)
@@ -134,6 +134,8 @@ class table__g_l_y_f(DefaultTable.DefaultTable):
134
134
  glyph.expand(self)
135
135
 
136
136
  def compile(self, ttFont):
137
+ optimizeSpeed = ttFont.cfg[ttLib.OPTIMIZE_FONT_SPEED]
138
+
137
139
  self.axisTags = (
138
140
  [axis.axisTag for axis in ttFont["fvar"].axes] if "fvar" in ttFont else []
139
141
  )
@@ -148,7 +150,12 @@ class table__g_l_y_f(DefaultTable.DefaultTable):
148
150
  boundsDone = set()
149
151
  for glyphName in self.glyphOrder:
150
152
  glyph = self.glyphs[glyphName]
151
- glyphData = glyph.compile(self, recalcBBoxes, boundsDone=boundsDone)
153
+ glyphData = glyph.compile(
154
+ self,
155
+ recalcBBoxes,
156
+ boundsDone=boundsDone,
157
+ optimizeSize=not optimizeSpeed,
158
+ )
152
159
  if padding > 1:
153
160
  glyphData = pad(glyphData, size=padding)
154
161
  locations.append(currentLocation)
@@ -714,7 +721,7 @@ class Glyph(object):
714
721
  self.decompileCoordinates(data)
715
722
 
716
723
  def compile(
717
- self, glyfTable, recalcBBoxes=True, *, boundsDone=None, optimizeSize=None
724
+ self, glyfTable, recalcBBoxes=True, *, boundsDone=None, optimizeSize=True
718
725
  ):
719
726
  if hasattr(self, "data"):
720
727
  if recalcBBoxes:
@@ -732,8 +739,6 @@ class Glyph(object):
732
739
  if self.isComposite():
733
740
  data = data + self.compileComponents(glyfTable)
734
741
  else:
735
- if optimizeSize is None:
736
- optimizeSize = getattr(glyfTable, "optimizeSize", True)
737
742
  data = data + self.compileCoordinates(optimizeSize=optimizeSize)
738
743
  return data
739
744
 
@@ -1220,7 +1225,7 @@ class Glyph(object):
1220
1225
  if boundsDone is not None:
1221
1226
  boundsDone.add(glyphName)
1222
1227
  # empty components shouldn't update the bounds of the parent glyph
1223
- if g.numberOfContours == 0:
1228
+ if g.yMin == g.yMax and g.xMin == g.xMax:
1224
1229
  continue
1225
1230
 
1226
1231
  x, y = compo.x, compo.y
@@ -1280,11 +1285,7 @@ class Glyph(object):
1280
1285
  # however, if the referenced component glyph is another composite, we
1281
1286
  # must not round here but only at the end, after all the nested
1282
1287
  # transforms have been applied, or else rounding errors will compound.
1283
- if (
1284
- round is not noRound
1285
- and g.numberOfContours > 0
1286
- and not compo._hasOnlyIntegerTranslate()
1287
- ):
1288
+ if round is not noRound and g.numberOfContours > 0:
1288
1289
  coordinates.toInt(round=round)
1289
1290
  if hasattr(compo, "firstPt"):
1290
1291
  # component uses two reference points: we apply the transform _before_
@@ -3,6 +3,7 @@ from functools import partial
3
3
  from fontTools.misc import sstruct
4
4
  from fontTools.misc.textTools import safeEval
5
5
  from fontTools.misc.lazyTools import LazyDict
6
+ from fontTools.ttLib import OPTIMIZE_FONT_SPEED
6
7
  from fontTools.ttLib.tables.TupleVariation import TupleVariation
7
8
  from . import DefaultTable
8
9
  import array
@@ -23,19 +24,24 @@ log = logging.getLogger(__name__)
23
24
  # FreeType2 source code for parsing 'gvar':
24
25
  # http://git.savannah.gnu.org/cgit/freetype/freetype2.git/tree/src/truetype/ttgxvar.c
25
26
 
26
- GVAR_HEADER_FORMAT = """
27
+ GVAR_HEADER_FORMAT_HEAD = """
27
28
  > # big endian
28
29
  version: H
29
30
  reserved: H
30
31
  axisCount: H
31
32
  sharedTupleCount: H
32
33
  offsetToSharedTuples: I
33
- glyphCount: H
34
+ """
35
+ # In between the HEAD and TAIL lies the glyphCount, which is
36
+ # of different size: 2 bytes for gvar, and 3 bytes for GVAR.
37
+ GVAR_HEADER_FORMAT_TAIL = """
38
+ > # big endian
34
39
  flags: H
35
40
  offsetToGlyphVariationData: I
36
41
  """
37
42
 
38
- GVAR_HEADER_SIZE = sstruct.calcsize(GVAR_HEADER_FORMAT)
43
+ GVAR_HEADER_SIZE_HEAD = sstruct.calcsize(GVAR_HEADER_FORMAT_HEAD)
44
+ GVAR_HEADER_SIZE_TAIL = sstruct.calcsize(GVAR_HEADER_FORMAT_TAIL)
39
45
 
40
46
 
41
47
  class table__g_v_a_r(DefaultTable.DefaultTable):
@@ -50,6 +56,7 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
50
56
  """
51
57
 
52
58
  dependencies = ["fvar", "glyf"]
59
+ gid_size = 2
53
60
 
54
61
  def __init__(self, tag=None):
55
62
  DefaultTable.DefaultTable.__init__(self, tag)
@@ -57,6 +64,7 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
57
64
  self.variations = {}
58
65
 
59
66
  def compile(self, ttFont):
67
+
60
68
  axisTags = [axis.axisTag for axis in ttFont["fvar"].axes]
61
69
  sharedTuples = tv.compileSharedTuples(
62
70
  axisTags, itertools.chain(*self.variations.values())
@@ -72,28 +80,33 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
72
80
  offsets.append(offset)
73
81
  compiledOffsets, tableFormat = self.compileOffsets_(offsets)
74
82
 
83
+ GVAR_HEADER_SIZE = GVAR_HEADER_SIZE_HEAD + self.gid_size + GVAR_HEADER_SIZE_TAIL
75
84
  header = {}
76
85
  header["version"] = self.version
77
86
  header["reserved"] = self.reserved
78
87
  header["axisCount"] = len(axisTags)
79
88
  header["sharedTupleCount"] = len(sharedTuples)
80
89
  header["offsetToSharedTuples"] = GVAR_HEADER_SIZE + len(compiledOffsets)
81
- header["glyphCount"] = len(compiledGlyphs)
82
90
  header["flags"] = tableFormat
83
91
  header["offsetToGlyphVariationData"] = (
84
92
  header["offsetToSharedTuples"] + sharedTupleSize
85
93
  )
86
- compiledHeader = sstruct.pack(GVAR_HEADER_FORMAT, header)
87
94
 
88
- result = [compiledHeader, compiledOffsets]
95
+ result = [
96
+ sstruct.pack(GVAR_HEADER_FORMAT_HEAD, header),
97
+ len(compiledGlyphs).to_bytes(self.gid_size, "big"),
98
+ sstruct.pack(GVAR_HEADER_FORMAT_TAIL, header),
99
+ ]
100
+
101
+ result.append(compiledOffsets)
89
102
  result.extend(sharedTuples)
90
103
  result.extend(compiledGlyphs)
91
104
  return b"".join(result)
92
105
 
93
106
  def compileGlyphs_(self, ttFont, axisTags, sharedCoordIndices):
107
+ optimizeSpeed = ttFont.cfg[OPTIMIZE_FONT_SPEED]
94
108
  result = []
95
109
  glyf = ttFont["glyf"]
96
- optimizeSize = getattr(self, "optimizeSize", True)
97
110
  for glyphName in ttFont.getGlyphOrder():
98
111
  variations = self.variations.get(glyphName, [])
99
112
  if not variations:
@@ -102,11 +115,12 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
102
115
  pointCountUnused = 0 # pointCount is actually unused by compileGlyph
103
116
  result.append(
104
117
  compileGlyph_(
118
+ self.gid_size,
105
119
  variations,
106
120
  pointCountUnused,
107
121
  axisTags,
108
122
  sharedCoordIndices,
109
- optimizeSize=optimizeSize,
123
+ optimizeSize=not optimizeSpeed,
110
124
  )
111
125
  )
112
126
  return result
@@ -114,7 +128,19 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
114
128
  def decompile(self, data, ttFont):
115
129
  axisTags = [axis.axisTag for axis in ttFont["fvar"].axes]
116
130
  glyphs = ttFont.getGlyphOrder()
117
- sstruct.unpack(GVAR_HEADER_FORMAT, data[0:GVAR_HEADER_SIZE], self)
131
+
132
+ # Parse the header
133
+ GVAR_HEADER_SIZE = GVAR_HEADER_SIZE_HEAD + self.gid_size + GVAR_HEADER_SIZE_TAIL
134
+ sstruct.unpack(GVAR_HEADER_FORMAT_HEAD, data[:GVAR_HEADER_SIZE_HEAD], self)
135
+ self.glyphCount = int.from_bytes(
136
+ data[GVAR_HEADER_SIZE_HEAD : GVAR_HEADER_SIZE_HEAD + self.gid_size], "big"
137
+ )
138
+ sstruct.unpack(
139
+ GVAR_HEADER_FORMAT_TAIL,
140
+ data[GVAR_HEADER_SIZE_HEAD + self.gid_size : GVAR_HEADER_SIZE],
141
+ self,
142
+ )
143
+
118
144
  assert len(glyphs) == self.glyphCount
119
145
  assert len(axisTags) == self.axisCount
120
146
  sharedCoords = tv.decompileSharedTuples(
@@ -144,7 +170,7 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
144
170
  glyph = glyf[glyphName]
145
171
  numPointsInGlyph = self.getNumPoints_(glyph)
146
172
  return decompileGlyph_(
147
- numPointsInGlyph, sharedCoords, axisTags, gvarData
173
+ self.gid_size, numPointsInGlyph, sharedCoords, axisTags, gvarData
148
174
  )
149
175
 
150
176
  return read_item
@@ -262,23 +288,42 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
262
288
 
263
289
 
264
290
  def compileGlyph_(
265
- variations, pointCount, axisTags, sharedCoordIndices, *, optimizeSize=True
291
+ dataOffsetSize,
292
+ variations,
293
+ pointCount,
294
+ axisTags,
295
+ sharedCoordIndices,
296
+ *,
297
+ optimizeSize=True,
266
298
  ):
299
+ assert dataOffsetSize in (2, 3)
267
300
  tupleVariationCount, tuples, data = tv.compileTupleVariationStore(
268
301
  variations, pointCount, axisTags, sharedCoordIndices, optimizeSize=optimizeSize
269
302
  )
270
303
  if tupleVariationCount == 0:
271
304
  return b""
272
- result = [struct.pack(">HH", tupleVariationCount, 4 + len(tuples)), tuples, data]
273
- if (len(tuples) + len(data)) % 2 != 0:
305
+
306
+ offsetToData = 2 + dataOffsetSize + len(tuples)
307
+
308
+ result = [
309
+ tupleVariationCount.to_bytes(2, "big"),
310
+ offsetToData.to_bytes(dataOffsetSize, "big"),
311
+ tuples,
312
+ data,
313
+ ]
314
+ if (offsetToData + len(data)) % 2 != 0:
274
315
  result.append(b"\0") # padding
275
316
  return b"".join(result)
276
317
 
277
318
 
278
- def decompileGlyph_(pointCount, sharedTuples, axisTags, data):
279
- if len(data) < 4:
319
+ def decompileGlyph_(dataOffsetSize, pointCount, sharedTuples, axisTags, data):
320
+ assert dataOffsetSize in (2, 3)
321
+ if len(data) < 2 + dataOffsetSize:
280
322
  return []
281
- tupleVariationCount, offsetToData = struct.unpack(">HH", data[:4])
323
+
324
+ tupleVariationCount = int.from_bytes(data[:2], "big")
325
+ offsetToData = int.from_bytes(data[2 : 2 + dataOffsetSize], "big")
326
+
282
327
  dataPos = offsetToData
283
328
  return tv.decompileTupleVariationStore(
284
329
  "gvar",
@@ -287,6 +332,6 @@ def decompileGlyph_(pointCount, sharedTuples, axisTags, data):
287
332
  pointCount,
288
333
  sharedTuples,
289
334
  data,
290
- 4,
335
+ 2 + dataOffsetSize,
291
336
  offsetToData,
292
337
  )
@@ -122,13 +122,16 @@ class table__p_o_s_t(DefaultTable.DefaultTable):
122
122
  glyphName = psName = self.glyphOrder[i]
123
123
  if glyphName == "":
124
124
  glyphName = "glyph%.5d" % i
125
+
125
126
  if glyphName in allNames:
126
127
  # make up a new glyphName that's unique
127
128
  n = allNames[glyphName]
128
- while (glyphName + "#" + str(n)) in allNames:
129
+ # check if the exists in any of the seen names or later ones
130
+ names = set(allNames.keys()) | set(self.glyphOrder)
131
+ while (glyphName + "." + str(n)) in names:
129
132
  n += 1
130
133
  allNames[glyphName] = n + 1
131
- glyphName = glyphName + "#" + str(n)
134
+ glyphName = glyphName + "." + str(n)
132
135
 
133
136
  self.glyphOrder[i] = glyphName
134
137
  allNames[glyphName] = 1
@@ -398,6 +398,7 @@ class OTTableWriter(object):
398
398
  self.localState = localState
399
399
  self.tableTag = tableTag
400
400
  self.parent = None
401
+ self.name = "<none>"
401
402
 
402
403
  def __setitem__(self, name, value):
403
404
  state = self.localState.copy() if self.localState else dict()
@@ -10,7 +10,7 @@ from fontTools.ttLib.tables.TupleVariation import TupleVariation
10
10
  from fontTools.misc.roundTools import nearestMultipleShortestRepr, otRound
11
11
  from fontTools.misc.textTools import bytesjoin, tobytes, tostr, pad, safeEval
12
12
  from fontTools.misc.lazyTools import LazyList
13
- from fontTools.ttLib import getSearchRange
13
+ from fontTools.ttLib import OPTIMIZE_FONT_SPEED, getSearchRange
14
14
  from .otBase import (
15
15
  CountReference,
16
16
  FormatSwitchingBaseTable,
@@ -1810,7 +1810,10 @@ class TupleValues:
1810
1810
  return TupleVariation.decompileDeltas_(None, data)[0]
1811
1811
 
1812
1812
  def write(self, writer, font, tableDict, values, repeatIndex=None):
1813
- return bytes(TupleVariation.compileDeltaValues_(values))
1813
+ optimizeSpeed = font.cfg[OPTIMIZE_FONT_SPEED]
1814
+ return bytes(
1815
+ TupleVariation.compileDeltaValues_(values, optimizeSize=not optimizeSpeed)
1816
+ )
1814
1817
 
1815
1818
  def xmlRead(self, attrs, content, font):
1816
1819
  return safeEval(attrs["value"])
@@ -11,6 +11,7 @@ from functools import reduce
11
11
  from math import radians
12
12
  import itertools
13
13
  from collections import defaultdict, namedtuple
14
+ from fontTools.ttLib import OPTIMIZE_FONT_SPEED
14
15
  from fontTools.ttLib.tables.TupleVariation import TupleVariation
15
16
  from fontTools.ttLib.tables.otTraverse import dfs_base_table
16
17
  from fontTools.misc.arrayTools import quantizeRect
@@ -236,6 +237,8 @@ class VarComponent:
236
237
  return data[i:]
237
238
 
238
239
  def compile(self, font):
240
+ optimizeSpeed = font.cfg[OPTIMIZE_FONT_SPEED]
241
+
239
242
  data = []
240
243
 
241
244
  flags = self.flags
@@ -259,7 +262,8 @@ class VarComponent:
259
262
  data.append(_write_uint32var(self.axisIndicesIndex))
260
263
  data.append(
261
264
  TupleVariation.compileDeltaValues_(
262
- [fl2fi(v, 14) for v in self.axisValues]
265
+ [fl2fi(v, 14) for v in self.axisValues],
266
+ optimizeSize=not optimizeSpeed,
263
267
  )
264
268
  )
265
269
  else: