[fonttools/fonttools] d3ede3: Avoid decompiling bitmap glyphs after raw data is ...

1 view
Skip to first unread message

Cosimo Lupo

unread,
Oct 5, 2026, 7:46:23 AM (5 days ago) Oct 5
to fontto...@googlegroups.com
Branch: refs/heads/main
Home: https://github.com/fonttools/fonttools
Commit: d3ede3b82b8e387bb5d2e596b9d3fdb2c529c41e
https://github.com/fonttools/fonttools/commit/d3ede3b82b8e387bb5d2e596b9d3fdb2c529c41e
Author: Sai Asish Y <say....@gmail.com>
Date: 2026-10-01 (Thu, 01 Oct 2026)

Changed paths:
M Lib/fontTools/ttLib/tables/E_B_D_T_.py
M README.rst
M Tests/ttLib/tables/E_B_D_T_test.py

Log Message:
-----------
Avoid decompiling bitmap glyphs after raw data is discarded


Commit: 4faf754a8bddec081e3b13d8b77d1656e94a59b8
https://github.com/fonttools/fonttools/commit/4faf754a8bddec081e3b13d8b77d1656e94a59b8
Author: Cosimo Lupo <cos...@anthrotype.com>
Date: 2026-10-05 (Mon, 05 Oct 2026)

Changed paths:
M Lib/fontTools/ttLib/tables/E_B_D_T_.py
M README.rst
M Tests/ttLib/tables/E_B_D_T_test.py

Log Message:
-----------
Merge pull request #4216 from SAY-5/fix-bitmap-glyph-lazy-metrics

Preserve bitmap metrics after missing-attribute probes


Compare: https://github.com/fonttools/fonttools/compare/fa4995b338b4...4faf754a8bdd

To unsubscribe from these emails, change your notification settings at https://github.com/fonttools/fonttools/settings/notifications

Behdad Esfahbod

unread,
Oct 5, 2026, 5:39:08 PM (5 days ago) Oct 5
to fontto...@googlegroups.com
Branch: refs/heads/beyond-64k
Home: https://github.com/fonttools/fonttools
Commit: d3ede3b82b8e387bb5d2e596b9d3fdb2c529c41e
https://github.com/fonttools/fonttools/commit/d3ede3b82b8e387bb5d2e596b9d3fdb2c529c41e
Author: Sai Asish Y <say....@gmail.com>
Date: 2026-10-01 (Thu, 01 Oct 2026)

Changed paths:
M Lib/fontTools/ttLib/tables/E_B_D_T_.py
M README.rst
M Tests/ttLib/tables/E_B_D_T_test.py

Log Message:
-----------
Avoid decompiling bitmap glyphs after raw data is discarded


Commit: 8bf02a38d087ec5dab1f18859ce4ec4a2da398c4
https://github.com/fonttools/fonttools/commit/8bf02a38d087ec5dab1f18859ce4ec4a2da398c4
Author: Sai Asish Y <say....@gmail.com>
Date: 2026-10-01 (Thu, 01 Oct 2026)

Changed paths:
M Lib/fontTools/varLib/__init__.py
M README.rst
A Tests/varLib/reversed_axis_test.py

Log Message:
-----------
Support decreasing design-space axis mappings in varLib


Commit: ddfe37b685ea9da48372e83bcf1ed39cba4bab45
https://github.com/fonttools/fonttools/commit/ddfe37b685ea9da48372e83bcf1ed39cba4bab45
Author: Cosimo Lupo <cos...@anthrotype.com>
Date: 2026-10-02 (Fri, 02 Oct 2026)

Changed paths:
M Tests/designspaceLib/designspace_test.py

Log Message:
-----------
[designspaceLib] reproduce axis and rule normalization bugs

normalizeLocation() expects design coordinates. normalize() passes it user-space axis bounds after replacing the axis map with normalized outputs. For a Weight axis with user bounds 0/400/1000 mapped to 0/100/1000, it produces 0/1/1 instead of -1/0/1. The mapped-axis tests check this case, shifted bounds and a default at the maximum.

Rule conditions are normalized after the axis bounds and maps have already changed, so interior design coordinates are clipped to the normalized endpoints. An unmapped axis spanning 0..1000 turns a rule interval of 400..600 into 1..1 instead of 0.4..0.6. The rule tests cover this case and an off-centre mapped default, and check that open bounds and condition-set grouping survive normalization.


Commit: af33ef09301a393cbd8f0874b3485a5c525cf106
https://github.com/fonttools/fonttools/commit/af33ef09301a393cbd8f0874b3485a5c525cf106
Author: Cosimo Lupo <cos...@anthrotype.com>
Date: 2026-10-02 (Fri, 02 Oct 2026)

Changed paths:
M Lib/fontTools/designspaceLib/__init__.py

Log Message:
-----------
[designspaceLib] fix normalization of mapped axis bounds

normalizeLocation() expects design coordinates, but normalize() passed it user-space axis bounds after replacing the map with normalized outputs. This could move an interior default to 1 instead of 0.

Map the bounds to design coordinates and normalize them against the original map before updating the axis. Preserve user-space map inputs.


