Branch: refs/heads/avar2-instancing
Home:
https://github.com/fonttools/fonttools
Commit: fb65a7a8b68b5e65ada2cb4644259d99d46957d4
https://github.com/fonttools/fonttools/commit/fb65a7a8b68b5e65ada2cb4644259d99d46957d4
Author: Behdad Esfahbod <
beh...@behdad.org>
Date: 2026-08-04 (Tue, 04 Aug 2026)
Changed paths:
A Doc/partial-instancing-avar2-design.md
M Lib/fontTools/varLib/varStore.py
M Tests/varLib/varStore_test.py
Log Message:
-----------
[varStore] Rewrite getExtremes as a sound breakpoint-grid / interval bound
The previous implementation sampled the store only at region peaks (plus
identity-axis kinks), but the sum of overlapping same-axis tents attains
its extremum at other tents' start/end kinks, which were never visited:
the returned range could be NARROWER than the truly reachable one. Since
the avar2 instancer uses this bound to delete "unreachable" variation
regions, an under-wide bound silently corrupts instanced fonts (e.g. two
tents (0, .4, .6)/(.2, .5, .8) with deltas -16384/+8192 reported max=0
while the true max is +5461 at wght=0.6). The recursion was also
exponential in the number of distinct peaked axes per row (25s at k=20).
The store's output is piecewise multilinear in the axis coordinates, so
its exact extremes lie on the grid of per-axis tent breakpoints. The new
implementation enumerates that grid when it is small (real fonts: exact
answer, milliseconds) and otherwise falls back to a per-region interval
bound: exact per-region scalar ranges, correlations between regions
ignored — conservative by construction, O(regions × axes), so crafted
many-axis rows can no longer hang the instancer.
Add unit tests: the deterministic counterexample above, a randomized
conservativeness property test against brute-force breakpoint-grid ground
truth (the old implementation fails both), and a 30-axis fallback test
(the old implementation hangs on it). Also document VarStoreInstancer's
region-support caching (construct a new instancer after mutating the
store).
Co-Authored-By: Claude Fable 5 <
nor...@anthropic.com>
Commit: 23b7e07e6da5c8d32e98a1b8ef68ce6ce28fcb1a
https://github.com/fonttools/fonttools/commit/23b7e07e6da5c8d32e98a1b8ef68ce6ce28fcb1a
Author: Behdad Esfahbod <
beh...@behdad.org>
Date: 2026-08-04 (Tue, 04 Aug 2026)
Changed paths:
M Lib/fontTools/ttLib/tables/_a_v_a_r.py
M Lib/fontTools/varLib/instancer/__init__.py
M Tests/ttLib/tables/_a_v_a_r_test.py
Log Message:
-----------
[avar/instancer] Fix avar v1 renormalization: quantize after map, don't warp distances
Three regressions in the refactored avar v1 normalization path (they
affected every avar font instanced with fontTools, not just avar2 ones),
plus removal of the dead avar v2 body:
- The F2Dot14 quantization is applied once, AFTER the avar v1 segment
map (normalize → map → quantize), matching main and what F2Dot14-based
renderers evaluate. It had moved to before the map, so instances were
sampled at post-avar coordinates not representable in F2Dot14 (e.g.
pinning wght=650 through {0.7: 0.2} landed on 0.142857… instead of the
quantized 0.14288330078125), diverging from HarfBuzz/FreeType and from
hb-subset instances.
- renormalizeAxisLimits now maps only the three coordinates of each
limit; the trailing NormalizedAxisTripleAndDistances fields are
USER-space distances and were being warped through the avar curve,
corrupting distance-weighted tent rebasing for axes with user-space
spans below 1.
- An avar 2.0 table with a NULL VarStore (legal; behaves as plain v1)
crashed normalize() by entering the version-2 body. That body was
unreachable from the library, internally wrong on several counts, and
forced ttLib to import varLib.instancer at module import time — a
layering inversion. Delete it: renormalizeAxisLimits is v1-only and
returns a plain dict, and the instancer/deepcopy imports are gone from
_a_v_a_r.py.
Also merge AxisLimits.normalize()'s two loops (removing the unreachable
'triple is None' second-loop branch), and drop _BaseAxisLimits.__setitem__
(only the deleted v2 body needed mutability), restoring the immutability
contract.
Co-Authored-By: Claude Fable 5 <
nor...@anthropic.com>
Commit: 9358bafaccffcb3f0f361408e9c6db266e744b1c
https://github.com/fonttools/fonttools/commit/9358bafaccffcb3f0f361408e9c6db266e744b1c
Author: Behdad Esfahbod <
beh...@behdad.org>
Date: 2026-08-04 (Tue, 04 Aug 2026)
Changed paths:
M Lib/fontTools/varLib/instancer/__init__.py
R Lib/fontTools/varLib/instancer/avar2.py
M Tests/varLib/instancer/instancer_test.py
Log Message:
-----------
[instancer] Fix avar2 partial-instancing edge cases from review
- Pad a truncated avar2 VarIdxMap (compilation trims trailing duplicate
entries; reads clamp) to fvar's axis count before the indexed writes in
offset compensation and shared-row privatization. Restricting an axis
whose entry lay beyond the stored mapping (e.g. wght in fontations'
Amstelvar-avar2.A.ttf, whose 12-axis font stores a 5-entry mapping)
raised IndexError.
- Drop the avar table early only when ALL fvar axes are pinned (or, for
fonts without an avar2 VarStore, when all segment-mapped axes are, as
before). segments can be empty or partial in a valid avar2 font, and
pinnedAxes.issuperset({}) is vacuously true, so a pure range
restriction deleted the entire avar2 VarStore and then crashed.
- Use strict comparisons in _isTupleVariationDead: supportScalar returns
1 (not 0) at v == peak, so a tent whose peak sits exactly on the
reachable boundary was culled while contributing at full weight.
- Fall back to the fvar default when a restricted axis's limit default
is unpopulated (2-tuple input) in offset compensation, instead of
TypeError; and make instantiateAvar's normalizedLimits parameter
optional again (derived when absent), restoring the public
(varfont, axisLimits) call form.
- Cull IVS tables from a fixed tag list (HVAR/VVAR/MVAR/GDEF/BASE/COLR)
instead of probing every table in the font, which force-decompiled
unrelated tables; VARC's MultiVarStore is left as a TODO.
- Deduplicate the twice-copied variation-table dispatch into
_instantiateVariationTables; the second copy had already drifted
(downgradeCFF2 silently dropped). In avar2 mode downgradeCFF2 is now
explicitly rejected with a warning (the result is still variable).
- Cleanups: import NO_VARIATION_INDEX from varStore instead of three
local re-definitions (and drop the unused OnlineVarStoreBuilder
import); build DeltaSetIndexMaps via builder.buildDeltaSetIndexMap;
_computeOldIntermediates reuses the module's normalize() helper;
_estimateAvar2OffsetError skips its second pass when breakpoint
collection added nothing and additionally samples every kink preimage
so the worst kink cannot fall between uniform samples; delete the
avar2.py __main__-only debug script that shipped in the package.
Add regression tests for each fix, plus a culling-soundness test that
maps a grid of user locations through the original font's spec pipeline
and requires every culled gvar variation to be dead at all of them.
Co-Authored-By: Claude Fable 5 <
nor...@anthropic.com>
Commit: d964f8b1be89d22bbb3d838c4f65f9a6e22a7c7d
https://github.com/fonttools/fonttools/commit/d964f8b1be89d22bbb3d838c4f65f9a6e22a7c7d
Author: Behdad Esfahbod <
beh...@behdad.org>
Date: 2026-08-04 (Tue, 04 Aug 2026)
Changed paths:
M Doc/partial-instancing-avar2-design.md
R partial-instancing-avar2-design.md
Log Message:
-----------
Move avar2 partial-instancing design doc under Doc/
Also drop the references to partial-instancing.pdf, which is not in the
repository.
Co-Authored-By: Claude Fable 5 <
nor...@anthropic.com>
Compare:
https://github.com/fonttools/fonttools/compare/33bc2aa52d98...d964f8b1be89
To unsubscribe from these emails, change your notification settings at
https://github.com/fonttools/fonttools/settings/notifications