Skip to content

Variable font support#3703

Merged
mattleibow merged 17 commits into
mono:mainfrom
ramezgerges:001-variable-fonts-support
Apr 26, 2026
Merged

Variable font support#3703
mattleibow merged 17 commits into
mono:mainfrom
ramezgerges:001-variable-fonts-support

Conversation

@ramezgerges

@ramezgerges ramezgerges commented Apr 20, 2026

Copy link
Copy Markdown
Contributor

Variable Font Support

Adds OpenType variable font APIs to SkiaSharp and HarfBuzzSharp, enabling runtime font variation axis control, typeface cloning with custom design positions, and full axis/instance querying.

Related skia PR: mono/skia#185

New Types

Type Description
SKFourByteTag Four-byte tag struct wrapping Skia's SkFourByteTag. Parse, ToString, implicit uint conversion.
SKFontVariationAxis Generated struct: Tag, Min, Default, Max, IsHidden
SKFontVariationPositionCoordinate Generated struct: Axis (tag), Value (float)
SKFontArguments ref struct: VariationDesignPosition, CollectionIndex — accepts spans, arrays, stackalloc

New SKTypeface APIs

API Description
VariationDesignParameters Property → SKFontVariationAxis[] — all axes the font supports
VariationDesignParameterCount Property → int — axis count for span pre-allocation
GetVariationDesignParameters(Span<>) Span overload — allocation-free
VariationDesignPosition Property → SKFontVariationPositionCoordinate[] — current axis values
VariationDesignPositionCount Property → int
GetVariationDesignPosition(Span<>) Span overload — allocation-free
Clone(ReadOnlySpan<SKFontVariationPositionCoordinate>) Clone typeface with new variation position
Clone(SKFontArguments) Clone with full control (position, collection index)

New HarfBuzzSharp Face APIs

API Description
HasVariationData Whether font has fvar table
VariationAxisCount Axis count property
VariationAxisInfos Property → OpenTypeVarAxisInfo[]
GetVariationAxisInfos(Span<>) Span overload
TryFindVariationAxis(Tag, out info) Find axis by tag
NamedInstanceCount Named instance count property
GetNamedInstanceDesignCoordsCount(int) Coord count for a named instance
GetNamedInstanceDesignCoords(int) Design coords array
GetNamedInstanceDesignCoords(int, Span<float>) Span overload
GetNamedInstanceSubfamilyNameId(int) Name table ID for subfamily
GetNamedInstancePostScriptNameId(int) Name table ID for PostScript name

New HarfBuzzSharp Font APIs

API Description
SetVariations(ReadOnlySpan<Variation>) Set variation values by tag
SetVariationCoordsDesign(ReadOnlySpan<float>) Set design-space coordinates
SetVariationCoordsNormalized(ReadOnlySpan<int>) Set normalized coordinates
VariationCoordsNormalized Property → int[] — current normalized coords
GetVariationCoordsNormalized(Span<int>) Span overload
SetVariationNamedInstance(int) Select a named instance

Usage — Array-based

using var data = SKData.Create(File.OpenRead("Inter.ttf"));
using var typeface = SKTypeface.FromData(data);

// Query axes
foreach (var axis in typeface.VariationDesignParameters)
    Console.WriteLine($"{axis.Tag}: {axis.Min} .. {axis.Default} .. {axis.Max}");
// Output: wght: 100 .. 400 .. 900

// Clone with bold weight
var position = new[] {
    new SKFontVariationPositionCoordinate {
        Axis = SKFourByteTag.Parse("wght"),
        Value = 900
    }
};
using var bold = typeface.Clone(position);

Usage — Allocation-free with Span

// Pre-allocate based on count
Span<SKFontVariationAxis> axes = stackalloc SKFontVariationAxis[typeface.VariationDesignParameterCount];
typeface.GetVariationDesignParameters(axes);

// Clone with stackalloc
Span<SKFontVariationPositionCoordinate> pos = stackalloc SKFontVariationPositionCoordinate[] {
    new() { Axis = SKFourByteTag.Parse("wght"), Value = 900 }
};
using var bold = typeface.Clone(pos);

Console output

Axes for Inter:
  opsz: 14 .. 14 .. 32
  wght: 100 .. 400 .. 900

Tag round-trip: wght → 0x77676874 → wght

Visual comparison

Base (wght: 400) Bold (wght: 900)
Base weight Bold weight

C API changes (companion PR in mono/skia)

  • sk_fourbytetag_t typedef matching SkFourByteTag
  • sk_fontarguments_variation_axis_t and sk_fontarguments_variation_position_coordinate_t structs
  • sk_typeface_get_variation_design_parameters(), sk_typeface_get_variation_design_position(), sk_typeface_clone_with_arguments()
  • DEF_MAP for layout-compatible struct conversion, static_assert in sk_structs.cpp

Tests

151 tests covering:

  • Exact axis values (tag, min, default, max, isHidden) for Distortable.ttf
  • SKFourByteTag: Parse, ToString, char constructor, implicit conversion, equality, edge cases (null, empty, short, long strings), known tag hex values, native round-trip
  • Span vs property equivalence for all overloads
  • Static font graceful handling (empty arrays, zero counts)
  • Clone preserves exact design position through native round-trip
  • Rendering tests: different weights produce different pixels, ink coverage varies, consistent clones match
  • HarfBuzz: axis queries, named instances, normalized coordinates, variation setting
  • Negative index validation on all instance methods

Gallery sample

Variable Fonts sample using Inter — weight and optical size sliders with weight spectrum comparison strip.

@github-actions

github-actions Bot commented Apr 20, 2026

Copy link
Copy Markdown
Contributor

📦 Try the packages from this PR

Warning

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 -- 3703

PowerShell / Windows:

iex "& { $(irm https://raw.githubusercontent.com/mono/SkiaSharp/main/scripts/get-skiasharp-pr.ps1) } 3703"

Step 2 — Add the local NuGet source

dotnet nuget add source ~/.skiasharp/hives/pr-3703/packages --name skiasharp-pr-3703
More options
Option Description
--successful-only / -SuccessfulOnly Only use successful builds
--force / -Force Overwrite previously downloaded packages
--list / -List List available artifacts without downloading
--build-id ID / -BuildId ID Download from a specific build

Or download manually from Azure Pipelines — look for the nuget artifact on the build for this PR.

Remove the source when you're done:

dotnet nuget remove source skiasharp-pr-3703

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds OpenType variable font (font variations) support to the managed SkiaSharp and HarfBuzzSharp APIs, including new querying/cloning APIs on SKTypeface, variation-axis APIs on HarfBuzzSharp.Face, and variation-setting APIs on HarfBuzzSharp.Font, along with new tests and updated generated bindings.

Changes:

  • Add SkiaSharp variable-font APIs (SKTypeface.GetVariationDesignParameters, GetVariationDesignPosition, CloneWithArguments) plus tag helper utilities.
  • Add HarfBuzzSharp variable-font APIs for axis queries, named instances, and setting/getting variation coordinates; enable generation for hb-ot-var.h.
  • Add SkiaSharp and HarfBuzzSharp tests covering variable-font behavior; update the generator’s include-path discovery for Linux.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
utils/SkiaSharpGenerator/BaseTool.cs Adds Linux system/Clang include discovery for header parsing.
tests/Tests/SkiaSharp/SKVariableFontRenderingTest.cs New rendering-based tests validating variable font rendering differences and clone stability.
tests/Tests/SkiaSharp/SKTypefaceTest.cs Adds SKTypeface-level tests for variation axes/position and cloning.
tests/Tests/HarfBuzzSharp/HBFontTest.cs Adds HarfBuzz Font tests for setting variations, coords, and named instances.
tests/Tests/HarfBuzzSharp/HBFaceTest.cs Adds HarfBuzz Face tests for variation axes, named instances, and presence checks.
binding/libHarfBuzzSharp.json Stops excluding hb-ot-var.h so bindings for var APIs can be generated.
binding/SkiaSharp/SkiaApi.generated.cs Adds P/Invokes for Skia typeface variation APIs and new structs.
binding/SkiaSharp/SKTypeface.cs Exposes the new variable-font APIs on SKTypeface.
binding/SkiaSharp/SKFontVariationAxis.cs Adds tag helpers (SKFontVariationTag) and readable tag-name properties on new structs.
binding/HarfBuzzSharp/HarfBuzzApi.generated.cs Adds P/Invoke surface for hb-ot-var.h.
binding/HarfBuzzSharp/Font.cs Adds variable-font setters/getters on HarfBuzzSharp.Font.
binding/HarfBuzzSharp/Face.cs Adds variable-font axis/named-instance query APIs on HarfBuzzSharp.Face.

Comment thread binding/HarfBuzzSharp/Font.cs Outdated
Comment thread utils/SkiaSharpGenerator/BaseTool.cs Outdated
Comment thread binding/HarfBuzzSharp/Face.cs Outdated
Comment thread binding/HarfBuzzSharp/Face.cs Outdated
Comment thread binding/HarfBuzzSharp/Face.cs Outdated
Comment thread binding/HarfBuzzSharp/Font.cs Outdated
Comment thread tests/Tests/HarfBuzzSharp/HBFaceTest.cs Outdated
Comment thread tests/Tests/HarfBuzzSharp/HBFontTest.cs
Comment thread binding/SkiaSharp/SKFontVariationAxis.cs Outdated
@mattleibow

mattleibow commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

/azp run

@azure-pipelines

Copy link
Copy Markdown
No pipelines are associated with this pull request.

@mattleibow

mattleibow commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Hey @ramezgerges, quick question: did you run pwsh ./utils/generate.ps1 to get this diff or was it something else?

Since we never got a chance to update the harfbuzz API bindings to the newer HarfBuzz version, I have been just bumping the underlying harfbuzz DEPS version and not re-generating the API. The changes now are so big that the work is a bit harder to do. So many weird types and things. What I usually do is drop back to 2.8.2 and the run the generator.

# In externals/skia/DEPS, revert harfbuzz back to the version we last generated from:
# Change: 2b3631a866b3077d9d675caa4ec9010b342b5a7c
# To:     63e15eac4f443fa53565d1e4fb9611cdd7814f28

dotnet cake --target=git-sync-deps
pwsh ./utils/generate.ps1

This will show us exactly what needs updating in binding/libHarfBuzzSharp.json so we can make sure the API is usable and not just direct mapping without the human touch.

There is a goal at some point to actually expose more, but never got to it. The version of harfbuzz being built is much later (still a bit old now) but not actually 2.8.2 - I think 8.x at least. There is a dream to one day bump harfbuzz and regen the full API and then be amazed.

@ramezgerges

Copy link
Copy Markdown
Contributor Author

@mattleibow Yes, I ran the script for harfbuzz and took only the relevant parts. The PR does contain changes to the json files.

@ramezgerges
ramezgerges force-pushed the 001-variable-fonts-support branch from 7f97e15 to 1c4fca3 Compare April 22, 2026 15:20
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>
@ramezgerges
ramezgerges force-pushed the 001-variable-fonts-support branch from 1c4fca3 to 379a101 Compare April 22, 2026 17:55
- 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>
@azure-pipelines

Copy link
Copy Markdown
No pipelines are associated with this pull request.

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>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command.

5 similar comments
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command.

mattleibow and others added 2 commits April 25, 2026 02:30
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>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command.

…pport

# Conflicts:
#	binding/SkiaSharp/SkiaApi.generated.cs
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command.

2 similar comments
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command.

github-actions Bot pushed a commit that referenced this pull request Apr 26, 2026
github-actions Bot pushed a commit that referenced this pull request Apr 26, 2026
mattleibow pushed a commit to mono/skia that referenced this pull request Apr 26, 2026
)

Add variable font C API for typeface variation queries and cloning (#185)

Requires: mono/SkiaSharp#3703

Expose SkTypeface's OpenType Font Variations functionality through the
SkiaSharp C API, enabling runtime variable font axis querying and
typeface cloning with specific design coordinates.

New functions:

  * sk_typeface_get_variation_design_position — query a typeface's
    current axis coordinate values (e.g. weight, width, optical size)
  * sk_typeface_get_variation_design_parameters — query axis metadata
    including tag, min, default, max, and isHidden
  * sk_typeface_clone_with_arguments — clone a typeface with specific
    variation coordinates via SkFontArguments

New types:

  * sk_fourbytetag_t — uint32_t typedef matching SkFourByteTag
  * sk_fontarguments_variation_position_coordinate_t — axis tag + value
  * sk_fontarguments_variation_axis_t — tag, min, def, max, isHidden

The coordinate struct is layout-compatible with Skia's
SkFontArguments::VariationPosition::Coordinate (verified by
static_assert in sk_structs.cpp), so it uses DEF_MAP for zero-cost
reinterpret_cast conversion. The axis struct requires a field-by-field
copy because SkFontParameters::Variation::Axis exposes isHidden through
a getter rather than a bare field.
@mattleibow
mattleibow requested a review from Copilot April 26, 2026 02:47
mattleibow and others added 2 commits April 26, 2026 04:48
Update externals/skia to include the merged variable font C API
(mono/skia#185) at the tip of the skiasharp branch.

Changes: https://github.com/mono/skia/compare/0afa08b9ac..b6a88e589f

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Distortable.ttf has 3 named instances, so the test should assert
count > 0 rather than silently returning. This was flagged in review
but the fix was not applied.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 19 changed files in this pull request and generated 4 comments.

Comment thread binding/HarfBuzzSharp/Face.cs
Comment thread tests/Tests/SkiaSharp/SKTypefaceTest.cs Outdated
Comment thread tests/Tests/SkiaSharp/SKTypefaceTest.cs Outdated
Comment thread binding/HarfBuzzSharp/Font.cs
mattleibow and others added 3 commits April 26, 2026 04:53
The test had no assertion on the cloned typeface — it passed as long
as Clone did not throw. Skia returns a valid typeface for static fonts,
so assert not-null to match the contract validated by the other clone
tests. Flagged in review but the fix was not applied.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Initialize axisInfo = default in TryFindVariationAxis before
  P/Invoke so callers never observe undefined data when HarfBuzz
  returns false
- Cast uint length to int in VariationCoordsNormalized property
  for correct array allocation and loop indexing
- Remove excessive blank lines in SKTypefaceTest.cs between
  test sections

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 19 changed files in this pull request and generated 3 comments.

Comment on lines +144 to +151
public int GetNamedInstanceDesignCoordsCount (int instanceIndex)
{
if (instanceIndex < 0)
throw new ArgumentOutOfRangeException (nameof (instanceIndex));

// Return value is the total number of design coordinates
return (int)HarfBuzzApi.hb_ot_var_named_instance_get_design_coords (Handle, (uint)instanceIndex, null, null);
}

Copilot AI Apr 26, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hb_ot_var_named_instance_get_design_coords takes a coords_length pointer (in/out). Passing null for coords_length (even when coords is null) risks a native null-dereference. For the “count query” call, pass a non-null uint coordsLength pointer and read the updated value (similar to how hb_face_get_table_tags is used elsewhere in this file).

Copilot uses AI. Check for mistakes.
Comment on lines +153 to +166
public float[] GetNamedInstanceDesignCoords (int instanceIndex)
{
if (instanceIndex < 0)
throw new ArgumentOutOfRangeException (nameof (instanceIndex));

// Return value is the total number of design coordinates
var totalCoords = (int)HarfBuzzApi.hb_ot_var_named_instance_get_design_coords (Handle, (uint)instanceIndex, null, null);
if (totalCoords == 0)
return Array.Empty<float> ();

uint coordsLength = (uint)totalCoords;
var coords = new float[totalCoords];
fixed (float* ptr = coords) {
HarfBuzzApi.hb_ot_var_named_instance_get_design_coords (Handle, (uint)instanceIndex, &coordsLength, ptr);

Copilot AI Apr 26, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method also queries hb_ot_var_named_instance_get_design_coords with coords_length = null. Besides the potential native crash, relying on the return value here may be incorrect if HarfBuzz reports the required length via the coords_length out-parameter. Use a non-null coordsLength variable for the size query, then allocate/copy based on that value.

Copilot uses AI. Check for mistakes.

public static implicit operator SKFourByteTag (uint tag) => new SKFourByteTag (tag);

public override bool Equals (object obj) =>

Copilot AI Apr 26, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With nullable enabled in this file, Equals(object obj) will produce a nullability-mismatch warning when overriding object.Equals(object?). Either update the signature to Equals(object? obj) (and adjust the pattern matching accordingly) or add #nullable disable at the top to match the rest of the SkiaSharp binding files (e.g., SKColor.cs, SKTypeface.cs).

Suggested change
public override bool Equals (object obj) =>
public override bool Equals (object? obj) =>

Copilot uses AI. Check for mistakes.
@azure-pipelines

Copy link
Copy Markdown
No pipelines are associated with this pull request.

@mattleibow
mattleibow merged commit 5734cf1 into mono:main Apr 26, 2026
10 of 11 checks passed
ramezgerges added a commit to ramezgerges/SkiaSharp that referenced this pull request Apr 26, 2026
Re-runs `pwsh ./utils/generate.ps1 binding/libSkiaSharp.json` against
the current submodule head so the bindings reflect the merged state of:

  - the m147 C API surface (sk_pathbuilder_t, R16Unorm, removed
    SkGradientShader, m144 SkColorType insertions, m145 SkCodec::Options,
    m147 VulkanYcbcrConversionInfo, ...),
  - the upstream-on-skiasharp-branch additions
    (sk_fontmgr_legacy_create_typeface, variable-font sk_typeface_*
    queries, sk_fontarguments_variation_axis_t, sk_fourbytetag_t,
    SkManagedStream::move int32_t signature),
  - the new XPS doc-with-options entry point.

HarfBuzzApi.generated.cs is intentionally NOT regenerated here.
origin/main's variable-fonts PR (mono#3703) already regenerated it
against the same HarfBuzz 8.3.1 tree as part of that PR; running
the generator a second time on our branch picks up extra HB 7.0+
callback-style APIs (color line, draw funcs, paint funcs) that
have no hand-written DelegateProxies companion in this repo, and
the resulting build fails with CS8795 / CS0246. Re-wiring those
proxies is a separate piece of work — out of scope for the m147
bump.

During the rebase onto origin/main I took --ours on the three
SkiaApi.generated.cs conflicts to keep the rebase moving (those
conflicts were just generator-output collisions and would have
been regenerated anyway). This commit is that regeneration.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
mattleibow added a commit that referenced this pull request Apr 26, 2026
…3742)

Add color font palette support and improve variable font robustness (#3742)

Requires: mono/skia#197

Extends the variable font support merged in #3703 with color font
palette APIs, Span method robustness fixes, and a pre-existing bug fix.

~~ Color font palette support ~~

SkiaSharp:
  * SKTypeface.Clone(int paletteIndex) — clone with a built-in palette
  * SKTypeface.Clone(SKFontArguments) — clone with palette index,
    per-color overrides, variation position, and collection index
  * SKFontArguments ref struct gains PaletteIndex and PaletteOverrides
  * SKFontPaletteOverride struct for individual color entry overrides

HarfBuzzSharp:
  * Face.HasPalettes, PaletteCount — query color font support
  * Face.GetPaletteColors (array + Span) — retrieve palette entries
  * Face.GetPaletteFlags, GetPaletteNameId, GetPaletteColorNameId
  * Face.HasColorLayers, HasColorPng, HasColorSvg

C API (mono/skia#197):
  * sk_fontarguments_palette_override_t struct
  * sk_typeface_clone_with_arguments extended with palette parameters
  * DEF_MAP + static_assert for Palette::Override

~~ Span method robustness ~~

Skia's getVariationDesignParameters/Position use all-or-nothing
semantics: if the buffer is undersized, they return the total count
but write nothing. The C# Span methods now detect this and retry with
a pooled buffer (ArrayPool), copying what fits. Empty spans return 0
immediately without native calls or allocations.

Font.GetVariationCoordsNormalized(Span) fixed to return the written
count instead of the total length when the buffer is undersized.

~~ Bug fix ~~

SKTypeface.ContainsGlyphs(ReadOnlySpan<byte>, SKTextEncoding) had a
pre-existing infinite recursion (called itself instead of
GetFont().ContainsGlyphs) causing StackOverflowException.

~~ Test coverage ~~

Every Span API tested across empty, undersized, exact, and oversized
buffers with exact assertions. Palette APIs tested for colors, flags,
name IDs, different palettes, and negative index validation. Added
InterVariable.ttf (multi-axis) and test_glyphs-COLRv1.ttf (3 palettes)
as test fonts. Gallery sample demonstrates Nabla (COLRv1, 7 palettes,
variable axes) with palette picker and weight/optical size sliders.

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>
mattleibow added a commit that referenced this pull request Apr 27, 2026
)

Expand API design docs and rename add-api skill to api-add-review (#3749)

Context: #3703

Patterns learned while building the variable font API (#3703) were not captured
in the developer documentation. Future API work — whether P/Invoke bindings or
pure C# libraries in source/ — kept rediscovering the same conventions around
Span overloads, struct conversion macros, generator config, and test strategies.

documentation/dev/api-design.md now covers:
  * Span overload triple (array property + count property + Span fill method)
  * properties vs methods decision rule
  * type wrapping for uint32_t typedefs (SKFourByteTag pattern)
  * ref struct parameter bags for complex APIs (SKFontArguments pattern)
  * test patterns: exact values, Span equivalence, interop round-trip,
    static font handling, negative index validation
  * XML doc and file convention guidelines

documentation/dev/adding-apis.md now covers:
  * DEF_MAP macro variants and when to use each
  * layout-compatible vs manual struct conversion
  * parameter bag helper functions
  * static_assert in sk_structs.cpp for ABI verification
  * libSkiaSharp.json configuration: type aliases, member renames,
    struct options, function parameter overrides
  * GPU-specific patterns: GR* prefixes, #if SK_GANESH guards

The add-api skill is renamed to api-add-review to reflect its two modes (add
new APIs and review existing PRs) and broader applicability to all C# library
code, not just P/Invoke bindings. The monolithic SKILL.md is split into a mode
dispatcher plus three focused references: add-workflow.md (9-phase),
review-workflow.md (structured checklist with auto-fix), and
api-design-rules.md (quick reference). All 9 cross-references updated.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mattleibow mattleibow added this to the 4.x Preview 1 milestone May 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants