Skip to content

[skera] Support TrueType partial instancing and axis ranges - #2269

Closed
behdad wants to merge 12 commits into
skera-tt-full-instancefrom
skera-tt-partial-instance
Closed

behdad wants to merge 12 commits into
skera-tt-full-instancefrom
skera-tt-partial-instance

Conversation

@behdad

@behdad behdad commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Pin TrueType axes or restrict their ranges, fold default gvar/cvar deltas into outlines, metrics, and control values, and emit residual tuples over the surviving axes. Rebase requested axes in tag order, infer missing IUP deltas, merge equal tents, discard constants, and compile shared peaks and packed streams. Preserve untouched avar segment maps.

The self-audit corrects four arithmetic mismatches against current HarfBuzz: reuse the reader's float tuple scalar, infer residual IUP deltas in float, use float tent rebasing, and renormalize changed avar knots in float with half-unit rounding toward positive infinity. The shared solver has one implementation parameterized by scalar type; existing CFF/ItemVariationStore types are preserved. Public regressions cover each mismatch, including second-stage instancing.

Validation: 485 Skera tests and 383 write-fonts tests with all features; Clippy with warnings denied; Rust 1.85 checks. All 204 full TrueType cases match HarfBuzz. The broader sweep covers 132 partial cases and 660 second-stage locations without execution/decoding errors. Two glyph-bounds discrepancies are HarfBuzz's stale-header default-pin bug, fixed and cross-checked in harfbuzz/harfbuzz#6436. The self-contained avar2 pin choice is handled in the later axis-compensation PRs. Other sampled partial outputs and second-stage results agree.

Checked-in references cover pins, shifted defaults, ranges, components, cvar, and flat avar segments. Stream serializers have writer-oracle tests at integer/count/run boundaries. Quantized residuals do not imply exact equivalence for every possible composition.

Stacked on #2268. Shared readers and write-fonts owned models remain in use; there is no secondary generated table tree.

@behdad
behdad added this pull request to stack #2267 October 10, 2026 03:27
@behdad
behdad force-pushed the skera-tt-partial-instance branch from d0dfddc to 221c27f Compare October 10, 2026 18:08
@behdad
behdad requested a review from qxliu76 October 10, 2026 18:09
@behdad
behdad force-pushed the skera-tt-partial-instance branch from 221c27f to f626b14 Compare October 12, 2026 03:19
@behdad
behdad removed this pull request from stack #2267 October 12, 2026 03:28
@behdad
behdad added this pull request to stack #2335 October 12, 2026 03:29
@behdad
behdad force-pushed the skera-tt-partial-instance branch 3 times, most recently from f5ddd30 to f53bd1d Compare October 12, 2026 22:04
@behdad
behdad force-pushed the skera-tt-partial-instance branch from f53bd1d to 73cb0fc Compare October 14, 2026 23:11
@behdad

behdad commented Oct 22, 2026

Copy link
Copy Markdown
Contributor Author