Commit: ffb9ae46603ab52ddab08f2c7e15f6bcd804b0ed
https://github.com/fonttools/fonttools/commit/ffb9ae46603ab52ddab08f2c7e15f6bcd804b0ed
Author: Cosimo Lupo <cos...@anthrotype.com>
Date: 2026-10-02 (Fri, 02 Oct 2026)

Changed paths:
M Lib/fontTools/designspaceLib/__init__.py

Log Message:
-----------
[designspaceLib] normalize rule conditions before changing axes

Rule bounds are design coordinates, but normalize() scaled them after replacing the axis bounds and maps with normalized values. Interior condition bounds were clamped to the endpoints instead of retaining their positions.

Normalize rules against the original axis data before updating the axes. Preserve open bounds and condition-set structure.


Commit: 58887c80023cbab27da9889260f621c5da1eaf88
https://github.com/fonttools/fonttools/commit/58887c80023cbab27da9889260f621c5da1eaf88
Author: Zihan Dai <dzh143...@gmail.com>
Date: 2026-10-03 (Sat, 03 Oct 2026)

Changed paths:
M Lib/fontTools/misc/transform.py
M README.rst
M Tests/misc/transform_test.py

Log Message:
-----------
Preserve collapsed-axis direction when decomposing transforms


Commit: fa4995b338b449c858d5ed3cab4c2500d9d3c9bc
https://github.com/fonttools/fonttools/commit/fa4995b338b449c858d5ed3cab4c2500d9d3c9bc
Author: Cosimo Lupo <cos...@anthrotype.com>
Date: 2026-10-05 (Mon, 05 Oct 2026)

Changed paths:
M Lib/fontTools/designspaceLib/__init__.py
M Tests/designspaceLib/designspace_test.py

Log Message:
-----------
Merge pull request #4218 from fonttools/designspace-normalize-mapped-axes

[designspaceLib] fix mapped axis bounds and rule normalization


Commit: 4faf754a8bddec081e3b13d8b77d1656e94a59b8
https://github.com/fonttools/fonttools/commit/4faf754a8bddec081e3b13d8b77d1656e94a59b8
Author: Cosimo Lupo <cos...@anthrotype.com>
Date: 2026-10-05 (Mon, 05 Oct 2026)

Changed paths:
M Lib/fontTools/ttLib/tables/E_B_D_T_.py
M README.rst
M Tests/ttLib/tables/E_B_D_T_test.py

Log Message:
-----------
Merge pull request #4216 from SAY-5/fix-bitmap-glyph-lazy-metrics

Preserve bitmap metrics after missing-attribute probes


Commit: 62f19fdaddf9c9fd65ec9467697ef408012c5b40
https://github.com/fonttools/fonttools/commit/62f19fdaddf9c9fd65ec9467697ef408012c5b40
Author: Cosimo Lupo <cos...@anthrotype.com>
Date: 2026-10-05 (Mon, 05 Oct 2026)

Changed paths:
M Lib/fontTools/varLib/__init__.py
A Tests/varLib/reversed_axis_test.py

Log Message:
-----------
Merge pull request #4217 from SAY-5/fix-decreasing-design-axis-mappings

Support decreasing design-space axis mappings in varLib


Commit: c3dbec4b18ebf383694d79ff052036938e2ed12e
https://github.com/fonttools/fonttools/commit/c3dbec4b18ebf383694d79ff052036938e2ed12e
Author: Cosimo Lupo <cos...@anthrotype.com>
Date: 2026-10-05 (Mon, 05 Oct 2026)

Changed paths:
M Lib/fontTools/misc/transform.py
M README.rst
M Tests/misc/transform_test.py

Log Message:
-----------
Merge pull request #4242 from PDGGK/fix-collapsed-axis-decomposition

Preserve collapsed-axis direction when decomposing transforms


Commit: 9b66a995e8dd4aca3d70600f7c90a71340a5141e
https://github.com/fonttools/fonttools/commit/9b66a995e8dd4aca3d70600f7c90a71340a5141e
Author: Cosimo Lupo <cos...@anthrotype.com>
Date: 2026-10-05 (Mon, 05 Oct 2026)

Changed paths:
M Lib/fontTools/designspaceLib/__init__.py
M Lib/fontTools/feaLib/variableScalar.py
M Lib/fontTools/varLib/__init__.py
M Lib/fontTools/varLib/interpolatable.py
M Lib/fontTools/varLib/interpolate_layout.py
M Lib/fontTools/varLib/models.py
M Tests/designspaceLib/designspace_test.py
M Tests/feaLib/variableScalar_test.py
M Tests/varLib/interpolatable_test.py
M Tests/varLib/interpolate_layout_test.py

Log Message:
-----------
[varLib] Add allow_decreasing to normalizeValue and use it in the other normalization sites

Let normalizeValue and normalizeLocation take a decreasing triple when asked to,
instead of keeping the helper private to varLib, and pass it in
DesignSpaceDocument.normalizeLocation, feaLib VariableScalarBuilder,
interpolate_layout and interpolatable, which raised the same ValueError on a
decreasing axis map. The default stays strict. Add a small test for each site.


Commit: df0b208d2b76d424efda00808120803f25bfe21b
https://github.com/fonttools/fonttools/commit/df0b208d2b76d424efda00808120803f25bfe21b
Author: Cosimo Lupo <cos...@anthrotype.com>
Date: 2026-10-05 (Mon, 05 Oct 2026)

