Conversation
Adds the variation-tracking layer on GlyphTypeface without yet plumbing the variation into Skia rendering or HarfBuzz shaping (those are pr4e1 and pr4e2, gated on the SkiaSharp 4.147 bump). After this slice, code holding a GlyphTypeface can call WithVariation(settings) to obtain a variation-bound clone that tracks the variation point; downstream slices that read GlyphTypeface.VariationSettings (gvar in pr4c, HVAR in pr4d, COLRv1 paint resolution) become variation-correct as they land. Interface additions: - IPlatformTypeface gains VariationSettings (default => default) and WithVariation (default => this) — the no-op contract that keeps Tier 1 shippable without a platform override. Skia implements the real Clone in PR4e1. - ITextShaperTypeface gains the symmetric pair with the same defaults for the HarfBuzz path in PR4e2. GlyphTypeface additions: - VariationSettings property exposes the bound variation point. - WithVariation(FontVariationSettings) returns a variation clone, with a per-source cache so repeat requests for equal settings return the same instance. The cache is unbounded in pr4b — LRU eviction is a follow-up (planning doc, "Cache sizing"). - Private clone constructor reference-shares every parsed table (cmap, glyf, fvar, avar, hmtx, vmtx, name, os2, etc.) and only varies the platform typeface (no-op in pr4b), variation settings, and lazy shaper. Memory cost per variation is a few dozen bytes, not the full table set. - TextShaperTypeface getter on clones derives from the source's shaper via ITextShaperTypeface.WithVariation so face-level shaping state stays shared. - Dispose disposes cached variation clones before the platform typeface to keep ownership clean. Clones skip platform typeface disposal in pr4b (shared with source); the _ownsPlatformTypeface flag flips on automatically once pr4e1 lands a real Skia clone. FontCollectionKey extension: - Adds an init-only Variation property (FontVariationSettings). Default-instance / static-font case uses default(...) so existing three-arg constructions and cache equality stay unchanged. - FontCollectionKeyExtensions.ToFontCollectionKey propagates the variation from both GlyphTypeface and IPlatformTypeface so future consumers building keys from a varied typeface get the right cache entry automatically. Behavior: - WithVariation(default) on a varied clone returns the source — the source IS the default-instance typeface, so cache identity holds. - WithVariation on a static font returns this unchanged (silent ignore — matches CSS and matches CreateVariationSettings). - WithVariation(equal settings) returns the same cached instance. The FontCollection-level projection (FontWeight=Bold on a variable font automatically resolves via wght axis) is deferred. The cache infrastructure is in place; pr4d will wire the projection through FontCollectionBase when HVAR makes it visibly correct. Tested with 15 unit tests covering static/variable behavior, cache identity, clone-routes-through-source, table sharing, and FontCollectionKey equality. All 493 Media tests still pass; render tests still green.
The lazy getter raced: concurrent first access could create two shapers and leak the loser. Use double-checked locking over a dedicated lock with the backing field marked volatile, so the lock-free fast path observes a fully published instance and exactly one shaper is ever created.
Exercises the synchronized TextShaperTypeface getter from many threads racing into the first access, asserting exactly one shaper is created (no losing-thread leak).
The record-synthesized equality includes the new Variation property but the hand-written CompareTo did not, so keys differing only in variation compared equal while Equals said otherwise - an IComparable contract violation. Compare the variation coordinates lexicographically (they are sorted by axis and zero-canonicalized at construction, so the element-wise order is a total order that agrees with Equals).
WithVariation and the platform/shaper seams traffic in normalized coordinates, which are internal after the user-space refactor. Give users a public entry point that speaks user space and normalizes inside: - public GlyphTypeface.WithVariations(FontVariationSettings, int? instanceIndex) = CreateNormalizedPosition + internal WithVariation - normalization runs before the cache lookup, so user-space requests that clamp to the same axis position share one clone (pinned by test) - named-instance selection by index rides the same path
A shaper without variation support returns the source's own shaper typeface from WithVariation, so the clone and its source hold the same instance. Disposing the clone must leave the source's shaper usable. - shared instance: the source's shaper must survive the clone's Dispose - distinct instance: the clone disposes the shaper it owns
ITextShaperTypeface.WithVariation defaults to returning the same instance, so a clone of a font whose shaper has no variation support holds its source's shaper typeface. Disposing the clone disposed that shared instance and left the source with a dead shaping font. - the clone records whether WithVariation produced a distinct instance - Dispose releases the shaper only when the clone owns it
Gillibald
force-pushed
the
pr4b/glyph-typeface-with-variation
branch
from
September 29, 2026 14:37
43f07e6 to
abe5493
Compare
NuGet packagesThe packages for this pull request have been published (workflow run):
|
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part 7 of 17 of the glyph-outline / variable-font workstream. Stacked on Part 6 (
pr4a/fvar-avar-parsing); please merge that one first. Parts 8 to 11 plug the individual variation tables into the clone this PR builds, so none of them can land before it.What does the pull request do?
Adds the variation clone and the public front door onto it.
WithVariationstakes user-space axis values, optionally on top of a named instance, and returns a typeface bound to that position. Explicit settings override the named instance per axis.WithVariation(NormalizedVariationPosition)is the normalized-space counterpart the variation tables use, and an internalVariationPositionreads the clone's position back.IPlatformTypefaceandITextShaperTypefacegain the same surface with default no-op overrides.FontCollectionKeycarries the variation, so the sameTypefaceat two differentwghtvalues resolves to two cache entries instead of colliding.What is the updated/expected behavior with this PR?
this. Nothing is allocated.GlyphTypeface.FontCollectionproduces the same identity-stable clones as callingWithVariationsdirectly.WithVariationreturns itself, and the clone shares the source's platform typeface rather than owning a second one. A platform that does override gets its instance owned by the clone. Ownership follows the returned reference, so disposal never double-frees.Outlines, advances and metrics are still the default instance here. A clone at
wght=900reportswght=900and draws exactly like the default until Parts 8 to 11 land.How was the solution implemented (if it's not obvious)?
FontCollectionKeyincludes the variation in its ordering as well as its equality, so keys remain totally ordered when a collection sorts them.Checklist
Breaking changes
None.
WithVariationsis new, the interface members have default implementations, andFontCollectionKey's new field defaults to no variation.Obsoletions / Deprecations
None.
Fixed issues
None.
🤖 Generated with Claude Code