The restack exposed one runtime regression in this PR and its four immediate descendants (#2270–#2273): removed_variation_stores_and_rows_evaluate_to_zero fails with a GDEF error. condition_delta assumes an out-of-range outer index returns None; the offset-array accessor instead returns an error for that index. An explicit outer-index bounds check is needed, along with the inner-row check. Positioning's wide_delta already returns zero, and #2274's replacement evaluator already handles these cases.

I compiled every restacked boundary and tested the final tip, but did not run each intermediate boundary's tests. That missed this regression. I will run the full Skera test suite on each of the five affected versions, then push the fixes and ancestry updates atomically. Later source trees can remain identical to their already validated versions.

Check both variation indexes before reading the row. Extend the existing
regression to cover large indexes and the no-variation sentinel.

Assisted-by: OpenAI Codex
@behdad

behdad commented Oct 22, 2026

Copy link
Copy Markdown
Contributor Author

Fixed and pushed the missing variation-row checks on all five affected PRs. The full Skera all-feature test suite passed separately at every affected boundary:

Clippy and formatting checks pass. All 50 later branches were updated with tree-preserving ancestry merges, and the 55 branches were pushed atomically. Replacement CI is running. The final source tree is unchanged from the previously validated integration tip.

Retain both full and partial instancing regressions while propagating the reviewed parent.

Assisted-by: OpenAI Codex
@behdad

behdad commented Oct 22, 2026

Copy link
Copy Markdown
Contributor Author

The self-audit of this PR found a reproducible regression against current HarfBuzz in the new tuple_scalar helper. It computes ratios/products in double precision, replacing the shared reader's float evaluation. HarfBuzz switched this path back to floats in #6424; the comment claiming double accumulation is stale.

A generated four-axis TrueType fixture demonstrates this through the public CLI in both full and partial instancing. Its three active peak coordinates are [15656, 10041, 998] / 16384, requested coordinates are [4226, 2619, 686] / 16384, and its outline/CVT delta is 28443. Skera currently stores 1377; HarfBuzz stores 1376. The extra precision moves the product across the half-unit rounding boundary.

I will remove the private scalar implementation and use TupleVariation::compute_scalar_f32 again, with a checked-in regression covering both outlines and CVT values, full and partial instancing. I am also checking residual IUP interpolation and tent rebasing for assumptions left over from the same HarfBuzz precision change.

Design review: the residual-tuple layer has a useful separation between reading tuples, rebasing/inference, and writing packed streams, and it bounds tuple/header/data sizes explicitly. Its direct packed-delta serializer already has a shared-writer oracle at integer and run boundaries. I will retain those boundaries while correcting the arithmetic and verifying second-stage instancing.

@behdad

behdad commented Oct 22, 2026

Copy link
Copy Markdown
Contributor Author

The residual-IUP check confirms a second instance of the same stale arithmetic policy. For contour x coordinates [-8619, 17995, 24288] and referenced x deltas [13272, None, 31608], float interpolation produces 28101.5, while the current double interpolation, cast back to float, produces 28101.498046875. The stored residual delta is consequently 28101 in Skera versus 28102 in current HarfBuzz. This is reproduced through ordinary partial instancing with an unrelated axis pinned, without changing the contour itself.

I will use float interpolation here too and retain the example in the public instancing regression fixture. This change concerns TrueType tuple evaluation and inference; the CFF/ItemVariationStore precision policies need to be assessed separately at their respective consumers.

@behdad

behdad commented Oct 22, 2026

Copy link
Copy Markdown
Contributor Author

The tent-rebasing check also reproduces a precision difference in serialized residual regions. For a tent (0.18243408203125, 0.64068603515625, 0.76654052734375) and requested range/default (0.4146728515625, 0.6053466796875, 0.81060791015625), Skera writes a split boundary at 3596/16384; current HarfBuzz writes 3595/16384. The deltas and default outlines agree, but the surviving variation function differs.

I will make the existing solver support both scalar types using one implementation, use f32 for TrueType tuple tents, and preserve the existing CFF/ItemVariationStore consumers' types. This avoids copying the solver and makes the precision policy explicit at the tuple layer. The fixture now covers the scalar, IUP, and tent-boundary cases independently.

Reuse the reader float scalar evaluator and infer residual IUP deltas in float. Parameterize the existing tent solver so TrueType uses float tents without changing CFF or ItemVariationStore precision. Keep independent public regressions for outline/CVT rounding, residual IUP, and serialized region boundaries.

All-feature Skera and writer suites, Clippy with warnings denied, and Rust 1.85 checks pass. Each regression reproduces a difference against current HarfBuzz before the fix.

Assisted-by: OpenAI Codex
@behdad

behdad commented Oct 22, 2026

Copy link
Copy Markdown
Contributor Author

The broader partial-instance sweep exposed one more float/double mismatch in rebuilding a changed avar segment map. With MPLUS1-Variable.ttf and wght=100:428:900, the knot at -6393/16384 maps to -9276/16384 in Skera versus -9275/16384 in current HarfBuzz. Although the residual gvar tuples are identical, a second full instance at weight 300 then differs at nine glyphs. The segment-map renormalization still uses double arithmetic, while HarfBuzz's renormalizeValue now takes floats. I will use the shared generic solver's float renormalization at this table boundary and keep a regression for the map and second-stage outline.

Two other sweep discrepancies are glyph bounding boxes when HarfBuzz pins an already-partial font at its default. Cross-instancing both partial outputs reproduces the same result: Skera recomputes the bounds, while HarfBuzz retains the source glyph header even though it recomputes metrics/global bounds. In the Anek example, the stored component offset 749 plus the child xMax 256 gives xMax 1005; HarfBuzz leaves 1004. I am treating this separately as a HarfBuzz follow-up rather than reproducing the stale bounds in Skera.

Renormalize changed map coordinates in float and use HarfBuzz rounding toward positive infinity at half units. Keep the MPLUS shifted-default regression and a second-stage outline assertion. Use a bounded sparse contour in the tuple arithmetic fixture so all axis extrema remain representable.

Validation: full Skera and writer all-feature tests, Clippy with warnings denied, Rust 1.85 checks, formatting, and typos pass.

Assisted-by: OpenAI Codex
@behdad

behdad commented Oct 22, 2026

Copy link
Copy Markdown
Contributor Author

Pushed all four precision corrections through the stack: shared-reader float tuple scalars, float residual IUP interpolation, one generic tent solver with TrueType float tents, and float avar-map renormalization with ties toward positive infinity. Public regressions exercise stored outline/CVT rounding, inferred residual deltas, rebased region boundaries, and a second-stage avar instance.

The reviewed boundary passes 485 Skera tests and 383 writer tests, Clippy and Rust 1.85 checks. All 204 full TrueType cases match HarfBuzz. The 132 partial cases and 660 second-stage locations have no execution/decoding errors. The Anek and Roboto default-pin header discrepancies match after HarfBuzz #6436; the self-contained avar2 policy remains the explicit later #2291 slice.

@behdad

behdad commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by the rebuilt, consolidated instancing stack in #2416–#2423. MATH and name-table overrides remain separately covered by #2415 and #2411.

@behdad behdad closed this Oct 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant