fonttools 4.58.0__cp39-cp39-win_amd64.whl → 4.58.2__cp39-cp39-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 (32) hide show
  1. fontTools/__init__.py +1 -1
  2. fontTools/cffLib/specializer.py +4 -1
  3. fontTools/cu2qu/cu2qu.cp39-win_amd64.pyd +0 -0
  4. fontTools/feaLib/ast.py +1 -72
  5. fontTools/feaLib/builder.py +55 -45
  6. fontTools/feaLib/lexer.cp39-win_amd64.pyd +0 -0
  7. fontTools/merge/cmap.py +33 -1
  8. fontTools/merge/tables.py +12 -1
  9. fontTools/misc/bezierTools.cp39-win_amd64.pyd +0 -0
  10. fontTools/misc/loggingTools.py +1 -1
  11. fontTools/misc/symfont.py +6 -8
  12. fontTools/mtiLib/__init__.py +1 -1
  13. fontTools/otlLib/builder.py +164 -0
  14. fontTools/pens/momentsPen.cp39-win_amd64.pyd +0 -0
  15. fontTools/pens/t2CharStringPen.py +31 -11
  16. fontTools/qu2cu/qu2cu.cp39-win_amd64.pyd +0 -0
  17. fontTools/subset/__init__.py +82 -2
  18. fontTools/ttLib/reorderGlyphs.py +8 -7
  19. fontTools/ufoLib/__init__.py +1 -1
  20. fontTools/varLib/__init__.py +18 -6
  21. fontTools/varLib/featureVars.py +13 -7
  22. fontTools/varLib/hvar.py +1 -1
  23. fontTools/varLib/instancer/__init__.py +14 -5
  24. fontTools/varLib/iup.cp39-win_amd64.pyd +0 -0
  25. {fonttools-4.58.0.dist-info → fonttools-4.58.2.dist-info}/METADATA +26 -1
  26. {fonttools-4.58.0.dist-info → fonttools-4.58.2.dist-info}/RECORD +32 -32
  27. {fonttools-4.58.0.dist-info → fonttools-4.58.2.dist-info}/WHEEL +1 -1
  28. {fonttools-4.58.0.data → fonttools-4.58.2.data}/data/share/man/man1/ttx.1 +0 -0
  29. {fonttools-4.58.0.dist-info → fonttools-4.58.2.dist-info}/entry_points.txt +0 -0
  30. {fonttools-4.58.0.dist-info → fonttools-4.58.2.dist-info}/licenses/LICENSE +0 -0
  31. {fonttools-4.58.0.dist-info → fonttools-4.58.2.dist-info}/licenses/LICENSE.external +0 -0
  32. {fonttools-4.58.0.dist-info → fonttools-4.58.2.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.58.0"
6
+ version = __version__ = "4.58.2"
7
7
 
8
8
  __all__ = ["version", "log", "configLogger"]
@@ -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.
Binary file
fontTools/feaLib/ast.py CHANGED
@@ -337,76 +337,6 @@ class AnonymousBlock(Statement):
337
337
  return res
338
338
 
339
339
 
340
- def _upgrade_mixed_subst_statements(statements):
341
- # https://github.com/fonttools/fonttools/issues/612
342
- # A multiple substitution may have a single destination, in which case
343
- # it will look just like a single substitution. So if there are both
344
- # multiple and single substitutions, upgrade all the single ones to
345
- # multiple substitutions. Similarly, a ligature substitution may have a
346
- # single source glyph, so if there are both ligature and single
347
- # substitutions, upgrade all the single ones to ligature substitutions.
348
-
349
- has_single = False
350
- has_multiple = False
351
- has_ligature = False
352
- for s in statements:
353
- if isinstance(s, SingleSubstStatement):
354
- has_single = not any([s.prefix, s.suffix, s.forceChain])
355
- elif isinstance(s, MultipleSubstStatement):
356
- has_multiple = not any([s.prefix, s.suffix, s.forceChain])
357
- elif isinstance(s, LigatureSubstStatement):
358
- has_ligature = not any([s.prefix, s.suffix, s.forceChain])
359
-
360
- to_multiple = False
361
- to_ligature = False
362
-
363
- # If we have mixed single and multiple substitutions,
364
- # upgrade all single substitutions to multiple substitutions.
365
- if has_single and has_multiple and not has_ligature:
366
- to_multiple = True
367
-
368
- # If we have mixed single and ligature substitutions,
369
- # upgrade all single substitutions to ligature substitutions.
370
- elif has_single and has_ligature and not has_multiple:
371
- to_ligature = True
372
-
373
- if to_multiple or to_ligature:
374
- ret = []
375
- for s in statements:
376
- if isinstance(s, SingleSubstStatement):
377
- glyphs = s.glyphs[0].glyphSet()
378
- replacements = s.replacements[0].glyphSet()
379
- if len(replacements) == 1:
380
- replacements *= len(glyphs)
381
- for glyph, replacement in zip(glyphs, replacements):
382
- if to_multiple:
383
- ret.append(
384
- MultipleSubstStatement(
385
- s.prefix,
386
- glyph,
387
- s.suffix,
388
- [replacement],
389
- s.forceChain,
390
- location=s.location,
391
- )
392
- )
393
- elif to_ligature:
394
- ret.append(
395
- LigatureSubstStatement(
396
- s.prefix,
397
- [GlyphName(glyph)],
398
- s.suffix,
399
- replacement,
400
- s.forceChain,
401
- location=s.location,
402
- )
403
- )
404
- else:
405
- ret.append(s)
406
- return ret
407
- return statements
408
-
409
-
410
340
  class Block(Statement):
411
341
  """A block of statements: feature, lookup, etc."""
412
342
 
@@ -418,8 +348,7 @@ class Block(Statement):
418
348
  """When handed a 'builder' object of comparable interface to
419
349
  :class:`fontTools.feaLib.builder`, walks the statements in this
420
350
  block, calling the builder callbacks."""
421
- statements = _upgrade_mixed_subst_statements(self.statements)
422
- for s in statements:
351
+ for s in self.statements:
423
352
  s.build(builder)
424
353
 
425
354
  def asFea(self, indent=""):
@@ -29,6 +29,7 @@ from fontTools.otlLib.builder import (
29
29
  PairPosBuilder,
30
30
  SinglePosBuilder,
31
31
  ChainContextualRule,
32
+ AnySubstBuilder,
32
33
  )
33
34
  from fontTools.otlLib.error import OpenTypeLibError
34
35
  from fontTools.varLib.varStore import OnlineVarStoreBuilder
@@ -866,13 +867,22 @@ class Builder(object):
866
867
  for lookup in self.lookups_:
867
868
  if lookup.table != tag:
868
869
  continue
869
- lookup.lookup_index = len(lookups)
870
- self.lookup_locations[tag][str(lookup.lookup_index)] = LookupDebugInfo(
871
- location=str(lookup.location),
872
- name=self.get_lookup_name_(lookup),
873
- feature=None,
874
- )
875
- lookups.append(lookup)
870
+ name = self.get_lookup_name_(lookup)
871
+ resolved = lookup.promote_lookup_type(is_named_lookup=name is not None)
872
+ if resolved is None:
873
+ raise FeatureLibError(
874
+ "Within a named lookup block, all rules must be of "
875
+ "the same lookup type and flag",
876
+ lookup.location,
877
+ )
878
+ for l in resolved:
879
+ lookup.lookup_index = len(lookups)
880
+ self.lookup_locations[tag][str(lookup.lookup_index)] = LookupDebugInfo(
881
+ location=str(lookup.location),
882
+ name=name,
883
+ feature=None,
884
+ )
885
+ lookups.append(l)
876
886
  otLookups = []
877
887
  for l in lookups:
878
888
  try:
@@ -1294,6 +1304,24 @@ class Builder(object):
1294
1304
 
1295
1305
  # GSUB rules
1296
1306
 
1307
+ def add_any_subst_(self, location, mapping):
1308
+ lookup = self.get_lookup_(location, AnySubstBuilder)
1309
+ for key, value in mapping.items():
1310
+ if key in lookup.mapping:
1311
+ if value == lookup.mapping[key]:
1312
+ log.info(
1313
+ 'Removing duplicate substitution from "%s" to "%s" at %s',
1314
+ ", ".join(key),
1315
+ ", ".join(value),
1316
+ location,
1317
+ )
1318
+ else:
1319
+ raise FeatureLibError(
1320
+ 'Already defined substitution for "%s"' % ", ".join(key),
1321
+ location,
1322
+ )
1323
+ lookup.mapping[key] = value
1324
+
1297
1325
  # GSUB 1
1298
1326
  def add_single_subst(self, location, prefix, suffix, mapping, forceChain):
1299
1327
  if self.cur_feature_name_ == "aalt":
@@ -1305,24 +1333,11 @@ class Builder(object):
1305
1333
  if prefix or suffix or forceChain:
1306
1334
  self.add_single_subst_chained_(location, prefix, suffix, mapping)
1307
1335
  return
1308
- lookup = self.get_lookup_(location, SingleSubstBuilder)
1309
- for from_glyph, to_glyph in mapping.items():
1310
- if from_glyph in lookup.mapping:
1311
- if to_glyph == lookup.mapping[from_glyph]:
1312
- log.info(
1313
- "Removing duplicate single substitution from glyph"
1314
- ' "%s" to "%s" at %s',
1315
- from_glyph,
1316
- to_glyph,
1317
- location,
1318
- )
1319
- else:
1320
- raise FeatureLibError(
1321
- 'Already defined rule for replacing glyph "%s" by "%s"'
1322
- % (from_glyph, lookup.mapping[from_glyph]),
1323
- location,
1324
- )
1325
- lookup.mapping[from_glyph] = to_glyph
1336
+
1337
+ self.add_any_subst_(
1338
+ location,
1339
+ {(key,): (value,) for key, value in mapping.items()},
1340
+ )
1326
1341
 
1327
1342
  # GSUB 2
1328
1343
  def add_multiple_subst(
@@ -1331,21 +1346,10 @@ class Builder(object):
1331
1346
  if prefix or suffix or forceChain:
1332
1347
  self.add_multi_subst_chained_(location, prefix, glyph, suffix, replacements)
1333
1348
  return
1334
- lookup = self.get_lookup_(location, MultipleSubstBuilder)
1335
- if glyph in lookup.mapping:
1336
- if replacements == lookup.mapping[glyph]:
1337
- log.info(
1338
- "Removing duplicate multiple substitution from glyph"
1339
- ' "%s" to %s%s',
1340
- glyph,
1341
- replacements,
1342
- f" at {location}" if location else "",
1343
- )
1344
- else:
1345
- raise FeatureLibError(
1346
- 'Already defined substitution for glyph "%s"' % glyph, location
1347
- )
1348
- lookup.mapping[glyph] = replacements
1349
+ self.add_any_subst_(
1350
+ location,
1351
+ {(glyph,): tuple(replacements)},
1352
+ )
1349
1353
 
1350
1354
  # GSUB 3
1351
1355
  def add_alternate_subst(self, location, prefix, glyph, suffix, replacement):
@@ -1375,9 +1379,6 @@ class Builder(object):
1375
1379
  location, prefix, glyphs, suffix, replacement
1376
1380
  )
1377
1381
  return
1378
- else:
1379
- lookup = self.get_lookup_(location, LigatureSubstBuilder)
1380
-
1381
1382
  if not all(glyphs):
1382
1383
  raise FeatureLibError("Empty glyph class in substitution", location)
1383
1384
 
@@ -1386,8 +1387,10 @@ class Builder(object):
1386
1387
  # substitutions to be specified on target sequences that contain
1387
1388
  # glyph classes, the implementation software will enumerate
1388
1389
  # all specific glyph sequences if glyph classes are detected"
1389
- for g in itertools.product(*glyphs):
1390
- lookup.ligatures[g] = replacement
1390
+ self.add_any_subst_(
1391
+ location,
1392
+ {g: (replacement,) for g in itertools.product(*glyphs)},
1393
+ )
1391
1394
 
1392
1395
  # GSUB 5/6
1393
1396
  def add_chain_context_subst(self, location, prefix, glyphs, suffix, lookups):
@@ -1445,6 +1448,13 @@ class Builder(object):
1445
1448
  sub = self.get_chained_lookup_(location, LigatureSubstBuilder)
1446
1449
 
1447
1450
  for g in itertools.product(*glyphs):
1451
+ existing = sub.ligatures.get(g, replacement)
1452
+ if existing != replacement:
1453
+ raise FeatureLibError(
1454
+ f"Conflicting ligature sub rules: '{g}' maps to '{existing}' and '{replacement}'",
1455
+ location,
1456
+ )
1457
+
1448
1458
  sub.ligatures[g] = replacement
1449
1459
 
1450
1460
  chain.rules.append(ChainContextualRule(prefix, glyphs, suffix, [sub]))
Binary file
fontTools/merge/cmap.py CHANGED
@@ -54,6 +54,28 @@ def _glyphsAreSame(
54
54
  return True
55
55
 
56
56
 
57
+ def computeMegaUvs(merger, uvsTables):
58
+ """Returns merged UVS subtable (cmap format=14)."""
59
+ uvsDict = {}
60
+ cmap = merger.cmap
61
+ for table in uvsTables:
62
+ for variationSelector, uvsMapping in table.uvsDict.items():
63
+ if variationSelector not in uvsDict:
64
+ uvsDict[variationSelector] = {}
65
+ for unicodeValue, glyphName in uvsMapping:
66
+ if cmap.get(unicodeValue) == glyphName:
67
+ # this is a default variation
68
+ glyphName = None
69
+ # prefer previous glyph id if both fonts defined UVS
70
+ if unicodeValue not in uvsDict[variationSelector]:
71
+ uvsDict[variationSelector][unicodeValue] = glyphName
72
+
73
+ for variationSelector in uvsDict:
74
+ uvsDict[variationSelector] = [*uvsDict[variationSelector].items()]
75
+
76
+ return uvsDict
77
+
78
+
57
79
  # Valid (format, platformID, platEncID) triplets for cmap subtables containing
58
80
  # Unicode BMP-only and Unicode Full Repertoire semantics.
59
81
  # Cf. OpenType spec for "Platform specific encodings":
@@ -61,24 +83,29 @@ def _glyphsAreSame(
61
83
  class _CmapUnicodePlatEncodings:
62
84
  BMP = {(4, 3, 1), (4, 0, 3), (4, 0, 4), (4, 0, 6)}
63
85
  FullRepertoire = {(12, 3, 10), (12, 0, 4), (12, 0, 6)}
86
+ UVS = {(14, 0, 5)}
64
87
 
65
88
 
66
89
  def computeMegaCmap(merger, cmapTables):
67
- """Sets merger.cmap and merger.glyphOrder."""
90
+ """Sets merger.cmap and merger.uvsDict."""
68
91
 
69
92
  # TODO Handle format=14.
70
93
  # Only merge format 4 and 12 Unicode subtables, ignores all other subtables
71
94
  # If there is a format 12 table for a font, ignore the format 4 table of it
72
95
  chosenCmapTables = []
96
+ chosenUvsTables = []
73
97
  for fontIdx, table in enumerate(cmapTables):
74
98
  format4 = None
75
99
  format12 = None
100
+ format14 = None
76
101
  for subtable in table.tables:
77
102
  properties = (subtable.format, subtable.platformID, subtable.platEncID)
78
103
  if properties in _CmapUnicodePlatEncodings.BMP:
79
104
  format4 = subtable
80
105
  elif properties in _CmapUnicodePlatEncodings.FullRepertoire:
81
106
  format12 = subtable
107
+ elif properties in _CmapUnicodePlatEncodings.UVS:
108
+ format14 = subtable
82
109
  else:
83
110
  log.warning(
84
111
  "Dropped cmap subtable from font '%s':\t"
@@ -93,6 +120,9 @@ def computeMegaCmap(merger, cmapTables):
93
120
  elif format4 is not None:
94
121
  chosenCmapTables.append((format4, fontIdx))
95
122
 
123
+ if format14 is not None:
124
+ chosenUvsTables.append(format14)
125
+
96
126
  # Build the unicode mapping
97
127
  merger.cmap = cmap = {}
98
128
  fontIndexForGlyph = {}
@@ -127,6 +157,8 @@ def computeMegaCmap(merger, cmapTables):
127
157
  "Dropped mapping from codepoint %#06X to glyphId '%s'", uni, gid
128
158
  )
129
159
 
160
+ merger.uvsDict = computeMegaUvs(merger, chosenUvsTables)
161
+
130
162
 
131
163
  def renameCFFCharStrings(merger, glyphOrder, cffTable):
132
164
  """Rename topDictIndex charStrings based on glyphOrder."""
fontTools/merge/tables.py CHANGED
@@ -312,7 +312,6 @@ def merge(self, m, tables):
312
312
 
313
313
  @add_method(ttLib.getTableClass("cmap"))
314
314
  def merge(self, m, tables):
315
- # TODO Handle format=14.
316
315
  if not hasattr(m, "cmap"):
317
316
  computeMegaCmap(m, tables)
318
317
  cmap = m.cmap
@@ -336,6 +335,18 @@ def merge(self, m, tables):
336
335
  cmapTable.cmap = cmapBmpOnly
337
336
  # ordered by platform then encoding
338
337
  self.tables.insert(0, cmapTable)
338
+
339
+ uvsDict = m.uvsDict
340
+ if uvsDict:
341
+ # format-14
342
+ uvsTable = module.cmap_classes[14](14)
343
+ uvsTable.platformID = 0
344
+ uvsTable.platEncID = 5
345
+ uvsTable.language = 0
346
+ uvsTable.cmap = {}
347
+ uvsTable.uvsDict = uvsDict
348
+ # ordered by platform then encoding
349
+ self.tables.insert(0, uvsTable)
339
350
  self.tableVersion = 0
340
351
  self.numSubTables = len(self.tables)
341
352
  return self
@@ -284,7 +284,7 @@ class Timer(object):
284
284
  """
285
285
 
286
286
  # timeit.default_timer choses the most accurate clock for each platform
287
- _time = timeit.default_timer
287
+ _time: Callable[[], float] = staticmethod(timeit.default_timer)
288
288
  default_msg = "elapsed time: %(time).3fs"
289
289
  default_format = "Took %(time).3fs to %(msg)s"
290
290
 
fontTools/misc/symfont.py CHANGED
@@ -234,11 +234,9 @@ if __name__ == '__main__':
234
234
 
235
235
 
236
236
  if __name__ == "__main__":
237
- pen = AreaPen()
238
- pen.moveTo((100, 100))
239
- pen.lineTo((100, 200))
240
- pen.lineTo((200, 200))
241
- pen.curveTo((200, 250), (300, 300), (250, 350))
242
- pen.lineTo((200, 100))
243
- pen.closePath()
244
- print(pen.value)
237
+ import sys
238
+
239
+ if sys.argv[1:]:
240
+ penName = sys.argv[1]
241
+ funcs = [(name, eval(f)) for name, f in zip(sys.argv[2::2], sys.argv[3::2])]
242
+ printGreenPen(penName, funcs, file=sys.stdout)
@@ -1375,7 +1375,7 @@ def main(args=None, font=None):
1375
1375
 
1376
1376
  for f in args.inputs:
1377
1377
  log.debug("Processing %s", f)
1378
- with open(f, "rt", encoding="utf-8") as f:
1378
+ with open(f, "rt", encoding="utf-8-sig") as f:
1379
1379
  table = build(f, font, tableTag=args.tableTag)
1380
1380
  blob = table.compile(font) # Make sure it compiles
1381
1381
  decompiled = table.__class__()
@@ -170,6 +170,9 @@ class LookupBuilder(object):
170
170
  and self.extension == other.extension
171
171
  )
172
172
 
173
+ def promote_lookup_type(self, is_named_lookup):
174
+ return [self]
175
+
173
176
  def inferGlyphClasses(self):
174
177
  """Infers glyph glasses for the GDEF table, such as {"cedilla":3}."""
175
178
  return {}
@@ -883,6 +886,14 @@ class LigatureSubstBuilder(LookupBuilder):
883
886
  )
884
887
  return self.buildLookup_(subtables)
885
888
 
889
+ def getAlternateGlyphs(self):
890
+ # https://github.com/fonttools/fonttools/issues/3845
891
+ return {
892
+ components[0]: [ligature]
893
+ for components, ligature in self.ligatures.items()
894
+ if len(components) == 1
895
+ }
896
+
886
897
  def add_subtable_break(self, location):
887
898
  self.ligatures[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_
888
899
 
@@ -921,6 +932,14 @@ class MultipleSubstBuilder(LookupBuilder):
921
932
  subtables = self.build_subst_subtables(self.mapping, buildMultipleSubstSubtable)
922
933
  return self.buildLookup_(subtables)
923
934
 
935
+ def getAlternateGlyphs(self):
936
+ # https://github.com/fonttools/fonttools/issues/3845
937
+ return {
938
+ glyph: replacements
939
+ for glyph, replacements in self.mapping.items()
940
+ if len(replacements) == 1
941
+ }
942
+
924
943
  def add_subtable_break(self, location):
925
944
  self.mapping[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_
926
945
 
@@ -1308,6 +1327,151 @@ class ReverseChainSingleSubstBuilder(LookupBuilder):
1308
1327
  pass
1309
1328
 
1310
1329
 
1330
+ class AnySubstBuilder(LookupBuilder):
1331
+ """A temporary builder for Single, Multiple, or Ligature substitution lookup.
1332
+
1333
+ Users are expected to manually add substitutions to the ``mapping``
1334
+ attribute after the object has been initialized, e.g.::
1335
+
1336
+ # sub x by y;
1337
+ builder.mapping[("x",)] = ("y",)
1338
+ # sub a by b c;
1339
+ builder.mapping[("a",)] = ("b", "c")
1340
+ # sub f i by f_i;
1341
+ builder.mapping[("f", "i")] = ("f_i",)
1342
+
1343
+ Then call `promote_lookup_type()` to convert this builder into the
1344
+ appropriate type of substitution lookup builder. This would promote single
1345
+ substitutions to either multiple or ligature substitutions, depending on the
1346
+ rest of the rules in the mapping.
1347
+
1348
+ Attributes:
1349
+ font (``fontTools.TTLib.TTFont``): A font object.
1350
+ location: A string or tuple representing the location in the original
1351
+ source which produced this lookup.
1352
+ mapping: An ordered dictionary mapping a tuple of glyph names to another
1353
+ tuple of glyph names.
1354
+ lookupflag (int): The lookup's flag
1355
+ markFilterSet: Either ``None`` if no mark filtering set is used, or
1356
+ an integer representing the filtering set to be used for this
1357
+ lookup. If a mark filtering set is provided,
1358
+ `LOOKUP_FLAG_USE_MARK_FILTERING_SET` will be set on the lookup's
1359
+ flags.
1360
+ """
1361
+
1362
+ def __init__(self, font, location):
1363
+ LookupBuilder.__init__(self, font, location, "GSUB", 0)
1364
+ self.mapping = OrderedDict()
1365
+
1366
+ def _add_to_single_subst(self, builder, key, value):
1367
+ if key[0] != self.SUBTABLE_BREAK_:
1368
+ key = key[0]
1369
+ builder.mapping[key] = value[0]
1370
+
1371
+ def _add_to_multiple_subst(self, builder, key, value):
1372
+ if key[0] != self.SUBTABLE_BREAK_:
1373
+ key = key[0]
1374
+ builder.mapping[key] = value
1375
+
1376
+ def _add_to_ligature_subst(self, builder, key, value):
1377
+ builder.ligatures[key] = value[0]
1378
+
1379
+ def promote_lookup_type(self, is_named_lookup):
1380
+ # https://github.com/fonttools/fonttools/issues/612
1381
+ # A multiple substitution may have a single destination, in which case
1382
+ # it will look just like a single substitution. So if there are both
1383
+ # multiple and single substitutions, upgrade all the single ones to
1384
+ # multiple substitutions. Similarly, a ligature substitution may have a
1385
+ # single source glyph, so if there are both ligature and single
1386
+ # substitutions, upgrade all the single ones to ligature substitutions.
1387
+ builder_classes = []
1388
+ for key, value in self.mapping.items():
1389
+ if key[0] == self.SUBTABLE_BREAK_:
1390
+ builder_classes.append(None)
1391
+ elif len(key) == 1 and len(value) == 1:
1392
+ builder_classes.append(SingleSubstBuilder)
1393
+ elif len(key) == 1 and len(value) != 1:
1394
+ builder_classes.append(MultipleSubstBuilder)
1395
+ elif len(key) > 1 and len(value) == 1:
1396
+ builder_classes.append(LigatureSubstBuilder)
1397
+ else:
1398
+ assert False, "Should not happen"
1399
+
1400
+ has_multiple = any(b is MultipleSubstBuilder for b in builder_classes)
1401
+ has_ligature = any(b is LigatureSubstBuilder for b in builder_classes)
1402
+
1403
+ # If we have mixed single and multiple substitutions,
1404
+ # upgrade all single substitutions to multiple substitutions.
1405
+ to_multiple = has_multiple and not has_ligature
1406
+
1407
+ # If we have mixed single and ligature substitutions,
1408
+ # upgrade all single substitutions to ligature substitutions.
1409
+ to_ligature = has_ligature and not has_multiple
1410
+
1411
+ # If we have only single substitutions, we can keep them as is.
1412
+ to_single = not has_ligature and not has_multiple
1413
+
1414
+ ret = []
1415
+ if to_single:
1416
+ builder = SingleSubstBuilder(self.font, self.location)
1417
+ for key, value in self.mapping.items():
1418
+ self._add_to_single_subst(builder, key, value)
1419
+ ret = [builder]
1420
+ elif to_multiple:
1421
+ builder = MultipleSubstBuilder(self.font, self.location)
1422
+ for key, value in self.mapping.items():
1423
+ self._add_to_multiple_subst(builder, key, value)
1424
+ ret = [builder]
1425
+ elif to_ligature:
1426
+ builder = LigatureSubstBuilder(self.font, self.location)
1427
+ for key, value in self.mapping.items():
1428
+ self._add_to_ligature_subst(builder, key, value)
1429
+ ret = [builder]
1430
+ elif is_named_lookup:
1431
+ # This is a named lookup with mixed substitutions that can’t be promoted,
1432
+ # since we can’t split it into multiple lookups, we return None here to
1433
+ # signal that to the caller
1434
+ return None
1435
+ else:
1436
+ curr_builder = None
1437
+ for builder_class, (key, value) in zip(
1438
+ builder_classes, self.mapping.items()
1439
+ ):
1440
+ if curr_builder is None or type(curr_builder) is not builder_class:
1441
+ curr_builder = builder_class(self.font, self.location)
1442
+ ret.append(curr_builder)
1443
+ if builder_class is SingleSubstBuilder:
1444
+ self._add_to_single_subst(curr_builder, key, value)
1445
+ elif builder_class is MultipleSubstBuilder:
1446
+ self._add_to_multiple_subst(curr_builder, key, value)
1447
+ elif builder_class is LigatureSubstBuilder:
1448
+ self._add_to_ligature_subst(curr_builder, key, value)
1449
+ else:
1450
+ assert False, "Should not happen"
1451
+
1452
+ for builder in ret:
1453
+ builder.extension = self.extension
1454
+ builder.lookupflag = self.lookupflag
1455
+ builder.markFilterSet = self.markFilterSet
1456
+ return ret
1457
+
1458
+ def equals(self, other):
1459
+ return LookupBuilder.equals(self, other) and self.mapping == other.mapping
1460
+
1461
+ def build(self):
1462
+ assert False
1463
+
1464
+ def getAlternateGlyphs(self):
1465
+ return {
1466
+ key[0]: value
1467
+ for key, value in self.mapping.items()
1468
+ if len(key) == 1 and len(value) == 1
1469
+ }
1470
+
1471
+ def add_subtable_break(self, location):
1472
+ self.mapping[(self.SUBTABLE_BREAK_, location)] = self.SUBTABLE_BREAK_
1473
+
1474
+
1311
1475
  class SingleSubstBuilder(LookupBuilder):
1312
1476
  """Builds a Single Substitution (GSUB1) lookup.
1313
1477
 
@@ -1,10 +1,14 @@
1
1
  # Copyright (c) 2009 Type Supply LLC
2
2
  # Author: Tal Leming
3
3
 
4
- from fontTools.misc.roundTools import otRound, roundFunc
4
+ from __future__ import annotations
5
+
6
+ from typing import Any, Dict, List, Tuple
7
+
8
+ from fontTools.cffLib.specializer import commandsToProgram, specializeCommands
5
9
  from fontTools.misc.psCharStrings import T2CharString
10
+ from fontTools.misc.roundTools import otRound, roundFunc
6
11
  from fontTools.pens.basePen import BasePen
7
- from fontTools.cffLib.specializer import specializeCommands, commandsToProgram
8
12
 
9
13
 
10
14
  class T2CharStringPen(BasePen):
@@ -18,36 +22,52 @@ class T2CharStringPen(BasePen):
18
22
  which are close to their integral part within the tolerated range.
19
23
  """
20
24
 
21
- def __init__(self, width, glyphSet, roundTolerance=0.5, CFF2=False):
25
+ def __init__(
26
+ self,
27
+ width: float | None,
28
+ glyphSet: Dict[str, Any] | None,
29
+ roundTolerance: float = 0.5,
30
+ CFF2: bool = False,
31
+ ) -> None:
22
32
  super(T2CharStringPen, self).__init__(glyphSet)
23
33
  self.round = roundFunc(roundTolerance)
24
34
  self._CFF2 = CFF2
25
35
  self._width = width
26
- self._commands = []
36
+ self._commands: List[Tuple[str | bytes, List[float]]] = []
27
37
  self._p0 = (0, 0)
28
38
 
29
- def _p(self, pt):
39
+ def _p(self, pt: Tuple[float, float]) -> List[float]:
30
40
  p0 = self._p0
31
41
  pt = self._p0 = (self.round(pt[0]), self.round(pt[1]))
32
42
  return [pt[0] - p0[0], pt[1] - p0[1]]
33
43
 
34
- def _moveTo(self, pt):
44
+ def _moveTo(self, pt: Tuple[float, float]) -> None:
35
45
  self._commands.append(("rmoveto", self._p(pt)))
36
46
 
37
- def _lineTo(self, pt):
47
+ def _lineTo(self, pt: Tuple[float, float]) -> None:
38
48
  self._commands.append(("rlineto", self._p(pt)))
39
49
 
40
- def _curveToOne(self, pt1, pt2, pt3):
50
+ def _curveToOne(
51
+ self,
52
+ pt1: Tuple[float, float],
53
+ pt2: Tuple[float, float],
54
+ pt3: Tuple[float, float],
55
+ ) -> None:
41
56
  _p = self._p
42
57
  self._commands.append(("rrcurveto", _p(pt1) + _p(pt2) + _p(pt3)))
43
58
 
44
- def _closePath(self):
59
+ def _closePath(self) -> None:
45
60
  pass
46
61
 
47
- def _endPath(self):
62
+ def _endPath(self) -> None:
48
63
  pass
49
64
 
50
- def getCharString(self, private=None, globalSubrs=None, optimize=True):
65
+ def getCharString(
66
+ self,
67
+ private: Dict | None = None,
68
+ globalSubrs: List | None = None,
69
+ optimize: bool = True,
70
+ ) -> T2CharString:
51
71
  commands = self._commands
52
72
  if optimize:
53
73
  maxstack = 48 if not self._CFF2 else 513
Binary file
@@ -2,6 +2,8 @@
2
2
  #
3
3
  # Google Author(s): Behdad Esfahbod
4
4
 
5
+ from __future__ import annotations
6
+
5
7
  from fontTools import config
6
8
  from fontTools.misc.roundTools import otRound
7
9
  from fontTools import ttLib
@@ -15,7 +17,7 @@ from fontTools.subset.util import _add_method, _uniq_sort
15
17
  from fontTools.subset.cff import *
16
18
  from fontTools.subset.svg import *
17
19
  from fontTools.varLib import varStore, multiVarStore # For monkey-patching
18
- from fontTools.ttLib.tables._n_a_m_e import NameRecordVisitor
20
+ from fontTools.ttLib.tables._n_a_m_e import NameRecordVisitor, makeName
19
21
  from fontTools.unicodedata import mirrored
20
22
  import sys
21
23
  import struct
@@ -3004,6 +3006,9 @@ def prune_pre_subset(self, font, options):
3004
3006
  return True
3005
3007
 
3006
3008
 
3009
+ NAME_IDS_TO_OBFUSCATE = {1, 2, 3, 4, 6, 16, 17, 18}
3010
+
3011
+
3007
3012
  @_add_method(ttLib.getTableClass("name"))
3008
3013
  def prune_post_subset(self, font, options):
3009
3014
  visitor = NameRecordVisitor()
@@ -3022,6 +3027,11 @@ def prune_post_subset(self, font, options):
3022
3027
  self.names = [n for n in self.names if n.langID in options.name_languages]
3023
3028
  if options.obfuscate_names:
3024
3029
  namerecs = []
3030
+ # Preserve names to be scrambled or dropped elsewhere so that other
3031
+ # parts of the font don't break.
3032
+ needRemapping = visitor.seen.intersection(NAME_IDS_TO_OBFUSCATE)
3033
+ if needRemapping:
3034
+ _remap_select_name_ids(font, needRemapping)
3025
3035
  for n in self.names:
3026
3036
  if n.nameID in [1, 4]:
3027
3037
  n.string = ".\x7f".encode("utf_16_be") if n.isUnicode() else ".\x7f"
@@ -3036,6 +3046,76 @@ def prune_post_subset(self, font, options):
3036
3046
  return True # Required table
3037
3047
 
3038
3048
 
3049
+ def _remap_select_name_ids(font: ttLib.TTFont, needRemapping: set[int]) -> None:
3050
+ """Remap a set of IDs so that the originals can be safely scrambled or
3051
+ dropped.
3052
+
3053
+ For each name record whose name id is in the `needRemapping` set, make a copy
3054
+ and allocate a new unused name id in the font-specific range (> 255).
3055
+
3056
+ Finally update references to these in the `fvar` and `STAT` tables.
3057
+ """
3058
+
3059
+ if "fvar" not in font and "STAT" not in font:
3060
+ return
3061
+
3062
+ name = font["name"]
3063
+
3064
+ # 1. Assign new IDs for names to be preserved.
3065
+ existingIds = {record.nameID for record in name.names}
3066
+ remapping = {}
3067
+ nextId = name._findUnusedNameID() - 1 # Should skip gaps in name IDs.
3068
+ for nameId in needRemapping:
3069
+ nextId += 1 # We should have complete freedom until 32767.
3070
+ assert nextId not in existingIds, "_findUnusedNameID did not skip gaps"
3071
+ if nextId > 32767:
3072
+ raise ValueError("Ran out of name IDs while trying to remap existing ones.")
3073
+ remapping[nameId] = nextId
3074
+
3075
+ # 2. Copy records to use the new ID. We can't rewrite them in place, because
3076
+ # that could make IDs 1 to 6 "disappear" from code that follows. Some
3077
+ # tools that produce EOT fonts expect them to exist, even when they're
3078
+ # scrambled. See https://github.com/fonttools/fonttools/issues/165.
3079
+ copiedRecords = []
3080
+ for record in name.names:
3081
+ if record.nameID not in needRemapping:
3082
+ continue
3083
+ recordCopy = makeName(
3084
+ record.string,
3085
+ remapping[record.nameID],
3086
+ record.platformID,
3087
+ record.platEncID,
3088
+ record.langID,
3089
+ )
3090
+ copiedRecords.append(recordCopy)
3091
+ name.names.extend(copiedRecords)
3092
+
3093
+ # 3. Rewrite the corresponding IDs in other tables. For now, care only about
3094
+ # STAT and fvar. If more tables need to be changed, consider adapting
3095
+ # NameRecordVisitor to rewrite IDs wherever it finds them.
3096
+ fvar = font.get("fvar")
3097
+ if fvar is not None:
3098
+ for axis in fvar.axes:
3099
+ axis.axisNameID = remapping.get(axis.axisNameID, axis.axisNameID)
3100
+ for instance in fvar.instances:
3101
+ nameID = instance.subfamilyNameID
3102
+ instance.subfamilyNameID = remapping.get(nameID, nameID)
3103
+ nameID = instance.postscriptNameID
3104
+ instance.postscriptNameID = remapping.get(nameID, nameID)
3105
+
3106
+ stat = font.get("STAT")
3107
+ if stat is None:
3108
+ return
3109
+ elidedNameID = stat.table.ElidedFallbackNameID
3110
+ stat.table.ElidedFallbackNameID = remapping.get(elidedNameID, elidedNameID)
3111
+ if stat.table.DesignAxisRecord:
3112
+ for axis in stat.table.DesignAxisRecord.Axis:
3113
+ axis.AxisNameID = remapping.get(axis.AxisNameID, axis.AxisNameID)
3114
+ if stat.table.AxisValueArray:
3115
+ for value in stat.table.AxisValueArray.AxisValue:
3116
+ value.ValueNameID = remapping.get(value.ValueNameID, value.ValueNameID)
3117
+
3118
+
3039
3119
  @_add_method(ttLib.getTableClass("head"))
3040
3120
  def prune_post_subset(self, font, options):
3041
3121
  # Force re-compiling head table, to update any recalculated values.
@@ -3757,7 +3837,7 @@ def main(args=None):
3757
3837
  text += g[7:]
3758
3838
  continue
3759
3839
  if g.startswith("--text-file="):
3760
- with open(g[12:], encoding="utf-8") as f:
3840
+ with open(g[12:], encoding="utf-8-sig") as f:
3761
3841
  text += f.read().replace("\n", "")
3762
3842
  continue
3763
3843
  if g.startswith("--unicodes="):
@@ -275,10 +275,11 @@ def reorderGlyphs(font: ttLib.TTFont, new_glyph_order: List[str]):
275
275
  for reorder in _REORDER_RULES.get(reorder_key, []):
276
276
  reorder.apply(font, value)
277
277
 
278
- if "CFF " in font:
279
- cff_table = font["CFF "]
280
- charstrings = cff_table.cff.topDictIndex[0].CharStrings.charStrings
281
- cff_table.cff.topDictIndex[0].charset = new_glyph_order
282
- cff_table.cff.topDictIndex[0].CharStrings.charStrings = {
283
- k: charstrings.get(k) for k in new_glyph_order
284
- }
278
+ for tag in ["CFF ", "CFF2"]:
279
+ if tag in font:
280
+ cff_table = font[tag]
281
+ charstrings = cff_table.cff.topDictIndex[0].CharStrings.charStrings
282
+ cff_table.cff.topDictIndex[0].charset = new_glyph_order
283
+ cff_table.cff.topDictIndex[0].CharStrings.charStrings = {
284
+ k: charstrings.get(k) for k in new_glyph_order
285
+ }
@@ -654,7 +654,7 @@ class UFOReader(_UFOBaseIO):
654
654
  The returned string is empty if the file is missing.
655
655
  """
656
656
  try:
657
- with self.fs.open(FEATURES_FILENAME, "r", encoding="utf-8") as f:
657
+ with self.fs.open(FEATURES_FILENAME, "r", encoding="utf-8-sig") as f:
658
658
  return f.read()
659
659
  except fs.errors.ResourceNotFound:
660
660
  return ""
@@ -109,6 +109,8 @@ def _add_fvar(font, axes, instances: List[InstanceDescriptor]):
109
109
  axis.flags = int(a.hidden)
110
110
  fvar.axes.append(axis)
111
111
 
112
+ default_coordinates = {axis.axisTag: axis.defaultValue for axis in fvar.axes}
113
+
112
114
  for instance in instances:
113
115
  # Filter out discrete axis locations
114
116
  coordinates = {
@@ -130,16 +132,26 @@ def _add_fvar(font, axes, instances: List[InstanceDescriptor]):
130
132
  psname = instance.postScriptFontName
131
133
 
132
134
  inst = NamedInstance()
133
- inst.subfamilyNameID = nameTable.addMultilingualName(
134
- localisedStyleName, mac=macNames
135
+ inst.coordinates = {
136
+ axes[k].tag: axes[k].map_backward(v) for k, v in coordinates.items()
137
+ }
138
+
139
+ subfamilyNameID = nameTable.findMultilingualName(
140
+ localisedStyleName, windows=True, mac=macNames
135
141
  )
142
+ if subfamilyNameID in {2, 17} and inst.coordinates == default_coordinates:
143
+ # Instances can only reuse an existing name ID 2 or 17 if they are at the
144
+ # default location across all axes, see:
145
+ # https://github.com/fonttools/fonttools/issues/3825.
146
+ inst.subfamilyNameID = subfamilyNameID
147
+ else:
148
+ inst.subfamilyNameID = nameTable.addMultilingualName(
149
+ localisedStyleName, windows=True, mac=macNames, minNameID=256
150
+ )
151
+
136
152
  if psname is not None:
137
153
  psname = tostr(psname)
138
154
  inst.postscriptNameID = nameTable.addName(psname, platforms=platforms)
139
- inst.coordinates = {
140
- axes[k].tag: axes[k].map_backward(v) for k, v in coordinates.items()
141
- }
142
- # inst.coordinates = {axes[k].tag:v for k,v in coordinates.items()}
143
155
  fvar.instances.append(inst)
144
156
 
145
157
  assert "fvar" not in font
@@ -392,10 +392,13 @@ def addFeatureVariationsRaw(font, table, conditionalSubstitutions, featureTag="r
392
392
 
393
393
  for scriptRecord in table.ScriptList.ScriptRecord:
394
394
  if scriptRecord.Script.DefaultLangSys is None:
395
- raise VarLibError(
396
- "Feature variations require that the script "
397
- f"'{scriptRecord.ScriptTag}' defines a default language system."
398
- )
395
+ # We need to have a default LangSys to attach variations to.
396
+ langSys = ot.LangSys()
397
+ langSys.LookupOrder = None
398
+ langSys.ReqFeatureIndex = 0xFFFF
399
+ langSys.FeatureIndex = []
400
+ langSys.FeatureCount = 0
401
+ scriptRecord.Script.DefaultLangSys = langSys
399
402
  langSystems = [lsr.LangSys for lsr in scriptRecord.Script.LangSysRecord]
400
403
  for langSys in [scriptRecord.Script.DefaultLangSys] + langSystems:
401
404
  langSys.FeatureIndex.append(varFeatureIndex)
@@ -597,9 +600,12 @@ def buildFeatureRecord(featureTag, lookupListIndices):
597
600
  def buildFeatureVariationRecord(conditionTable, substitutionRecords):
598
601
  """Build a FeatureVariationRecord."""
599
602
  fvr = ot.FeatureVariationRecord()
600
- fvr.ConditionSet = ot.ConditionSet()
601
- fvr.ConditionSet.ConditionTable = conditionTable
602
- fvr.ConditionSet.ConditionCount = len(conditionTable)
603
+ if len(conditionTable) != 0:
604
+ fvr.ConditionSet = ot.ConditionSet()
605
+ fvr.ConditionSet.ConditionTable = conditionTable
606
+ fvr.ConditionSet.ConditionCount = len(conditionTable)
607
+ else:
608
+ fvr.ConditionSet = None
603
609
  fvr.FeatureTableSubstitution = ot.FeatureTableSubstitution()
604
610
  fvr.FeatureTableSubstitution.Version = 0x00010000
605
611
  fvr.FeatureTableSubstitution.SubstitutionRecord = substitutionRecords
fontTools/varLib/hvar.py CHANGED
@@ -18,7 +18,7 @@ def _get_advance_metrics(font, axisTags, tableFields):
18
18
  # advance width at that peak. Then pass these all to a VariationModel
19
19
  # builder to compute back the deltas.
20
20
  # 2. For each master peak, pull out the deltas of the advance width directly,
21
- # and feed these to the VarStoreBuilder, forgoing the remoding step.
21
+ # and feed these to the VarStoreBuilder, forgoing the remodeling step.
22
22
  # We'll go with the second option, as it's simpler, faster, and more direct.
23
23
  gvar = font["gvar"]
24
24
  vhAdvanceDeltasAndSupports = {}
@@ -675,6 +675,7 @@ def instantiateCFF2(
675
675
  privateDicts.append(fd.Private)
676
676
 
677
677
  allCommands = []
678
+ allCommandPrivates = []
678
679
  for cs in charStrings:
679
680
  assert cs.private.vstore.otVarStore is varStore # Or in many places!!
680
681
  commands = programToCommands(cs.program, getNumRegions=getNumRegions)
@@ -683,6 +684,7 @@ def instantiateCFF2(
683
684
  if specialize:
684
685
  commands = specializeCommands(commands, generalizeFirst=not generalize)
685
686
  allCommands.append(commands)
687
+ allCommandPrivates.append(cs.private)
686
688
 
687
689
  def storeBlendsToVarStore(arg):
688
690
  if not isinstance(arg, list):
@@ -742,8 +744,8 @@ def instantiateCFF2(
742
744
  assert varData.ItemCount == 0
743
745
 
744
746
  # Add charstring blend lists to VarStore so we can instantiate them
745
- for commands in allCommands:
746
- vsindex = 0
747
+ for commands, private in zip(allCommands, allCommandPrivates):
748
+ vsindex = getattr(private, "vsindex", 0)
747
749
  for command in commands:
748
750
  if command[0] == "vsindex":
749
751
  vsindex = command[1][0]
@@ -752,7 +754,6 @@ def instantiateCFF2(
752
754
  storeBlendsToVarStore(arg)
753
755
 
754
756
  # Add private blend lists to VarStore so we can instantiate values
755
- vsindex = 0
756
757
  for opcode, name, arg_type, default, converter in privateDictOperators2:
757
758
  if arg_type not in ("number", "delta", "array"):
758
759
  continue
@@ -763,6 +764,7 @@ def instantiateCFF2(
763
764
  continue
764
765
  values = getattr(private, name)
765
766
 
767
+ # This is safe here since "vsindex" is the first in the privateDictOperators2
766
768
  if name == "vsindex":
767
769
  vsindex = values[0]
768
770
  continue
@@ -783,8 +785,8 @@ def instantiateCFF2(
783
785
 
784
786
  # Read back new charstring blends from the instantiated VarStore
785
787
  varDataCursor = [0] * len(varStore.VarData)
786
- for commands in allCommands:
787
- vsindex = 0
788
+ for commands, private in zip(allCommands, allCommandPrivates):
789
+ vsindex = getattr(private, "vsindex", 0)
788
790
  for command in commands:
789
791
  if command[0] == "vsindex":
790
792
  vsindex = command[1][0]
@@ -799,9 +801,16 @@ def instantiateCFF2(
799
801
  if arg_type not in ("number", "delta", "array"):
800
802
  continue
801
803
 
804
+ vsindex = 0
802
805
  for private in privateDicts:
803
806
  if not hasattr(private, name):
804
807
  continue
808
+
809
+ # This is safe here since "vsindex" is the first in the privateDictOperators2
810
+ if name == "vsindex":
811
+ vsindex = values[0]
812
+ continue
813
+
805
814
  values = getattr(private, name)
806
815
  if arg_type == "number":
807
816
  values = [values]
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fonttools
3
- Version: 4.58.0
3
+ Version: 4.58.2
4
4
  Summary: Tools to manipulate font files
5
5
  Home-page: http://github.com/fonttools/fonttools
6
6
  Author: Just van Rossum
@@ -388,6 +388,31 @@ Have fun!
388
388
  Changelog
389
389
  ~~~~~~~~~
390
390
 
391
+ 4.58.2 (released 2025-06-06)
392
+ ----------------------------
393
+
394
+ - [ttLib.reorderGlyphs] Handle CFF2 when reordering glyphs (#3852)
395
+ - [subset] Copy name IDs in use before scrapping or scrambling them for webfonts (#3853)
396
+
397
+ 4.58.1 (released 2025-05-28)
398
+ ----------------------------
399
+
400
+ - [varLib] Make sure that fvar named instances only reuse name ID 2 or 17 if they are at the default location across all axes, to match OT spec requirement (#3831).
401
+ - [feaLib] Improve single substitution promotion to multiple/ligature substitutions, fixing a few bugs as well (#3849).
402
+ - [loggingTools] Make ``Timer._time`` a static method that doesn't take self, makes it easier to override (#3836).
403
+ - [featureVars] Use ``None`` for empty ConditionSet, which translates to a null offset in the compiled table (#3850).
404
+ - [feaLib] Raise an error on conflicting ligature substitution rules instead of silently taking the last one (#3835).
405
+ - Add typing annotations to T2CharStringPen (#3837).
406
+ - [feaLib] Add single substitutions that were promoted to multiple or ligature substitutions to ``aalt`` feature (#3847).
407
+ - [featureVars] Create a default ``LangSys`` in a ``ScriptRecord`` if missing when adding feature variations to existing GSUB later in the build (#3838).
408
+ - [symfont] Added a ``main()``.
409
+ - [cffLib.specializer] Fix rmoveto merging when blends used (#3839, #3840).
410
+ - [pyftmerge] Add support for cmap format 14 in the merge tool (#3830).
411
+ - [varLib.instancer/cff2] Fix vsindex of Private dicts when instantiating (#3828, #3232).
412
+ - Update text file read to use UTF-8 with optional BOM so it works with e.g. Windows Notepad.exe (#3824).
413
+ - [varLib] Ensure that instances only reuse name ID 2 or 17 if they are at the default location across all axes (#3831).
414
+ - [varLib] Create a dflt LangSys in a ScriptRecord when adding variations later, to fix an avoidable crash in an edge case (#3838).
415
+
391
416
  4.58.0 (released 2025-05-10)
392
417
  ----------------------------
393
418
 
@@ -1,4 +1,4 @@
1
- fontTools/__init__.py,sha256=zYDhScd7NONF6a7m-H0pM0J8faTRUVwlSTYnJVFCa70,191
1
+ fontTools/__init__.py,sha256=UvWXkVLLqpOsZda5YVO6MIZaGGL77dNOsROqCdvyMuk,191
2
2
  fontTools/__main__.py,sha256=T8Tg8xPKHOCVoYVG82p_zpQXfW7_ERRAphBkZVvhWN8,960
3
3
  fontTools/afmLib.py,sha256=YbmmjT8Du6qFUhFHwnAhOdvsyfXszODVjSJtd18CCjY,13603
4
4
  fontTools/agl.py,sha256=4aKwnbvSVUa39eV5Ka8e5ULwV-IEp4pcfwlMwEH_z3k,118208
@@ -10,7 +10,7 @@ fontTools/unicode.py,sha256=a7460sU25TnVYGzrVl0uv0lI_pDbANZp8Jfmqx9tAag,1287
10
10
  fontTools/cffLib/CFF2ToCFF.py,sha256=5uPKDFwoJvH0KVDrCjpf3MdOpqbyvdZMe0jZ3emjdsQ,6291
11
11
  fontTools/cffLib/CFFToCFF2.py,sha256=0dCYSSozptUC9BVUre49e6LgjSxJRtVyMl8vDB6i3r4,10424
12
12
  fontTools/cffLib/__init__.py,sha256=E4wzLsJ1LxWO7CIR7fjZMHaYQJSVdqCO08fOVFowwpM,111580
13
- fontTools/cffLib/specializer.py,sha256=TTPFdTmtpirYFpTCTbaL-wFL0a3GXwRmjSEbPIWltV0,33468
13
+ fontTools/cffLib/specializer.py,sha256=dznFa-7VrKZkx6D8klaixTaqEAnrnT6YLX9jzA6S0Cc,33536
14
14
  fontTools/cffLib/transforms.py,sha256=8hffhsWRhBhVukNSL-7ieuygTVV5Ta3Cz9s4s8Awvgg,17861
15
15
  fontTools/cffLib/width.py,sha256=3L9NWI0uQrJHvHF_IvC_tbW1cq94zgDEPSjubdug8qM,6284
16
16
  fontTools/colorLib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -24,7 +24,7 @@ fontTools/cu2qu/__init__.py,sha256=OoM_nBJAleZal6kxeNJn1ESy1pNm5c3DG417yVIE0-Q,6
24
24
  fontTools/cu2qu/__main__.py,sha256=6Vb8Ler3yqJ5w84UwlMJV6cS01uhV4PN10OlXQ6jlqo,98
25
25
  fontTools/cu2qu/benchmark.py,sha256=FwdvNjKfWHo18_CX0CO8AY5c68XSBE4M4TJo_EkB4q8,1350
26
26
  fontTools/cu2qu/cli.py,sha256=CvWzC5a6XF_v5o0yrS4vGI1JXiVVLzSJahTIqpJmiPk,6274
27
- fontTools/cu2qu/cu2qu.cp39-win_amd64.pyd,sha256=WB0PzTukJYRkJB49LiSrm5HDf1QxfpiaMpbInf-5VPY,99328
27
+ fontTools/cu2qu/cu2qu.cp39-win_amd64.pyd,sha256=xTMdzHXsbGxWYbavyWrWMU106yKUVs3NIxm_DSbmpB4,99328
28
28
  fontTools/cu2qu/cu2qu.py,sha256=XH2bnQ5aG9ic921ZWzQzU1-q3MQU6INCjLk4XjRj5_Y,16970
29
29
  fontTools/cu2qu/errors.py,sha256=uYyPSs_x-EMJKO2S3cLGWyk_KlHoOoh_XEtdB_oKBp0,2518
30
30
  fontTools/cu2qu/ufo.py,sha256=Mpd_7Be9jxNcOKFqkyRp8Oem3CS3R-ZYMMSD03LJL6o,12143
@@ -39,10 +39,10 @@ fontTools/encodings/__init__.py,sha256=QoK6HlOoqtVqX5gOyv0bJiTXsVBbBRreUifdccWNp
39
39
  fontTools/encodings/codecs.py,sha256=bSpO6kuPbEIDsXSVHhzftqsm_FFUiXpLVfPSk410SqE,4856
40
40
  fontTools/feaLib/__init__.py,sha256=RprjP6BKswq4pt0J-9L1XGuZfjIFAGD6HDly_haMAN4,217
41
41
  fontTools/feaLib/__main__.py,sha256=niUAPkiYxeRAJMlJuvVJZism2VFufZrNaQtieA7sNLk,2318
42
- fontTools/feaLib/ast.py,sha256=EPaomHkTcO_vFZfZ7nL2YHg_G1hGHEJ0u10SyNyE0yU,79300
43
- fontTools/feaLib/builder.py,sha256=ZTO2aWlnExs72JwkDSW7HpS374-WqiXRscIqRH-oK4E,74645
42
+ fontTools/feaLib/ast.py,sha256=5VNYmlsp_cUG0dvWnm4WtQSAqE1OfOp8G9y2GIUkoXI,76284
43
+ fontTools/feaLib/builder.py,sha256=6ubKf1ZF6xj6o2_P19NS85y9JOlS82I4UksdswMO5fk,74848
44
44
  fontTools/feaLib/error.py,sha256=pqi8F2tnH2h7pXVffxwzuBuWaSHMzZsXs5VckdQKQAI,670
45
- fontTools/feaLib/lexer.cp39-win_amd64.pyd,sha256=pReNRqUz9HFcpT98J_CCU3zQ5I7pGMR8mTIPuA5NgQw,121344
45
+ fontTools/feaLib/lexer.cp39-win_amd64.pyd,sha256=NrvV5XvEO-bV113rhoiiDqlYlRkxdfGY91DTRAIFhbw,121344
46
46
  fontTools/feaLib/lexer.py,sha256=7VZ3NPFH7V1mvRbym111BNKvbB4hLfGLTMS0VV_3Ipw,11408
47
47
  fontTools/feaLib/location.py,sha256=teHrhjT8zzImcGBEJS1J43oaX9onCPu_pynxS8d-tUg,246
48
48
  fontTools/feaLib/lookupDebugInfo.py,sha256=h4Ig8kmEk5WlGf1C9JJAbbOKQK5OwkFLdj8CT7fOkmU,316
@@ -51,15 +51,15 @@ fontTools/feaLib/variableScalar.py,sha256=RiLHKQh2-wa-BZ015H2e7XkbshssTj2PjlapaM
51
51
  fontTools/merge/__init__.py,sha256=6MOtk0FXWmSmZsLf1sfjiN2lteVm-u9tI0RVWFewYHM,8498
52
52
  fontTools/merge/__main__.py,sha256=3_u3dnyEOyh0O-SrLMLlkXxOfCFT-0SlwJpimosVJ-c,100
53
53
  fontTools/merge/base.py,sha256=LPJKOwMiDwayLGzA1xH325CtYHPvahAA17lihvKjiPw,2470
54
- fontTools/merge/cmap.py,sha256=jnWRpxy1Y8J6vcCnnjW3vMWbxL89FbqNgY5FzP1Z1PY,5686
54
+ fontTools/merge/cmap.py,sha256=zoOze0gVp4YQXGs-zFf5k7DgEPdFMs-A3sm5v-Rtz5M,6901
55
55
  fontTools/merge/layout.py,sha256=S9j0FOUDOtXAzfO7_L6IrLBHplSLfxFqIi_IJUunXCg,16601
56
56
  fontTools/merge/options.py,sha256=b-9GZ-nN7fh1VrpnEFhK_eRZPIIlRArtYOndOCetoUY,2586
57
- fontTools/merge/tables.py,sha256=qEvXhTZE4tEOmsst8_YKsC6Pvl18VlUr90Fujh5b_gg,10981
57
+ fontTools/merge/tables.py,sha256=xjWt2uqgfxmrDvpLfo_ngsPr7aY8CTkDwwjYBToLnm0,11310
58
58
  fontTools/merge/unicode.py,sha256=mgqRFhRugda62Xt0r28SduaN7YBzRfHxrpNprjLqoX8,4351
59
59
  fontTools/merge/util.py,sha256=3alo4b7mhFNC6h8PjeqNU99dS7EuO8sdZkZpvRsEE6E,3521
60
60
  fontTools/misc/__init__.py,sha256=QoK6HlOoqtVqX5gOyv0bJiTXsVBbBRreUifdccWNp2k,76
61
61
  fontTools/misc/arrayTools.py,sha256=baENNALPvYRUhS4rdx_F3ltOmVIf1PV9G2EaMt7gAHM,11907
62
- fontTools/misc/bezierTools.cp39-win_amd64.pyd,sha256=P9RBNA80Hg4gzGkT3-xnprAYJNp3iRaoLMoK0PClle8,345600
62
+ fontTools/misc/bezierTools.cp39-win_amd64.pyd,sha256=rZeRAgD0jka51o9BcHUGIo_Hepj2nEHLck_E9BNR1PA,345600
63
63
  fontTools/misc/bezierTools.py,sha256=m4j14ckKYtrKy8NhFFFY_Uv3kuL8g-SWNdEKUzqGjRQ,46535
64
64
  fontTools/misc/classifyTools.py,sha256=wLTjOhLiZaLiwwUTj2Ad5eZ5T_38W0Eo_uzRGWHWYvE,5783
65
65
  fontTools/misc/cliTools.py,sha256=7zKOXczaCKRMW6Yv5jdCZYHco8y0-lfimhIWzQ2IL8A,1915
@@ -74,7 +74,7 @@ fontTools/misc/fixedTools.py,sha256=3HzMFAs57LqsGBnbslq2btQ3KJbKwxmxkJPvTvOi8sY,
74
74
  fontTools/misc/intTools.py,sha256=kRNjD5_2jyTKo07C0sFT0jT3dcVnU5XGJEjbXCErm4E,611
75
75
  fontTools/misc/iterTools.py,sha256=hyLQrAPuUOzDoQWKtKhFLjV8-Gx3jHd9SvBEwQRSeTE,402
76
76
  fontTools/misc/lazyTools.py,sha256=LJ7QvDG65xOBw2AI43qGCLxVmfdbsf-PUECfrenbkAU,1062
77
- fontTools/misc/loggingTools.py,sha256=iUvIUZPoxwYLlxaaprHpqBO4143P_nRthalGqrd3lGQ,20441
77
+ fontTools/misc/loggingTools.py,sha256=27VatVrX8Yu-w5rFYSUjOnPLJIJ9Hx2R6hJ5YpP_djA,20476
78
78
  fontTools/misc/macCreatorType.py,sha256=5JZKTsnkI_VBhC52lwMSrdmzqgUOhwC42jPvbGahsPo,1649
79
79
  fontTools/misc/macRes.py,sha256=ewiYDKioxxBKW6JQcRmxpNYw5JgtJZIJyqWBG_KplUo,8840
80
80
  fontTools/misc/psCharStrings.py,sha256=e5kR55Gm3orJsDLo3eu6CxpoZ1pMNZh5Wm-Zj4m7wJs,44532
@@ -83,7 +83,7 @@ fontTools/misc/psOperators.py,sha256=9nZ4ymbiiCApY9V8OARpYqvO73OEcJgGyTtCuGzD-rw
83
83
  fontTools/misc/py23.py,sha256=BhByQabxZis6fDvK3ZVeI-YRj_1rMQeBZCFzGWIac0U,2334
84
84
  fontTools/misc/roundTools.py,sha256=2rmbuk73NYGPmJqP58FQCFioSLilvNffd0WbL5znKUg,3283
85
85
  fontTools/misc/sstruct.py,sha256=RG8qOzTkp9LIN5bis5XkbA-6amnuv2Pi-foZTzIQRRE,7389
86
- fontTools/misc/symfont.py,sha256=ZxyD-mipj7raOtXDdCakpwoSo0hsKPJXLlp3OBPHraE,7235
86
+ fontTools/misc/symfont.py,sha256=KYAtw-ZnG5YReS8XkSDIvxc1bl0xzZl-Wx4J7k7u7LA,7219
87
87
  fontTools/misc/testTools.py,sha256=SG48M4TJIQ4_cPpitUzGEITPnwL-o0yNZKXzWSQdwVE,7285
88
88
  fontTools/misc/textTools.py,sha256=NIBmM6k9PXIs8DMpio-9ckHS35QxL2EMFwBXP6zG-8w,3531
89
89
  fontTools/misc/timeTools.py,sha256=lmncKUKvxQKO4Kqx2k7UNFkYYpj2n5CwR1lPiLZv3tA,2322
@@ -95,10 +95,10 @@ fontTools/misc/xmlReader.py,sha256=gqYg3qlDkrKsO55DPaJ-dU0i5rltqZgnKlrXmR2Z7dQ,6
95
95
  fontTools/misc/xmlWriter.py,sha256=3gHeiyhbXDqDK-jn44f4znND3nEPWnk2Bdlm2Y8JZYo,6250
96
96
  fontTools/misc/plistlib/__init__.py,sha256=doPqlGry1mRywSup0ahnwuT7mNeClhYQ82y7kd86hWQ,21794
97
97
  fontTools/misc/plistlib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
98
- fontTools/mtiLib/__init__.py,sha256=6MGgDNXfVduHKZx4GKQohzIrhdb6nB-06jcVM14zRKw,47998
98
+ fontTools/mtiLib/__init__.py,sha256=izRpPCoQfLoDjrlgKqP6gAE6JF9LU73aqH2_qi0NpaM,48002
99
99
  fontTools/mtiLib/__main__.py,sha256=MnVcMQ1TxmYged20wKcjrpZDIvetmkzfRVKHCb5dsUc,99
100
100
  fontTools/otlLib/__init__.py,sha256=WhTONAtlItZxWAkHNit_EBW19pP32TFZSqIJ_GG6Peg,46
101
- fontTools/otlLib/builder.py,sha256=tCLwZLxa0jBh4GOEtqN31ve9zSMlFA8Q9Tw6N0tBGnI,124051
101
+ fontTools/otlLib/builder.py,sha256=sT3eYWVvWimnb1yb0jJZ0RtO4Bi2twGHU6qKZpOWI40,131104
102
102
  fontTools/otlLib/error.py,sha256=0OQ2AuxKNEqvoHIkgouf47LDGDEmPUlhdZIW5DROL8k,346
103
103
  fontTools/otlLib/maxContextCalc.py,sha256=sVU7LLwkjhV16ADcpjbUwCt5PZbWWdc8_yZo9Lv7HaI,3271
104
104
  fontTools/otlLib/optimize/__init__.py,sha256=NKqA7fqHyzjkmuBL_ZVpc3u9OMbWxbKDtymC8CnVGNY,1583
@@ -115,7 +115,7 @@ fontTools/pens/explicitClosingLinePen.py,sha256=knCXcjSl2iPy6mLCDnsdDYx6J5rV7FH4
115
115
  fontTools/pens/filterPen.py,sha256=tWhgklyaCTUt7oQRTBbFUcOlc702V0NfadCH3X93CYg,8031
116
116
  fontTools/pens/freetypePen.py,sha256=NqNzXrOTDckoH4N6WLnj-KuxGcg6z7DlqSCfmpq8qAE,20370
117
117
  fontTools/pens/hashPointPen.py,sha256=ZAU87uw5ge3Kb4i9kRV28a5VFeZ_TWSsJabyAzwAHrU,3662
118
- fontTools/pens/momentsPen.cp39-win_amd64.pyd,sha256=lNzxuzToQ-Ap_uW-Iwh3j6MXxo3c79umyWZ3Jjkul4Y,89088
118
+ fontTools/pens/momentsPen.cp39-win_amd64.pyd,sha256=AEWb77T5lleCKDfPwuDF4DjPiLONYg9YLmFzkJtFMSk,89088
119
119
  fontTools/pens/momentsPen.py,sha256=Z-V5CjQBSj3qPxg3C_DBFKExqno89nOe3jWwHT9_xsM,26537
120
120
  fontTools/pens/perimeterPen.py,sha256=Zy5F8QzaNJAkkQQSb2QJCp-wZTvDAjBn-B099t2ABds,2222
121
121
  fontTools/pens/pointInsidePen.py,sha256=Hy48iR5NWV3x_wWoos-UC7GMtwvvUhd_q_ykiwaWdzQ,6547
@@ -129,7 +129,7 @@ fontTools/pens/reverseContourPen.py,sha256=E_Ny86JfiMoQ04VfswMtdpaKCU37wNy9ifOcc
129
129
  fontTools/pens/roundingPen.py,sha256=AHC1J0dgRChtFmkkgeR1D1ZNFUoHZTcHpWRIyL5d1_Q,4779
130
130
  fontTools/pens/statisticsPen.py,sha256=F_JjbNtvYmJ0b3Fbv3BA3-LZhecodPr4tJEQZZd4Jxc,10120
131
131
  fontTools/pens/svgPathPen.py,sha256=4aU4iTlnGuzzyXrBgfHvrjMOkC2rdSF8HOkJ_q8tZ38,8882
132
- fontTools/pens/t2CharStringPen.py,sha256=vG6gmTe2bWc-bCn4-LQgv1f16p62oNWJUn8FiXTl0EM,2459
132
+ fontTools/pens/t2CharStringPen.py,sha256=g0lcaRhSAs4T2NuWvn89TODikC1t9x4KyBq0Dnkso-0,3019
133
133
  fontTools/pens/teePen.py,sha256=19N3FEaFm4mGMTZrEn5Qg4YiXGGK61zcXjh2LcRxe_s,1345
134
134
  fontTools/pens/transformPen.py,sha256=_Zvyxp0yQ7iFZ1_FYfr3KFWKWYOUY2eSxrRk41BRO2w,4171
135
135
  fontTools/pens/ttGlyphPen.py,sha256=gAglwTL9DSsJGI8TUPVz-YBdPSMUcvd2S9jF-FzmckE,12205
@@ -138,9 +138,9 @@ fontTools/qu2cu/__init__.py,sha256=MpdE0XsHSDo9M3hyHLkPPLxB3FKr3aiT0dPW5qHCuSo,6
138
138
  fontTools/qu2cu/__main__.py,sha256=leKpToUNNyHf0nobr1I19vus2ziA1pO7rRKkreat-Xw,100
139
139
  fontTools/qu2cu/benchmark.py,sha256=PFxx2Bfu7-KuNrzdOIBXHPZvyNphqqcTVy4CneaCo3M,1456
140
140
  fontTools/qu2cu/cli.py,sha256=1QLBTSZW7e_VATJN9vjszRxIk_-Xjxu1KP53yX4T7q8,3839
141
- fontTools/qu2cu/qu2cu.cp39-win_amd64.pyd,sha256=Y_2St97YnHSvDVtV-LKcL24KnIuP8HNa2A8FZeeiIyE,107008
141
+ fontTools/qu2cu/qu2cu.cp39-win_amd64.pyd,sha256=qTARub92KE8_O9MgNu_KOp3s6zg8-udSwhXqGqBINWs,107008
142
142
  fontTools/qu2cu/qu2cu.py,sha256=dtp5Zqhcs_NePwA2U5fgG2LtWleRwmBilTurau8sLL0,12693
143
- fontTools/subset/__init__.py,sha256=C6ELYeIWovPTPUoSMq0mOMGYyqn6eimJs-fXqD9dLDI,138191
143
+ fontTools/subset/__init__.py,sha256=UUOidAx7b_LNNsRnEizxG8tPF5JX5-4fooerwyTWFvU,141588
144
144
  fontTools/subset/__main__.py,sha256=cEIC52EtGOJvFDfHXzi0M2EAYmyHAcI-ZZ0lb2y4r7s,101
145
145
  fontTools/subset/cff.py,sha256=GSmxdsokxuFKvJJQVcAIOhd5hYQq8KkzxnXE_dgm8yo,6329
146
146
  fontTools/subset/svg.py,sha256=y_yTZuAm3bjcoEOFu5likXoHuG5u1oNiv0mOni2Z9fQ,9637
@@ -155,7 +155,7 @@ fontTools/ttLib/__init__.py,sha256=2dJ9-KzN_5AwttwMEhmusrxR2IdFTZ73hJiPjeVwuwU,6
155
155
  fontTools/ttLib/__main__.py,sha256=gSaKy1O2Hws3_1xGHGdLL-lEUVxw9q8ymNx9YlwIFXs,4881
156
156
  fontTools/ttLib/macUtils.py,sha256=B5UhZU8gQerJMXEG9-BGZsuv3aewFRAGQ5HCuZMzMkQ,1791
157
157
  fontTools/ttLib/removeOverlaps.py,sha256=PTxICjLx89JxKfboLruoV_OwuwCIxcJ4feNcCCkrsTQ,13005
158
- fontTools/ttLib/reorderGlyphs.py,sha256=_sTg1wR9q8ZFeo4n7H7iUcqGIWOroqDQd_UbZ-1kZh0,10600
158
+ fontTools/ttLib/reorderGlyphs.py,sha256=PAHvoh4yN3u-_aDACH8H1ResVMCmVE7Kp5_mIKAG0TI,10656
159
159
  fontTools/ttLib/scaleUpem.py,sha256=Qz-kS48q7a5GibgnPoUglyVk_qIVkYp5KZ-r1aMx_7Q,15054
160
160
  fontTools/ttLib/sfnt.py,sha256=7X9xujgV0Za4nOEfUD3mSrrRb-f9NuzEqgJ-IFLNVQU,23494
161
161
  fontTools/ttLib/standardGlyphOrder.py,sha256=VG-8hW1VgQIro7cDJusSXThILIr4pQgmU37t85SQ65Y,6056
@@ -264,7 +264,7 @@ fontTools/ttLib/tables/sbixGlyph.py,sha256=a-mCmO5EibN_He7QQohG06Qg-fCOHWiNFMAbC
264
264
  fontTools/ttLib/tables/sbixStrike.py,sha256=9-UIVPormVd27lOU5fuGZvzkckA2U5975jBXXUEPxKA,6840
265
265
  fontTools/ttLib/tables/table_API_readme.txt,sha256=E9lwGW1P_dGqy1FYBcYLVEDDmikbsqW4pUtpv1RKCJU,2839
266
266
  fontTools/ttLib/tables/ttProgram.py,sha256=vkRtptH7QXD0Ng8LNzh-A_Ln27VPCxSJOXgW8878nSo,36482
267
- fontTools/ufoLib/__init__.py,sha256=hsMbUulB4fUmBROJS3xpHLiQ2ojL0WrIUEfTZT7Sy88,96904
267
+ fontTools/ufoLib/__init__.py,sha256=LYIIhFjl4flv5NidE5T6u57Sda5-xYu82Vrva7dN1aY,96908
268
268
  fontTools/ufoLib/converters.py,sha256=hPVFC0K1IPXG8tCGZQOIUXB4ILdiGPuzbgtqSiWHZn4,13442
269
269
  fontTools/ufoLib/errors.py,sha256=pgJKS2A5RcsfQS2Z6Y_l3mIz62-VD_SrpIysKmywuYA,875
270
270
  fontTools/ufoLib/etree.py,sha256=kTUP1EzN2wSXZ4jwAX8waNfKz52u7jc2qQ2LrqPYLBw,237
@@ -281,22 +281,22 @@ fontTools/unicodedata/OTTags.py,sha256=IAt8NXaZOhu5cuuks46DDX3E7Ovoqp-PMUQC-WJUP
281
281
  fontTools/unicodedata/ScriptExtensions.py,sha256=eIAXBnM9BbI5V_MWeA9I9Iv2rvgWi8mt8dCWN3cN1gY,29033
282
282
  fontTools/unicodedata/Scripts.py,sha256=jCKY8wlKrSFmsFndzLegVS6vrhVGZ-S3T0dw2vO9Drg,133888
283
283
  fontTools/unicodedata/__init__.py,sha256=isIrE4vSaQoKiU9Hrz07UuVNp9pctma5GmESj53WBMw,9285
284
- fontTools/varLib/__init__.py,sha256=hGUuYC-4Eo7FeOo95u8FSyrayxAfUzVK2Lm9OWjpP5Y,55211
284
+ fontTools/varLib/__init__.py,sha256=E38iOYGLna0PhK-t7G33KNl36B11w_Lq7rd6KV5Pt8s,55753
285
285
  fontTools/varLib/__main__.py,sha256=ykyZY5GG9IPDsPrUWiHgXEnsgKrQudZkneCTes6GUpU,101
286
286
  fontTools/varLib/avar.py,sha256=tRgKAUn_K5MTCSkB2MgPYYZ2U6Qo_Cg3jFQV0TDKFgc,9907
287
287
  fontTools/varLib/avarPlanner.py,sha256=orjyFvg3YkC-slt7fgSEU1AGjLCkGgMEJ7hTRV6CqUA,28362
288
288
  fontTools/varLib/builder.py,sha256=1k-N-rTwnZqQpzhNLBx2tqu2oYGG44sJSXKTCjAvIVM,6824
289
289
  fontTools/varLib/cff.py,sha256=bl8rrPHHpwzUdZBY80_5JJLWYcXQOolhKKvTJiiU-Bs,23532
290
290
  fontTools/varLib/errors.py,sha256=mXl-quT2Z75_t7Uwb6ug3VMhmbQjO841YNLeghwuY_s,7153
291
- fontTools/varLib/featureVars.py,sha256=e7e4UFG3t5GaNLXxsA6mcn7Cs3L1UjwKzXoHPnamWJE,26137
292
- fontTools/varLib/hvar.py,sha256=EFmOQt1bUPiKPPM10oGAn_9prrITwDC_i0LcFKZe7Sw,3806
291
+ fontTools/varLib/featureVars.py,sha256=fBt7iJtohfsfqO7AULmYMD56hb3apCDXRgpR18pDoG8,26390
292
+ fontTools/varLib/hvar.py,sha256=3bd_J1eRrF7q1YIKFF-NBZo_lU-r3VlgDwhtkMo9MSc,3808
293
293
  fontTools/varLib/interpolatable.py,sha256=8AXrhsnYY1z0hR6gskqYRYx8qcFsvUKmIIHZRpIOlAU,46430
294
294
  fontTools/varLib/interpolatableHelpers.py,sha256=JnabttZY7sY9-QzdiqkgzQ_S5nG8k_O1TzLEmfNUvNo,11892
295
295
  fontTools/varLib/interpolatablePlot.py,sha256=tUKFd8H9B2eD_GE6jV13J-dZkkIeLmk3ojAYrf-edsA,45644
296
296
  fontTools/varLib/interpolatableTestContourOrder.py,sha256=Pbt0jW0LoVggIwrtADZ7HWK6Ftdoo1bjuWz0ost0HD0,3103
297
297
  fontTools/varLib/interpolatableTestStartingPoint.py,sha256=f5MJ3mj8MctJCvDJwqmW1fIVOgovUMYAOela9HweaRU,4403
298
298
  fontTools/varLib/interpolate_layout.py,sha256=tTPUes_K7MwooUO_wac9AeFEVgL1uGSz4ITYiOizaME,3813
299
- fontTools/varLib/iup.cp39-win_amd64.pyd,sha256=xRVAcZip8OFo7oX4J6YXx8KvsG4P-gVtBrAMCUfUCyI,129024
299
+ fontTools/varLib/iup.cp39-win_amd64.pyd,sha256=42dTJo7zel_FbyocqW4QhBo1oLESVrFenFFO1PHvloo,129024
300
300
  fontTools/varLib/iup.py,sha256=O_xPJOBECrNDbQqCC3e5xf9KsWXUd1i3BAp9Fl6Hv2Y,15474
301
301
  fontTools/varLib/merger.py,sha256=V-B17poOYbbrRsfUYJbdqt46GtRfG833MKwtv9NOB3Q,62519
302
302
  fontTools/varLib/models.py,sha256=ZqQb1Lapj5dCO8dwa3UTx1LsIpF0-GiDte32t_TMJJQ,23040
@@ -306,7 +306,7 @@ fontTools/varLib/mvar.py,sha256=Gf3q54ICH-E9oAwKYeIKUPLZabfjY0bUT4t220zLzYI,2489
306
306
  fontTools/varLib/plot.py,sha256=BtozrcnKoEyCs0rGy7PZmrUvUNTmZT-5_sylW5PuJ28,7732
307
307
  fontTools/varLib/stat.py,sha256=ScaVFIVpXTqA-F07umv_66GoxtcjaZ54MPLFvFK4s68,4960
308
308
  fontTools/varLib/varStore.py,sha256=GWz-B1YcR-JnIh2aDmeQg621GDEBj9M4pKYcbZraA3w,24808
309
- fontTools/varLib/instancer/__init__.py,sha256=oWhXm_LpZbP29JT_aw2fLPeZadz5LaKSLBsNDv7bacA,73283
309
+ fontTools/varLib/instancer/__init__.py,sha256=h8jYMTPeSTrc2TZaVArHekvPBfa6d3n2k7qlENd_jPM,73773
310
310
  fontTools/varLib/instancer/__main__.py,sha256=YN_tyJDdmLlH3umiLDS2ue0Zc3fSFexa9wCuk3Wuod0,109
311
311
  fontTools/varLib/instancer/featureVars.py,sha256=b3qtGCYVZ9fqkqcgFQUikYQBX_3_x0YgdrvvxIALbuU,7300
312
312
  fontTools/varLib/instancer/names.py,sha256=vmHi7JZlh-N4amxKdaTJ-5DN9mDJ8Wnh_s9W1gJAQ4Y,15338
@@ -318,11 +318,11 @@ fontTools/voltLib/error.py,sha256=3TsaZBA82acFd2j5Beq3WUQTURTKM0zxOnUFGZovSNA,40
318
318
  fontTools/voltLib/lexer.py,sha256=v9V4zdBO2VqVJG__IWrL8fv_CRURmh2eD_1UpbIJn9g,3467
319
319
  fontTools/voltLib/parser.py,sha256=HS72gxtFzvcPSwEbUYj3E41CPK7ZqK9mSe0nLRxn-IY,26060
320
320
  fontTools/voltLib/voltToFea.py,sha256=nS-OSlx_a-TngGICFNKyFxMhjqkV3OQLcvyzw4sQFyk,37460
321
- fonttools-4.58.0.data/data/share/man/man1/ttx.1,sha256=E71F9mRNWlttVpzlnP7w_fqkQygPkph5s-AtVa0Js50,5601
322
- fonttools-4.58.0.dist-info/licenses/LICENSE,sha256=Ir74Bpfs-qF_l-YrmibfoSggvgVYPo3RKtFpskEnTJk,1093
323
- fonttools-4.58.0.dist-info/licenses/LICENSE.external,sha256=p5eWRJLxSGv9_M1uYYVeOjFkXzYCPqXeeF2jfqwvy04,19046
324
- fonttools-4.58.0.dist-info/METADATA,sha256=JmPrtjdiDu2aSYW33uKkm-H7m3nZibPhECGG3ehj3ck,106574
325
- fonttools-4.58.0.dist-info/WHEEL,sha256=RXjBKdSUIP0YC6JPJG7bc0sl_UrYP7JdTYfEui8v8Eo,99
326
- fonttools-4.58.0.dist-info/entry_points.txt,sha256=8kVHddxfFWA44FSD4mBpmC-4uCynQnkoz_9aNJb227Y,147
327
- fonttools-4.58.0.dist-info/top_level.txt,sha256=rRgRylrXzekqWOsrhygzib12pQ7WILf7UGjqEwkIFDM,10
328
- fonttools-4.58.0.dist-info/RECORD,,
321
+ fonttools-4.58.2.data/data/share/man/man1/ttx.1,sha256=E71F9mRNWlttVpzlnP7w_fqkQygPkph5s-AtVa0Js50,5601
322
+ fonttools-4.58.2.dist-info/licenses/LICENSE,sha256=Ir74Bpfs-qF_l-YrmibfoSggvgVYPo3RKtFpskEnTJk,1093
323
+ fonttools-4.58.2.dist-info/licenses/LICENSE.external,sha256=p5eWRJLxSGv9_M1uYYVeOjFkXzYCPqXeeF2jfqwvy04,19046
324
+ fonttools-4.58.2.dist-info/METADATA,sha256=tDfUNh6jIkCSHk7wFdRBfdVurFsGEspQym7od3HGGUU,108469
325
+ fonttools-4.58.2.dist-info/WHEEL,sha256=XkFE14KmFh7mutkkb-qn_ueuH2lwfT8rLdfc5xpQ7wE,99
326
+ fonttools-4.58.2.dist-info/entry_points.txt,sha256=8kVHddxfFWA44FSD4mBpmC-4uCynQnkoz_9aNJb227Y,147
327
+ fonttools-4.58.2.dist-info/top_level.txt,sha256=rRgRylrXzekqWOsrhygzib12pQ7WILf7UGjqEwkIFDM,10
328
+ fonttools-4.58.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.4.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: false
4
4
  Tag: cp39-cp39-win_amd64
5
5