summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrobot-piglet <[email protected]>2025-08-29 11:40:50 +0300
committerrobot-piglet <[email protected]>2025-08-29 12:04:59 +0300
commiteb4fa69a58c58a2f36a4cda1b61972ad7037eee6 (patch)
tree35d7c1d1f7ce3cb83cd04bedd711ab451719882c
parentccfbaaf1ad9afe621cb75dc5296d2e0de0e758b4 (diff)
Intermediate changes
commit_hash:efca680e102a12bb0a656779dafefc81261b3eac
-rw-r--r--contrib/python/fonttools/.dist-info/METADATA34
-rw-r--r--contrib/python/fonttools/README.rst20
-rw-r--r--contrib/python/fonttools/fontTools/__init__.py2
-rw-r--r--contrib/python/fonttools/fontTools/cffLib/CFF2ToCFF.py50
-rw-r--r--contrib/python/fonttools/fontTools/cffLib/transforms.py17
-rw-r--r--contrib/python/fonttools/fontTools/feaLib/builder.py7
-rw-r--r--contrib/python/fonttools/fontTools/misc/psCharStrings.py19
-rw-r--r--contrib/python/fonttools/fontTools/ttLib/tables/_g_v_a_r.py9
-rw-r--r--contrib/python/fonttools/fontTools/ttLib/tables/_h_m_t_x.py10
-rw-r--r--contrib/python/fonttools/fontTools/varLib/featureVars.py8
-rw-r--r--contrib/python/fonttools/fontTools/varLib/instancer/__init__.py70
-rw-r--r--contrib/python/fonttools/fontTools/varLib/mutator.py11
-rw-r--r--contrib/python/fonttools/ya.make2
13 files changed, 208 insertions, 51 deletions
diff --git a/contrib/python/fonttools/.dist-info/METADATA b/contrib/python/fonttools/.dist-info/METADATA
index 19786640848..1272aa27e6f 100644
--- a/contrib/python/fonttools/.dist-info/METADATA
+++ b/contrib/python/fonttools/.dist-info/METADATA
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: fonttools
-Version: 4.59.0
+Version: 4.59.1
Summary: Tools to manipulate font files
Home-page: http://github.com/fonttools/fonttools
Author: Just van Rossum
@@ -168,15 +168,6 @@ are required to unlock the extra features named "ufo", etc.
*Extra:* ``lxml``
-- ``Lib/fontTools/ufoLib``
-
- Package for reading and writing UFO source files; it requires:
-
- * `fs <https://pypi.org/pypi/fs>`__: (aka ``pyfilesystem2``) filesystem
- abstraction layer.
-
- *Extra:* ``ufo``
-
- ``Lib/fontTools/ttLib/woff2.py``
Module to compress/decompress WOFF 2.0 web fonts; it requires:
@@ -269,6 +260,17 @@ are required to unlock the extra features named "ufo", etc.
*Extra:* ``pathops``
+- ``Lib/fontTools/ufoLib``
+
+ Package for reading and writing UFO source files; if available, it will use:
+
+ * `fs <https://pypi.org/pypi/fs>`__: (aka ``pyfilesystem2``) filesystem abstraction layer
+
+ for reading and writing UFOs to the local filesystem or zip files (.ufoz), instead of
+ the built-in ``fontTools.misc.filesystem`` package.
+ The reader and writer classes can in theory also accept any object compatible the
+ ``fs.base.FS`` interface, although not all have been tested.
+
- ``Lib/fontTools/pens/cocoaPen.py`` and ``Lib/fontTools/pens/quartzPen.py``
Pens for drawing glyphs with Cocoa ``NSBezierPath`` or ``CGPath`` require:
@@ -386,6 +388,18 @@ Have fun!
Changelog
~~~~~~~~~
+4.59.1 (released 2025-08-14)
+----------------------------
+
+- [featureVars] Update OS/2.usMaxContext if possible after addFeatureVariationsRaw (#3894).
+- [vhmtx] raise TTLibError('not enough data...') when hmtx/vmtx are truncated (#3843, #3901).
+- [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).
+- [varLib] Deprecate ``varLib.mutator`` in favor of ``varLib.instancer``. The latter
+ provides equivalent full (static font) instancing in addition to partial VF instancing.
+ CLI users should replace ``fonttools varLib.mutator`` with ``fonttools varLib.instancer``.
+ API users should migrate to ``fontTools.varLib.instancer.instantiateVariableFont`` (#2680).
+
+
4.59.0 (released 2025-07-16)
----------------------------
diff --git a/contrib/python/fonttools/README.rst b/contrib/python/fonttools/README.rst
index e40554dae8f..6d638f89c0e 100644
--- a/contrib/python/fonttools/README.rst
+++ b/contrib/python/fonttools/README.rst
@@ -81,15 +81,6 @@ are required to unlock the extra features named "ufo", etc.
*Extra:* ``lxml``
-- ``Lib/fontTools/ufoLib``
-
- Package for reading and writing UFO source files; it requires:
-
- * `fs <https://pypi.org/pypi/fs>`__: (aka ``pyfilesystem2``) filesystem
- abstraction layer.
-
- *Extra:* ``ufo``
-
- ``Lib/fontTools/ttLib/woff2.py``
Module to compress/decompress WOFF 2.0 web fonts; it requires:
@@ -182,6 +173,17 @@ are required to unlock the extra features named "ufo", etc.
*Extra:* ``pathops``
+- ``Lib/fontTools/ufoLib``
+
+ Package for reading and writing UFO source files; if available, it will use:
+
+ * `fs <https://pypi.org/pypi/fs>`__: (aka ``pyfilesystem2``) filesystem abstraction layer
+
+ for reading and writing UFOs to the local filesystem or zip files (.ufoz), instead of
+ the built-in ``fontTools.misc.filesystem`` package.
+ The reader and writer classes can in theory also accept any object compatible the
+ ``fs.base.FS`` interface, although not all have been tested.
+
- ``Lib/fontTools/pens/cocoaPen.py`` and ``Lib/fontTools/pens/quartzPen.py``
Pens for drawing glyphs with Cocoa ``NSBezierPath`` or ``CGPath`` require:
diff --git a/contrib/python/fonttools/fontTools/__init__.py b/contrib/python/fonttools/fontTools/__init__.py
index d2ff98f6a68..75e3e32e488 100644
--- a/contrib/python/fonttools/fontTools/__init__.py
+++ b/contrib/python/fonttools/fontTools/__init__.py
@@ -3,6 +3,6 @@ from fontTools.misc.loggingTools import configLogger
log = logging.getLogger(__name__)
-version = __version__ = "4.59.0"
+version = __version__ = "4.59.1"
__all__ = ["version", "log", "configLogger"]
diff --git a/contrib/python/fonttools/fontTools/cffLib/CFF2ToCFF.py b/contrib/python/fonttools/fontTools/cffLib/CFF2ToCFF.py
index f929cc96869..e0ec956b60d 100644
--- a/contrib/python/fonttools/fontTools/cffLib/CFF2ToCFF.py
+++ b/contrib/python/fonttools/fontTools/cffLib/CFF2ToCFF.py
@@ -2,13 +2,17 @@
from fontTools.ttLib import TTFont, newTable
from fontTools.misc.cliTools import makeOutputFileName
+from fontTools.misc.psCharStrings import T2StackUseExtractor
from fontTools.cffLib import (
TopDictIndex,
buildOrder,
buildDefaults,
topDictOperators,
privateDictOperators,
+ FDSelect,
)
+from .transforms import desubroutinizeCharString
+from .specializer import specializeProgram
from .width import optimizeWidths
from collections import defaultdict
import logging
@@ -27,7 +31,7 @@ def _convertCFF2ToCFF(cff, otFont):
The CFF2 font cannot be variable. (TODO Accept those and convert to the
default instance?)
- This assumes a decompiled CFF table. (i.e. that the object has been
+ This assumes a decompiled CFF2 table. (i.e. that the object has been
filled via :meth:`decompile` and e.g. not loaded from XML.)"""
cff.major = 1
@@ -51,9 +55,14 @@ def _convertCFF2ToCFF(cff, otFont):
if hasattr(topDict, key):
delattr(topDict, key)
- fdArray = topDict.FDArray
charStrings = topDict.CharStrings
+ fdArray = topDict.FDArray
+ if not hasattr(topDict, "FDSelect"):
+ # FDSelect is optional in CFF2, but required in CFF.
+ fdSelect = topDict.FDSelect = FDSelect()
+ fdSelect.gidArray = [0] * len(charStrings.charStrings)
+
defaults = buildDefaults(privateDictOperators)
order = buildOrder(privateDictOperators)
for fd in fdArray:
@@ -69,6 +78,7 @@ def _convertCFF2ToCFF(cff, otFont):
if hasattr(privateDict, key):
delattr(privateDict, key)
+ # Add ending operators
for cs in charStrings.values():
cs.decompile()
cs.program.append("endchar")
@@ -100,23 +110,43 @@ def _convertCFF2ToCFF(cff, otFont):
if width != private.defaultWidthX:
cs.program.insert(0, width - private.nominalWidthX)
+ # Handle stack use since stack-depth is lower in CFF than in CFF2.
+ for glyphName in charStrings.keys():
+ cs, fdIndex = charStrings.getItemAndSelector(glyphName)
+ if fdIndex is None:
+ fdIndex = 0
+ private = fdArray[fdIndex].Private
+ extractor = T2StackUseExtractor(
+ getattr(private, "Subrs", []), cff.GlobalSubrs, private=private
+ )
+ stackUse = extractor.execute(cs)
+ if stackUse > 48: # CFF stack depth is 48
+ desubroutinizeCharString(cs)
+ cs.program = specializeProgram(cs.program)
+
+ # Unused subroutines are still in CFF2 (ie. lacking 'return' operator)
+ # because they were not decompiled when we added the 'return'.
+ # Moreover, some used subroutines may have become unused after the
+ # stack-use fixup. So we remove all unused subroutines now.
+ cff.remove_unused_subroutines()
+
mapping = {
- name: ("cid" + str(n) if n else ".notdef")
+ name: ("cid" + str(n).zfill(5) if n else ".notdef")
for n, name in enumerate(topDict.charset)
}
topDict.charset = [
- "cid" + str(n) if n else ".notdef" for n in range(len(topDict.charset))
+ "cid" + str(n).zfill(5) if n else ".notdef" for n in range(len(topDict.charset))
]
charStrings.charStrings = {
mapping[name]: v for name, v in charStrings.charStrings.items()
}
- # I'm not sure why the following is *not* necessary. And it breaks
- # the output if I add it.
- # topDict.ROS = ("Adobe", "Identity", 0)
+ topDict.ROS = ("Adobe", "Identity", 0)
def convertCFF2ToCFF(font, *, updatePostTable=True):
+ if "CFF2" not in font:
+ raise ValueError("Input font does not contain a CFF2 table.")
cff = font["CFF2"].cff
_convertCFF2ToCFF(cff, font)
del font["CFF2"]
@@ -131,7 +161,7 @@ def convertCFF2ToCFF(font, *, updatePostTable=True):
def main(args=None):
- """Convert CFF OTF font to CFF2 OTF font"""
+ """Convert CFF2 OTF font to CFF OTF font"""
if args is None:
import sys
@@ -140,8 +170,8 @@ def main(args=None):
import argparse
parser = argparse.ArgumentParser(
- "fonttools cffLib.CFFToCFF2",
- description="Upgrade a CFF font to CFF2.",
+ "fonttools cffLib.CFF2ToCFF",
+ description="Convert a non-variable CFF2 font to CFF.",
)
parser.add_argument(
"input", metavar="INPUT.ttf", help="Input OTF file with CFF table."
diff --git a/contrib/python/fonttools/fontTools/cffLib/transforms.py b/contrib/python/fonttools/fontTools/cffLib/transforms.py
index 82c70f81f49..b9b7c86c8b4 100644
--- a/contrib/python/fonttools/fontTools/cffLib/transforms.py
+++ b/contrib/python/fonttools/fontTools/cffLib/transforms.py
@@ -94,17 +94,22 @@ class _DesubroutinizingT2Decompiler(SimpleT2Decompiler):
cs._patches.append((index, subr._desubroutinized))
+def desubroutinizeCharString(cs):
+ """Desubroutinize a charstring in-place."""
+ cs.decompile()
+ subrs = getattr(cs.private, "Subrs", [])
+ decompiler = _DesubroutinizingT2Decompiler(subrs, cs.globalSubrs, cs.private)
+ decompiler.execute(cs)
+ cs.program = cs._desubroutinized
+ del cs._desubroutinized
+
+
def desubroutinize(cff):
for fontName in cff.fontNames:
font = cff[fontName]
cs = font.CharStrings
for c in cs.values():
- c.decompile()
- subrs = getattr(c.private, "Subrs", [])
- decompiler = _DesubroutinizingT2Decompiler(subrs, c.globalSubrs, c.private)
- decompiler.execute(c)
- c.program = c._desubroutinized
- del c._desubroutinized
+ desubroutinizeCharString(c)
# Delete all the local subrs
if hasattr(font, "FDArray"):
for fd in font.FDArray:
diff --git a/contrib/python/fonttools/fontTools/feaLib/builder.py b/contrib/python/fonttools/fontTools/feaLib/builder.py
index 25b319c0b01..0d253222552 100644
--- a/contrib/python/fonttools/fontTools/feaLib/builder.py
+++ b/contrib/python/fonttools/fontTools/feaLib/builder.py
@@ -926,6 +926,11 @@ class Builder(object):
l.lookup_index for l in lookups if l.lookup_index is not None
)
)
+ # order doesn't matter, but lookup_indices preserves it.
+ # We want to combine identical sets of lookups (order doesn't matter)
+ # but also respect the order provided by the user (although there's
+ # a reasonable argument to just sort and dedupe, which fontc does)
+ lookup_key = frozenset(lookup_indices)
size_feature = tag == "GPOS" and feature_tag == "size"
force_feature = self.any_feature_variations(feature_tag, tag)
@@ -943,7 +948,7 @@ class Builder(object):
"stash debug information. See fonttools#2065."
)
- feature_key = (feature_tag, lookup_indices)
+ feature_key = (feature_tag, lookup_key)
feature_index = feature_indices.get(feature_key)
if feature_index is None:
feature_index = len(table.FeatureList.FeatureRecord)
diff --git a/contrib/python/fonttools/fontTools/misc/psCharStrings.py b/contrib/python/fonttools/fontTools/misc/psCharStrings.py
index 5d881c5816c..db837248dea 100644
--- a/contrib/python/fonttools/fontTools/misc/psCharStrings.py
+++ b/contrib/python/fonttools/fontTools/misc/psCharStrings.py
@@ -338,7 +338,7 @@ class SimpleT2Decompiler(object):
self.numRegions = 0
self.vsIndex = 0
- def execute(self, charString):
+ def execute(self, charString, *, pushToStack=None):
self.callingStack.append(charString)
needsDecompilation = charString.needsDecompilation()
if needsDecompilation:
@@ -346,7 +346,8 @@ class SimpleT2Decompiler(object):
pushToProgram = program.append
else:
pushToProgram = lambda x: None
- pushToStack = self.operandStack.append
+ if pushToStack is None:
+ pushToStack = self.operandStack.append
index = 0
while True:
token, isOperator, index = charString.getToken(index)
@@ -551,6 +552,20 @@ t1Operators = [
]
+class T2StackUseExtractor(SimpleT2Decompiler):
+
+ def execute(self, charString):
+ maxStackUse = 0
+
+ def pushToStack(value):
+ nonlocal maxStackUse
+ self.operandStack.append(value)
+ maxStackUse = max(maxStackUse, len(self.operandStack))
+
+ super().execute(charString, pushToStack=pushToStack)
+ return maxStackUse
+
+
class T2WidthExtractor(SimpleT2Decompiler):
def __init__(
self,
diff --git a/contrib/python/fonttools/fontTools/ttLib/tables/_g_v_a_r.py b/contrib/python/fonttools/fontTools/ttLib/tables/_g_v_a_r.py
index 07d3befb7aa..15cc6fab5bd 100644
--- a/contrib/python/fonttools/fontTools/ttLib/tables/_g_v_a_r.py
+++ b/contrib/python/fonttools/fontTools/ttLib/tables/_g_v_a_r.py
@@ -64,7 +64,6 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
self.variations = {}
def compile(self, ttFont):
-
axisTags = [axis.axisTag for axis in ttFont["fvar"].axes]
sharedTuples = tv.compileSharedTuples(
axisTags, itertools.chain(*self.variations.values())
@@ -141,8 +140,12 @@ class table__g_v_a_r(DefaultTable.DefaultTable):
self,
)
- assert len(glyphs) == self.glyphCount
- assert len(axisTags) == self.axisCount
+ assert len(glyphs) == self.glyphCount, (len(glyphs), self.glyphCount)
+ assert len(axisTags) == self.axisCount, (
+ len(axisTags),
+ self.axisCount,
+ axisTags,
+ )
sharedCoords = tv.decompileSharedTuples(
axisTags, self.sharedTupleCount, data, self.offsetToSharedTuples
)
diff --git a/contrib/python/fonttools/fontTools/ttLib/tables/_h_m_t_x.py b/contrib/python/fonttools/fontTools/ttLib/tables/_h_m_t_x.py
index 43d49b09256..0dc5077588b 100644
--- a/contrib/python/fonttools/fontTools/ttLib/tables/_h_m_t_x.py
+++ b/contrib/python/fonttools/fontTools/ttLib/tables/_h_m_t_x.py
@@ -40,15 +40,19 @@ class table__h_m_t_x(DefaultTable.DefaultTable):
% (self.headerTag, self.numberOfMetricsName)
)
numberOfMetrics = numGlyphs
- if len(data) < 4 * numberOfMetrics:
- raise ttLib.TTLibError("not enough '%s' table data" % self.tableTag)
+ numberOfSideBearings = numGlyphs - numberOfMetrics
+ tableSize = 4 * numberOfMetrics + 2 * numberOfSideBearings
+ if len(data) < tableSize:
+ raise ttLib.TTLibError(
+ f"not enough '{self.tableTag}' table data: "
+ f"expected {tableSize} bytes, got {len(data)}"
+ )
# Note: advanceWidth is unsigned, but some font editors might
# read/write as signed. We can't be sure whether it was a mistake
# or not, so we read as unsigned but also issue a warning...
metricsFmt = ">" + self.longMetricFormat * numberOfMetrics
metrics = struct.unpack(metricsFmt, data[: 4 * numberOfMetrics])
data = data[4 * numberOfMetrics :]
- numberOfSideBearings = numGlyphs - numberOfMetrics
sideBearings = array.array("h", data[: 2 * numberOfSideBearings])
data = data[2 * numberOfSideBearings :]
diff --git a/contrib/python/fonttools/fontTools/varLib/featureVars.py b/contrib/python/fonttools/fontTools/varLib/featureVars.py
index 856f00bcb9c..40ad9dfca6c 100644
--- a/contrib/python/fonttools/fontTools/varLib/featureVars.py
+++ b/contrib/python/fonttools/fontTools/varLib/featureVars.py
@@ -95,6 +95,14 @@ def addFeatureVariations(font, conditionalSubstitutions, featureTag="rvrn"):
addFeatureVariationsRaw(font, font["GSUB"].table, conditionsAndLookups, featureTags)
+ # Update OS/2.usMaxContext in case the font didn't have features before, but
+ # does now, if the OS/2 table exists. The table may be required, but
+ # fontTools needs to be able to deal with non-standard fonts. Since feature
+ # variations are always 1:1 mappings, we can set the value to at least 1
+ # instead of recomputing it with `otlLib.maxContextCalc.maxCtxFont()`.
+ if (os2 := font.get("OS/2")) is not None:
+ os2.usMaxContext = max(1, os2.usMaxContext)
+
def _existingVariableFeatures(table):
existingFeatureVarsTags = set()
diff --git a/contrib/python/fonttools/fontTools/varLib/instancer/__init__.py b/contrib/python/fonttools/fontTools/varLib/instancer/__init__.py
index 76901880553..d635045c3f4 100644
--- a/contrib/python/fonttools/fontTools/varLib/instancer/__init__.py
+++ b/contrib/python/fonttools/fontTools/varLib/instancer/__init__.py
@@ -120,6 +120,7 @@ from fontTools.cffLib.specializer import (
specializeCommands,
generalizeCommands,
)
+from fontTools.cffLib.CFF2ToCFF import convertCFF2ToCFF
from fontTools.varLib import builder
from fontTools.varLib.mvar import MVAR_ENTRIES
from fontTools.varLib.merger import MutatorMerger
@@ -136,6 +137,7 @@ from enum import IntEnum
import logging
import os
import re
+import io
from typing import Dict, Iterable, Mapping, Optional, Sequence, Tuple, Union
import warnings
@@ -643,7 +645,11 @@ def instantiateCFF2(
# the Private dicts.
#
# Then prune unused things and possibly drop the VarStore if it's empty.
- # In which case, downgrade to CFF table if requested.
+ #
+ # If the downgrade parameter is True, no actual downgrading is done, but
+ # the function returns True if the VarStore was empty after instantiation,
+ # and hence a downgrade to CFF is possible. In all other cases it returns
+ # False.
log.info("Instantiating CFF2 table")
@@ -882,9 +888,9 @@ def instantiateCFF2(
del private.vstore
if downgrade:
- from fontTools.cffLib.CFF2ToCFF import convertCFF2ToCFF
+ return True
- convertCFF2ToCFF(varfont)
+ return False
def _instantiateGvarGlyph(
@@ -1377,6 +1383,52 @@ def _isValidAvarSegmentMap(axisTag, segmentMap):
return True
+def downgradeCFF2ToCFF(varfont):
+
+ # Save these properties
+ recalcTimestamp = varfont.recalcTimestamp
+ recalcBBoxes = varfont.recalcBBoxes
+
+ # Disable them
+ varfont.recalcTimestamp = False
+ varfont.recalcBBoxes = False
+
+ # Save to memory, reload, downgrade and save again, reload.
+ # We do this dance because the convertCFF2ToCFF changes glyph
+ # names, so following save would fail if any other table was
+ # loaded and referencing glyph names.
+ #
+ # The second save+load is unfortunate but also necessary.
+
+ stream = io.BytesIO()
+ log.info("Saving CFF2 font to memory for downgrade")
+ varfont.save(stream)
+ stream.seek(0)
+ varfont = TTFont(stream, recalcTimestamp=False, recalcBBoxes=False)
+
+ convertCFF2ToCFF(varfont)
+
+ stream = io.BytesIO()
+ log.info("Saving downgraded CFF font to memory")
+ varfont.save(stream)
+ stream.seek(0)
+ varfont = TTFont(stream, recalcTimestamp=False, recalcBBoxes=False)
+
+ # Uncomment, to see test all tables can be loaded. This fails without
+ # the extra save+load above.
+ """
+ for tag in varfont.keys():
+ print("Loading", tag)
+ varfont[tag]
+ """
+
+ # Restore them
+ varfont.recalcTimestamp = recalcTimestamp
+ varfont.recalcBBoxes = recalcBBoxes
+
+ return varfont
+
+
def instantiateAvar(varfont, axisLimits):
# 'axisLimits' dict must contain user-space (non-normalized) coordinates.
@@ -1665,7 +1717,9 @@ def instantiateVariableFont(
instantiateVARC(varfont, normalizedLimits)
if "CFF2" in varfont:
- instantiateCFF2(varfont, normalizedLimits, downgrade=downgradeCFF2)
+ downgradeCFF2 = instantiateCFF2(
+ varfont, normalizedLimits, downgrade=downgradeCFF2
+ )
if "gvar" in varfont:
instantiateGvar(varfont, normalizedLimits, optimize=optimize)
@@ -1720,6 +1774,12 @@ def instantiateVariableFont(
# name table has been updated.
setRibbiBits(varfont)
+ if downgradeCFF2:
+ origVarfont = varfont
+ varfont = downgradeCFF2ToCFF(varfont)
+ if inplace:
+ origVarfont.__dict__ = varfont.__dict__.copy()
+
return varfont
@@ -1929,7 +1989,7 @@ def main(args=None):
if limit is None or limit[0] == limit[2]
}.issuperset(axis.axisTag for axis in varfont["fvar"].axes)
- instantiateVariableFont(
+ varfont = instantiateVariableFont(
varfont,
axisLimits,
inplace=True,
diff --git a/contrib/python/fonttools/fontTools/varLib/mutator.py b/contrib/python/fonttools/fontTools/varLib/mutator.py
index f9f93790263..cfa7607c4f2 100644
--- a/contrib/python/fonttools/fontTools/varLib/mutator.py
+++ b/contrib/python/fonttools/fontTools/varLib/mutator.py
@@ -4,9 +4,16 @@ Instantiate a variation font. Run, eg:
.. code-block:: sh
$ fonttools varLib.mutator ./NotoSansArabic-VF.ttf wght=140 wdth=85
+
+.. warning::
+ ``fontTools.varLib.mutator`` is deprecated in favor of :mod:`fontTools.varLib.instancer`
+ which provides equivalent full instancing and also supports partial instancing.
+ Please migrate CLI usage to ``fonttools varLib.instancer`` and API usage to
+ :func:`fontTools.varLib.instancer.instantiateVariableFont`.
"""
from fontTools.misc.fixedTools import floatToFixedToFloat, floatToFixed
+from fontTools.misc.loggingTools import deprecateFunction
from fontTools.misc.roundTools import otRound
from fontTools.pens.boundsPen import BoundsPen
from fontTools.ttLib import TTFont, newTable
@@ -159,6 +166,10 @@ def interpolate_cff2_metrics(varfont, topDict, glyphOrder, loc):
hmtx[gname] = tuple(entry)
+@deprecateFunction(
+ "use fontTools.varLib.instancer.instantiateVariableFont instead "
+ "for either full or partial instancing",
+)
def instantiateVariableFont(varfont, location, inplace=False, overlap=True):
"""Generate a static instance from a variable TTFont and a dictionary
defining the desired location along the variable font's axes.
diff --git a/contrib/python/fonttools/ya.make b/contrib/python/fonttools/ya.make
index 0632f1b026f..a084add2583 100644
--- a/contrib/python/fonttools/ya.make
+++ b/contrib/python/fonttools/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(4.59.0)
+VERSION(4.59.1)
LICENSE(MIT)