Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
379a101
Add variable font support to SkiaSharp and HarfBuzzSharp
ramezgerges Apr 22, 2026
602c496
Address PR review: input validation, cast safety, and blob disposal
ramezgerges Apr 22, 2026
385ed8f
Add Variable Fonts sample to Gallery with Inter font
ramezgerges Apr 24, 2026
39b5386
Replace default parameter with overloads in SKTypeface.Clone
mattleibow Apr 25, 2026
b24f0b7
Update skia submodule: merge origin/skiasharp
mattleibow Apr 25, 2026
ec65fe0
Merge remote-tracking branch 'origin/main' into 001-variable-fonts-su…
mattleibow Apr 25, 2026
15b4761
Review fixes: API improvements, SKFourByteTag, Span overloads, tests
mattleibow Apr 25, 2026
cf45dc9
Add PR screenshots for variable font comparison
mattleibow Apr 25, 2026
af42511
Remove screenshots from repo (permalinked via SHA)
mattleibow Apr 25, 2026
4b93266
Update skia submodule: merge origin/skiasharp
mattleibow Apr 25, 2026
a18b2f2
Merge remote-tracking branch 'origin/main' into 001-variable-fonts-su…
mattleibow Apr 25, 2026
896f457
Split SKFourByteTag tests into dedicated test file
mattleibow Apr 25, 2026
a0c773b
Update skia submodule to skiasharp branch tip
mattleibow Apr 26, 2026
85dc06a
Assert named instance count in CanSetVarNamedInstance test
mattleibow Apr 26, 2026
3edbb88
Assert clone result in CloneOnStaticFontReturnsTypeface test
mattleibow Apr 26, 2026
9ebcf55
Address remaining review comments from fourth review round
mattleibow Apr 26, 2026
b769642
Remove extra blank line before interop safety tests section
mattleibow Apr 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -320,3 +320,4 @@ fastlane/screenshots

# Docs worktree (docs branch)
.docs
.playwright-mcp/
95 changes: 95 additions & 0 deletions binding/HarfBuzzSharp/Face.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,101 @@ public Blob ReferenceTable (Tag table) =>

public void MakeImmutable () => HarfBuzzApi.hb_face_make_immutable (Handle);

// Variable font support

public bool HasVariationData => HarfBuzzApi.hb_ot_var_has_data (Handle);

public int VariationAxisCount =>
(int)HarfBuzzApi.hb_ot_var_get_axis_count (Handle);

public OpenTypeVarAxisInfo[] VariationAxisInfos
{
get {
var count = HarfBuzzApi.hb_ot_var_get_axis_count (Handle);
if (count == 0)
return Array.Empty<OpenTypeVarAxisInfo> ();

var axes = new OpenTypeVarAxisInfo[(int)count];
fixed (OpenTypeVarAxisInfo* ptr = axes) {
HarfBuzzApi.hb_ot_var_get_axis_infos (Handle, 0, &count, ptr);
}
return axes;
}
}

public int GetVariationAxisInfos (Span<OpenTypeVarAxisInfo> axes)
{
uint count = (uint)axes.Length;
fixed (OpenTypeVarAxisInfo* ptr = axes) {
HarfBuzzApi.hb_ot_var_get_axis_infos (Handle, 0, &count, ptr);
}
return (int)count;
}

public bool TryFindVariationAxis (Tag tag, out OpenTypeVarAxisInfo axisInfo)
{
Comment thread
mattleibow marked this conversation as resolved.
axisInfo = default;
fixed (OpenTypeVarAxisInfo* ptr = &axisInfo) {
return HarfBuzzApi.hb_ot_var_find_axis_info (Handle, tag, ptr);
}
}

public int NamedInstanceCount =>
(int)HarfBuzzApi.hb_ot_var_get_named_instance_count (Handle);

public OpenTypeNameId GetNamedInstanceSubfamilyNameId (int instanceIndex)
{
if (instanceIndex < 0)
throw new ArgumentOutOfRangeException (nameof (instanceIndex));
return HarfBuzzApi.hb_ot_var_named_instance_get_subfamily_name_id (Handle, (uint)instanceIndex);
}

public OpenTypeNameId GetNamedInstancePostScriptNameId (int instanceIndex)
{
if (instanceIndex < 0)
throw new ArgumentOutOfRangeException (nameof (instanceIndex));
return HarfBuzzApi.hb_ot_var_named_instance_get_postscript_name_id (Handle, (uint)instanceIndex);
}

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);
Comment thread
mattleibow marked this conversation as resolved.
}
Comment on lines +144 to +151

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.

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> ();
Comment thread
mattleibow marked this conversation as resolved.

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);
Comment on lines +153 to +166

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.
}
return coords;
}

public int GetNamedInstanceDesignCoords (int instanceIndex, Span<float> coords)
{
if (instanceIndex < 0)
throw new ArgumentOutOfRangeException (nameof (instanceIndex));

uint coordsLength = (uint)coords.Length;
fixed (float* ptr = coords) {
HarfBuzzApi.hb_ot_var_named_instance_get_design_coords (Handle, (uint)instanceIndex, &coordsLength, ptr);
}
return (int)coordsLength;
}

protected override void Dispose (bool disposing) =>
base.Dispose (disposing);

Expand Down
59 changes: 59 additions & 0 deletions binding/HarfBuzzSharp/Font.cs
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,65 @@ public bool TryGetGlyphFromString (string s, out uint glyph)
}
}

// Variable font support

public void SetVariations (ReadOnlySpan<Variation> variations)
{
fixed (Variation* ptr = variations) {
HarfBuzzApi.hb_font_set_variations (Handle, ptr, (uint)variations.Length);
}
}

public void SetVariationCoordsDesign (ReadOnlySpan<float> coords)
{
fixed (float* ptr = coords) {
HarfBuzzApi.hb_font_set_var_coords_design (Handle, ptr, (uint)coords.Length);
}
}

public void SetVariationCoordsNormalized (ReadOnlySpan<int> coords)
{
fixed (int* ptr = coords) {
HarfBuzzApi.hb_font_set_var_coords_normalized (Handle, ptr, (uint)coords.Length);
}
}

public int[] VariationCoordsNormalized
{
get {
uint length;
var ptr = HarfBuzzApi.hb_font_get_var_coords_normalized (Handle, &length);
if (length == 0 || ptr == null)
return Array.Empty<int> ();

var count = (int)length;
var coords = new int[count];
for (int i = 0; i < count; i++)
coords[i] = ptr[i];
Comment thread
mattleibow marked this conversation as resolved.
return coords;
}
}

public int GetVariationCoordsNormalized (Span<int> coords)
{
uint length;
var ptr = HarfBuzzApi.hb_font_get_var_coords_normalized (Handle, &length);
if (length == 0 || ptr == null)
return 0;

var count = Math.Min ((int)length, coords.Length);
for (int i = 0; i < count; i++)
coords[i] = ptr[i];
return (int)length;
}

public void SetVariationNamedInstance (int instanceIndex)
{
if (instanceIndex < 0)
throw new ArgumentOutOfRangeException (nameof (instanceIndex));
HarfBuzzApi.hb_font_set_var_named_instance (Handle, (uint)instanceIndex);
}

public void SetFunctionsOpenType () =>
HarfBuzzApi.hb_ot_font_set_funcs (Handle);

Expand Down
Loading
Loading