summaryrefslogtreecommitdiffstats
path: root/contrib/python/fonttools
diff options
context:
space:
mode:
authorrobot-piglet <[email protected]>2025-12-28 13:20:09 +0300
committerrobot-piglet <[email protected]>2025-12-28 13:37:00 +0300
commit9cb602af1d0734ea260ccc8c9914fd1717e46209 (patch)
tree1d387785e5e9fc5adfeee11b5cab4d32e530cf9e /contrib/python/fonttools
parent1f49530d91149641b10db4d1771db584eecc34b3 (diff)
Intermediate changes
commit_hash:ae3fb6278831e776f34d8ad9de96961a6369f05c
Diffstat (limited to 'contrib/python/fonttools')
-rw-r--r--contrib/python/fonttools/.dist-info/METADATA22
-rw-r--r--contrib/python/fonttools/fontTools/__init__.py2
-rw-r--r--contrib/python/fonttools/fontTools/designspaceLib/__init__.py6
-rw-r--r--contrib/python/fonttools/fontTools/fontBuilder.py86
-rw-r--r--contrib/python/fonttools/fontTools/misc/configTools.py4
-rw-r--r--contrib/python/fonttools/fontTools/misc/fixedTools.py2
-rw-r--r--contrib/python/fonttools/fontTools/misc/xmlWriter.py25
-rw-r--r--contrib/python/fonttools/fontTools/otlLib/builder.py23
-rw-r--r--contrib/python/fonttools/fontTools/ttLib/sfnt.py15
-rw-r--r--contrib/python/fonttools/fontTools/ttLib/tables/_a_v_a_r.py11
-rw-r--r--contrib/python/fonttools/fontTools/ttLib/ttFont.py747
-rw-r--r--contrib/python/fonttools/fontTools/varLib/instancer/__init__.py2
-rw-r--r--contrib/python/fonttools/fontTools/varLib/models.py19
-rw-r--r--contrib/python/fonttools/ya.make2
14 files changed, 788 insertions, 178 deletions
diff --git a/contrib/python/fonttools/.dist-info/METADATA b/contrib/python/fonttools/.dist-info/METADATA
index 49fac2b52f5..fcaae0d0409 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.61.0
+Version: 4.61.1
Summary: Tools to manipulate font files
Home-page: http://github.com/fonttools/fonttools
Author: Just van Rossum
@@ -21,6 +21,7 @@ Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Text Processing :: Fonts
Classifier: Topic :: Multimedia :: Graphics
@@ -391,11 +392,28 @@ Have fun!
Changelog
~~~~~~~~~
+4.61.1 (released 2025-12-12)
+----------------------------
+
+- [otlLib] buildCoverage: return empty Coverage instead of None (#4003, #4004).
+- [instancer] bug fix in ``avar2`` full instancing (#4002).
+- [designspaceLib] Preserve empty conditionsets when serializing to XML (#4001).
+- [fontBu ilder] Fix FontBuilder ``setupOS2()`` default params globally polluted (#3996, #3997).
+- [ttFont] Add more typing annotations to ttFont, xmlWriter, sfnt, varLib.models and others (#3952, #3826).
+- Explicitly test and declare support for Python 3.14, even though we were already shipping pre-built wheels for it (#3990).
+
+4.60.2 (released 2025-12-09)
+----------------------------
+
+- **Backport release** Same as 4.61.0 but without "Drop support for EOL Python 3.9" change to allow
+ downstream projects still on Python 3.9 to avail of the security fix for CVE-2025-66034 (#3994, #3999).
+
4.61.0 (released 2025-11-28)
----------------------------
- [varLib.main]: **SECURITY** Only use basename(vf.filename) to prevent path traversal attacks when
- running `fonttools varLib` command. Fixes CVE-2025-66034, see:
+ running ``fonttools varLib`` command, or code which invokes ``fonttools.varLib.main()``.
+ Fixes CVE-2025-66034, see:
https://github.com/fonttools/fonttools/security/advisories/GHSA-768j-98cg-p3fv.
- [feaLib] Sort BaseLangSysRecords by tag (#3986).
- Drop support for EOL Python 3.9 (#3982).
diff --git a/contrib/python/fonttools/fontTools/__init__.py b/contrib/python/fonttools/fontTools/__init__.py
index 19dc53b5b65..953cda045ee 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.61.0"
+version = __version__ = "4.61.1"
__all__ = ["version", "log", "configLogger"]
diff --git a/contrib/python/fonttools/fontTools/designspaceLib/__init__.py b/contrib/python/fonttools/fontTools/designspaceLib/__init__.py
index 0996e7b69e9..4782041b43a 100644
--- a/contrib/python/fonttools/fontTools/designspaceLib/__init__.py
+++ b/contrib/python/fonttools/fontTools/designspaceLib/__init__.py
@@ -1559,7 +1559,6 @@ class BaseDocWriter(object):
return ("%f" % num).rstrip("0").rstrip(".")
def _addRule(self, ruleObject):
- # if none of the conditions have minimum or maximum values, do not add the rule.
ruleElement = ET.Element("rule")
if ruleObject.name is not None:
ruleElement.attrib["name"] = ruleObject.name
@@ -1580,8 +1579,9 @@ class BaseDocWriter(object):
cond.get("maximum")
)
conditionsetElement.append(conditionElement)
- if len(conditionsetElement):
- ruleElement.append(conditionsetElement)
+ # Serialize the conditionset even if it is empty, as this is the
+ # canonical way of defining a rule that is always true.
+ ruleElement.append(conditionsetElement)
for sub in ruleObject.subs:
subElement = ET.Element("sub")
subElement.attrib["name"] = sub[0]
diff --git a/contrib/python/fonttools/fontTools/fontBuilder.py b/contrib/python/fonttools/fontTools/fontBuilder.py
index f8da717babb..b2b93f6f66f 100644
--- a/contrib/python/fonttools/fontTools/fontBuilder.py
+++ b/contrib/python/fonttools/fontTools/fontBuilder.py
@@ -264,49 +264,49 @@ _nameIDs = dict(
# to insert in setupNameTable doc string:
# print("\n".join(("%s (nameID %s)" % (k, v)) for k, v in sorted(_nameIDs.items(), key=lambda x: x[1])))
-_panoseDefaults = Panose()
-_OS2Defaults = dict(
- version=3,
- xAvgCharWidth=0,
- usWeightClass=400,
- usWidthClass=5,
- fsType=0x0004, # default: Preview & Print embedding
- ySubscriptXSize=0,
- ySubscriptYSize=0,
- ySubscriptXOffset=0,
- ySubscriptYOffset=0,
- ySuperscriptXSize=0,
- ySuperscriptYSize=0,
- ySuperscriptXOffset=0,
- ySuperscriptYOffset=0,
- yStrikeoutSize=0,
- yStrikeoutPosition=0,
- sFamilyClass=0,
- panose=_panoseDefaults,
- ulUnicodeRange1=0,
- ulUnicodeRange2=0,
- ulUnicodeRange3=0,
- ulUnicodeRange4=0,
- achVendID="????",
- fsSelection=0,
- usFirstCharIndex=0,
- usLastCharIndex=0,
- sTypoAscender=0,
- sTypoDescender=0,
- sTypoLineGap=0,
- usWinAscent=0,
- usWinDescent=0,
- ulCodePageRange1=0,
- ulCodePageRange2=0,
- sxHeight=0,
- sCapHeight=0,
- usDefaultChar=0, # .notdef
- usBreakChar=32, # space
- usMaxContext=0,
- usLowerOpticalPointSize=0,
- usUpperOpticalPointSize=0,
-)
+def _getOS2Defaults():
+ return dict(
+ version=3,
+ xAvgCharWidth=0,
+ usWeightClass=400,
+ usWidthClass=5,
+ fsType=0x0004, # default: Preview & Print embedding
+ ySubscriptXSize=0,
+ ySubscriptYSize=0,
+ ySubscriptXOffset=0,
+ ySubscriptYOffset=0,
+ ySuperscriptXSize=0,
+ ySuperscriptYSize=0,
+ ySuperscriptXOffset=0,
+ ySuperscriptYOffset=0,
+ yStrikeoutSize=0,
+ yStrikeoutPosition=0,
+ sFamilyClass=0,
+ panose=Panose(),
+ ulUnicodeRange1=0,
+ ulUnicodeRange2=0,
+ ulUnicodeRange3=0,
+ ulUnicodeRange4=0,
+ achVendID="????",
+ fsSelection=0,
+ usFirstCharIndex=0,
+ usLastCharIndex=0,
+ sTypoAscender=0,
+ sTypoDescender=0,
+ sTypoLineGap=0,
+ usWinAscent=0,
+ usWinDescent=0,
+ ulCodePageRange1=0,
+ ulCodePageRange2=0,
+ sxHeight=0,
+ sCapHeight=0,
+ usDefaultChar=0, # .notdef
+ usBreakChar=32, # space
+ usMaxContext=0,
+ usLowerOpticalPointSize=0,
+ usUpperOpticalPointSize=0,
+ )
class FontBuilder(object):
@@ -493,7 +493,7 @@ class FontBuilder(object):
"""Create a new `OS/2` table and initialize it with default values,
which can be overridden by keyword arguments.
"""
- self._initTableWithValues("OS/2", _OS2Defaults, values)
+ self._initTableWithValues("OS/2", _getOS2Defaults(), values)
if "xAvgCharWidth" not in values:
assert (
"hmtx" in self.font
diff --git a/contrib/python/fonttools/fontTools/misc/configTools.py b/contrib/python/fonttools/fontTools/misc/configTools.py
index 7eb1854fdf3..c928840a515 100644
--- a/contrib/python/fonttools/fontTools/misc/configTools.py
+++ b/contrib/python/fonttools/fontTools/misc/configTools.py
@@ -224,7 +224,9 @@ class AbstractConfig(MutableMapping):
def __init__(
self,
- values: Union[AbstractConfig, Dict[Union[Option, str], Any]] = {},
+ values: Union[
+ AbstractConfig, Dict[Union[Option, str], Any], Mapping[str, Any]
+ ] = {},
parse_values: bool = False,
skip_unknown: bool = False,
):
diff --git a/contrib/python/fonttools/fontTools/misc/fixedTools.py b/contrib/python/fonttools/fontTools/misc/fixedTools.py
index 330042871c5..b7086c8ac1e 100644
--- a/contrib/python/fonttools/fontTools/misc/fixedTools.py
+++ b/contrib/python/fonttools/fontTools/misc/fixedTools.py
@@ -39,7 +39,7 @@ __all__ = [
MAX_F2DOT14 = 0x7FFF / (1 << 14)
-def fixedToFloat(value, precisionBits):
+def fixedToFloat(value: float, precisionBits: int) -> float:
"""Converts a fixed-point number to a float given the number of
precision bits.
diff --git a/contrib/python/fonttools/fontTools/misc/xmlWriter.py b/contrib/python/fonttools/fontTools/misc/xmlWriter.py
index 240d762aec2..0553e885e74 100644
--- a/contrib/python/fonttools/fontTools/misc/xmlWriter.py
+++ b/contrib/python/fonttools/fontTools/misc/xmlWriter.py
@@ -1,5 +1,8 @@
"""xmlWriter.py -- Simple XML authoring class"""
+from __future__ import annotations
+
+from typing import BinaryIO, Callable, TextIO
from fontTools.misc.textTools import byteord, strjoin, tobytes, tostr
import sys
import os
@@ -25,17 +28,22 @@ ILLEGAL_XML_CHARS = dict.fromkeys(
class XMLWriter(object):
def __init__(
self,
- fileOrPath,
- indentwhite=INDENT,
- idlefunc=None,
- encoding="utf_8",
- newlinestr="\n",
- ):
+ fileOrPath: str | os.PathLike[str] | BinaryIO | TextIO,
+ indentwhite: str = INDENT,
+ idlefunc: Callable[[], None] | None = None,
+ encoding: str = "utf_8",
+ newlinestr: str | bytes = "\n",
+ ) -> None:
if encoding.lower().replace("-", "").replace("_", "") != "utf8":
raise Exception("Only UTF-8 encoding is supported.")
if fileOrPath == "-":
fileOrPath = sys.stdout
+ self.filename: str | os.PathLike[str] | None
if not hasattr(fileOrPath, "write"):
+ if not isinstance(fileOrPath, (str, os.PathLike)):
+ raise TypeError(
+ "fileOrPath must be a file path (str or PathLike) if it isn't an object with a `write` method."
+ )
self.filename = fileOrPath
self.file = open(fileOrPath, "wb")
self._closeStream = True
@@ -74,8 +82,9 @@ class XMLWriter(object):
def __exit__(self, exception_type, exception_value, traceback):
self.close()
- def close(self):
+ def close(self) -> None:
if self._closeStream:
+ assert not isinstance(self.file, (str, os.PathLike))
self.file.close()
def write(self, string, indent=True):
@@ -196,7 +205,7 @@ def escape(data):
if len(data) > maxLen:
preview = repr(data[:maxLen])[1:-1] + "..."
TTX_LOG.warning(
- "Illegal XML character(s) found; replacing offending " "string %r with %r",
+ "Illegal XML character(s) found; replacing offending string %r with %r",
preview,
REPLACEMENT,
)
diff --git a/contrib/python/fonttools/fontTools/otlLib/builder.py b/contrib/python/fonttools/fontTools/otlLib/builder.py
index c4b482f02b4..7e9b707bb33 100644
--- a/contrib/python/fonttools/fontTools/otlLib/builder.py
+++ b/contrib/python/fonttools/fontTools/otlLib/builder.py
@@ -53,18 +53,21 @@ def buildCoverage(glyphs, glyphMap):
``font.getReverseGlyphMap()``.
Returns:
- An ``otTables.Coverage`` object or ``None`` if there are no glyphs
- supplied.
+ An ``otTables.Coverage`` object (empty if no glyphs supplied).
"""
-
- if not glyphs:
- return None
+ # Per the OpenType spec: "For cases in which subtable offset fields are not
+ # documented as permitting NULL values, font compilers must include a subtable
+ # of the indicated format, even if it is a header stub without further data
+ # (for example, a coverage table with no glyph IDs)."
+ # https://github.com/fonttools/fonttools/issues/4003
self = ot.Coverage()
- try:
- self.glyphs = sorted(set(glyphs), key=glyphMap.__getitem__)
- except KeyError as e:
- raise ValueError(f"Could not find glyph {e} in font") from e
-
+ if glyphs:
+ try:
+ self.glyphs = sorted(set(glyphs), key=glyphMap.__getitem__)
+ except KeyError as e:
+ raise ValueError(f"Could not find glyph {e} in font") from e
+ else:
+ self.glyphs = []
return self
diff --git a/contrib/python/fonttools/fontTools/ttLib/sfnt.py b/contrib/python/fonttools/fontTools/ttLib/sfnt.py
index a9d07e615de..d0ed17ae294 100644
--- a/contrib/python/fonttools/fontTools/ttLib/sfnt.py
+++ b/contrib/python/fonttools/fontTools/ttLib/sfnt.py
@@ -13,6 +13,9 @@ classes, since whenever the number of tables changes or whenever
a table's length changes you need to rewrite the whole file anyway.
"""
+from __future__ import annotations
+
+from collections.abc import KeysView
from io import BytesIO
from types import SimpleNamespace
from fontTools.misc.textTools import Tag
@@ -84,7 +87,7 @@ class SFNTReader(object):
if self.sfntVersion not in ("\x00\x01\x00\x00", "OTTO", "true"):
raise TTLibError("Not a TrueType or OpenType font (bad sfntVersion)")
- tables = {}
+ tables: dict[Tag, DirectoryEntry] = {}
for i in range(self.numTables):
entry = self.DirectoryEntry()
entry.fromFile(self.file)
@@ -96,15 +99,15 @@ class SFNTReader(object):
if self.flavor == "woff":
self.flavorData = WOFFFlavorData(self)
- def has_key(self, tag):
+ def has_key(self, tag: str | bytes) -> bool:
return tag in self.tables
__contains__ = has_key
- def keys(self):
+ def keys(self) -> KeysView[Tag]:
return self.tables.keys()
- def __getitem__(self, tag):
+ def __getitem__(self, tag: str | bytes) -> bytes:
"""Fetch the raw table data."""
entry = self.tables[Tag(tag)]
data = entry.loadData(self.file)
@@ -122,10 +125,10 @@ class SFNTReader(object):
log.warning("bad checksum for '%s' table", tag)
return data
- def __delitem__(self, tag):
+ def __delitem__(self, tag: str | bytes) -> None:
del self.tables[Tag(tag)]
- def close(self):
+ def close(self) -> None:
self.file.close()
# We define custom __getstate__ and __setstate__ to make SFNTReader pickle-able
diff --git a/contrib/python/fonttools/fontTools/ttLib/tables/_a_v_a_r.py b/contrib/python/fonttools/fontTools/ttLib/tables/_a_v_a_r.py
index 8b54b47d7e6..cb4593712c9 100644
--- a/contrib/python/fonttools/fontTools/ttLib/tables/_a_v_a_r.py
+++ b/contrib/python/fonttools/fontTools/ttLib/tables/_a_v_a_r.py
@@ -1,3 +1,6 @@
+from __future__ import annotations
+
+from typing import Mapping, TYPE_CHECKING
from fontTools.misc import sstruct
from fontTools.misc.fixedTools import (
fixedToFloat as fi2fl,
@@ -20,6 +23,9 @@ log = logging.getLogger(__name__)
from .otBase import BaseTTXConverter
+if TYPE_CHECKING:
+ from ..ttFont import TTFont
+
class table__a_v_a_r(BaseTTXConverter):
"""Axis Variations table
@@ -143,8 +149,9 @@ class table__a_v_a_r(BaseTTXConverter):
else:
super().fromXML(name, attrs, content, ttFont)
- def renormalizeLocation(self, location, font, dropZeroes=True):
-
+ def renormalizeLocation(
+ self, location: Mapping[str, float], font: TTFont, dropZeroes: bool = True
+ ) -> dict[str, float]:
majorVersion = getattr(self, "majorVersion", 1)
if majorVersion not in (1, 2):
diff --git a/contrib/python/fonttools/fontTools/ttLib/ttFont.py b/contrib/python/fonttools/fontTools/ttLib/ttFont.py
index 2b3338e5784..4407e567a36 100644
--- a/contrib/python/fonttools/fontTools/ttLib/ttFont.py
+++ b/contrib/python/fonttools/fontTools/ttLib/ttFont.py
@@ -1,24 +1,130 @@
+from __future__ import annotations
+
+import logging
+import os
+import traceback
+from io import BytesIO, StringIO, UnsupportedOperation
+from typing import TYPE_CHECKING, TypedDict, TypeVar, overload
+
from fontTools.config import Config
from fontTools.misc import xmlWriter
from fontTools.misc.configTools import AbstractConfig
-from fontTools.misc.textTools import Tag, byteord, tostr
from fontTools.misc.loggingTools import deprecateArgument
+from fontTools.misc.textTools import Tag, byteord, tostr
from fontTools.ttLib import TTLibError
+from fontTools.ttLib.sfnt import SFNTReader, SFNTWriter
from fontTools.ttLib.ttGlyphSet import (
- _TTGlyph,
+ _TTGlyph, # noqa: F401
+ _TTGlyphSet,
_TTGlyphSetCFF,
_TTGlyphSetGlyf,
_TTGlyphSetVARC,
)
-from fontTools.ttLib.sfnt import SFNTReader, SFNTWriter
-from io import BytesIO, StringIO, UnsupportedOperation
-import os
-import logging
-import traceback
+
+if TYPE_CHECKING:
+ from collections.abc import Mapping, MutableMapping
+ from types import ModuleType, TracebackType
+ from typing import Any, BinaryIO, Literal, Sequence, TextIO
+
+ from typing_extensions import Self, Unpack
+
+ from fontTools.ttLib.tables import (
+ B_A_S_E_,
+ C_B_D_T_,
+ C_B_L_C_,
+ C_F_F_,
+ C_F_F__2,
+ C_O_L_R_,
+ C_P_A_L_,
+ D_S_I_G_,
+ E_B_D_T_,
+ E_B_L_C_,
+ F_F_T_M_,
+ G_D_E_F_,
+ G_M_A_P_,
+ G_P_K_G_,
+ G_P_O_S_,
+ G_S_U_B_,
+ G_V_A_R_,
+ H_V_A_R_,
+ J_S_T_F_,
+ L_T_S_H_,
+ M_A_T_H_,
+ M_E_T_A_,
+ M_V_A_R_,
+ S_I_N_G_,
+ S_T_A_T_,
+ S_V_G_,
+ T_S_I__0,
+ T_S_I__1,
+ T_S_I__2,
+ T_S_I__3,
+ T_S_I__5,
+ T_S_I_B_,
+ T_S_I_C_,
+ T_S_I_D_,
+ T_S_I_J_,
+ T_S_I_P_,
+ T_S_I_S_,
+ T_S_I_V_,
+ T_T_F_A_,
+ V_A_R_C_,
+ V_D_M_X_,
+ V_O_R_G_,
+ V_V_A_R_,
+ D__e_b_g,
+ F__e_a_t,
+ G__l_a_t,
+ G__l_o_c,
+ O_S_2f_2,
+ S__i_l_f,
+ S__i_l_l,
+ _a_n_k_r,
+ _a_v_a_r,
+ _b_s_l_n,
+ _c_i_d_g,
+ _c_m_a_p,
+ _c_v_a_r,
+ _c_v_t,
+ _f_e_a_t,
+ _f_p_g_m,
+ _f_v_a_r,
+ _g_a_s_p,
+ _g_c_i_d,
+ _g_l_y_f,
+ _g_v_a_r,
+ _h_d_m_x,
+ _h_e_a_d,
+ _h_h_e_a,
+ _h_m_t_x,
+ _k_e_r_n,
+ _l_c_a_r,
+ _l_o_c_a,
+ _l_t_a_g,
+ _m_a_x_p,
+ _m_e_t_a,
+ _m_o_r_t,
+ _m_o_r_x,
+ _n_a_m_e,
+ _o_p_b_d,
+ _p_o_s_t,
+ _p_r_e_p,
+ _p_r_o_p,
+ _s_b_i_x,
+ _t_r_a_k,
+ _v_h_e_a,
+ _v_m_t_x,
+ )
+ from fontTools.ttLib.tables.DefaultTable import DefaultTable
+
+ _VT_co = TypeVar("_VT_co", covariant=True) # Value type covariant containers.
log = logging.getLogger(__name__)
+_NumberT = TypeVar("_NumberT", bound=float)
+
+
class TTFont(object):
"""Represents a TrueType font.
@@ -103,24 +209,44 @@ class TTFont(object):
The default is ``lazy=None`` which is somewhere in between.
"""
+ tables: dict[Tag, DefaultTable | GlyphOrder]
+ reader: SFNTReader | None
+ sfntVersion: str
+ flavor: str | None
+ flavorData: Any | None
+ lazy: bool | None
+ recalcBBoxes: bool
+ recalcTimestamp: bool
+ ignoreDecompileErrors: bool
+ cfg: AbstractConfig
+ glyphOrder: list[str]
+ _reverseGlyphOrderDict: dict[str, int]
+ _tableCache: MutableMapping[tuple[Tag, bytes], DefaultTable] | None
+ disassembleInstructions: bool
+ bitmapGlyphDataFormat: str
+ # Deprecated attributes
+ verbose: bool | None
+ quiet: bool | None
+
def __init__(
self,
- file=None,
- res_name_or_index=None,
- sfntVersion="\000\001\000\000",
- flavor=None,
- checkChecksums=0,
- verbose=None,
- recalcBBoxes=True,
- allowVID=NotImplemented,
- ignoreDecompileErrors=False,
- recalcTimestamp=True,
- fontNumber=-1,
- lazy=None,
- quiet=None,
- _tableCache=None,
- cfg={},
- ):
+ file: str | os.PathLike[str] | BinaryIO | None = None,
+ res_name_or_index: str | int | None = None,
+ sfntVersion: str = "\000\001\000\000",
+ flavor: str | None = None,
+ checkChecksums: int = 0,
+ verbose: bool | None = None, # Deprecated
+ recalcBBoxes: bool = True,
+ allowVID: Any = NotImplemented, # Deprecated/Unused
+ ignoreDecompileErrors: bool = False,
+ recalcTimestamp: bool = True,
+ fontNumber: int = -1,
+ lazy: bool | None = None,
+ quiet: bool | None = None, # Deprecated
+ _tableCache: MutableMapping[tuple[Tag, bytes], DefaultTable] | None = None,
+ cfg: Mapping[str, Any] | AbstractConfig = {},
+ ) -> None:
+ # Set deprecated attributes
for name in ("verbose", "quiet"):
val = locals().get(name)
if val is not None:
@@ -142,6 +268,10 @@ class TTFont(object):
return
seekable = True
if not hasattr(file, "read"):
+ if not isinstance(file, (str, os.PathLike)):
+ raise TypeError(
+ "fileOrPath must be a file path (str or PathLike) if it isn't an object with a `read` method."
+ )
closeStream = True
# assume file is a string
if res_name_or_index is not None:
@@ -160,6 +290,7 @@ class TTFont(object):
file = open(file, "rb")
else:
# assume "file" is a readable file object
+ assert not isinstance(file, (str, os.PathLike))
closeStream = False
# SFNTReader wants the input file to be seekable.
# SpooledTemporaryFile has no seekable() on < 3.11, but still can seek:
@@ -191,23 +322,31 @@ class TTFont(object):
self.flavor = self.reader.flavor
self.flavorData = self.reader.flavorData
- def __enter__(self):
+ def __enter__(self) -> Self:
return self
- def __exit__(self, type, value, traceback):
+ def __exit__(
+ self,
+ exc_type: type[BaseException] | None,
+ exc_value: BaseException | None,
+ traceback: TracebackType | None,
+ ) -> None:
self.close()
- def close(self):
+ def close(self) -> None:
"""If we still have a reader object, close it."""
if self.reader is not None:
self.reader.close()
+ self.reader = None
- def save(self, file, reorderTables=True):
+ def save(
+ self, file: str | os.PathLike[str] | BinaryIO, reorderTables: bool | None = True
+ ) -> None:
"""Save the font to disk.
Args:
file: Similarly to the constructor, can be either a pathname or a writable
- file object.
+ binary file object.
reorderTables (Option[bool]): If true (the default), reorder the tables,
sorting them by tag (recommended by the OpenType specification). If
false, retain the original font order. If None, reorder by table
@@ -232,6 +371,10 @@ class TTFont(object):
):
if reorderTables is False:
# sort tables using the original font's order
+ if self.reader is None:
+ raise TTLibError(
+ "The original table order is unavailable because there isn't a font to read it from."
+ )
tableOrder = list(self.reader.keys())
else:
# use the recommended order from the OpenType specification
@@ -244,20 +387,25 @@ class TTFont(object):
if createStream:
# "file" is a path
+ assert isinstance(file, (str, os.PathLike))
with open(file, "wb") as file:
file.write(tmp.getvalue())
else:
+ assert not isinstance(file, (str, os.PathLike))
file.write(tmp.getvalue())
tmp.close()
- def _save(self, file, tableCache=None):
+ def _save(
+ self,
+ file: BinaryIO,
+ tableCache: MutableMapping[tuple[Tag, bytes], Any] | None = None,
+ ) -> bool:
"""Internal function, to be shared by save() and TTCollection.save()"""
if self.recalcTimestamp and "head" in self:
- self[
- "head"
- ] # make sure 'head' is loaded so the recalculation is actually done
+ # make sure 'head' is loaded so the recalculation is actually done
+ self["head"]
tags = self.keys()
tags.pop(0) # skip GlyphOrder tag
@@ -275,7 +423,22 @@ class TTFont(object):
return writer.reordersTables()
- def saveXML(self, fileOrPath, newlinestr="\n", **kwargs):
+ class XMLSavingOptions(TypedDict):
+ writeVersion: bool
+ quiet: bool | None
+ tables: Sequence[str | bytes] | None
+ skipTables: Sequence[str] | None
+ splitTables: bool
+ splitGlyphs: bool
+ disassembleInstructions: bool
+ bitmapGlyphDataFormat: str
+
+ def saveXML(
+ self,
+ fileOrPath: str | os.PathLike[str] | BinaryIO | TextIO,
+ newlinestr: str = "\n",
+ **kwargs: Unpack[XMLSavingOptions],
+ ) -> None:
"""Export the font as TTX (an XML-based text file), or as a series of text
files when splitTables is true. In the latter case, the 'fileOrPath'
argument should be a path to a directory.
@@ -290,16 +453,16 @@ class TTFont(object):
def _saveXML(
self,
- writer,
- writeVersion=True,
- quiet=None,
- tables=None,
- skipTables=None,
- splitTables=False,
- splitGlyphs=False,
- disassembleInstructions=True,
- bitmapGlyphDataFormat="raw",
- ):
+ writer: xmlWriter.XMLWriter,
+ writeVersion: bool = True,
+ quiet: bool | None = None, # Deprecated
+ tables: Sequence[str | bytes] | None = None,
+ skipTables: Sequence[str] | None = None,
+ splitTables: bool = False,
+ splitGlyphs: bool = False,
+ disassembleInstructions: bool = True,
+ bitmapGlyphDataFormat: str = "raw",
+ ) -> None:
if quiet is not None:
deprecateArgument("quiet", "configure logging instead")
@@ -329,6 +492,10 @@ class TTFont(object):
if not splitTables:
writer.newline()
else:
+ if writer.filename is None:
+ raise TTLibError(
+ "splitTables requires the file name to be a file system path, not a stream."
+ )
path, ext = os.path.splitext(writer.filename)
for tag in tables:
@@ -352,7 +519,13 @@ class TTFont(object):
writer.endtag("ttFont")
writer.newline()
- def _tableToXML(self, writer, tag, quiet=None, splitGlyphs=False):
+ def _tableToXML(
+ self,
+ writer: xmlWriter.XMLWriter,
+ tag: str | bytes,
+ quiet: bool | None = None,
+ splitGlyphs: bool = False,
+ ) -> None:
if quiet is not None:
deprecateArgument("quiet", "configure logging instead")
if tag in self:
@@ -364,7 +537,7 @@ class TTFont(object):
if tag not in self:
return
xmlTag = tagToXML(tag)
- attrs = dict()
+ attrs: dict[str, Any] = {}
if hasattr(table, "ERROR"):
attrs["ERROR"] = "decompilation error"
from .tables.DefaultTable import DefaultTable
@@ -381,7 +554,9 @@ class TTFont(object):
writer.newline()
writer.newline()
- def importXML(self, fileOrPath, quiet=None):
+ def importXML(
+ self, fileOrPath: str | os.PathLike[str] | BinaryIO, quiet: bool | None = None
+ ) -> None:
"""Import a TTX file (an XML-based text format), so as to recreate
a font object.
"""
@@ -400,12 +575,12 @@ class TTFont(object):
reader = xmlReader.XMLReader(fileOrPath, self)
reader.read()
- def isLoaded(self, tag):
+ def isLoaded(self, tag: str | bytes) -> bool:
"""Return true if the table identified by ``tag`` has been
decompiled and loaded into memory."""
return tag in self.tables
- def has_key(self, tag):
+ def has_key(self, tag: str | bytes) -> bool:
"""Test if the table identified by ``tag`` is present in the font.
As well as this method, ``tag in font`` can also be used to determine the
@@ -421,7 +596,7 @@ class TTFont(object):
__contains__ = has_key
- def keys(self):
+ def keys(self) -> list[str]:
"""Returns the list of tables in the font, along with the ``GlyphOrder`` pseudo-table."""
keys = list(self.tables.keys())
if self.reader:
@@ -434,7 +609,7 @@ class TTFont(object):
keys = sortedTagList(keys)
return ["GlyphOrder"] + keys
- def ensureDecompiled(self, recurse=None):
+ def ensureDecompiled(self, recurse: bool | None = None) -> None:
"""Decompile all the tables, even if a TTFont was opened in 'lazy' mode."""
for tag in self.keys():
table = self[tag]
@@ -444,10 +619,185 @@ class TTFont(object):
table.ensureDecompiled(recurse=recurse)
self.lazy = False
- def __len__(self):
+ def __len__(self) -> int:
return len(list(self.keys()))
- def __getitem__(self, tag):
+ @overload
+ def __getitem__(self, tag: Literal["BASE"]) -> B_A_S_E_.table_B_A_S_E_: ...
+ @overload
+ def __getitem__(self, tag: Literal["CBDT"]) -> C_B_D_T_.table_C_B_D_T_: ...
+ @overload
+ def __getitem__(self, tag: Literal["CBLC"]) -> C_B_L_C_.table_C_B_L_C_: ...
+ @overload
+ def __getitem__(self, tag: Literal["CFF "]) -> C_F_F_.table_C_F_F_: ...
+ @overload
+ def __getitem__(self, tag: Literal["CFF2"]) -> C_F_F__2.table_C_F_F__2: ...
+ @overload
+ def __getitem__(self, tag: Literal["COLR"]) -> C_O_L_R_.table_C_O_L_R_: ...
+ @overload
+ def __getitem__(self, tag: Literal["CPAL"]) -> C_P_A_L_.table_C_P_A_L_: ...
+ @overload
+ def __getitem__(self, tag: Literal["DSIG"]) -> D_S_I_G_.table_D_S_I_G_: ...
+ @overload
+ def __getitem__(self, tag: Literal["EBDT"]) -> E_B_D_T_.table_E_B_D_T_: ...
+ @overload
+ def __getitem__(self, tag: Literal["EBLC"]) -> E_B_L_C_.table_E_B_L_C_: ...
+ @overload
+ def __getitem__(self, tag: Literal["FFTM"]) -> F_F_T_M_.table_F_F_T_M_: ...
+ @overload
+ def __getitem__(self, tag: Literal["GDEF"]) -> G_D_E_F_.table_G_D_E_F_: ...
+ @overload
+ def __getitem__(self, tag: Literal["GMAP"]) -> G_M_A_P_.table_G_M_A_P_: ...
+ @overload
+ def __getitem__(self, tag: Literal["GPKG"]) -> G_P_K_G_.table_G_P_K_G_: ...
+ @overload
+ def __getitem__(self, tag: Literal["GPOS"]) -> G_P_O_S_.table_G_P_O_S_: ...
+ @overload
+ def __getitem__(self, tag: Literal["GSUB"]) -> G_S_U_B_.table_G_S_U_B_: ...
+ @overload
+ def __getitem__(self, tag: Literal["GVAR"]) -> G_V_A_R_.table_G_V_A_R_: ...
+ @overload
+ def __getitem__(self, tag: Literal["HVAR"]) -> H_V_A_R_.table_H_V_A_R_: ...
+ @overload
+ def __getitem__(self, tag: Literal["JSTF"]) -> J_S_T_F_.table_J_S_T_F_: ...
+ @overload
+ def __getitem__(self, tag: Literal["LTSH"]) -> L_T_S_H_.table_L_T_S_H_: ...
+ @overload
+ def __getitem__(self, tag: Literal["MATH"]) -> M_A_T_H_.table_M_A_T_H_: ...
+ @overload
+ def __getitem__(self, tag: Literal["META"]) -> M_E_T_A_.table_M_E_T_A_: ...
+ @overload
+ def __getitem__(self, tag: Literal["MVAR"]) -> M_V_A_R_.table_M_V_A_R_: ...
+ @overload
+ def __getitem__(self, tag: Literal["SING"]) -> S_I_N_G_.table_S_I_N_G_: ...
+ @overload
+ def __getitem__(self, tag: Literal["STAT"]) -> S_T_A_T_.table_S_T_A_T_: ...
+ @overload
+ def __getitem__(self, tag: Literal["SVG "]) -> S_V_G_.table_S_V_G_: ...
+ @overload
+ def __getitem__(self, tag: Literal["TSI0"]) -> T_S_I__0.table_T_S_I__0: ...
+ @overload
+ def __getitem__(self, tag: Literal["TSI1"]) -> T_S_I__1.table_T_S_I__1: ...
+ @overload
+ def __getitem__(self, tag: Literal["TSI2"]) -> T_S_I__2.table_T_S_I__2: ...
+ @overload
+ def __getitem__(self, tag: Literal["TSI3"]) -> T_S_I__3.table_T_S_I__3: ...
+ @overload
+ def __getitem__(self, tag: Literal["TSI5"]) -> T_S_I__5.table_T_S_I__5: ...
+ @overload
+ def __getitem__(self, tag: Literal["TSIB"]) -> T_S_I_B_.table_T_S_I_B_: ...
+ @overload
+ def __getitem__(self, tag: Literal["TSIC"]) -> T_S_I_C_.table_T_S_I_C_: ...
+ @overload
+ def __getitem__(self, tag: Literal["TSID"]) -> T_S_I_D_.table_T_S_I_D_: ...
+ @overload
+ def __getitem__(self, tag: Literal["TSIJ"]) -> T_S_I_J_.table_T_S_I_J_: ...
+ @overload
+ def __getitem__(self, tag: Literal["TSIP"]) -> T_S_I_P_.table_T_S_I_P_: ...
+ @overload
+ def __getitem__(self, tag: Literal["TSIS"]) -> T_S_I_S_.table_T_S_I_S_: ...
+ @overload
+ def __getitem__(self, tag: Literal["TSIV"]) -> T_S_I_V_.table_T_S_I_V_: ...
+ @overload
+ def __getitem__(self, tag: Literal["TTFA"]) -> T_T_F_A_.table_T_T_F_A_: ...
+ @overload
+ def __getitem__(self, tag: Literal["VARC"]) -> V_A_R_C_.table_V_A_R_C_: ...
+ @overload
+ def __getitem__(self, tag: Literal["VDMX"]) -> V_D_M_X_.table_V_D_M_X_: ...
+ @overload
+ def __getitem__(self, tag: Literal["VORG"]) -> V_O_R_G_.table_V_O_R_G_: ...
+ @overload
+ def __getitem__(self, tag: Literal["VVAR"]) -> V_V_A_R_.table_V_V_A_R_: ...
+ @overload
+ def __getitem__(self, tag: Literal["Debg"]) -> D__e_b_g.table_D__e_b_g: ...
+ @overload
+ def __getitem__(self, tag: Literal["Feat"]) -> F__e_a_t.table_F__e_a_t: ...
+ @overload
+ def __getitem__(self, tag: Literal["Glat"]) -> G__l_a_t.table_G__l_a_t: ...
+ @overload
+ def __getitem__(self, tag: Literal["Gloc"]) -> G__l_o_c.table_G__l_o_c: ...
+ @overload
+ def __getitem__(self, tag: Literal["OS/2"]) -> O_S_2f_2.table_O_S_2f_2: ...
+ @overload
+ def __getitem__(self, tag: Literal["Silf"]) -> S__i_l_f.table_S__i_l_f: ...
+ @overload
+ def __getitem__(self, tag: Literal["Sill"]) -> S__i_l_l.table_S__i_l_l: ...
+ @overload
+ def __getitem__(self, tag: Literal["ankr"]) -> _a_n_k_r.table__a_n_k_r: ...
+ @overload
+ def __getitem__(self, tag: Literal["avar"]) -> _a_v_a_r.table__a_v_a_r: ...
+ @overload
+ def __getitem__(self, tag: Literal["bsln"]) -> _b_s_l_n.table__b_s_l_n: ...
+ @overload
+ def __getitem__(self, tag: Literal["cidg"]) -> _c_i_d_g.table__c_i_d_g: ...
+ @overload
+ def __getitem__(self, tag: Literal["cmap"]) -> _c_m_a_p.table__c_m_a_p: ...
+ @overload
+ def __getitem__(self, tag: Literal["cvar"]) -> _c_v_a_r.table__c_v_a_r: ...
+ @overload
+ def __getitem__(self, tag: Literal["cvt "]) -> _c_v_t.table__c_v_t: ...
+ @overload
+ def __getitem__(self, tag: Literal["feat"]) -> _f_e_a_t.table__f_e_a_t: ...
+ @overload
+ def __getitem__(self, tag: Literal["fpgm"]) -> _f_p_g_m.table__f_p_g_m: ...
+ @overload
+ def __getitem__(self, tag: Literal["fvar"]) -> _f_v_a_r.table__f_v_a_r: ...
+ @overload
+ def __getitem__(self, tag: Literal["gasp"]) -> _g_a_s_p.table__g_a_s_p: ...
+ @overload
+ def __getitem__(self, tag: Literal["gcid"]) -> _g_c_i_d.table__g_c_i_d: ...
+ @overload
+ def __getitem__(self, tag: Literal["glyf"]) -> _g_l_y_f.table__g_l_y_f: ...
+ @overload
+ def __getitem__(self, tag: Literal["gvar"]) -> _g_v_a_r.table__g_v_a_r: ...
+ @overload
+ def __getitem__(self, tag: Literal["hdmx"]) -> _h_d_m_x.table__h_d_m_x: ...
+ @overload
+ def __getitem__(self, tag: Literal["head"]) -> _h_e_a_d.table__h_e_a_d: ...
+ @overload
+ def __getitem__(self, tag: Literal["hhea"]) -> _h_h_e_a.table__h_h_e_a: ...
+ @overload
+ def __getitem__(self, tag: Literal["hmtx"]) -> _h_m_t_x.table__h_m_t_x: ...
+ @overload
+ def __getitem__(self, tag: Literal["kern"]) -> _k_e_r_n.table__k_e_r_n: ...
+ @overload
+ def __getitem__(self, tag: Literal["lcar"]) -> _l_c_a_r.table__l_c_a_r: ...
+ @overload
+ def __getitem__(self, tag: Literal["loca"]) -> _l_o_c_a.table__l_o_c_a: ...
+ @overload
+ def __getitem__(self, tag: Literal["ltag"]) -> _l_t_a_g.table__l_t_a_g: ...
+ @overload
+ def __getitem__(self, tag: Literal["maxp"]) -> _m_a_x_p.table__m_a_x_p: ...
+ @overload
+ def __getitem__(self, tag: Literal["meta"]) -> _m_e_t_a.table__m_e_t_a: ...
+ @overload
+ def __getitem__(self, tag: Literal["mort"]) -> _m_o_r_t.table__m_o_r_t: ...
+ @overload
+ def __getitem__(self, tag: Literal["morx"]) -> _m_o_r_x.table__m_o_r_x: ...
+ @overload
+ def __getitem__(self, tag: Literal["name"]) -> _n_a_m_e.table__n_a_m_e: ...
+ @overload
+ def __getitem__(self, tag: Literal["opbd"]) -> _o_p_b_d.table__o_p_b_d: ...
+ @overload
+ def __getitem__(self, tag: Literal["post"]) -> _p_o_s_t.table__p_o_s_t: ...
+ @overload
+ def __getitem__(self, tag: Literal["prep"]) -> _p_r_e_p.table__p_r_e_p: ...
+ @overload
+ def __getitem__(self, tag: Literal["prop"]) -> _p_r_o_p.table__p_r_o_p: ...
+ @overload
+ def __getitem__(self, tag: Literal["sbix"]) -> _s_b_i_x.table__s_b_i_x: ...
+ @overload
+ def __getitem__(self, tag: Literal["trak"]) -> _t_r_a_k.table__t_r_a_k: ...
+ @overload
+ def __getitem__(self, tag: Literal["vhea"]) -> _v_h_e_a.table__v_h_e_a: ...
+ @overload
+ def __getitem__(self, tag: Literal["vmtx"]) -> _v_m_t_x.table__v_m_t_x: ...
+ @overload
+ def __getitem__(self, tag: Literal["GlyphOrder"]) -> GlyphOrder: ...
+ @overload
+ def __getitem__(self, tag: str | bytes) -> DefaultTable | GlyphOrder: ...
+
+ def __getitem__(self, tag: str | bytes) -> DefaultTable | GlyphOrder:
tag = Tag(tag)
table = self.tables.get(tag)
if table is None:
@@ -460,8 +810,9 @@ class TTFont(object):
raise KeyError("'%s' table not found" % tag)
return table
- def _readTable(self, tag):
+ def _readTable(self, tag: Tag) -> DefaultTable:
log.debug("Reading '%s' table from disk", tag)
+ assert self.reader is not None
data = self.reader[tag]
if self._tableCache is not None:
table = self._tableCache.get((tag, data))
@@ -492,10 +843,10 @@ class TTFont(object):
self._tableCache[(tag, data)] = table
return table
- def __setitem__(self, tag, table):
+ def __setitem__(self, tag: str | bytes, table: DefaultTable) -> None:
self.tables[Tag(tag)] = table
- def __delitem__(self, tag):
+ def __delitem__(self, tag: str | bytes) -> None:
if tag not in self:
raise KeyError("'%s' table not found" % tag)
if tag in self.tables:
@@ -503,14 +854,195 @@ class TTFont(object):
if self.reader and tag in self.reader:
del self.reader[tag]
- def get(self, tag, default=None):
+ @overload
+ def get(self, tag: Literal["BASE"]) -> B_A_S_E_.table_B_A_S_E_ | None: ...
+ @overload
+ def get(self, tag: Literal["CBDT"]) -> C_B_D_T_.table_C_B_D_T_ | None: ...
+ @overload
+ def get(self, tag: Literal["CBLC"]) -> C_B_L_C_.table_C_B_L_C_ | None: ...
+ @overload
+ def get(self, tag: Literal["CFF "]) -> C_F_F_.table_C_F_F_ | None: ...
+ @overload
+ def get(self, tag: Literal["CFF2"]) -> C_F_F__2.table_C_F_F__2 | None: ...
+ @overload
+ def get(self, tag: Literal["COLR"]) -> C_O_L_R_.table_C_O_L_R_ | None: ...
+ @overload
+ def get(self, tag: Literal["CPAL"]) -> C_P_A_L_.table_C_P_A_L_ | None: ...
+ @overload
+ def get(self, tag: Literal["DSIG"]) -> D_S_I_G_.table_D_S_I_G_ | None: ...
+ @overload
+ def get(self, tag: Literal["EBDT"]) -> E_B_D_T_.table_E_B_D_T_ | None: ...
+ @overload
+ def get(self, tag: Literal["EBLC"]) -> E_B_L_C_.table_E_B_L_C_ | None: ...
+ @overload
+ def get(self, tag: Literal["FFTM"]) -> F_F_T_M_.table_F_F_T_M_ | None: ...
+ @overload
+ def get(self, tag: Literal["GDEF"]) -> G_D_E_F_.table_G_D_E_F_ | None: ...
+ @overload
+ def get(self, tag: Literal["GMAP"]) -> G_M_A_P_.table_G_M_A_P_ | None: ...
+ @overload
+ def get(self, tag: Literal["GPKG"]) -> G_P_K_G_.table_G_P_K_G_ | None: ...
+ @overload
+ def get(self, tag: Literal["GPOS"]) -> G_P_O_S_.table_G_P_O_S_ | None: ...
+ @overload
+ def get(self, tag: Literal["GSUB"]) -> G_S_U_B_.table_G_S_U_B_ | None: ...
+ @overload
+ def get(self, tag: Literal["GVAR"]) -> G_V_A_R_.table_G_V_A_R_ | None: ...
+ @overload
+ def get(self, tag: Literal["HVAR"]) -> H_V_A_R_.table_H_V_A_R_ | None: ...
+ @overload
+ def get(self, tag: Literal["JSTF"]) -> J_S_T_F_.table_J_S_T_F_ | None: ...
+ @overload
+ def get(self, tag: Literal["LTSH"]) -> L_T_S_H_.table_L_T_S_H_ | None: ...
+ @overload
+ def get(self, tag: Literal["MATH"]) -> M_A_T_H_.table_M_A_T_H_ | None: ...
+ @overload
+ def get(self, tag: Literal["META"]) -> M_E_T_A_.table_M_E_T_A_ | None: ...
+ @overload
+ def get(self, tag: Literal["MVAR"]) -> M_V_A_R_.table_M_V_A_R_ | None: ...
+ @overload
+ def get(self, tag: Literal["SING"]) -> S_I_N_G_.table_S_I_N_G_ | None: ...
+ @overload
+ def get(self, tag: Literal["STAT"]) -> S_T_A_T_.table_S_T_A_T_ | None: ...
+ @overload
+ def get(self, tag: Literal["SVG "]) -> S_V_G_.table_S_V_G_ | None: ...
+ @overload
+ def get(self, tag: Literal["TSI0"]) -> T_S_I__0.table_T_S_I__0 | None: ...
+ @overload
+ def get(self, tag: Literal["TSI1"]) -> T_S_I__1.table_T_S_I__1 | None: ...
+ @overload
+ def get(self, tag: Literal["TSI2"]) -> T_S_I__2.table_T_S_I__2 | None: ...
+ @overload
+ def get(self, tag: Literal["TSI3"]) -> T_S_I__3.table_T_S_I__3 | None: ...
+ @overload
+ def get(self, tag: Literal["TSI5"]) -> T_S_I__5.table_T_S_I__5 | None: ...
+ @overload
+ def get(self, tag: Literal["TSIB"]) -> T_S_I_B_.table_T_S_I_B_ | None: ...
+ @overload
+ def get(self, tag: Literal["TSIC"]) -> T_S_I_C_.table_T_S_I_C_ | None: ...
+ @overload
+ def get(self, tag: Literal["TSID"]) -> T_S_I_D_.table_T_S_I_D_ | None: ...
+ @overload
+ def get(self, tag: Literal["TSIJ"]) -> T_S_I_J_.table_T_S_I_J_ | None: ...
+ @overload
+ def get(self, tag: Literal["TSIP"]) -> T_S_I_P_.table_T_S_I_P_ | None: ...
+ @overload
+ def get(self, tag: Literal["TSIS"]) -> T_S_I_S_.table_T_S_I_S_ | None: ...
+ @overload
+ def get(self, tag: Literal["TSIV"]) -> T_S_I_V_.table_T_S_I_V_ | None: ...
+ @overload
+ def get(self, tag: Literal["TTFA"]) -> T_T_F_A_.table_T_T_F_A_ | None: ...
+ @overload
+ def get(self, tag: Literal["VARC"]) -> V_A_R_C_.table_V_A_R_C_ | None: ...
+ @overload
+ def get(self, tag: Literal["VDMX"]) -> V_D_M_X_.table_V_D_M_X_ | None: ...
+ @overload
+ def get(self, tag: Literal["VORG"]) -> V_O_R_G_.table_V_O_R_G_ | None: ...
+ @overload
+ def get(self, tag: Literal["VVAR"]) -> V_V_A_R_.table_V_V_A_R_ | None: ...
+ @overload
+ def get(self, tag: Literal["Debg"]) -> D__e_b_g.table_D__e_b_g | None: ...
+ @overload
+ def get(self, tag: Literal["Feat"]) -> F__e_a_t.table_F__e_a_t | None: ...
+ @overload
+ def get(self, tag: Literal["Glat"]) -> G__l_a_t.table_G__l_a_t | None: ...
+ @overload
+ def get(self, tag: Literal["Gloc"]) -> G__l_o_c.table_G__l_o_c | None: ...
+ @overload
+ def get(self, tag: Literal["OS/2"]) -> O_S_2f_2.table_O_S_2f_2 | None: ...
+ @overload
+ def get(self, tag: Literal["Silf"]) -> S__i_l_f.table_S__i_l_f | None: ...
+ @overload
+ def get(self, tag: Literal["Sill"]) -> S__i_l_l.table_S__i_l_l | None: ...
+ @overload
+ def get(self, tag: Literal["ankr"]) -> _a_n_k_r.table__a_n_k_r | None: ...
+ @overload
+ def get(self, tag: Literal["avar"]) -> _a_v_a_r.table__a_v_a_r | None: ...
+ @overload
+ def get(self, tag: Literal["bsln"]) -> _b_s_l_n.table__b_s_l_n | None: ...
+ @overload
+ def get(self, tag: Literal["cidg"]) -> _c_i_d_g.table__c_i_d_g | None: ...
+ @overload
+ def get(self, tag: Literal["cmap"]) -> _c_m_a_p.table__c_m_a_p | None: ...
+ @overload
+ def get(self, tag: Literal["cvar"]) -> _c_v_a_r.table__c_v_a_r | None: ...
+ @overload
+ def get(self, tag: Literal["cvt "]) -> _c_v_t.table__c_v_t | None: ...
+ @overload
+ def get(self, tag: Literal["feat"]) -> _f_e_a_t.table__f_e_a_t | None: ...
+ @overload
+ def get(self, tag: Literal["fpgm"]) -> _f_p_g_m.table__f_p_g_m | None: ...
+ @overload
+ def get(self, tag: Literal["fvar"]) -> _f_v_a_r.table__f_v_a_r | None: ...
+ @overload
+ def get(self, tag: Literal["gasp"]) -> _g_a_s_p.table__g_a_s_p | None: ...
+ @overload
+ def get(self, tag: Literal["gcid"]) -> _g_c_i_d.table__g_c_i_d | None: ...
+ @overload
+ def get(self, tag: Literal["glyf"]) -> _g_l_y_f.table__g_l_y_f | None: ...
+ @overload
+ def get(self, tag: Literal["gvar"]) -> _g_v_a_r.table__g_v_a_r | None: ...
+ @overload
+ def get(self, tag: Literal["hdmx"]) -> _h_d_m_x.table__h_d_m_x | None: ...
+ @overload
+ def get(self, tag: Literal["head"]) -> _h_e_a_d.table__h_e_a_d | None: ...
+ @overload
+ def get(self, tag: Literal["hhea"]) -> _h_h_e_a.table__h_h_e_a | None: ...
+ @overload
+ def get(self, tag: Literal["hmtx"]) -> _h_m_t_x.table__h_m_t_x | None: ...
+ @overload
+ def get(self, tag: Literal["kern"]) -> _k_e_r_n.table__k_e_r_n | None: ...
+ @overload
+ def get(self, tag: Literal["lcar"]) -> _l_c_a_r.table__l_c_a_r | None: ...
+ @overload
+ def get(self, tag: Literal["loca"]) -> _l_o_c_a.table__l_o_c_a | None: ...
+ @overload
+ def get(self, tag: Literal["ltag"]) -> _l_t_a_g.table__l_t_a_g | None: ...
+ @overload
+ def get(self, tag: Literal["maxp"]) -> _m_a_x_p.table__m_a_x_p | None: ...
+ @overload
+ def get(self, tag: Literal["meta"]) -> _m_e_t_a.table__m_e_t_a | None: ...
+ @overload
+ def get(self, tag: Literal["mort"]) -> _m_o_r_t.table__m_o_r_t | None: ...
+ @overload
+ def get(self, tag: Literal["morx"]) -> _m_o_r_x.table__m_o_r_x | None: ...
+ @overload
+ def get(self, tag: Literal["name"]) -> _n_a_m_e.table__n_a_m_e | None: ...
+ @overload
+ def get(self, tag: Literal["opbd"]) -> _o_p_b_d.table__o_p_b_d | None: ...
+ @overload
+ def get(self, tag: Literal["post"]) -> _p_o_s_t.table__p_o_s_t | None: ...
+ @overload
+ def get(self, tag: Literal["prep"]) -> _p_r_e_p.table__p_r_e_p | None: ...
+ @overload
+ def get(self, tag: Literal["prop"]) -> _p_r_o_p.table__p_r_o_p | None: ...
+ @overload
+ def get(self, tag: Literal["sbix"]) -> _s_b_i_x.table__s_b_i_x | None: ...
+ @overload
+ def get(self, tag: Literal["trak"]) -> _t_r_a_k.table__t_r_a_k | None: ...
+ @overload
+ def get(self, tag: Literal["vhea"]) -> _v_h_e_a.table__v_h_e_a | None: ...
+ @overload
+ def get(self, tag: Literal["vmtx"]) -> _v_m_t_x.table__v_m_t_x | None: ...
+ @overload
+ def get(self, tag: Literal["GlyphOrder"]) -> GlyphOrder: ...
+ @overload
+ def get(self, tag: str | bytes) -> DefaultTable | GlyphOrder | Any | None: ...
+ @overload
+ def get(
+ self, tag: str | bytes, default: _VT_co
+ ) -> DefaultTable | GlyphOrder | Any | _VT_co: ...
+
+ def get(
+ self, tag: str | bytes, default: Any | None = None
+ ) -> DefaultTable | GlyphOrder | Any | None:
"""Returns the table if it exists or (optionally) a default if it doesn't."""
try:
return self[tag]
except KeyError:
return default
- def setGlyphOrder(self, glyphOrder):
+ def setGlyphOrder(self, glyphOrder: list[str]) -> None:
"""Set the glyph order
Args:
@@ -522,7 +1054,7 @@ class TTFont(object):
if self.isLoaded("glyf"):
self["glyf"].setGlyphOrder(glyphOrder)
- def getGlyphOrder(self):
+ def getGlyphOrder(self) -> list[str]:
"""Returns a list of glyph names ordered by their position in the font."""
try:
return self.glyphOrder
@@ -557,7 +1089,7 @@ class TTFont(object):
self._getGlyphNamesFromCmap()
return self.glyphOrder
- def _getGlyphNamesFromCmap(self):
+ def _getGlyphNamesFromCmap(self) -> None:
#
# This is rather convoluted, but then again, it's an interesting problem:
# - we need to use the unicode values found in the cmap table to
@@ -623,7 +1155,7 @@ class TTFont(object):
self.tables["cmap"] = cmapLoading
@staticmethod
- def _makeGlyphName(codepoint):
+ def _makeGlyphName(codepoint: int) -> str:
from fontTools import agl # Adobe Glyph List
if codepoint in agl.UV2AGL:
@@ -633,12 +1165,12 @@ class TTFont(object):
else:
return "u%X" % codepoint
- def getGlyphNames(self):
+ def getGlyphNames(self) -> list[str]:
"""Get a list of glyph names, sorted alphabetically."""
glyphNames = sorted(self.getGlyphOrder())
return glyphNames
- def getGlyphNames2(self):
+ def getGlyphNames2(self) -> list[str]:
"""Get a list of glyph names, sorted alphabetically,
but not case sensitive.
"""
@@ -646,7 +1178,7 @@ class TTFont(object):
return textTools.caselessSort(self.getGlyphOrder())
- def getGlyphName(self, glyphID):
+ def getGlyphName(self, glyphID: int) -> str:
"""Returns the name for the glyph with the given ID.
If no name is available, synthesises one with the form ``glyphXXXXX``` where
@@ -657,13 +1189,13 @@ class TTFont(object):
except IndexError:
return "glyph%.5d" % glyphID
- def getGlyphNameMany(self, lst):
+ def getGlyphNameMany(self, lst: Sequence[int]) -> list[str]:
"""Converts a list of glyph IDs into a list of glyph names."""
glyphOrder = self.getGlyphOrder()
cnt = len(glyphOrder)
return [glyphOrder[gid] if gid < cnt else "glyph%.5d" % gid for gid in lst]
- def getGlyphID(self, glyphName):
+ def getGlyphID(self, glyphName: str) -> int:
"""Returns the ID of the glyph with the given name."""
try:
return self.getReverseGlyphMap()[glyphName]
@@ -675,7 +1207,7 @@ class TTFont(object):
raise KeyError(glyphName)
raise
- def getGlyphIDMany(self, lst):
+ def getGlyphIDMany(self, lst: Sequence[str]) -> list[int]:
"""Converts a list of glyph names into a list of glyph IDs."""
d = self.getReverseGlyphMap()
try:
@@ -684,19 +1216,25 @@ class TTFont(object):
getGlyphID = self.getGlyphID
return [getGlyphID(glyphName) for glyphName in lst]
- def getReverseGlyphMap(self, rebuild=False):
+ def getReverseGlyphMap(self, rebuild: bool = False) -> dict[str, int]:
"""Returns a mapping of glyph names to glyph IDs."""
if rebuild or not hasattr(self, "_reverseGlyphOrderDict"):
self._buildReverseGlyphOrderDict()
return self._reverseGlyphOrderDict
- def _buildReverseGlyphOrderDict(self):
+ def _buildReverseGlyphOrderDict(self) -> dict[str, int]:
self._reverseGlyphOrderDict = d = {}
for glyphID, glyphName in enumerate(self.getGlyphOrder()):
d[glyphName] = glyphID
return d
- def _writeTable(self, tag, writer, done, tableCache=None):
+ def _writeTable(
+ self,
+ tag: str | bytes,
+ writer: SFNTWriter,
+ done: list[str | bytes], # Use list as original
+ tableCache: MutableMapping[tuple[Tag, bytes], DefaultTable] | None = None,
+ ) -> None:
"""Internal helper function for self.save(). Keeps track of
inter-table dependencies.
"""
@@ -722,7 +1260,7 @@ class TTFont(object):
if tableCache is not None:
tableCache[(Tag(tag), tabledata)] = writer[tag]
- def getTableData(self, tag):
+ def getTableData(self, tag: str | bytes) -> bytes:
"""Returns the binary representation of a table.
If the table is currently loaded and in memory, the data is compiled to
@@ -740,8 +1278,12 @@ class TTFont(object):
raise KeyError(tag)
def getGlyphSet(
- self, preferCFF=True, location=None, normalized=False, recalcBounds=True
- ):
+ self,
+ preferCFF: bool = True,
+ location: Mapping[str, _NumberT] | None = None,
+ normalized: bool = False,
+ recalcBounds: bool = True,
+ ) -> _TTGlyphSet:
"""Return a generic GlyphSet, which is a dict-like object
mapping glyph names to glyph objects. The returned glyph objects
have a ``.draw()`` method that supports the Pen protocol, and will
@@ -780,7 +1322,7 @@ class TTFont(object):
glyphSet = _TTGlyphSetVARC(self, location, glyphSet)
return glyphSet
- def normalizeLocation(self, location):
+ def normalizeLocation(self, location: Mapping[str, float]) -> dict[str, float]:
"""Normalize a ``location`` from the font's defined axes space (also
known as user space) into the normalized (-1..+1) space. It applies
``avar`` mapping if the font contains an ``avar`` table.
@@ -803,7 +1345,7 @@ class TTFont(object):
def getBestCmap(
self,
- cmapPreferences=(
+ cmapPreferences: Sequence[tuple[int, int]] = (
(3, 10),
(0, 6),
(0, 4),
@@ -813,7 +1355,7 @@ class TTFont(object):
(0, 1),
(0, 0),
),
- ):
+ ) -> dict[int, str] | None:
"""Returns the 'best' Unicode cmap dictionary available in the font
or ``None``, if no Unicode cmap subtable is available.
@@ -838,7 +1380,7 @@ class TTFont(object):
"""
return self["cmap"].getBestCmap(cmapPreferences=cmapPreferences)
- def reorderGlyphs(self, new_glyph_order):
+ def reorderGlyphs(self, new_glyph_order: list[str]) -> None:
from .reorderGlyphs import reorderGlyphs
reorderGlyphs(self, new_glyph_order)
@@ -849,20 +1391,22 @@ class GlyphOrder(object):
table, but it's nice to present it as such in the TTX format.
"""
- def __init__(self, tag=None):
+ def __init__(self, tag: str | None = None) -> None:
pass
- def toXML(self, writer, ttFont):
+ def toXML(self, writer: xmlWriter.XMLWriter, ttFont: TTFont) -> None:
glyphOrder = ttFont.getGlyphOrder()
writer.comment(
- "The 'id' attribute is only for humans; " "it is ignored when parsed."
+ "The 'id' attribute is only for humans; it is ignored when parsed."
)
writer.newline()
for i, glyphName in enumerate(glyphOrder):
writer.simpletag("GlyphID", id=i, name=glyphName)
writer.newline()
- def fromXML(self, name, attrs, content, ttFont):
+ def fromXML(
+ self, name: str, attrs: dict[str, str], content: list[Any], ttFont: TTFont
+ ) -> None:
if not hasattr(self, "glyphOrder"):
self.glyphOrder = []
if name == "GlyphID":
@@ -870,7 +1414,7 @@ class GlyphOrder(object):
ttFont.setGlyphOrder(self.glyphOrder)
-def getTableModule(tag):
+def getTableModule(tag: str | bytes) -> ModuleType | None:
"""Fetch the packer/unpacker module for a table.
Return None when no module is found.
"""
@@ -895,10 +1439,12 @@ def getTableModule(tag):
# Registry for custom table packer/unpacker classes. Keys are table
# tags, values are (moduleName, className) tuples.
# See registerCustomTableClass() and getCustomTableClass()
-_customTableRegistry = {}
+_customTableRegistry: dict[str | bytes, tuple[str, str]] = {}
-def registerCustomTableClass(tag, moduleName, className=None):
+def registerCustomTableClass(
+ tag: str | bytes, moduleName: str, className: str | None = None
+) -> None:
"""Register a custom packer/unpacker class for a table.
The 'moduleName' must be an importable module. If no 'className'
@@ -913,12 +1459,12 @@ def registerCustomTableClass(tag, moduleName, className=None):
_customTableRegistry[tag] = (moduleName, className)
-def unregisterCustomTableClass(tag):
+def unregisterCustomTableClass(tag: str | bytes) -> None:
"""Unregister the custom packer/unpacker class for a table."""
del _customTableRegistry[tag]
-def getCustomTableClass(tag):
+def getCustomTableClass(tag: str | bytes) -> type[DefaultTable] | None:
"""Return the custom table class for tag, if one has been registered
with 'registerCustomTableClass()'. Else return None.
"""
@@ -931,7 +1477,7 @@ def getCustomTableClass(tag):
return getattr(module, className)
-def getTableClass(tag):
+def getTableClass(tag: str | bytes) -> type[DefaultTable]:
"""Fetch the packer/unpacker class for a table."""
tableClass = getCustomTableClass(tag)
if tableClass is not None:
@@ -946,7 +1492,7 @@ def getTableClass(tag):
return tableClass
-def getClassTag(klass):
+def getClassTag(klass: type[DefaultTable]) -> str | bytes:
"""Fetch the table tag for a class object."""
name = klass.__name__
assert name[:6] == "table_"
@@ -954,13 +1500,13 @@ def getClassTag(klass):
return identifierToTag(name)
-def newTable(tag):
+def newTable(tag: str | bytes) -> DefaultTable:
"""Return a new instance of a table."""
tableClass = getTableClass(tag)
return tableClass(tag)
-def _escapechar(c):
+def _escapechar(c: str) -> str:
"""Helper function for tagToIdentifier()"""
import re
@@ -972,7 +1518,7 @@ def _escapechar(c):
return hex(byteord(c))[2:]
-def tagToIdentifier(tag):
+def tagToIdentifier(tag: str | bytes) -> str:
"""Convert a table tag to a valid (but UGLY) python identifier,
as well as a filename that's guaranteed to be unique even on a
caseless file system. Each character is mapped to two characters.
@@ -1007,7 +1553,7 @@ def tagToIdentifier(tag):
return ident
-def identifierToTag(ident):
+def identifierToTag(ident: str) -> str:
"""the opposite of tagToIdentifier()"""
if ident == "GlyphOrder":
return ident
@@ -1028,7 +1574,7 @@ def identifierToTag(ident):
return Tag(tag)
-def tagToXML(tag):
+def tagToXML(tag: str | bytes) -> str:
"""Similarly to tagToIdentifier(), this converts a TT tag
to a valid XML element name. Since XML element names are
case sensitive, this is a fairly simple/readable translation.
@@ -1046,7 +1592,7 @@ def tagToXML(tag):
return tagToIdentifier(tag)
-def xmlToTag(tag):
+def xmlToTag(tag: str) -> str:
"""The opposite of tagToXML()"""
if tag == "OS_2":
return Tag("OS/2")
@@ -1082,7 +1628,9 @@ TTFTableOrder = [
OTFTableOrder = ["head", "hhea", "maxp", "OS/2", "name", "cmap", "post", "CFF "]
-def sortedTagList(tagList, tableOrder=None):
+def sortedTagList(
+ tagList: Sequence[str], tableOrder: Sequence[str] | None = None
+) -> list[str]:
"""Return a sorted copy of tagList, sorted according to the OpenType
specification, or according to a custom tableOrder. If given and not
None, tableOrder needs to be a list of tag names.
@@ -1106,7 +1654,12 @@ def sortedTagList(tagList, tableOrder=None):
return orderedTables
-def reorderFontTables(inFile, outFile, tableOrder=None, checkChecksums=False):
+def reorderFontTables(
+ inFile: BinaryIO, # Takes file-like object as per original
+ outFile: BinaryIO, # Takes file-like object
+ tableOrder: Sequence[str] | None = None,
+ checkChecksums: bool = False, # Keep param even if reader handles it
+) -> None:
"""Rewrite a font file, ordering the tables as recommended by the
OpenType specification 1.4.
"""
@@ -1126,7 +1679,7 @@ def reorderFontTables(inFile, outFile, tableOrder=None, checkChecksums=False):
writer.close()
-def maxPowerOfTwo(x):
+def maxPowerOfTwo(x: int) -> int:
"""Return the highest exponent of two, so that
(2 ** exponent) <= x. Return 0 if x is 0.
"""
@@ -1137,7 +1690,7 @@ def maxPowerOfTwo(x):
return max(exponent - 1, 0)
-def getSearchRange(n, itemSize=16):
+def getSearchRange(n: int, itemSize: int = 16) -> tuple[int, int, int]:
"""Calculate searchRange, entrySelector, rangeShift."""
# itemSize defaults to 16, for backward compatibility
# with upstream fonttools.
diff --git a/contrib/python/fonttools/fontTools/varLib/instancer/__init__.py b/contrib/python/fonttools/fontTools/varLib/instancer/__init__.py
index fa84e3a57e5..2993bf38bfa 100644
--- a/contrib/python/fonttools/fontTools/varLib/instancer/__init__.py
+++ b/contrib/python/fonttools/fontTools/varLib/instancer/__init__.py
@@ -448,7 +448,7 @@ class AxisLimits(_BaseAxisLimits):
# Full instancing of avar2 font. Use avar table to normalize location and return.
location = self.pinnedLocation()
location = {
- tag: normalize(value, axes[tag], avarSegments.get(tag, None))
+ tag: normalize(value, axes[tag], None)
for tag, value in location.items()
}
return NormalizedAxisLimits(
diff --git a/contrib/python/fonttools/fontTools/varLib/models.py b/contrib/python/fonttools/fontTools/varLib/models.py
index 52433a66a82..1d836aeb163 100644
--- a/contrib/python/fonttools/fontTools/varLib/models.py
+++ b/contrib/python/fonttools/fontTools/varLib/models.py
@@ -1,5 +1,7 @@
"""Variation fonts interpolation models."""
+from __future__ import annotations
+
__all__ = [
"normalizeValue",
"normalizeLocation",
@@ -8,10 +10,15 @@ __all__ = [
"VariationModel",
]
+from typing import TYPE_CHECKING
from fontTools.misc.roundTools import noRound
from .errors import VariationModelError
+if TYPE_CHECKING:
+ from typing import Mapping, Sequence
+
+
def nonNone(lst):
return [l for l in lst if l is not None]
@@ -44,7 +51,9 @@ def subList(truth, lst):
return [l for l, t in zip(lst, truth) if t]
-def normalizeValue(v, triple, extrapolate=False):
+def normalizeValue(
+ v: float, triple: Sequence[float], extrapolate: bool = False
+) -> float:
"""Normalizes value based on a min/default/max triple.
>>> normalizeValue(400, (100, 400, 900))
@@ -75,7 +84,13 @@ def normalizeValue(v, triple, extrapolate=False):
return (v - default) / (upper - default)
-def normalizeLocation(location, axes, extrapolate=False, *, validate=False):
+def normalizeLocation(
+ location: Mapping[str, float],
+ axes: Mapping[str, tuple[float, float, float]],
+ extrapolate: bool = False,
+ *,
+ validate: bool = False,
+) -> dict[str, float]:
"""Normalizes location based on axis min/default/max values from axes.
>>> axes = {"wght": (100, 400, 900)}
diff --git a/contrib/python/fonttools/ya.make b/contrib/python/fonttools/ya.make
index d34ce34755c..3f8f9d4e88a 100644
--- a/contrib/python/fonttools/ya.make
+++ b/contrib/python/fonttools/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(4.61.0)
+VERSION(4.61.1)
LICENSE(MIT)