Changed paths:
M Lib/fontTools/designspaceLib/__init__.py
M Lib/fontTools/feaLib/variableScalar.py
M Lib/fontTools/varLib/__init__.py
M Lib/fontTools/varLib/interpolatable.py
M Lib/fontTools/varLib/interpolate_layout.py
M Lib/fontTools/varLib/models.py
M Tests/designspaceLib/designspace_test.py
M Tests/feaLib/variableScalar_test.py
M Tests/varLib/interpolatable_test.py
M Tests/varLib/interpolate_layout_test.py

Log Message:
-----------
Merge pull request #4243 from anthrotype/normalize-design-value-siblings

[varLib] add allow_decreasing to normalizeValue and use it in the other normalization sites


Commit: 48c15efd2aee26cc5143c90a25455f73bc897f0f
https://github.com/fonttools/fonttools/commit/48c15efd2aee26cc5143c90a25455f73bc897f0f
Author: Cosimo Lupo <cos...@anthrotype.com>
Date: 2026-10-05 (Mon, 05 Oct 2026)

Changed paths:
M Lib/fontTools/designspaceLib/split.py
M Tests/designspaceLib/split_test.py

Log Message:
-----------
[designspaceLib] Warn when dropping sources or instances outside document axes

In splitInterpolable and splitVariableFonts, drop sources and instances whose location is outside the document's own axes with a warning instead of skipping them silently. Sources and instances outside an explicitly requested <variable-font> sub-space remain silently dropped.


Commit: 4cc164be6ec3046ebabf4cc635961e4d03f42da5
https://github.com/fonttools/fonttools/commit/4cc164be6ec3046ebabf4cc635961e4d03f42da5
Author: Cosimo Lupo <cos...@anthrotype.com>
Date: 2026-10-05 (Mon, 05 Oct 2026)

Changed paths:
M Lib/fontTools/designspaceLib/split.py
M Tests/designspaceLib/split_test.py

Log Message:
-----------
Merge pull request #4244 from fonttools/split-out-of-range-sources

[designspaceLib] warn when split drops sources or instances outside the document's axes


Commit: bfca03f16e6cc46260790ef54fd128ef48a30517
https://github.com/fonttools/fonttools/commit/bfca03f16e6cc46260790ef54fd128ef48a30517
Author: Behdad Esfahbod <beh...@behdad.org>
Date: 2026-10-05 (Mon, 05 Oct 2026)

Changed paths:
M Lib/fontTools/varLib/instancer/__init__.py
M Tests/varLib/instancer/instancer_test.py

