fonttools 4.58.0__py3-none-any.whl → 4.58.2__py3-none-any.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.

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.
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]))
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
@@ -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]
@@ -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=uLgwtnzJcdJ-4fDWUiSW9elw3dtHBfQZHx6l_-L1i2s,183
1
+ fontTools/__init__.py,sha256=XeIRdnWncwRzBzhwui9It6L8mVeq0VAJRsvcJorF_AY,183
2
2
  fontTools/__main__.py,sha256=VjkGh1UD-i1zTDA1dXo1uecSs6PxHdGQ5vlCk_mCCYs,925
3
3
  fontTools/afmLib.py,sha256=1MagIItOzRV4vV5kKPxeDZbPJsfxLB3wdHLFkQvl0uk,13164
4
4
  fontTools/agl.py,sha256=05bm8Uq45uVWW8nPbP6xbNgmFyxQr8sWhYAiP0VSjnI,112975
@@ -10,7 +10,7 @@ fontTools/unicode.py,sha256=ZZ7OMmWvIyV1IL1k6ioTzaRAh3tUvm6gvK7QgFbOIHY,1237
10
10
  fontTools/cffLib/CFF2ToCFF.py,sha256=pvwh6qxJ0D7c4xgXBcyAdmZGzpTiywMy45-jjp7dKck,6088
11
11
  fontTools/cffLib/CFFToCFF2.py,sha256=Qnk7lYlsTRHnlZQ6NXNdr_f4MJwZQ21kcS08KFbsyY8,10119
12
12
  fontTools/cffLib/__init__.py,sha256=62vpcR7u8cE407kXduAwnFttHnsoCpDQ7IBK-qOYFQ8,107886
13
- fontTools/cffLib/specializer.py,sha256=uyzL0YGs07qICImEifWh8wC53YyelftwDeUrLlWHJok,32544
13
+ fontTools/cffLib/specializer.py,sha256=vsOPkR_jHNe6tESQEjmm0i76y7sWI5MKo3bsTmI3sNM,32609
14
14
  fontTools/cffLib/transforms.py,sha256=kHBnYQmcJBLIMUC6Uws4eor2mJiNNHiR_eRePXHDPC8,17371
15
15
  fontTools/cffLib/width.py,sha256=IqGL0CLyCZqi_hvsHySG08qpYxS3kaqW-tsAT-bjHV4,6074
16
16
  fontTools/colorLib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -38,8 +38,8 @@ fontTools/encodings/__init__.py,sha256=DJBWmoX_Haau7qlgmvWyfbhSzrX2qL636Rns7CG01
38
38
  fontTools/encodings/codecs.py,sha256=u50ruwz9fcRsrUrRGpR17Cr55Ovn1fvCHCKrElVumDE,4721
39
39
  fontTools/feaLib/__init__.py,sha256=jlIru2ghxvb1HhC5Je2BCXjFJmFQlYKpruorPoz3BvQ,213
40
40
  fontTools/feaLib/__main__.py,sha256=Df2PA6LXwna98lSXiL7R4as_ZEdWCIk3egSM5w7GpvM,2240
41
- fontTools/feaLib/ast.py,sha256=e-THLe3oOlLtlxYreCZJdEGk3Kd_8bzSLHgaCaEPFkQ,77087
42
- fontTools/feaLib/builder.py,sha256=wqec6HRhFjAwu2oVLKcFSSGe_AQf2ae9QC8dLp3XJBY,72859
41
+ fontTools/feaLib/ast.py,sha256=ElVBjb_Ut5kVgdu5_XyWLYiWIc66ZnXEj_lhynPn5Mg,74142
42
+ fontTools/feaLib/builder.py,sha256=cJGF2d4ueHO5vvzcxnKRufFsIbaJsYvTlBsrDyxDMKI,73052
43
43
  fontTools/feaLib/error.py,sha256=Bz_5tNcNVcY7_nrAmFlQNhQldtqZWd8WUGQ2E3PWhZo,648
44
44
  fontTools/feaLib/lexer.py,sha256=emyMPmRoqNZkzxnJyI6JRCCtXrbCOFofwa9O6ABGLiw,11121
45
45
  fontTools/feaLib/location.py,sha256=JXzHqGV56EHdcq823AwA5oaK05hf_1ySWpScbo3zGC0,234
