pypi / fonttools
fonttools
What changed in fonttools 4, from its official release notes, upgrade guides and advisories. Sources checked 2026-10-21.
13 breaking2 security1 deprecations6 new APIs and options6 behavior and defaults1 tooling49 fixes and other
See which of these apply to your code, ranked, in your agent:
npx @unstale/cli initfonttools 478 changes
Breaking changes 13
Bounds-checks count and offSize on read, raising TTLibError instead of allocating huge lazy lists.
Fixes format 2 compilation edge cases, rejects glyph IDs above 65535 in formats 2 and 4, splits long format 14 default UVS ranges, and round-trips empty formats 12 and 13.
Prefers the Windows English name over the Mac name.
Raises NotImplementedError for an all-off-curve cubic contour, which the point pen protocol cannot express.
Rejects in-place union with |=.
Raises ValueError instead of IndexError for a truncated path command.
Updates the variation-store binary format; VARC fonts built with earlier fontTools versions must be rebuilt.
When splitting a DesignSpace v5 document with makeNames=True, explicitly set family and style names take precedence over names computed from STAT labels in all languages; an instance with its own style name no longer gets a PostScript name made from…
Decompiling a format 4 subtable with an idRangeOffset outside glyphIndexArray now raises TTLibError instead of silently mapping to the wrong glyph or raising a bare AssertionError.
Entity resolution is disabled altogether on lxml 5.0 and later to prevent crafted DTDs from reading local files into parsed XML content.
Python 3.10 support is dropped; fontTools now requires Python 3.11 or later.
When unicodedata2 is used, version 18.0.0 is required.
The instancer rejects pinning or restricting axes referenced by VARC components.
Security 2
Expansion of format 4 segments and format 12/13 groups is bounded; out-of-range, inverted, overlapping, and missing-glyph groups are handled without unbounded expansion.
Escapes axis names and tags in emitted designspace snippets to prevent crafted fonts from injecting markup.
Deprecations 1
This module now only re-exports enum.StrEnum and is deprecated.
New APIs and options 6
Adds a keyword-only argument to support decreasing design-space axis mappings.
Adds support for instancing variable COLRv1 fonts.
Allows requesting an axis's original range on VARC fonts.
A new axisCount element provides the axis count when gvar data is processed without fvar.
The new languages attribute contains all language tags in a LanguageStatement; language remains the first tag.
Static VARC fonts can omit fvar while retaining gvar or CFF2 component-internal variation data; hidden axes are addressed by index, and gvar supports compilation, decompilation, and TTX round-tripping without fvar.
Behavior and default changes 6
Accepts flattened per-region vector tuples.
Supports merging fonts that both have a MATH table.
Warns when a source or instance is dropped because it lies outside the document's own axes.
Warns when a source or instance is dropped because it lies outside the document's own axes.
feaLib supports listing multiple language tags; following rules and lookup references are registered under every listed language, and dflt cannot be combined with other tags.
When --flavor is omitted, pyftsubset preserves the input font's WOFF or WOFF2 flavor; use --flavor=none to force uncompressed output.
Tooling 1
Support for Python 3.15 is explicitly tested and declared.
Fixes and other changes 49
Omitted range attributes on a DesignSpace v5 axis subset are taken from the parent axis.
Corrects BCP 47 tags for some Mac and Windows language IDs.
Deleting a CFF dict attribute now actually removes it.
Reads CFF font names as Latin-1, matching how they are written.
Preserves operands when a blend has zero regions.
Normalizes mapped-axis bounds and rule conditions from their original values.
Keeps glyph metrics after probing a missing attribute.
Rejects duplicate use of a mark class in one anchor list, handles value records with all-NULL devices, and pads a short OS/2 Vendor with spaces.
Invalid BASE, UnicodeRange, name, STAT location, and OS/2 Vendor inputs now raise FeatureLibError with a source location instead of a bare Python error.
Fixes size, ssXX, and cvXX FeatureParams in FeatureVariations alternate feature tables so they compile and round-trip.
Adding rvrn shifts lookup indices inside existing FeatureVariations; null offsets are handled and substitution records stay sorted after merging.
The subsetter handles a null GDEF variation store.
The instancer CLI raises ValueError with a missing fvar table message for a static font instead of a bare KeyError.
Folds, removes, or remaps MATH VariationIndex devices when instancing, subsetting the GDEF store, or optimizing it.
Fixes tokenizing escaped parentheses in PostScript strings.
Preserves the direction of the remaining axis when decomposing a transform whose first column is zero.
Closes owned input streams when parsing fails.
Handles empty packed deltas when this setting is enabled.
Avoids a crash on a format 2.0 table with numGlyphs set to 0.
Recursive decompilation fully decodes lazily loaded ligatures, SinglePos, and PairPos arrays, preventing corruption or assertions on lazy fonts.
Passes the all-off-curve None sentinel through.
Scales per-glyph VORG origins, fixes VARC scaling for static fonts and condition variations, and avoids mutating the shared default CFF FontMatrix.
Fixes compiling a Silf table decompiled from binary.
Returns NotImplemented for unsupported types.
Fixes cubic off-curve detection across contour starts.
Computes glyph width lazily for glyf variable fonts without HVAR, making width correct before drawing.
Fixes component conditions, implicit scaleY import, reported coordinates after axis resets, and inherited coordinates when drawing CFF2 components.
Applies VVAR vertical-origin deltas to VORG and uses OpenType rounding for instanced advances and vertical origins.
Keeps a universal FeatureVariations record when pinning removes its last condition but other axes remain.
Preserves ppem Device corrections in GDEF, BASE, GPOS, and JSTF instead of asserting on them.
Respects designspace source layers and fixes --json output with SciPy.
Raises ValueError with a missing fvar table message for a static font instead of a bare KeyError.
Recomputes numRecs and numRatios when compiling.
Preserves minorVersion when copying flavor data.
Can be called more than once.
Compiling succeeds when the lowest glyph ID in a lead-byte row is 32768 or higher, instead of failing with struct.error.
The instancer no longer culls avar2 ranges for component-internal variations that can reach outside font-level ranges.
The VarStore is always recompiled when saving, preventing stale data when a font is modified in place after an earlier save.
Its type annotation and documentation now correctly specify that it holds a SourceDescriptor, not a source name.
A STAT table block missing ElidedFallbackName or ElidedFallbackNameID now raises FeatureLibError instead of UnboundLocalError.
Collinear vertical lines are treated as parallel, avoiding ZeroDivisionError.
Repeated script/language pairs within a feature block no longer replace the language system's lookups with a fresh copy of the defaults.
Preserves exact endpoints when splitting a cubic curve.
Preserves exact endpoints when splitting a quadratic curve.
Inconsistent WOFF table, metadata, or private-data lengths raise TTLibError instead of AssertionError, including under python -O.
Bundled script, script extension, block, and bidi-mirroring tables are updated to Unicode 18.0.0.
Merge reports incompatible unitsPerEm values by name and includes the input values instead of raising a bare assertion.
The instancer fixes stale VARC axis references after removing an unrelated axis.
Drawing VARC components with a negated condition (format 5) no longer raises AttributeError.