fonttools 4.58.5__cp39-cp39-win_amd64.whl → 4.59.1__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 (61) hide show
  1. fontTools/__init__.py +1 -1
  2. fontTools/cffLib/CFF2ToCFF.py +40 -10
  3. fontTools/cffLib/transforms.py +11 -6
  4. fontTools/cu2qu/cu2qu.cp39-win_amd64.pyd +0 -0
  5. fontTools/feaLib/builder.py +6 -1
  6. fontTools/feaLib/lexer.cp39-win_amd64.pyd +0 -0
  7. fontTools/merge/__init__.py +1 -1
  8. fontTools/misc/bezierTools.cp39-win_amd64.pyd +0 -0
  9. fontTools/misc/filesystem/__init__.py +68 -0
  10. fontTools/misc/filesystem/_base.py +134 -0
  11. fontTools/misc/filesystem/_copy.py +45 -0
  12. fontTools/misc/filesystem/_errors.py +54 -0
  13. fontTools/misc/filesystem/_info.py +75 -0
  14. fontTools/misc/filesystem/_osfs.py +164 -0
  15. fontTools/misc/filesystem/_path.py +67 -0
  16. fontTools/misc/filesystem/_subfs.py +92 -0
  17. fontTools/misc/filesystem/_tempfs.py +34 -0
  18. fontTools/misc/filesystem/_tools.py +34 -0
  19. fontTools/misc/filesystem/_walk.py +55 -0
  20. fontTools/misc/filesystem/_zipfs.py +204 -0
  21. fontTools/misc/psCharStrings.py +17 -2
  22. fontTools/misc/sstruct.py +2 -6
  23. fontTools/misc/xmlWriter.py +28 -1
  24. fontTools/pens/momentsPen.cp39-win_amd64.pyd +0 -0
  25. fontTools/pens/roundingPen.py +2 -2
  26. fontTools/qu2cu/qu2cu.cp39-win_amd64.pyd +0 -0
  27. fontTools/subset/__init__.py +11 -11
  28. fontTools/ttLib/sfnt.py +2 -3
  29. fontTools/ttLib/tables/S__i_l_f.py +2 -2
  30. fontTools/ttLib/tables/T_S_I__1.py +2 -5
  31. fontTools/ttLib/tables/T_S_I__5.py +2 -2
  32. fontTools/ttLib/tables/_c_m_a_p.py +1 -1
  33. fontTools/ttLib/tables/_c_v_t.py +1 -2
  34. fontTools/ttLib/tables/_g_l_y_f.py +9 -10
  35. fontTools/ttLib/tables/_g_v_a_r.py +6 -3
  36. fontTools/ttLib/tables/_h_d_m_x.py +4 -4
  37. fontTools/ttLib/tables/_h_m_t_x.py +7 -3
  38. fontTools/ttLib/tables/_l_o_c_a.py +2 -2
  39. fontTools/ttLib/tables/_p_o_s_t.py +3 -4
  40. fontTools/ttLib/tables/otBase.py +2 -4
  41. fontTools/ttLib/tables/otTables.py +7 -12
  42. fontTools/ttLib/tables/sbixStrike.py +3 -3
  43. fontTools/ttLib/ttFont.py +7 -16
  44. fontTools/ttLib/woff2.py +10 -13
  45. fontTools/ufoLib/__init__.py +20 -25
  46. fontTools/ufoLib/glifLib.py +12 -17
  47. fontTools/ufoLib/validators.py +2 -4
  48. fontTools/unicodedata/__init__.py +2 -0
  49. fontTools/varLib/featureVars.py +8 -0
  50. fontTools/varLib/hvar.py +1 -1
  51. fontTools/varLib/instancer/__init__.py +65 -5
  52. fontTools/varLib/iup.cp39-win_amd64.pyd +0 -0
  53. fontTools/varLib/mutator.py +11 -0
  54. {fonttools-4.58.5.dist-info → fonttools-4.59.1.dist-info}/METADATA +42 -12
  55. {fonttools-4.58.5.dist-info → fonttools-4.59.1.dist-info}/RECORD +61 -49
  56. {fonttools-4.58.5.dist-info → fonttools-4.59.1.dist-info}/licenses/LICENSE.external +29 -0
  57. {fonttools-4.58.5.data → fonttools-4.59.1.data}/data/share/man/man1/ttx.1 +0 -0
  58. {fonttools-4.58.5.dist-info → fonttools-4.59.1.dist-info}/WHEEL +0 -0
  59. {fonttools-4.58.5.dist-info → fonttools-4.59.1.dist-info}/entry_points.txt +0 -0
  60. {fonttools-4.58.5.dist-info → fonttools-4.59.1.dist-info}/licenses/LICENSE +0 -0
  61. {fonttools-4.58.5.dist-info → fonttools-4.59.1.dist-info}/top_level.txt +0 -0
@@ -32,30 +32,27 @@ Value conversion functions are available for converting
32
32
  - :func:`.convertFontInfoValueForAttributeFromVersion3ToVersion2`
33
33
  """
34
34
 
35
- import os
36
- from copy import deepcopy
37
- from os import fsdecode
35
+ import enum
38
36
  import logging
37
+ import os
39
38
  import zipfile
40
- import enum
41
39
  from collections import OrderedDict
42
- import fs
43
- import fs.base
44
- import fs.subfs
45
- import fs.errors
46
- import fs.copy
47
- import fs.osfs
48
- import fs.zipfs
49
- import fs.tempfs
50
- import fs.tools
40
+ from copy import deepcopy
41
+ from os import fsdecode
42
+
43
+ from fontTools.misc import filesystem as fs
51
44
  from fontTools.misc import plistlib
52
- from fontTools.ufoLib.validators import *
53
- from fontTools.ufoLib.filenames import userNameToFileName
54
45
  from fontTools.ufoLib.converters import convertUFO1OrUFO2KerningToUFO3Kerning
55
46
  from fontTools.ufoLib.errors import UFOLibError
56
- from fontTools.ufoLib.utils import numberTypes, _VersionTupleEnumMixin
47
+ from fontTools.ufoLib.filenames import userNameToFileName
48
+ from fontTools.ufoLib.utils import _VersionTupleEnumMixin, numberTypes
49
+ from fontTools.ufoLib.validators import *
50
+
51
+ # client code can check this to see if the upstream `fs` package is being used
52
+ haveFS = fs._haveFS
57
53
 
58
54
  __all__ = [
55
+ "haveFS",
59
56
  "makeUFOPath",
60
57
  "UFOLibError",
61
58
  "UFOReader",
@@ -184,7 +181,7 @@ class _UFOBaseIO:
184
181
  return
185
182
  self.fs.writebytes(fileName, data)
186
183
  else:
187
- with self.fs.openbin(fileName, mode="w") as fp:
184
+ with self.fs.open(fileName, mode="wb") as fp:
188
185
  try:
189
186
  plistlib.dump(obj, fp)
190
187
  except Exception as e:
@@ -412,7 +409,7 @@ class UFOReader(_UFOBaseIO):
412
409
  path = fsdecode(path)
413
410
  try:
414
411
  if encoding is None:
415
- return self.fs.openbin(path)
412
+ return self.fs.open(path, mode="rb")
416
413
  else:
417
414
  return self.fs.open(path, mode="r", encoding=encoding)
418
415
  except fs.errors.ResourceNotFound:
@@ -818,7 +815,7 @@ class UFOReader(_UFOBaseIO):
818
815
  # systems often have hidden directories
819
816
  continue
820
817
  if validate:
821
- with imagesFS.openbin(path.name) as fp:
818
+ with imagesFS.open(path.name, "rb") as fp:
822
819
  valid, error = pngValidator(fileObj=fp)
823
820
  if valid:
824
821
  result.append(path.name)
@@ -984,18 +981,16 @@ class UFOWriter(UFOReader):
984
981
  % len(rootDirs)
985
982
  )
986
983
  else:
987
- # 'ClosingSubFS' ensures that the parent filesystem is closed
988
- # when its root subdirectory is closed
989
- self.fs = parentFS.opendir(
990
- rootDirs[0], factory=fs.subfs.ClosingSubFS
991
- )
984
+ rootDir = rootDirs[0]
992
985
  else:
993
986
  # if the output zip file didn't exist, we create the root folder;
994
987
  # we name it the same as input 'path', but with '.ufo' extension
995
988
  rootDir = os.path.splitext(os.path.basename(path))[0] + ".ufo"
996
989
  parentFS = fs.zipfs.ZipFS(path, write=True, encoding="utf-8")
997
990
  parentFS.makedir(rootDir)
998
- self.fs = parentFS.opendir(rootDir, factory=fs.subfs.ClosingSubFS)
991
+ # 'ClosingSubFS' ensures that the parent filesystem is closed
992
+ # when its root subdirectory is closed
993
+ self.fs = parentFS.opendir(rootDir, factory=fs.subfs.ClosingSubFS)
999
994
  else:
1000
995
  self.fs = fs.osfs.OSFS(path, create=True)
1001
996
  self._fileStructure = structure
@@ -3,7 +3,7 @@ Generic module for reading and writing the .glif format.
3
3
 
4
4
  More info about the .glif format (GLyphInterchangeFormat) can be found here:
5
5
 
6
- http://unifiedfontobject.org
6
+ http://unifiedfontobject.org
7
7
 
8
8
  The main class in this module is :class:`GlyphSet`. It manages a set of .glif files
9
9
  in a folder. It offers two ways to read glyph data, and one way to write
@@ -12,33 +12,28 @@ glyph data. See the class doc string for details.
12
12
 
13
13
  from __future__ import annotations
14
14
 
15
- import logging
16
15
  import enum
17
- from warnings import warn
16
+ import logging
18
17
  from collections import OrderedDict
19
- import fs
20
- import fs.base
21
- import fs.errors
22
- import fs.osfs
23
- import fs.path
18
+ from warnings import warn
19
+
20
+ import fontTools.misc.filesystem as fs
21
+ from fontTools.misc import etree, plistlib
24
22
  from fontTools.misc.textTools import tobytes
25
- from fontTools.misc import plistlib
26
23
  from fontTools.pens.pointPen import AbstractPointPen, PointToSegmentPen
24
+ from fontTools.ufoLib import UFOFormatVersion, _UFOBaseIO
27
25
  from fontTools.ufoLib.errors import GlifLibError
28
26
  from fontTools.ufoLib.filenames import userNameToFileName
27
+ from fontTools.ufoLib.utils import _VersionTupleEnumMixin, numberTypes
29
28
  from fontTools.ufoLib.validators import (
30
- genericTypeValidator,
29
+ anchorsValidator,
31
30
  colorValidator,
31
+ genericTypeValidator,
32
+ glyphLibValidator,
32
33
  guidelinesValidator,
33
- anchorsValidator,
34
34
  identifierValidator,
35
35
  imageValidator,
36
- glyphLibValidator,
37
36
  )
38
- from fontTools.misc import etree
39
- from fontTools.ufoLib import _UFOBaseIO, UFOFormatVersion
40
- from fontTools.ufoLib.utils import numberTypes, _VersionTupleEnumMixin
41
-
42
37
 
43
38
  __all__ = [
44
39
  "GlyphSet",
@@ -206,7 +201,7 @@ class GlyphSet(_UFOBaseIO):
206
201
  # 'dirName' is kept for backward compatibility only, but it's DEPRECATED
207
202
  # as it's not guaranteed that it maps to an existing OSFS directory.
208
203
  # Client could use the FS api via the `self.fs` attribute instead.
209
- self.dirName = fs.path.parts(path)[-1]
204
+ self.dirName = fs.path.basename(path)
210
205
  self.fs = filesystem
211
206
  # if glyphSet contains no 'contents.plist', we consider it empty
212
207
  self._havePreviousFile = filesystem.exists(CONTENTS_FILENAME)
@@ -1,14 +1,12 @@
1
1
  """Various low level data validators."""
2
2
 
3
3
  import calendar
4
+ from collections.abc import Mapping
4
5
  from io import open
5
- import fs.base
6
- import fs.osfs
7
6
 
8
- from collections.abc import Mapping
7
+ import fontTools.misc.filesystem as fs
9
8
  from fontTools.ufoLib.utils import numberTypes
10
9
 
11
-
12
10
  # -------
13
11
  # Generic
14
12
  # -------
@@ -197,6 +197,8 @@ RTL_SCRIPTS = {
197
197
  "Yezi", # Yezidi
198
198
  # Unicode-14.0 additions
199
199
  "Ougr", # Old Uyghur
200
+ # Unicode-16.0 additions
201
+ "Gara", # Garay
200
202
  }
201
203
 
202
204
 
@@ -95,6 +95,14 @@ def addFeatureVariations(font, conditionalSubstitutions, featureTag="rvrn"):
95
95
 
96
96
  addFeatureVariationsRaw(font, font["GSUB"].table, conditionsAndLookups, featureTags)
97
97
 
98
+ # Update OS/2.usMaxContext in case the font didn't have features before, but
99
+ # does now, if the OS/2 table exists. The table may be required, but
100
+ # fontTools needs to be able to deal with non-standard fonts. Since feature
101
+ # variations are always 1:1 mappings, we can set the value to at least 1
102
+ # instead of recomputing it with `otlLib.maxContextCalc.maxCtxFont()`.
103
+ if (os2 := font.get("OS/2")) is not None:
104
+ os2.usMaxContext = max(1, os2.usMaxContext)
105
+
98
106
 
99
107
  def _existingVariableFeatures(table):
100
108
  existingFeatureVarsTags = set()
fontTools/varLib/hvar.py CHANGED
@@ -56,7 +56,7 @@ def add_HVAR(font):
56
56
  def add_VVAR(font):
57
57
  if "VVAR" in font:
58
58
  del font["VVAR"]
59
- getAdvanceMetrics = partial(_get_advance_metrics, font, axisTags, HVAR_FIELDS)
59
+ getAdvanceMetrics = partial(_get_advance_metrics, font, axisTags, VVAR_FIELDS)
60
60
  axisTags = [axis.axisTag for axis in font["fvar"].axes]
61
61
  _add_VHVAR(font, axisTags, VVAR_FIELDS, getAdvanceMetrics)
62
62
 
@@ -120,6 +120,7 @@ from fontTools.cffLib.specializer import (
120
120
  specializeCommands,
121
121
  generalizeCommands,
122
122
  )
123
+ from fontTools.cffLib.CFF2ToCFF import convertCFF2ToCFF
123
124
  from fontTools.varLib import builder
124
125
  from fontTools.varLib.mvar import MVAR_ENTRIES
125
126
  from fontTools.varLib.merger import MutatorMerger
@@ -136,6 +137,7 @@ from enum import IntEnum
136
137
  import logging
137
138
  import os
138
139
  import re
140
+ import io
139
141
  from typing import Dict, Iterable, Mapping, Optional, Sequence, Tuple, Union
140
142
  import warnings
141
143
 
@@ -643,7 +645,11 @@ def instantiateCFF2(
643
645
  # the Private dicts.
644
646
  #
645
647
  # Then prune unused things and possibly drop the VarStore if it's empty.
646
- # In which case, downgrade to CFF table if requested.
648
+ #
649
+ # If the downgrade parameter is True, no actual downgrading is done, but
650
+ # the function returns True if the VarStore was empty after instantiation,
651
+ # and hence a downgrade to CFF is possible. In all other cases it returns
652
+ # False.
647
653
 
648
654
  log.info("Instantiating CFF2 table")
649
655
 
@@ -882,9 +888,9 @@ def instantiateCFF2(
882
888
  del private.vstore
883
889
 
884
890
  if downgrade:
885
- from fontTools.cffLib.CFF2ToCFF import convertCFF2ToCFF
891
+ return True
886
892
 
887
- convertCFF2ToCFF(varfont)
893
+ return False
888
894
 
889
895
 
890
896
  def _instantiateGvarGlyph(
@@ -1377,6 +1383,52 @@ def _isValidAvarSegmentMap(axisTag, segmentMap):
1377
1383
  return True
1378
1384
 
1379
1385
 
1386
+ def downgradeCFF2ToCFF(varfont):
1387
+
1388
+ # Save these properties
1389
+ recalcTimestamp = varfont.recalcTimestamp
1390
+ recalcBBoxes = varfont.recalcBBoxes
1391
+
1392
+ # Disable them
1393
+ varfont.recalcTimestamp = False
1394
+ varfont.recalcBBoxes = False
1395
+
1396
+ # Save to memory, reload, downgrade and save again, reload.
1397
+ # We do this dance because the convertCFF2ToCFF changes glyph
1398
+ # names, so following save would fail if any other table was
1399
+ # loaded and referencing glyph names.
1400
+ #
1401
+ # The second save+load is unfortunate but also necessary.
1402
+
1403
+ stream = io.BytesIO()
1404
+ log.info("Saving CFF2 font to memory for downgrade")
1405
+ varfont.save(stream)
1406
+ stream.seek(0)
1407
+ varfont = TTFont(stream, recalcTimestamp=False, recalcBBoxes=False)
1408
+
1409
+ convertCFF2ToCFF(varfont)
1410
+
1411
+ stream = io.BytesIO()
1412
+ log.info("Saving downgraded CFF font to memory")
1413
+ varfont.save(stream)
1414
+ stream.seek(0)
1415
+ varfont = TTFont(stream, recalcTimestamp=False, recalcBBoxes=False)
1416
+
1417
+ # Uncomment, to see test all tables can be loaded. This fails without
1418
+ # the extra save+load above.
1419
+ """
1420
+ for tag in varfont.keys():
1421
+ print("Loading", tag)
1422
+ varfont[tag]
1423
+ """
1424
+
1425
+ # Restore them
1426
+ varfont.recalcTimestamp = recalcTimestamp
1427
+ varfont.recalcBBoxes = recalcBBoxes
1428
+
1429
+ return varfont
1430
+
1431
+
1380
1432
  def instantiateAvar(varfont, axisLimits):
1381
1433
  # 'axisLimits' dict must contain user-space (non-normalized) coordinates.
1382
1434
 
@@ -1665,7 +1717,9 @@ def instantiateVariableFont(
1665
1717
  instantiateVARC(varfont, normalizedLimits)
1666
1718
 
1667
1719
  if "CFF2" in varfont:
1668
- instantiateCFF2(varfont, normalizedLimits, downgrade=downgradeCFF2)
1720
+ downgradeCFF2 = instantiateCFF2(
1721
+ varfont, normalizedLimits, downgrade=downgradeCFF2
1722
+ )
1669
1723
 
1670
1724
  if "gvar" in varfont:
1671
1725
  instantiateGvar(varfont, normalizedLimits, optimize=optimize)
@@ -1720,6 +1774,12 @@ def instantiateVariableFont(
1720
1774
  # name table has been updated.
1721
1775
  setRibbiBits(varfont)
1722
1776
 
1777
+ if downgradeCFF2:
1778
+ origVarfont = varfont
1779
+ varfont = downgradeCFF2ToCFF(varfont)
1780
+ if inplace:
1781
+ origVarfont.__dict__ = varfont.__dict__.copy()
1782
+
1723
1783
  return varfont
1724
1784
 
1725
1785
 
@@ -1929,7 +1989,7 @@ def main(args=None):
1929
1989
  if limit is None or limit[0] == limit[2]
1930
1990
  }.issuperset(axis.axisTag for axis in varfont["fvar"].axes)
1931
1991
 
1932
- instantiateVariableFont(
1992
+ varfont = instantiateVariableFont(
1933
1993
  varfont,
1934
1994
  axisLimits,
1935
1995
  inplace=True,
Binary file
@@ -4,9 +4,16 @@ Instantiate a variation font. Run, eg:
4
4
  .. code-block:: sh
5
5
 
6
6
  $ fonttools varLib.mutator ./NotoSansArabic-VF.ttf wght=140 wdth=85
7
+
8
+ .. warning::
9
+ ``fontTools.varLib.mutator`` is deprecated in favor of :mod:`fontTools.varLib.instancer`
10
+ which provides equivalent full instancing and also supports partial instancing.
11
+ Please migrate CLI usage to ``fonttools varLib.instancer`` and API usage to
12
+ :func:`fontTools.varLib.instancer.instantiateVariableFont`.
7
13
  """
8
14
 
9
15
  from fontTools.misc.fixedTools import floatToFixedToFloat, floatToFixed
16
+ from fontTools.misc.loggingTools import deprecateFunction
10
17
  from fontTools.misc.roundTools import otRound
11
18
  from fontTools.pens.boundsPen import BoundsPen
12
19
  from fontTools.ttLib import TTFont, newTable
@@ -159,6 +166,10 @@ def interpolate_cff2_metrics(varfont, topDict, glyphOrder, loc):
159
166
  hmtx[gname] = tuple(entry)
160
167
 
161
168
 
169
+ @deprecateFunction(
170
+ "use fontTools.varLib.instancer.instantiateVariableFont instead "
171
+ "for either full or partial instancing",
172
+ )
162
173
  def instantiateVariableFont(varfont, location, inplace=False, overlap=True):
163
174
  """Generate a static instance from a variable TTFont and a dictionary
164
175
  defining the desired location along the variable font's axes.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fonttools
3
- Version: 4.58.5
3
+ Version: 4.59.1
4
4
  Summary: Tools to manipulate font files
5
5
  Home-page: http://github.com/fonttools/fonttools
6
6
  Author: Just van Rossum
@@ -31,7 +31,6 @@ Description-Content-Type: text/x-rst
31
31
  License-File: LICENSE
32
32
  License-File: LICENSE.external
33
33
  Provides-Extra: ufo
34
- Requires-Dist: fs<3,>=2.2.0; extra == "ufo"
35
34
  Provides-Extra: lxml
36
35
  Requires-Dist: lxml>=4.0; extra == "lxml"
37
36
  Provides-Extra: woff
@@ -57,7 +56,6 @@ Requires-Dist: skia-pathops>=0.5.0; extra == "pathops"
57
56
  Provides-Extra: repacker
58
57
  Requires-Dist: uharfbuzz>=0.23.0; extra == "repacker"
59
58
  Provides-Extra: all
60
- Requires-Dist: fs<3,>=2.2.0; extra == "all"
61
59
  Requires-Dist: lxml>=4.0; extra == "all"
62
60
  Requires-Dist: brotli>=1.0.1; platform_python_implementation == "CPython" and extra == "all"
63
61
  Requires-Dist: brotlicffi>=0.8.0; platform_python_implementation != "CPython" and extra == "all"
@@ -170,15 +168,6 @@ are required to unlock the extra features named "ufo", etc.
170
168
 
171
169
  *Extra:* ``lxml``
172
170
 
173
- - ``Lib/fontTools/ufoLib``
174
-
175
- Package for reading and writing UFO source files; it requires:
176
-
177
- * `fs <https://pypi.org/pypi/fs>`__: (aka ``pyfilesystem2``) filesystem
178
- abstraction layer.
179
-
180
- *Extra:* ``ufo``
181
-
182
171
  - ``Lib/fontTools/ttLib/woff2.py``
183
172
 
184
173
  Module to compress/decompress WOFF 2.0 web fonts; it requires:
@@ -271,6 +260,17 @@ are required to unlock the extra features named "ufo", etc.
271
260
 
272
261
  *Extra:* ``pathops``
273
262
 
263
+ - ``Lib/fontTools/ufoLib``
264
+
265
+ Package for reading and writing UFO source files; if available, it will use:
266
+
267
+ * `fs <https://pypi.org/pypi/fs>`__: (aka ``pyfilesystem2``) filesystem abstraction layer
268
+
269
+ for reading and writing UFOs to the local filesystem or zip files (.ufoz), instead of
270
+ the built-in ``fontTools.misc.filesystem`` package.
271
+ The reader and writer classes can in theory also accept any object compatible the
272
+ ``fs.base.FS`` interface, although not all have been tested.
273
+
274
274
  - ``Lib/fontTools/pens/cocoaPen.py`` and ``Lib/fontTools/pens/quartzPen.py``
275
275
 
276
276
  Pens for drawing glyphs with Cocoa ``NSBezierPath`` or ``CGPath`` require:
@@ -388,6 +388,36 @@ Have fun!
388
388
  Changelog
389
389
  ~~~~~~~~~
390
390
 
391
+ 4.59.1 (released 2025-08-14)
392
+ ----------------------------
393
+
394
+ - [featureVars] Update OS/2.usMaxContext if possible after addFeatureVariationsRaw (#3894).
395
+ - [vhmtx] raise TTLibError('not enough data...') when hmtx/vmtx are truncated (#3843, #3901).
396
+ - [feaLib] Combine duplicate features that have the same set of lookups regardless of the order in which those lookups are added to the feature (#3895).
397
+ - [varLib] Deprecate ``varLib.mutator`` in favor of ``varLib.instancer``. The latter
398
+ provides equivalent full (static font) instancing in addition to partial VF instancing.
399
+ CLI users should replace ``fonttools varLib.mutator`` with ``fonttools varLib.instancer``.
400
+ API users should migrate to ``fontTools.varLib.instancer.instantiateVariableFont`` (#2680).
401
+
402
+
403
+ 4.59.0 (released 2025-07-16)
404
+ ----------------------------
405
+
406
+ - Removed hard-dependency on pyfilesystem2 (``fs`` package) from ``fonttools[ufo]`` extra.
407
+ This is replaced by the `fontTools.misc.filesystem` package, a stdlib-only, drop-in
408
+ replacement for the subset of the pyfilesystem2's API used by ``fontTools.ufoLib``.
409
+ The latter should continue to work with the upstream ``fs`` (we even test with/without).
410
+ Clients who wish to continue using ``fs`` can do so by depending on it directly instead
411
+ of via the ``fonttools[ufo]`` extra (#3885, #3620).
412
+ - [xmlWriter] Replace illegal XML characters (e.g. control or non-characters) with "?"
413
+ when dumping to ttx (#3868, #71).
414
+ - [varLib.hvar] Fixed vertical metrics fields copy/pasta error (#3884).
415
+ - Micro optimizations in ttLib and sstruct modules (#3878, #3879).
416
+ - [unicodedata] Add Garay script to RTL_SCRIPTS (#3882).
417
+ - [roundingPen] Remove unreliable kwarg usage. Argument names aren’t consistent among
418
+ point pens’ ``.addComponent()`` implementations, in particular ``baseGlyphName``
419
+ vs ``glyphName`` (#3880).
420
+
391
421
  4.58.5 (released 2025-07-03)
392
422
  ----------------------------
393
423