@@ -49,10 +49,10 @@ fontTools/feaLib/variableScalar.py,sha256=Xu8tpDlQbfIfjnKnYDEf43EqVdyIJUy8_1ROVP
49
49
  fontTools/merge/__init__.py,sha256=-l65-mbTwSh0gjarnojIfsAX-ZkMtdz3vGTjtYHQ2ws,8250
50
50
  fontTools/merge/__main__.py,sha256=hDx3gfbUBO83AJKumSEhiV-xqNTJNNgK2uFjazOGTmw,94
51
51
  fontTools/merge/base.py,sha256=l0G1Px98E9ZdVuFLMUBKWdtr7Jb8JX8vxcjeaDUUnzY,2389
52
- fontTools/merge/cmap.py,sha256=_oCBnZfm5M7ebYRJnOYw5wUEICFmdR6kMUe1w6jsVuM,5545
52
+ fontTools/merge/cmap.py,sha256=HpthxVH5lA7VegJ8yHoBjd9vrFBV7UB5OknKGYpxWY8,6728
53
53
  fontTools/merge/layout.py,sha256=fkMPGPLxEdxohS3scVM4W7LmNthSz-UPyocsffe2KqE,16075
54
54
  fontTools/merge/options.py,sha256=xko_1-WErcNQkirECzIOOYxSJR_bRtdQYQYOtmgccYI,2501
55
- fontTools/merge/tables.py,sha256=uBD1-XqOCDzFxp0D7ZDvrMRdd8R7eAm58WtYKhz-m5w,10640
55
+ fontTools/merge/tables.py,sha256=7SzXYL04awDEDhvU2-9T_8A2gAjvgGyYAHUICUJOpZg,10958
56
56
  fontTools/merge/unicode.py,sha256=kb1Jrfuoq1KUcVhhSKnflAED_wMZxXDjVwB-CI9k05Y,4273
57
57
  fontTools/merge/util.py,sha256=BH3bZWNFy-Tsj1cth7aSpGVJ18YXKXqDakPn6Wzku6U,3378
58
58
  fontTools/misc/__init__.py,sha256=DJBWmoX_Haau7qlgmvWyfbhSzrX2qL636Rns7CG01pk,75
@@ -71,7 +71,7 @@ fontTools/misc/fixedTools.py,sha256=gsotTCOJLyMis13M4_jQJ8-QPob2Gl2TtNJhW6FER1I,
71
71
  fontTools/misc/intTools.py,sha256=l6pjk4UYlXcyLtfC0DdOC5RL6UJ8ihRR0zRiYow5xA8,586
72
72
  fontTools/misc/iterTools.py,sha256=17H6LPZszp32bTKoNorp6uZF1PKj47BAbe5QG8irUjo,390
73
73
  fontTools/misc/lazyTools.py,sha256=BC6MmF-OzJ3GrBD8TYDZ-VCSN4UOx0pN0r3oF4GSoiw,1020
74
- fontTools/misc/loggingTools.py,sha256=2uXks8fEnBjdgJEcxMLvD77-lbOPto3neJ86bMqV_qM,19898
74
+ fontTools/misc/loggingTools.py,sha256=NOYROsLK5TzONK5967OGdVonNyXC6kP_CmPr7M2PW_c,19933
75
75
  fontTools/misc/macCreatorType.py,sha256=Je9jtqUr7EPbpH3QxlVl3pizoQ-1AOPMBIctHIMTM3k,1593
76
76
  fontTools/misc/macRes.py,sha256=GT_pnfPw2NCvvOF86nHLAnOtZ6SMHqEuLntaplXzvHM,8579
77
77
  fontTools/misc/psCharStrings.py,sha256=Tb5-k_5krP0eu7qD054iGxE4Zybk9oB4jdiKzcsV0rw,43036
@@ -80,7 +80,7 @@ fontTools/misc/psOperators.py,sha256=9SLl5PPBulLo0Xxg_dqlJMitNIBdiGKdkXhOWsNSYZE
80
80
  fontTools/misc/py23.py,sha256=aPVCEUz_deggwLBCeTSsccX6QgJavZqvdVtuhpzrPvA,2238
81
81
  fontTools/misc/roundTools.py,sha256=1RSXZ0gyi1qW42tz6WSBMJD1FlPdtgqKfWixVN9bd78,3173
82
82
  fontTools/misc/sstruct.py,sha256=HuXwoRr9-mAbBxI3gJ3n34ML7NAGSHsAAazaaloWQB4,7158
83
- fontTools/misc/symfont.py,sha256=SJtc3-9VdhT6fokx5Vvzs7YErqRib1u-rItNqXI0rTM,6991
83
+ fontTools/misc/symfont.py,sha256=x5ZwqK9Ik9orG6qSftgVGygBFE1wTSngrMK2We1Z5AM,6977
84
84
  fontTools/misc/testTools.py,sha256=3vj_KllUQVEiVFbS0SzTmeuKv44-L-disI1dZ4XhOfw,7052
85
85
  fontTools/misc/textTools.py,sha256=pbhr6LVhm3J-0Z4saYnJfxBDzyoiw4BR9pAgwypiOw8,3377
86
86
  fontTools/misc/timeTools.py,sha256=e9h5pgzL04tBDXmCv_8eRGB4boFV8GKXlS6dq3ggEpw,2234
@@ -92,10 +92,10 @@ fontTools/misc/xmlReader.py,sha256=igut4_d13RT4WarliqVvuuPybO1uSXVeoBOeW4j0_e4,6
92
92
  fontTools/misc/xmlWriter.py,sha256=CA1c-Ov5vFTF9tT4bGk-f3yBvaX7lVmSdLPYygUqlAE,6046
93
93
  fontTools/misc/plistlib/__init__.py,sha256=1HfhHPt3As6u2eRSlFfl6XdnXv_ypQImeQdWIw6wK7Y,21113
94
94
  fontTools/misc/plistlib/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
95
- fontTools/mtiLib/__init__.py,sha256=3kkb3G86QIiskaP8gNQIKLSBG2v6FfTsxQRfhHtZVo0,46598
95
+ fontTools/mtiLib/__init__.py,sha256=EzYwNaENLf906h1THBeq6nSRHUKpOAYxuzO9x9PHzh8,46602
96
96
  fontTools/mtiLib/__main__.py,sha256=gd8X89jnZOe-752k7uaR1lWoiju-2zIT5Yx35Kl0Xek,94
97
97
  fontTools/otlLib/__init__.py,sha256=D2leUW-3gsUTOFcJYGC18edBYjIJ804ut4qitJYWsaQ,45
98
- fontTools/otlLib/builder.py,sha256=nCJJGYuToDSDeJ5prQSgk5Bt-mgY_s9avp6Odcl2Eqc,120780
98
+ fontTools/otlLib/builder.py,sha256=nv1bEbN-Q3OhDZJyz3t1CK4M-EoFZ1iPHIPHIQUBNB8,127669
99
99
  fontTools/otlLib/error.py,sha256=cthuhBuOwZYpkTLi5gFPupUxkXkCHe-L_YgkE7N1wCI,335
100
100
  fontTools/otlLib/maxContextCalc.py,sha256=3es4Kt84TaZ49sA2ev1zrlwPJikJCAECx5KavwhyB-I,3175
101
101
  fontTools/otlLib/optimize/__init__.py,sha256=UUQRpNkHU2RczCRt-Gz7sEiYE9AQq9BHLXZEOyvsnX4,1530
@@ -125,7 +125,7 @@ fontTools/pens/reverseContourPen.py,sha256=oz64ZRhLAvT7DYMAwGKoLzZXQK8l81jRiYnTZ
125
125
  fontTools/pens/roundingPen.py,sha256=Q4vvG0Esq_sLNODU0TITU4F3wcXcKWo4BA7DWdDaVcM,4649
126
126
  fontTools/pens/statisticsPen.py,sha256=piWK6NjjWqk9MLROjeE2-4EsxVYMyNU7UQFGD_trE9g,9808
127
127
  fontTools/pens/svgPathPen.py,sha256=T3b6SZS9B9sVWMK9mSFDtjHeviQs_yOJOZKq5Sg5Zdg,8572
128
- fontTools/pens/t2CharStringPen.py,sha256=uq9KCOxrk5TEZGYpcOG-pgkWHYCe4dMwb2hx5uYOmWA,2391
128
+ fontTools/pens/t2CharStringPen.py,sha256=GgGklb5XsCer0w37ujgRLRXx-EuzdFsyCYuzCx4n-Qs,2931
129
129
  fontTools/pens/teePen.py,sha256=P1ARJOCMJ6MxK-PB1yZ-ips3CUfnadWYnQ_do6VIasQ,1290
130
130
  fontTools/pens/transformPen.py,sha256=s0kUyQdnemUwHvYr2SFboFmh4WY1S9OHBL8L4PJKRwE,4056
131
131
  fontTools/pens/ttGlyphPen.py,sha256=yLtB-E5pTQR59OKVYySttWBu1xC2vR8ezSaRhIMtVwg,11870
@@ -135,7 +135,7 @@ fontTools/qu2cu/__main__.py,sha256=9FWf6SIZaRaC8SiL0LhjAWC2yIdY9N_9wlRko8m1l2Q,9
135
135
  fontTools/qu2cu/benchmark.py,sha256=GMcr_4r7L6K9SmJ13itt-_XKhnKqSVUDPlXUG6IZmmM,1400
136
136
  fontTools/qu2cu/cli.py,sha256=U2rooYnVVEalGRAWGFHk-Kp6Okys8wtzdaWLjw1bngY,3714
137
137
  fontTools/qu2cu/qu2cu.py,sha256=IYtpkwHdfKOXJr65Y_pJ9Lrt_MgJaISAKGMAs5ilFSM,12288
138
- fontTools/subset/__init__.py,sha256=FTJvbeLvC_5uwLBonOd6AIgpLLFsjJs3SELor0cqy68,134342
138
+ fontTools/subset/__init__.py,sha256=c3EulDBf-RYUaUFTM_JoIuVHvmLK7Fwf4_IlgxS46BY,137659
139
139
  fontTools/subset/__main__.py,sha256=bhtfP2SqP4k799pxtksFgnC-XGNQDr3LcO4lc8T5e5g,95
140
140
  fontTools/subset/cff.py,sha256=rqMRJOlX5FacV1LW8aDlVOglgEM87TkMA9bdsYenask,6145
141
141
  fontTools/subset/svg.py,sha256=8dLBzQlnIt4_fOKEFDAVlKTucdHvcbCcyG9-a6UBZZ0,9384
@@ -150,7 +150,7 @@ fontTools/ttLib/__init__.py,sha256=1k7qp9z04gA3m6GvxDaINjqrKbzOkdTA_4RnqW_-LrA,6
150
150
  fontTools/ttLib/__main__.py,sha256=lHMPWsnzjKPuMFavf6i1gpk9KexiAk4qzgDd50Mbby0,4733
151
151
  fontTools/ttLib/macUtils.py,sha256=lj3oeFpyjV7ko_JqnluneITmAtlc119J-vwTTg2s73A,1737
152
152
  fontTools/ttLib/removeOverlaps.py,sha256=YBtj1PX-d2jMgCiWGuI6ibghWApUWqH2trJGXNxrbjQ,12612
153
- fontTools/ttLib/reorderGlyphs.py,sha256=8ClsX9-tnPfuiD8kHY4jPliGJ-31-JdybA4s1UNWx4w,10316
153
+ fontTools/ttLib/reorderGlyphs.py,sha256=TbxLxqPTUGiKRX3ulGFCwVm2lEisFYlX6caONJr_4oY,10371
154
154
  fontTools/ttLib/scaleUpem.py,sha256=U_-NGkwfS9GRIackdEXjGYZ-wSomcUPXQahDneLeArI,14618
155
155
  fontTools/ttLib/sfnt.py,sha256=rkznKfteU_Rn9P65WSjFaiwQgpEAoh-TrQpvkQhdIlo,22832
156
156
  fontTools/ttLib/standardGlyphOrder.py,sha256=7AY_fVWdtwZ4iv5uWdyKAUcbEQiSDt1lN4sqx9xXwE0,5785
@@ -259,7 +259,7 @@ fontTools/ttLib/tables/sbixGlyph.py,sha256=tjEUPVRfx6gr5yme8UytGTtVrimKN5qmbzT1G
259
259
  fontTools/ttLib/tables/sbixStrike.py,sha256=gFyOlhRIGnd59y0SrhtsT2Ce4L3yaBrLoFJ_dK9u9mQ,6663
260
260
  fontTools/ttLib/tables/table_API_readme.txt,sha256=eZlRTLUkLzc_9Ot3pdfhyMb3ahU0_Iipx0vSbzOVGy8,2748
261
261
  fontTools/ttLib/tables/ttProgram.py,sha256=tgtxgd-EnOq-2PUlYEihp-6NHu_7HnE5rxeSAtmXOtU,35888
262
- fontTools/ufoLib/__init__.py,sha256=f5PUv0b4kDBIjnzhXRj5yd3n8qWJm256TjmBaC0P9xA,94427
262
+ fontTools/ufoLib/__init__.py,sha256=_Um-de5MGbr7lxarlsoHSdxLpzAD88LfLEYMrnup2Sc,94431
263
263
  fontTools/ufoLib/converters.py,sha256=9j4BQ7EXVfWiVoB7OZCzRGsRJWi9G9w_27iFuiJNphw,13044
264
264
  fontTools/ufoLib/errors.py,sha256=9f8l5NaFAj3BZPa6Bbqt06FL4afffLuMzy4nPf-eOlE,845
265
265
  fontTools/ufoLib/etree.py,sha256=T3sjLTgjMAq6VyYRicWPaMIVBJ2YSuwZxV6Vc5yZtQI,231
@@ -276,15 +276,15 @@ fontTools/unicodedata/OTTags.py,sha256=wOPpbMsNcp_gdvPFeITtgVMnTN8TJSNAsVEdu_nuP
276
276
  fontTools/unicodedata/ScriptExtensions.py,sha256=YTZr2bOteHiz_7I4108PRy0Is4kFof-32yFuoKjFHjc,28207
277
277
  fontTools/unicodedata/Scripts.py,sha256=I0nY08ovsZ4pHU5wYchjat9DjnxcpoTzaXAn5oFaKNI,130271
278
278
  fontTools/unicodedata/__init__.py,sha256=Ega5OM4ksWZPCeJv99NLo2pksMpzMqlhkXCScGE6byM,8983
279
- fontTools/varLib/__init__.py,sha256=4gOg4J8ghtvNP1wyL1NS5ZQqnWPEQh-3hi0VIDVLUVQ,53706
279
+ fontTools/varLib/__init__.py,sha256=dc1_8itv2pS3q6mO8vMuEe-XdWOiJLIWVsJ8-bhPflI,54236
280
280
  fontTools/varLib/__main__.py,sha256=wbdYC5bPjWCxA0I4SKcLO88gl-UMtsYS8MxdW9ySTkY,95
281
281
  fontTools/varLib/avar.py,sha256=Ye_u0HHznaPQaTzufNFKDj_v9o_LxOKJoa_eTK1D1F0,9647
282
282
  fontTools/varLib/avarPlanner.py,sha256=uLMGsL6cBbEMq5YItwABG_vXlXV3bxquM93WGDJ1brA,27358
283
283
  fontTools/varLib/builder.py,sha256=mSKOCcnnw-WzmZs15FayoqCDh77Ts7o9Tre9psh8CUc,6609
284
284
  fontTools/varLib/cff.py,sha256=EVgaQcoROIrYQsRuftnxFuGGldEPYbrIh5yBckylJC4,22901
285
285
  fontTools/varLib/errors.py,sha256=dMo8eGj76I7H4hrBEiNbYrGs2J1K1SwdsUyTHpkVOrQ,6934
286
- fontTools/varLib/featureVars.py,sha256=BCOBGjGUv2Rw_z0rlVi1ZYkTDcCMh0LyAUzDVJ2PYm4,25448
287
- fontTools/varLib/hvar.py,sha256=4W5t3s7GOBN07x9KNDmA_4NGXxVEEahzkn3cWJZ4oGw,3693
286
+ fontTools/varLib/featureVars.py,sha256=ZmHPyy4KuamR4bI1PfH-Umk4EN_CfwvNfchu7BOmECg,25695
287
+ fontTools/varLib/hvar.py,sha256=u7ppYCyWRBbJhIYyBcOxuVvwbZHnlOmXlUJH74a3LHs,3695
288
288
  fontTools/varLib/interpolatable.py,sha256=Bhlq_LhEZ-sXfLNY8aFEChFrsKuT2kzmnuMfG5qi0v4,45221
289
289
  fontTools/varLib/interpolatableHelpers.py,sha256=lXd7kwfIVl-4opd-vxCDhf48RnJ7IQKv_uuFQM_6vaU,11496
290
290
  fontTools/varLib/interpolatablePlot.py,sha256=w393P6mGLRhYkIjSxMww3qyoYxAUZzCXlmPBbI_84C0,44375
@@ -300,7 +300,7 @@ fontTools/varLib/mvar.py,sha256=LTV77vH_3Ecg_qKBO5xQzjLOlJir_ppEr7mPVZRgad8,2449
300
300
  fontTools/varLib/plot.py,sha256=NoSZkJ5ndxNcDvJIvd5pQ9_jX6X1oM1K2G_tR4sdPVs,7494
