Add color font palette support and improve variable font robustness#3742
Conversation
Native layer (mono/skia submodule):
- C API: sk_typeface_get_variation_design_position,
sk_typeface_get_variation_design_parameters, sk_typeface_clone_with_arguments
- New structs: sk_fontarguments_variation_axis_t,
sk_fontarguments_variation_position_coordinate_t
SkiaSharp managed bindings:
- SKTypeface: GetVariationDesignParameters, GetVariationDesignPosition, Clone
- SKFontVariationTag: Make("wght") / GetName(tag) helper
- TagName property on SKFontVariationAxis and SKFontVariationDesignPositionCoordinate
- Generated P/Invoke bindings and struct type mappings in libSkiaSharp.json
HarfBuzzSharp managed bindings:
- Face: HasVariationData, axis queries, named instance enumeration
- Font: SetVariations, Set/GetVarCoords, SetVarNamedInstance
- P/Invoke bindings for hb-ot-var.h (removed from exclude list)
Tests:
- HBFaceTest, HBFontTest, SKTypefaceTest, SKVariableFontRenderingTest
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Face: validate instanceIndex >= 0 on named instance methods - Face: explicit (int) cast on uint array lengths - Font: validate instanceIndex >= 0 on SetVarNamedInstance - Tests: dispose Blob in CreateVariableFace/CreateVariableFontPair helpers Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Interactive demo that showcases the new variable font APIs: - Weight axis slider (100–900) with real-time text rendering - Optical size axis slider (14–32) - Weight spectrum showing all 9 standard weights - Uses Inter Variable font (SIL OFL license, 2 axes: wght, opsz) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Default parameters break ABI stability. Split into two overloads: - Clone(position) delegates to Clone(position, 0) - Clone(position, collectionIndex) contains the implementation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Incorporates libpng 1.6.58, libexpat 2.7.5, and stream move int32 fix. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…pport # Conflicts: # binding/SkiaSharp/SkiaApi.generated.cs
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Face: - GetVariationAxisCount() → VariationAxisCount property - GetNamedInstanceCount() → NamedInstanceCount property - Add GetVariationAxisInfos(Span<>) overload - Add GetNamedInstanceDesignCoordsCount() - Add GetNamedInstanceDesignCoords(int, Span<float>) overload Font: - Rename SetVarCoordsDesign → SetVariationCoordsDesign - Rename SetVarCoordsNormalized → SetVariationCoordsNormalized - Rename SetVarNamedInstance → SetVariationNamedInstance - GetVarCoordsNormalized() → VariationCoordsNormalized property - Add GetVariationCoordsNormalized(Span<int>) overload Tests: add span-vs-array equivalence tests for all overloads. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Face tests: - VariationAxisCount matches GetVariationAxisInfos().Length - Span overload with oversized buffer - NamedInstanceCount asserts > 0 (Distortable.ttf has 3 instances) - DesignCoordsCount matches AxisCount (per HB docs, return value) - DesignCoords array length matches count - Span vs array equivalence for design coords - All named instances have non-empty design coords - SubfamilyNameId returns valid name ID - PostScriptNameId does not throw - Negative index throws for all 5 instance methods Font tests: - Span GetVariationCoordsNormalized returns total length with empty buffer - SetVariationCoordsDesign affects normalized coords - Negative index throws for SetVariationNamedInstance - Span returns 0 for static font Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Follows existing HarfBuzzSharp convention where parameterless array-returning getters are properties (Tables, GlyphInfos, GlyphPositions, VariationCoordsNormalized). The Span<> overload remains a method since it takes a parameter. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
SKFontVariationTag was a static helper class that didn't correspond to any Skia type. Replace it with SKFourByteTag, a proper struct matching Skia's SkFourByteTag (uint32_t) with the same bit layout. SKFourByteTag provides: - Parse(string) for creating tags from strings like "wght" - ToString() for converting back to four-character strings - Implicit uint conversion (matches how Skia uses uint32_t) - IEquatable<T>, ==, != operators - char constructor matching SkSetFourByteTag(a,b,c,d) Follows the same pattern as HarfBuzzSharp.Tag but in the SkiaSharp namespace, wrapping Skia's native type rather than HarfBuzz's. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The primary type in this file is now SKFourByteTag. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The generator's member mapping only supports name remapping (string), not type overrides, so the generated struct fields stay as UInt32. With implicit uint <-> SKFourByteTag conversion, users cast explicitly: SKFourByteTag tag = axis.Tag; string name = tag.ToString(); Revert the libSkiaSharp.json change that tried object-style members. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add sk_fourbytetag_t → SKFourByteTag mapping in libSkiaSharp.json - Regenerate SkiaApi.generated.cs: SKFontVariationAxis.Tag and SKFontVariationDesignPositionCoordinate.Axis are now SKFourByteTag - axis.Tag.ToString() now returns "wght" directly (no cast needed) - Update tests to use SKFourByteTag.Parse instead of raw hex Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Doc comments are inserted via a localized repo process, not manually. Fix Assert.NotEqual type inference after Tag became SKFourByteTag. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pure value type with no reference fields — nullable context is fine. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…and Span overloads Rename: - SKFontVariationDesignPositionCoordinate → SKFontVariationPositionCoordinate (matches C++ SkFontArguments::VariationPosition::Coordinate — no 'Design' in the struct name; 'Design' belongs in the method names only) Add count properties for span-based usage: - VariationDesignParameterCount - VariationDesignPositionCount Clone now takes ReadOnlySpan instead of array. Tests: span vs property equivalence, static font span returns, ReadOnlySpan clone. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Skia: - Add sk_fontarguments_palette_override_t C struct - Extend sk_typeface_clone_with_arguments with palette parameters - Add SKFontPaletteOverride generated struct - Add Clone overload with paletteIndex + palette overrides HarfBuzz Face: - HasPalettes, PaletteCount properties - GetPaletteColors() + Span<uint> overload - GetPaletteFlags(), GetPaletteNameId(), GetPaletteColorNameId() - HasColorLayers, HasColorPng, HasColorSvg properties Tests (18 new): - Color font test_glyphs-COLRv1.ttf (21KB, 3 palettes, 14 entries) - Palette queries: count, colors, flags, name IDs - Span vs array equivalence for palette colors - Different palettes have different colors - Negative index validation for all palette methods - SKTypeface Clone with palette override and palette index Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Demonstrates COLRv1 color font palette selection using the test glyph font (3 palettes, 14 color entries). Shows: - Palette index slider to switch between built-in palettes - Color swatch display for current palette entries - Glyph picker for different Unicode codepoints - Text size slider Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
API surface improvements: - SKFontVariationTag → SKFourByteTag struct (matches Skia's SkFourByteTag) - SKFontVariationDesignPositionCoordinate → SKFontVariationPositionCoordinate - Properties instead of Get*() for parameterless array getters - Span<T> and ReadOnlySpan<T> overloads for all APIs - Count properties (VariationDesignParameterCount, VariationDesignPositionCount) - Overloads instead of default parameters (ABI stability) - File-scoped namespaces in new files - Consistent 'Variation' naming (not 'Var') in HarfBuzz C API cleanup (skia submodule): - sk_fourbytetag_t typedef, DEF_MAP for converters - static_assert in sk_structs.cpp - As*/To* helpers in sk_types_priv.h HarfBuzz Face: - VariationAxisCount, NamedInstanceCount → properties - VariationAxisInfos → property - Span overloads + GetNamedInstanceDesignCoordsCount - Fixed return-value semantics for hb_ot_var_named_instance_get_design_coords HarfBuzz Font: - Renamed SetVar* → SetVariation* for consistency - VariationCoordsNormalized property + Span overload Tests: comprehensive coverage for all new APIs (131 tests). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
📦 Try the packages from this PRWarning Do not run these scripts without first reviewing the code in this PR. Step 1 — Download the packages bash / macOS / Linux: curl -fsSL https://raw.githubusercontent.com/mono/SkiaSharp/main/scripts/get-skiasharp-pr.sh | bash -s -- 3742PowerShell / Windows: iex "& { $(irm https://raw.githubusercontent.com/mono/SkiaSharp/main/scripts/get-skiasharp-pr.ps1) } 3742"Step 2 — Add the local NuGet source dotnet nuget add source ~/.skiasharp/hives/pr-3742/packages --name skiasharp-pr-3742More options
Or download manually from Azure Pipelines — look for the Remove the source when you're done: dotnet nuget remove source skiasharp-pr-3742 |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
📖 Documentation Preview The documentation for this PR has been deployed and is available at: 🔗 View Staging Site This preview will be updated automatically when you push new commits to this PR. This comment is automatically updated by the documentation staging workflow. |
SKFontArguments is a ref struct with ReadOnlySpan properties for allocation-free usage. Clone API: - Clone(ReadOnlySpan<Position>) — variable font shortcut - Clone(int paletteIndex) — color palette shortcut - Clone(SKFontArguments) — full control (position, collection, palette, overrides) Removes the noisy (span, int, int, span) overload. Simplifies ColorFontSample from Clone(emptySpan, 0, idx, emptySpan) to Clone(idx). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Incorporates sk_fontmgr_legacy_create_typeface C API fix (#198). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds OpenType variable-font robustness and introduces color font palette support across SkiaSharp + HarfBuzzSharp, with new samples and expanded test coverage.
Changes:
- Add SKTypeface palette cloning support via
SKFontArguments(palette index + overrides) and a newSKFontPaletteOverrideinterop struct. - Add HarfBuzzSharp
FaceAPIs for querying palettes/colors/flags/name IDs and color table presence. - Expand tests for undersized Span buffer behavior and add a new Gallery sample for color palettes (Nabla).
Reviewed changes
Copilot reviewed 12 out of 16 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Tests/SkiaSharp/SKTypefaceTest.cs | Adds tests for undersized Span buffers and palette clone APIs. |
| tests/Tests/HarfBuzzSharp/HBFontTest.cs | Tightens variation tests and aligns Span semantics with “written count”. |
| tests/Tests/HarfBuzzSharp/HBFaceTest.cs | Adds palette/color font API tests (colors, flags, IDs, negative index checks). |
| tests/Content/fonts/test_glyphs-COLRv1.ttf | Adds a COLRv1/CPAL test font for palette coverage. |
| samples/Gallery/Shared/Samples/ColorFontSample.cs | New Gallery sample demonstrating palette selection and preview grid. |
| samples/Gallery/Shared/SampleMedia.cs | Exposes embedded Nabla + COLRv1 test glyphs fonts to samples. |
| samples/Gallery/Shared/Media/COLRv1-TestGlyphs.ttf | Adds COLRv1 test font for sample media. |
| binding/libSkiaSharp.json | Maps new native palette override struct to managed type. |
| binding/SkiaSharp/SkiaApi.generated.cs | Updates native clone signature + generates SKFontPaletteOverride. |
| binding/SkiaSharp/SKTypeface.cs | Implements palette clone overload and Span “undersized buffer” behavior. |
| binding/SkiaSharp/SKFontArguments.cs | Extends arguments with PaletteIndex and PaletteOverrides. |
| binding/HarfBuzzSharp/Font.cs | Fixes Span getter to return written count for normalized coords. |
| binding/HarfBuzzSharp/Face.cs | Adds palette/color-table query APIs and palette color retrieval. |
Fix pre-existing bug: SKTypeface.ContainsGlyphs(ReadOnlySpan<byte>, SKTextEncoding) called itself instead of GetFont().ContainsGlyphs(), causing StackOverflowException. Add tests: - ContainsGlyphsWithByteSpanDoesNotStackOverflow (confirms fix) - HasColorPngIsFalseForStaticFont - HasColorSvgIsFalseForStaticFont Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Empty span (length 0) now returns 0 immediately without calling native or allocating from the pool - Clone(int paletteIndex) now throws ArgumentOutOfRangeException for negative indices, matching HarfBuzz Face palette methods - Add test: CloneWithNegativePaletteIndexThrows Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add missing tests to ensure every Span API is tested for: empty (0), undersized, exact, and oversized buffers. HarfBuzz Face: - GetVariationAxisInfos: add empty buffer test - GetNamedInstanceDesignCoords: add empty and oversized tests - GetPaletteColors: add empty buffer test HarfBuzz Font: - GetVariationCoordsNormalized: add oversized test Skia SKTypeface: - GetVariationDesignParameters: add oversized test - GetVariationDesignPosition: add oversized test Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
80170a5 to
323d3f7
Compare
COLRv1-TestGlyphs.ttf and its SampleMedia property were never used by any sample. The font remains in tests/Content/fonts/ where it's used by unit tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds variable font robustness plus color font palette APIs across SkiaSharp/HarfBuzzSharp, with expanded tests and a new gallery sample.
Changes:
- Fixes/extends variable font Span APIs (undersized/oversized/empty buffers) and related regression tests.
- Introduces color font palette support (palette counts/flags/name IDs/colors, palette overrides, palette-aware typeface cloning).
- Adds a new Color Fonts gallery sample and updates embedded media.
Reviewed changes
Copilot reviewed 12 out of 15 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Tests/SkiaSharp/SKTypefaceTest.cs | Adds regression + buffer-sizing tests for variable fonts and palette cloning. |
| tests/Tests/HarfBuzzSharp/HBFontTest.cs | Tightens variation tests and adds undersized-buffer coverage. |
| tests/Tests/HarfBuzzSharp/HBFaceTest.cs | Adds extensive palette/color font API tests. |
| samples/Gallery/Shared/Samples/ColorFontSample.cs | New interactive sample demonstrating palette selection rendering. |
| samples/Gallery/Shared/SampleMedia.cs | Adds Nabla font stream for the new sample. |
| externals/skia | Updates Skia submodule for new C APIs. |
| binding/libSkiaSharp.json | Maps new native palette override struct to managed type. |
| binding/SkiaSharp/SkiaApi.generated.cs | Updates clone_with_arguments P/Invoke signature + adds SKFontPaletteOverride struct. |
| binding/SkiaSharp/SKTypeface.cs | Fixes recursion bug, improves Span behavior, and adds palette cloning/args. |
| binding/SkiaSharp/SKFontArguments.cs | Extends font arguments with palette index and overrides. |
| binding/HarfBuzzSharp/Font.cs | Changes Span getter semantics to return written count. |
| binding/HarfBuzzSharp/Face.cs | Adds HarfBuzz color palette APIs (counts, colors, flags, name IDs, formats). |
Add color font palette support to C API (#197) Requires: mono/SkiaSharp#3742 Extends the variable font C API (merged in #185) with color palette support, enabling SkiaSharp to clone typefaces with specific palette selections and per-color overrides for COLRv1/COLR fonts. New C API surface: * sk_fontarguments_palette_override_t — struct mapping to SkFontArguments::Palette::Override (index + color) * sk_typeface_clone_with_arguments — extended from 4 to 7 parameters, adding paletteIndex, paletteOverrides, and paletteOverrideCount Supporting changes: * DEF_MAP for Palette::Override (reinterpret_cast, layout-compatible) * static_assert in sk_structs.cpp for ABI verification * AsSkFontArguments helper updated to call setPalette() Co-authored-by: Ramez Gerges <ramezragaa@proton.me> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pulls origin/skiasharp commit b9b89f23a5 ("Add color font palette
support to C API") into our dev/update-skia-147 work tree. The new
sk_typeface_clone_with_arguments signature gains paletteIndex +
palette-override array parameters; AsSkFontArguments grows to match.
Required by mono/skiasharp PR mono#3742 (origin/main HEAD), which adds
the corresponding managed wrappers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Re-runs the SkiaSharp generator against the merged submodule head so
the bindings reflect the union of:
- origin/main's color font palette additions (sk_typeface_clone_with_arguments
new signature with paletteIndex / palette-override array, plus the
sk_fontarguments_palette_override_t struct, from PR mono#3742 on the
managed side and PR mono#197 on the C-API side),
- our existing m147 surface carries.
HarfBuzzApi.generated.cs is intentionally not regenerated here — see
the prior "Regenerate SkiaApi.generated.cs" commit for rationale
(generator picks up extra HB 7.0+ callback APIs that have no
hand-written DelegateProxies companion in this repo).
During the rebase onto origin/main I took --ours on the
SkiaApi.generated.cs conflict to keep the rebase moving; this is
that regeneration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Builds on top of the variable font support merged in #3703. Adds color font palette APIs (Skia + HarfBuzz), improves Span method robustness, and significantly expands test coverage.
Companion skia PR: mono/skia#197
Color Font Palette Support
SkiaSharp
SKTypeface.Clone(int paletteIndex)— clone with a specific paletteSKTypeface.Clone(SKFontArguments)— clone with palette index, overrides, variation position, and collection indexSKFontArguments.PaletteIndex/PaletteOverrides— ref struct properties for palette configurationSKFontPaletteOverridestruct — override individual palette color entriesHarfBuzzSharp
Face.HasPalettes/PaletteCount— query color font palette supportFace.GetPaletteColors(int)/GetPaletteColors(int, Span<uint>)— retrieve palette color entriesFace.GetPaletteFlags(int)/GetPaletteNameId(int)/GetPaletteColorNameId(int)— palette metadataFace.HasColorLayers/HasColorPng/HasColorSvg— color font capability detectionC API (skia submodule)
sk_fontarguments_palette_override_tstructsk_typeface_clone_with_argumentsextended withpaletteIndex,paletteOverrides,paletteOverrideCountDEF_MAP+static_assertforPalette::OverrideSpan Method Robustness
GetVariationDesignParameters(Span)andGetVariationDesignPosition(Span)now handle Skia's all-or-nothing native semantics — if the buffer is too small, they retry with a pooled buffer (ArrayPool) and copy what fits, returning the actual written countFont.GetVariationCoordsNormalized(Span): fixed to return written count instead of total length when buffer is undersizedBug Fix
SKTypeface.ContainsGlyphs(ReadOnlySpan<byte>, SKTextEncoding): fixed pre-existing infinite recursion (called itself instead ofGetFont().ContainsGlyphs(...)) that causedStackOverflowExceptionValidation & Tests
All Span APIs tested across 5 buffer scenarios with exact assertions:
ArgumentOutOfRangeExceptionNew test coverage
HasColorPng/HasColorSvgfalse-path testsCloneWithPaletteOverride/CloneWithDifferentPaletteIndex/CloneWithNegativePaletteIndexThrowsContainsGlyphsWithByteSpanDoesNotStackOverflowGallery Sample
Clone(SKFontArguments)with palette + variation axesFiles Changed
SKTypeface.cs,SKFontArguments.cs,Face.cs,Font.csSkiaApi.generated.cs,libSkiaSharp.jsonsk_typeface.h,sk_typeface.cpp,sk_types_priv.h,sk_structs.cppSKTypefaceTest.cs,HBFaceTest.cs,HBFontTest.csColorFontSample.cs,Nabla.ttfInterVariable.ttf,test_glyphs-COLRv1.ttf