Log Message:
-----------
[varLib.instancer] Apply VVAR origin deltas to VORG (#4221)

* [varLib.instancer] Apply VVAR origin deltas to VORG

Instancing a CFF2 font folded default deltas out of VVAR but only
updated vmtx advances. Vertical-origin deltas were lost when dropping
VVAR for a full instance or changing the default in a partial instance.

Apply the folded VOrgMap deltas to VORG before dropping or remapping
VVAR. Preserve implicit default origins and NO_VARIATION_INDEX entries.

Add regressions for full instancing, axis pinning, and moving the
default, including evaluation of remaining variations and VORG binary
round-tripping.

Tests: full pytest suite (4992 passed, 47 skipped, 2 xfailed); Black
26.3.1; CFF2 full/partial instances compared with HarfBuzz v-origins.

Assisted-by: OpenAI Codex <co...@openai.com>

* [instancer] Compact VORG origins and use OpenType rounding


Commit: 7dec8509e668f26560948c3a791f2c6640c353d1
https://github.com/fonttools/fonttools/commit/7dec8509e668f26560948c3a791f2c6640c353d1
Author: Behdad Esfahbod <beh...@behdad.org>
Date: 2026-10-05 (Mon, 05 Oct 2026)

Changed paths:
M Lib/fontTools/ttLib/scaleUpem.py
M Tests/ttLib/scaleUpem_test.py

Log Message:
-----------
[scaleUpem] Scale VORG per-glyph vertical origins (#4222)

Register VOriginRecords with VORG instead of VMTX. The old registration
scaled the default origin but left glyph-specific overrides unchanged.

Add a binary-roundtrip regression for positive and negative overrides
and a glyph using the default origin.

Testing: full pytest suite (4989 passed, 47 skipped, 2 xfailed);
Black; VORG version 1 and 2 scaling and binary roundtrips.

Assisted-by: OpenAI Codex


Commit: 41ffe51acb3ac29cc911acb8d9c2b8d522db5145
https://github.com/fonttools/fonttools/commit/41ffe51acb3ac29cc911acb8d9c2b8d522db5145
Author: Behdad Esfahbod <beh...@behdad.org>
Date: 2026-10-05 (Mon, 05 Oct 2026)

Changed paths:
M Lib/fontTools/ttLib/scaleUpem.py
M Lib/fontTools/ttLib/ttGlyphSet.py
M Tests/ttLib/scaleUpem_test.py

Log Message:
-----------
[scaleUpem] Fix VARC scaling for static fonts and condition variations (#4223)

* [scaleUpem] Reject VARC fonts before scaling

The partial VARC scaler assumes fvar is present, can read uninitialized
variation data, and rebuilds the multi-store without preserving condition
references. It can fail partway through scaling or silently change which
components are drawn.

Reject VARC fonts before visiting any tables. Remove the unsafe scaling
handler and make direct VARC visitor use fail explicitly as well.

Test rejection of variable, conditional, and static fonts, checking that
their saved bytes are unchanged after the error.

Testing: full pytest suite (4995 passed, 47 skipped, 2 xfailed); Black.

Assisted-by: OpenAI Codex

* [scaleUpem] Preserve VARC scaling and remap condition variations

* [tests] Keep regression additions independent of sibling PRs


Commit: 26ac511fe7c7bbac98d61a00f2301bad3142f68a
https://github.com/fonttools/fonttools/commit/26ac511fe7c7bbac98d61a00f2301bad3142f68a
Author: Behdad Esfahbod <beh...@behdad.org>
Date: 2026-10-05 (Mon, 05 Oct 2026)

Changed paths:
M Lib/fontTools/pens/ttGlyphPen.py
M Tests/pens/ttGlyphPen_test.py

Log Message:
-----------
[pens] Keep cubic control resolution inside the current contour (#4224)

Wrap the backward scan at the current contour's start on every step,
not just before the scan. Cubic controls split across a later contour's
start were left quadratic, or controls from a previous quadratic contour
were incorrectly marked cubic.

Test the wrap-around case alone and after line and all-off-curve
quadratic contours, checking both flags and drawing.

Testing: full pytest suite (4991 passed, 47 skipped, 2 xfailed); Black.

Assisted-by: OpenAI Codex


Commit: 2b2f1b3fb9ec050f90ca74925c77c07756aef3e4
https://github.com/fonttools/fonttools/commit/2b2f1b3fb9ec050f90ca74925c77c07756aef3e4
Author: Behdad Esfahbod <beh...@behdad.org>
Date: 2026-10-05 (Mon, 05 Oct 2026)

Changed paths:
M Lib/fontTools/ttLib/tables/_g_l_y_f.py
M Tests/ttLib/tables/_g_l_y_f_test.py
M Tests/ttLib/ttGlyphSet_test.py

Log Message:
-----------
Reject ambiguous all-off-curve cubic point outlines (#4225)

* [glyf] Reject ambiguous all-off-curve cubic point outlines

drawPoints emits all controls with segmentType=None for a contour with
no on-curve points. That represents a quadratic contour, so drawing a
cubic contour through a point pen silently changes its curve type.

Raise NotImplementedError for this unsupported representation rather
than emit misleading points. Keep the point-index-preserving contract
and direct callers to draw(), which emits the correct cubic segments.
This is a safeguard, not a redesign of the point-pen protocol.

Update the existing known-limitation assertion and test two, four, and
eight controls, with quadratic controls and segment drawing as controls.
Full pytest suite: 4994 passed, 47 skipped, 2 xfailed. Black passes.

Refs: https://github.com/fonttools/fonttools/issues/3191
Assisted-by: OpenAI Codex

* [glyf] Reject unsupported cubic contours before emitting points


Commit: 6bd9bc627c9926ff7f030c9c073f0a876c0bd61b
https://github.com/fonttools/fonttools/commit/6bd9bc627c9926ff7f030c9c073f0a876c0bd61b
Author: Behdad Esfahbod <beh...@behdad.org>
Date: 2026-10-05 (Mon, 05 Oct 2026)

Changed paths:
M Lib/fontTools/ttLib/tables/_c_m_a_p.py
M Tests/ttLib/tables/_c_m_a_p_test.py

Log Message:
-----------
[cmap] Reject out-of-range glyph IDs in formats 2 and 4 (#4226)

The idDelta optimizations can wrap oversized glyph IDs modulo 65536.
Format 4 can silently discard a single high-GID mapping, and format 2
can discard one mixed with a lower-GID mapping.

Check the original IDs before applying delta arithmetic, including
virtual gidNNN names. Raise struct.error consistently with other
unrepresentable fields instead of producing a lossy subtable.

Binary tests cover single/mixed mappings, real/virtual glyph names,
and glyph IDs 65535, 65536, and 65537 in both formats.
Full pytest suite: 4989 passed, 47 skipped, 2 xfailed, 63 passing
subtests. Black passes.

Assisted-by: OpenAI Codex


Commit: 0b5d186d8533fa59b17d1817361c75083bfb86ac
https://github.com/fonttools/fonttools/commit/0b5d186d8533fa59b17d1817361c75083bfb86ac
Author: Behdad Esfahbod <beh...@behdad.org>
Date: 2026-10-05 (Mon, 05 Oct 2026)

Changed paths:
M Lib/fontTools/ttLib/tables/_g_l_y_f.py
M Tests/ttLib/tables/_g_l_y_f_test.py

Log Message:
-----------
glyf: use unsigned glyph instruction lengths (#4227)

* [glyf] Read and write unsigned glyph instruction lengths

Simple and composite glyph instruction lengths are uint16 fields, but
the encoder and decoder used signed int16 formats. Programs longer
than 32767 bytes could not be compiled; decoding such a simple glyph
also used a negative offset and corrupted its coordinates.

Use unsigned formats in all four paths. Exercise simple and composite
glyphs at 32767, 32768, and 65535 bytes, and retain rejection at 65536.

Testing: full pytest suite (4996 passed); Black check.

Assisted-by: OpenAI Codex

* [tests] Remove unused composite glyph-order entry

* [tests] Keep regression additions independent of sibling PRs


Commit: 8014c52cee3c632dfbb82c75203805223525b7ee
https://github.com/fonttools/fonttools/commit/8014c52cee3c632dfbb82c75203805223525b7ee
Author: Behdad Esfahbod <beh...@behdad.org>
Date: 2026-10-05 (Mon, 05 Oct 2026)

Changed paths:
M Lib/fontTools/ttLib/tables/_g_l_y_f.py
M Tests/ttLib/tables/_g_l_y_f_test.py

Log Message:
-----------
[glyf] Preserve instructions in zero-contour glyphs (#4228)

Zero-contour glyphs can contain instructions that operate on phantom
points. Expanding or compiling them discarded those instructions, and
the raw trimming path read the bounding box as a contour endpoint before
trying to read nonexistent coordinate flags.

Handle their optional instruction payload explicitly in binary, XML,
and trimming paths. Keep existing empty-glyph output when no program
remains, including after hint removal.

Testing: 24 new cases covering raw/expanded glyphs, trimming, boundary
lengths, and ordinary/split TTX roundtrips. Full pytest: 5012 passed.
Black check passed.

Assisted-by: OpenAI Codex


Commit: 51ae3460fbdc00ef61e30db1aed77197cc96e9c3
https://github.com/fonttools/fonttools/commit/51ae3460fbdc00ef61e30db1aed77197cc96e9c3
Author: Behdad Esfahbod <beh...@behdad.org>
Date: 2026-10-05 (Mon, 05 Oct 2026)

Changed paths:
M Lib/fontTools/ttLib/tables/_c_m_a_p.py
M Tests/ttLib/tables/_c_m_a_p_test.py

Log Message:
-----------
cmap: split default UVS ranges at the 256-codepoint limit (#4229)

* [cmap] Split default UVS runs at the 256-codepoint limit

A UnicodeValueRange has an 8-bit additionalCount. Compiling a default
UVS run longer than 256 codepoints exceeded that field and raised a
struct.error. Split consecutive runs before the count overflows.

Add roundtrip cases at 255, 256, 257, 512, 513, and 768 codepoints,
with and without gaps and alongside a non-default mapping.

Testing: full pytest suite (4989 passed, 51 subtests passed); Black check.

Assisted-by: OpenAI Codex

* [tests] Pin default UVS records to the 256-codepoint boundary


Commit: e8f74e6548b4e9f7f3d879709dfa9c3ac1607b5d
https://github.com/fonttools/fonttools/commit/e8f74e6548b4e9f7f3d879709dfa9c3ac1607b5d
Author: Behdad Esfahbod <beh...@behdad.org>
Date: 2026-10-05 (Mon, 05 Oct 2026)

Changed paths:
M Lib/fontTools/subset/__init__.py
M Tests/subset/subset_test.py

Log Message:
-----------
[subset] Handle a null GDEF variation store (#4230)

GDEF 1.3 permits a null ItemVariationStore offset, but pruning checked
only for the VarStore attribute and then dereferenced None. This also
affects the extended GDEF 1.4 header on the beyond-64k branch.

Skip store pruning when the offset is null and treat the absent store
like an empty store for the existing GDEF version downgrade. Cover
both retained glyph classes and an entirely pruned GDEF, including
binary roundtrips before and after subsetting.

Ran the complete test suite: 4990 passed, 47 skipped, 2 xfailed,
39 subtests passed. Black 26.3.1 passes.

Assisted-by: OpenAI Codex


Commit: 6ce8b63ce28ce601ddf786160cf48bf4215bd46d
https://github.com/fonttools/fonttools/commit/6ce8b63ce28ce601ddf786160cf48bf4215bd46d
Author: Behdad Esfahbod <beh...@behdad.org>
Date: 2026-10-05 (Mon, 05 Oct 2026)

Changed paths:
M Lib/fontTools/ttLib/tables/otTables.py
M Tests/ttLib/reorderGlyphs_test.py

Log Message:
-----------
Fix lazy ligature decoding before glyph reordering (#4231)

* [ttLib] Recursively decode lazy ligature records

LigatureSubst stores its Ligature records in a dictionary that is not
described by otData converters. Recursive decompilation therefore left
those records lazy. Reordering glyphs then decoded their old glyph IDs
using the new glyph order, corrupting ligature components and outputs.

Decode the dictionary's record lists when recursive decompilation is
requested. Keep non-recursive reads lazy and preserve the high-level
tuple-to-glyph mapping API.

Test glyph reordering after loading with all three lazy modes, along with
empty objects and the high-level mapping. All 4992 tests and 39 subtests
pass. Black 26.3.1 passes.

Assisted-by: OpenAI Codex

* [tests] Simplify high-level ligature decompilation coverage


Commit: 09d90c03e5f8a3fa21ddd7db2bb98940dfff969d
https://github.com/fonttools/fonttools/commit/09d90c03e5f8a3fa21ddd7db2bb98940dfff969d
Author: Behdad Esfahbod <beh...@behdad.org>
Date: 2026-10-05 (Mon, 05 Oct 2026)

Changed paths:
M Lib/fontTools/ttLib/tables/otBase.py
M Tests/ttLib/reorderGlyphs_test.py
M Tests/ttLib/tables/otBase_test.py

Log Message:
-----------
Resolve lazy arrays during recursive OpenType decompilation (#4232)

* [ttLib] Resolve lazy arrays during recursive decompilation

BaseTable.iterSubTables only traverses ordinary lists, so recursive
decompilation skipped LazyList arrays. Large SinglePos value arrays
remained lazy and caused glyph reordering to assert; lazy child-table
arrays could also retain glyph IDs until after a glyph-order change.

Materialize lazy arrays when recursive decompilation is requested, before
traversing their child tables. Non-recursive reads remain lazy.

Test recursive/non-recursive decoding directly and reorder ten-glyph
positioning arrays under all lazy loading modes. All 4995 tests and 39
subtests pass, as does Black 26.3.1.

Assisted-by: OpenAI Codex

* [tests] Cover lazy PairPos kerning records during glyph reordering

* [tests] Keep regression additions independent of sibling PRs


Commit: 99ef7ab3e5e8a9e588c08a50780cc5ef065aea4c
https://github.com/fonttools/fonttools/commit/99ef7ab3e5e8a9e588c08a50780cc5ef065aea4c
Author: Behdad Esfahbod <beh...@behdad.org>
Date: 2026-10-05 (Mon, 05 Oct 2026)

Changed paths:
M Lib/fontTools/varLib/featureVars.py
M Tests/varLib/featureVars_test.py

Log Message:
-----------
[varLib] Shift existing feature-variation lookups when prepending (#4233)

Adding rvrn inserts its substitution lookups at the start of GSUB.
The shifter updated ordinary feature and contextual lookup references
but skipped existing FeatureVariations, leaving them pointing at the
new or wrong lookup.

Visit the existing FeatureVariations with the same index shifter. Test
adding rvrn after ccmp variations with both matching and distinct
conditions. All 4990 tests and 39 subtests pass; Black 26.3.1 passes.

Assisted-by: OpenAI Codex


Commit: 2491bd476e264d7993ddb6ec0d361fddbebd11a6
https://github.com/fonttools/fonttools/commit/2491bd476e264d7993ddb6ec0d361fddbebd11a6
Author: Behdad Esfahbod <beh...@behdad.org>
Date: 2026-10-05 (Mon, 05 Oct 2026)

Changed paths:
M Lib/fontTools/varLib/featureVars.py
M Lib/fontTools/varLib/mutator.py
M Tests/varLib/featureVars_test.py
M Tests/varLib/mutator_test.py

Log Message:
-----------
Handle null condition sets when adding feature variations (#4234)

* [varLib] Match null feature-variation condition sets as empty

A zero ConditionSet offset is a universal condition, equivalent to an
empty condition set. The variation-record finder dereferenced it while
adding another feature variation and raised AttributeError.

Normalize null condition sets to empty condition lists when comparing
records. Preserve the existing record and merge matching universal
conditions without changing first-match ordering.

Test null and explicitly empty condition sets, matching and distinct
new conditions, lazy/eager reads, and GSUB binary roundtrips. All 4996
tests and 39 subtests pass; Black 26.3.1 passes.

Assisted-by: OpenAI Codex

* [mutator] Match null feature-variation condition sets universally


Commit: 9a967f4dbabd067855c2befc13ac45877cf7a28b
https://github.com/fonttools/fonttools/commit/9a967f4dbabd067855c2befc13ac45877cf7a28b
Author: Behdad Esfahbod <beh...@behdad.org>
Date: 2026-10-05 (Mon, 05 Oct 2026)

Changed paths:
M Lib/fontTools/subset/__init__.py
M Lib/fontTools/varLib/featureVars.py
M Lib/fontTools/varLib/instancer/featureVars.py
M Lib/fontTools/varLib/mutator.py
M Tests/subset/subset_test.py
M Tests/varLib/featureVars_test.py
M Tests/varLib/instancer/instancer_test.py
M Tests/varLib/mutator_test.py

Log Message:
-----------
Handle null FeatureTableSubstitution offsets (#4235)

* [varLib/subset] Handle null feature table substitutions

A zero FeatureTableSubstitution offset is valid and means that the
matching variation record makes no substitutions. The builder, subsetter,
and instancer instead dereferenced None and failed on these fonts.

Treat null substitutions as empty while preserving first-match behavior.
Retain empty records before later substitutions, prune only trailing
empty records, and materialize a substitution table when adding rules
to a matching record.

Add 44 binary-roundtrip cases covering null and explicit empty tables,
lazy decoding, building, subsetting, and full/partial instancing.
Full suite: 5032 passed, 47 skipped, 2 xfailed; Black check passed.

Assisted-by: OpenAI Codex

* [mutator] Preserve first-match behavior for null substitutions

* [tests] Keep regression additions independent of sibling PRs


Commit: 81ac97ca6de42b0e4b1b588e0f483ec1a47d3ba3
https://github.com/fonttools/fonttools/commit/81ac97ca6de42b0e4b1b588e0f483ec1a47d3ba3
Author: Behdad Esfahbod <beh...@behdad.org>
Date: 2026-10-05 (Mon, 05 Oct 2026)

Changed paths:
M Lib/fontTools/ttLib/ttGlyphSet.py
M Tests/ttLib/ttGlyphSet_test.py

Log Message:
-----------
[ttGlyphSet] Preserve inherited CFF2 VARC coordinates (#4236)

* [ttGlyphSet] Preserve inherited CFF2 VARC coordinates

Configure the CFF2 blend evaluator after pushLocation has merged the
component overrides with the inherited or reset coordinates. Restore
the evaluator only after the parent location has been restored, also
when drawing raises an exception.

Previously the evaluator used only the overrides, and retained child
coordinates on return even though the location dictionary was restored.
This produced incorrect outlines for nested CFF2 VARC components.

Add seven regressions covering inheritance, reset, empty overrides,
exception cleanup, repeated drawing, and a binary VARC round-trip.
All 4995 tests pass (47 skipped, 2 expected failures); Black passes.

Assisted-by: OpenAI Codex

* [ttGlyphSet] Explain CFF2 blender restoration order

* [tests] Keep regression additions independent of sibling PRs


Commit: da011760c492216cce91981e4d819993c5f92967
https://github.com/fonttools/fonttools/commit/da011760c492216cce91981e4d819993c5f92967
Author: Behdad Esfahbod <beh...@behdad.org>
Date: 2026-10-05 (Mon, 05 Oct 2026)

Changed paths:
M Lib/fontTools/ttLib/tables/otTables.py
M Lib/fontTools/ttLib/ttGlyphSet.py
M Tests/ttLib/ttGlyphSet_test.py

Log Message:
-----------
[VARC] Derive component conditions from conditionIndex (#4237)

* [VARC] Derive component conditions from conditionIndex

Use conditionIndex rather than the derived HAVE_CONDITION flag when
drawing components. Constructors and XML import do not populate this
flag, so their component conditions were ignored until binary reload.

Clear HAVE_CONDITION on serialization when conditionIndex is None.
Removing a condition from a decoded component otherwise left the flag
set without writing an index, producing malformed component records.

Add regressions for both pen protocols, true and false conditions, XML
import, flag-free in-memory components, and condition removal before
and after binary round-tripping.

Testing: full pytest suite (5000 passed, 47 skipped, 2 xfailed); Black.

Assisted-by: OpenAI Codex

* [tests] Keep regression additions independent of sibling PRs


Commit: 4afd160ebeb052bb8a688a480198a3879d42f875
https://github.com/fonttools/fonttools/commit/4afd160ebeb052bb8a688a480198a3879d42f875
Author: Behdad Esfahbod <beh...@behdad.org>
Date: 2026-10-05 (Mon, 05 Oct 2026)

Changed paths:
M Lib/fontTools/ttLib/tables/otTables.py
M Tests/ttLib/tables/otTables_test.py
M Tests/ttLib/ttGlyphSet_test.py

Log Message:
-----------
[VARC] Initialize implicit scaleY when importing XML (#4238)

* [VARC] Initialize implicit scaleY when importing XML

When HAVE_SCALE_Y is absent, VARC uses scaleX for both dimensions.
Binary decoding already does this, but XML import left scaleY at 1.
Drawing imported XML before binary reload therefore changed outlines.

Initialize the implicit scaleY after reading the component's XML,
without setting HAVE_SCALE_Y or changing the serialized representation.

Test negative, zero, identity, and positive scales, explicit scaleY,
component binary round-tripping, and drawing a full XML-imported font.

Testing: full pytest suite (5005 passed, 47 skipped, 2 xfailed); Black.

Assisted-by: OpenAI Codex

* [tests] Move VARC XML imports to module scope


Commit: 82add4f1506691115d0469517a3e9cb74ba17682
https://github.com/fonttools/fonttools/commit/82add4f1506691115d0469517a3e9cb74ba17682
Author: Behdad Esfahbod <beh...@behdad.org>
Date: 2026-10-05 (Mon, 05 Oct 2026)

Changed paths:
M Lib/fontTools/ttLib/ttGlyphSet.py
M Tests/ttLib/ttGlyphSet_test.py

Log Message:
-----------
[VARC] Report current font coordinates for reset components (#4239)

RESET_UNSPECIFIED_AXES resets inherited coordinates to the font's
current settings, not to zero. Drawing already uses those settings,
but rawLocation passed to addVarComponent pens still contains zeroes
for unspecified axes, so replaying the component changes its outline.

Overlay the original font location on the full zero-filled location
before adding component overrides. Test segment and point pens at
default and non-default locations, with and without inherited axes,
and compare replayed recordings with directly decomposed outlines.

Testing: full pytest suite, 5004 passed; Black check.

Assisted-by: OpenAI Codex


Commit: f204843cb96af33395e51ebb2664eade7a54fb29
https://github.com/fonttools/fonttools/commit/f204843cb96af33395e51ebb2664eade7a54fb29
Author: Behdad Esfahbod <beh...@behdad.org>
Date: 2026-10-05 (Mon, 05 Oct 2026)

Changed paths:
M Lib/fontTools/cffLib/specializer.py
M Lib/fontTools/misc/psCharStrings.py
M Tests/cffLib/specializer_test.py
M Tests/misc/psCharStrings_test.py

Log Message:
-----------
[CFF2] Preserve operands when blending with zero regions (#4240)

* [CFF2] Preserve operands when blending with zero regions

Without a blender callback, op_blend discards delta operands from the
end of the stack. With zero active regions the slice starts at -0,
which clears the entire stack instead of preserving the default values
and any earlier operands. Default-instance drawing then fails at the
next path operator, while drawing with a blender works.

Only discard operands when there are active regions. Add source and
bytecode regressions covering zero, one, and two regions, one and two
blended values, and drawing with and without a blender.

Testing: full pytest suite, 5012 passed; 216 CFF/charstring tests;
Black check. Generated-font bounds agree with HarfBuzz and FreeType.

Assisted-by: OpenAI Codex

* [CFF2] Handle explicit and specialized zero-region blends

* [tests] Keep the default CFF2 region count nonzero in vsindex coverage


Commit: 3ca0f167722f1d3650c954d7fde772be573cbc1b
https://github.com/fonttools/fonttools/commit/3ca0f167722f1d3650c954d7fde772be573cbc1b
Author: Behdad Esfahbod <beh...@behdad.org>
Date: 2026-10-05 (Mon, 05 Oct 2026)

Changed paths:
M Lib/fontTools/ttLib/tables/TupleVariation.py
M Tests/ttLib/tables/TupleVariation_test.py
M Tests/ttLib/tables/otConverters_test.py

Log Message:
-----------
[ttLib] Handle empty packed deltas when optimizing for speed (#4241)

* [ttLib] Handle empty packed deltas when optimizing for speed

The speed-optimized packed-delta encoder calls min() and max() on its
input even when the input is empty. Saving a VARC AxisIndicesList with
an empty tuple then raises ValueError, whereas size-optimized saving
round-trips it correctly. Direct scalar TupleVariation.compileDeltas
calls also fail because their Y delta stream is empty.

Skip the fixed-width selection for an empty stream. Preserve the
existing bytearray when appending no deltas, and test scalar encoding
and VARC binary round trips under both optimization modes, including
lazy INDEX decoding.

Testing: full pytest suite, 4993 passed; Black check.

Assisted-by: OpenAI Codex

* [tests] Explain the lazy INDEX threshold in empty-delta coverage


Commit: 80451a197b42e5ee18c6c9d190ba9a4636752d35
https://github.com/fonttools/fonttools/commit/80451a197b42e5ee18c6c9d190ba9a4636752d35
Author: Behdad Esfahbod <beh...@behdad.org>
Date: 2026-10-05 (Mon, 05 Oct 2026)

Changed paths:
M Lib/fontTools/cffLib/specializer.py
M Lib/fontTools/designspaceLib/__init__.py
M Lib/fontTools/designspaceLib/split.py
M Lib/fontTools/feaLib/variableScalar.py
M Lib/fontTools/misc/psCharStrings.py
M Lib/fontTools/misc/transform.py
M Lib/fontTools/ttLib/scaleUpem.py
M Lib/fontTools/ttLib/tables/E_B_D_T_.py
M Lib/fontTools/ttLib/tables/_g_l_y_f.py
M Lib/fontTools/ttLib/ttGlyphSet.py
M Lib/fontTools/varLib/__init__.py
M Lib/fontTools/varLib/instancer/__init__.py
M Lib/fontTools/varLib/interpolatable.py
M Lib/fontTools/varLib/interpolate_layout.py
M Lib/fontTools/varLib/models.py
M Lib/fontTools/varLib/mutator.py
M README.rst
M Tests/cffLib/specializer_test.py
M Tests/designspaceLib/designspace_test.py
M Tests/designspaceLib/split_test.py
M Tests/feaLib/variableScalar_test.py
M Tests/misc/psCharStrings_test.py
M Tests/misc/transform_test.py
M Tests/ttLib/reorderGlyphs_test.py
M Tests/ttLib/scaleUpem_test.py
M Tests/ttLib/tables/E_B_D_T_test.py
M Tests/ttLib/tables/_c_m_a_p_test.py
M Tests/ttLib/tables/_g_l_y_f_test.py
M Tests/ttLib/tables/otBase_test.py
M Tests/ttLib/tables/otConverters_test.py
M Tests/ttLib/tables/otTables_test.py
M Tests/ttLib/ttGlyphSet_test.py
M Tests/varLib/featureVars_test.py
M Tests/varLib/instancer/instancer_test.py
M Tests/varLib/interpolatable_test.py
M Tests/varLib/interpolate_layout_test.py
M Tests/varLib/mutator_test.py
A Tests/varLib/reversed_axis_test.py

Log Message:
-----------
Merge main into beyond-64k after standalone fixes landed

Preserve the reviewed VORG compaction/rounding, lazy PairPos tests, atomic point-pen validation, zero-region CFF2 handling, null feature substitutions, and restored VARC scaling. Resolve the condition evaluator interaction and exercise VARC scaling for uppercase outline families.

Validation: 6352 tests and 83 subtests passed; 41 skipped, 2 xfailed. Black 26.3.1 and configured mypy passed.


Compare: https://github.com/fonttools/fonttools/compare/c74a5feb6998...80451a197b42
Reply all
Reply to author
Forward
0 new messages