301
301
  fontTools/varLib/stat.py,sha256=XuNKKZxGlBrl4OGFDAwVXhpBwJi23U3BdHmNTKoJnvE,4811
302
302
  fontTools/varLib/varStore.py,sha256=2QA9SDI6jQyQ_zq82OOwa3FBkfl-ksaSo1KGmVFpa9Q,24069
303
- fontTools/varLib/instancer/__init__.py,sha256=wFqRVbww2CjuJk3MPDQ2HGmpNBGNQd9JF58KQoBl8_c,71346
303
+ fontTools/varLib/instancer/__init__.py,sha256=4I3M5PL3rLcch_yw_rflb0f09SG3zA3eADws-purFXg,71827
304
304
  fontTools/varLib/instancer/__main__.py,sha256=zfULwcP01FhplS1IlcMgNQnLxk5RVfmOuinWjqeid-g,104
305
305
  fontTools/varLib/instancer/featureVars.py,sha256=oPqSlnHLMDTtOsmQMi6gkzLox7ymCrqlRAkvC_EJ4bc,7110
306
306
  fontTools/varLib/instancer/names.py,sha256=IPRqel_M8zVU0jl30WsfgufxUm9PBBQDQCY3VHapeHc,14950
@@ -312,11 +312,11 @@ fontTools/voltLib/error.py,sha256=phcQOQj-xOspCXu9hBJQRhSOBDzxHRgZd3fWQOFNJzw,39
312
312
  fontTools/voltLib/lexer.py,sha256=OvuETOSvlS6v7iCVeJ3IdH2Cg71n3OJoEyiB3-h6vhE,3368
313
313
  fontTools/voltLib/parser.py,sha256=rkw2IHBZPsrhGVC7Kw7V501m0u52kh1JSM5HXp-xchM,25396
314
314
  fontTools/voltLib/voltToFea.py,sha256=Z2yvnaZLQXzPLT86Uta0zRsXIYgj6NnvZtSWt5xmw2s,36549
315
- fonttools-4.58.0.data/data/share/man/man1/ttx.1,sha256=cLbm_pOOj1C76T2QXvDxzwDj9gk-GTd5RztvTMsouFw,5377
316
- fonttools-4.58.0.dist-info/licenses/LICENSE,sha256=Z4cgj4P2Wcy8IiOy_elS_6b36KymLxqKK_W8UbsbI4M,1072
317
- fonttools-4.58.0.dist-info/licenses/LICENSE.external,sha256=1IdixYQuQZseNUNQ1bR3SJQhW1d5SGRUR23TNBJjzwo,18687
318
- fonttools-4.58.0.dist-info/METADATA,sha256=AY_LgEzUlDOyf6osicMuexAXywU_LKzx3aaw416143c,104471
319
- fonttools-4.58.0.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
320
- fonttools-4.58.0.dist-info/entry_points.txt,sha256=8kVHddxfFWA44FSD4mBpmC-4uCynQnkoz_9aNJb227Y,147
321
- fonttools-4.58.0.dist-info/top_level.txt,sha256=rRgRylrXzekqWOsrhygzib12pQ7WILf7UGjqEwkIFDM,10
322
- fonttools-4.58.0.dist-info/RECORD,,
315
+ fonttools-4.58.2.data/data/share/man/man1/ttx.1,sha256=cLbm_pOOj1C76T2QXvDxzwDj9gk-GTd5RztvTMsouFw,5377
316
+ fonttools-4.58.2.dist-info/licenses/LICENSE,sha256=Z4cgj4P2Wcy8IiOy_elS_6b36KymLxqKK_W8UbsbI4M,1072
317
+ fonttools-4.58.2.dist-info/licenses/LICENSE.external,sha256=1IdixYQuQZseNUNQ1bR3SJQhW1d5SGRUR23TNBJjzwo,18687
318
+ fonttools-4.58.2.dist-info/METADATA,sha256=rzM2sNi609hxqlGZhiaNgS6kxP83lj4AqumcSBLDxO8,106341
319
+ fonttools-4.58.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
320
+ fonttools-4.58.2.dist-info/entry_points.txt,sha256=8kVHddxfFWA44FSD4mBpmC-4uCynQnkoz_9aNJb227Y,147
321
+ fonttools-4.58.2.dist-info/top_level.txt,sha256=rRgRylrXzekqWOsrhygzib12pQ7WILf7UGjqEwkIFDM,10
322
+ 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: true
4
4
  Tag: py3-none-any
5
5