-
-
Notifications
You must be signed in to change notification settings - Fork 72
Support COLRv1 and SVG Fonts #464
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
@tocsoft @brianpopow I don't actually expect you to review this as it's massive. I just thought you might like a look. |
There was a problem hiding this comment.
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 comprehensive support for COLRv1 and SVG color font formats, representing a significant enhancement to the font rendering capabilities. The changes refactor the rendering API to enable multi-layer glyph rendering with custom composition and intersection rules.
Key changes:
- Implements COLRv1 and SVG font parsing and rendering with gradient support and compositing modes
- Refactors
IGlyphRendererinterface to support layered rendering via newBeginLayer/EndLayermethods - Consolidates multi-layer glyph metrics into single operations for improved performance
- Adds
DecorationPositioningModeoption for text decoration rendering
Reviewed Changes
Copilot reviewed 133 out of 153 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
src/SixLabors.Fonts/Tables/Svg/*.cs |
New SVG table parsing and glyph source implementation with path command conversion |
src/SixLabors.Fonts/Tables/General/Colr/*.cs |
Complete COLRv1 table parsing with paint graph flattening and layer resolution |
src/SixLabors.Fonts/Rendering/*.cs |
New rendering types for paints, gradients, and path commands |
src/SixLabors.Fonts/StreamFontMetrics*.cs |
Refactored glyph metrics to support color fonts via PaintedGlyphMetrics |
tests/SixLabors.Fonts.Tests/*.cs |
Updated tests with new namespace imports and signature changes |
src/SixLabors.Fonts/Tables/General/CpalTable.cs |
Fixed color component order from BGR to RGB |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
|
|
||
| return Span<LayerRecord>.Empty; | ||
| return []; |
Copilot
AI
Oct 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Consider using Span<LayerRecord>.Empty instead of [] for consistency with the previous implementation and to make the intent more explicit.
| return []; | |
| return Span<LayerRecord>.Empty; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The future is NOW Copilot
| /// <summary> | ||
| /// Canvas metadata describing the document-space coordinate system for a painted glyph. | ||
| /// </summary> | ||
| internal readonly struct PaintedCanvas |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this rather be called PaintedCanvasMetadata ?
| namespace SixLabors.Fonts.Tables.General.Colr; | ||
|
|
||
| // Affine matrices used by PaintTransform variants | ||
| internal readonly struct Affine2x3 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could trimm this down to
internal readonly record struct Affine2x3(float Xx, float Yx, float Xy, float Yy, float Dx, float Dy);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would give you implemented Equals methods etc.
it compiles to:
// Decompiled with JetBrains decompiler
// Type: SixLabors.Fonts.Tables.General.Colr.Affine2x3
// Assembly: SixLabors.Fonts, Version=3.0.0.0, Culture=neutral, PublicKeyToken=d998eea7b14cab13
// MVID: F8B78715-8A9B-48FD-B293-7BC7DFBD3A37
// Assembly location: /Users/stefannikolei/projects/SixLabors/Fonts/artifacts/bin/src/SixLabors.Fonts/Debug/net8.0/SixLabors.Fonts.dll
// Local variable names from /users/stefannikolei/projects/sixlabors/fonts/artifacts/bin/src/sixlabors.fonts/debug/net8.0/sixlabors.fonts.pdb
// Compiler-generated code is shown
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace SixLabors.Fonts.Tables.General.Colr;
[IsReadOnly]
[StructLayout(LayoutKind.Sequential)]
internal readonly struct Affine2x3 : IEquatable<Affine2x3>
{
[CompilerGenerated]
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly float <Xx>k__BackingField;
[CompilerGenerated]
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly float <Yx>k__BackingField;
[CompilerGenerated]
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly float <Xy>k__BackingField;
[CompilerGenerated]
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly float <Yy>k__BackingField;
[CompilerGenerated]
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly float <Dx>k__BackingField;
[CompilerGenerated]
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly float <Dy>k__BackingField;
public Affine2x3(float Xx, float Yx, float Xy, float Yy, float Dx, float Dy)
{
this.<Xx>k__BackingField = Xx;
this.<Yx>k__BackingField = Yx;
this.<Xy>k__BackingField = Xy;
this.<Yy>k__BackingField = Yy;
this.<Dx>k__BackingField = Dx;
this.<Dy>k__BackingField = Dy;
}
public float Xx
{
[CompilerGenerated] get
{
return this.<Xx>k__BackingField;
}
[CompilerGenerated] init
{
this.<Xx>k__BackingField = value;
}
}
public float Yx
{
[CompilerGenerated] get
{
return this.<Yx>k__BackingField;
}
[CompilerGenerated] init
{
this.<Yx>k__BackingField = value;
}
}
public float Xy
{
[CompilerGenerated] get
{
return this.<Xy>k__BackingField;
}
[CompilerGenerated] init
{
this.<Xy>k__BackingField = value;
}
}
public float Yy
{
[CompilerGenerated] get
{
return this.<Yy>k__BackingField;
}
[CompilerGenerated] init
{
this.<Yy>k__BackingField = value;
}
}
public float Dx
{
[CompilerGenerated] get
{
return this.<Dx>k__BackingField;
}
[CompilerGenerated] init
{
this.<Dx>k__BackingField = value;
}
}
public float Dy
{
[CompilerGenerated] get
{
return this.<Dy>k__BackingField;
}
[CompilerGenerated] init
{
this.<Dy>k__BackingField = value;
}
}
[CompilerGenerated]
public override string ToString()
{
StringBuilder builder = new StringBuilder();
builder.Append("Affine2x3");
builder.Append(" { ");
if (this.PrintMembers(builder))
builder.Append(' ');
builder.Append('}');
return builder.ToString();
}
[CompilerGenerated]
private bool PrintMembers(StringBuilder builder)
{
builder.Append("Xx = ");
builder.Append(this.Xx.ToString());
builder.Append(", Yx = ");
builder.Append(this.Yx.ToString());
builder.Append(", Xy = ");
builder.Append(this.Xy.ToString());
builder.Append(", Yy = ");
builder.Append(this.Yy.ToString());
builder.Append(", Dx = ");
builder.Append(this.Dx.ToString());
builder.Append(", Dy = ");
builder.Append(this.Dy.ToString());
return true;
}
[CompilerGenerated]
[SpecialName]
public static bool op_Inequality(Affine2x3 left, Affine2x3 right)
{
return !Affine2x3.op_Equality(left, right);
}
[CompilerGenerated]
[SpecialName]
public static bool op_Equality(Affine2x3 left, Affine2x3 right)
{
return left.Equals(right);
}
[CompilerGenerated]
public override int GetHashCode()
{
return ((((EqualityComparer<float>.Default.GetHashCode(this.<Xx>k__BackingField) * -1521134295 + EqualityComparer<float>.Default.GetHashCode(this.<Yx>k__BackingField)) * -1521134295 + EqualityComparer<float>.Default.GetHashCode(this.<Xy>k__BackingField)) * -1521134295 + EqualityComparer<float>.Default.GetHashCode(this.<Yy>k__BackingField)) * -1521134295 + EqualityComparer<float>.Default.GetHashCode(this.<Dx>k__BackingField)) * -1521134295 + EqualityComparer<float>.Default.GetHashCode(this.<Dy>k__BackingField);
}
[CompilerGenerated]
public override bool Equals(object obj)
{
return obj is Affine2x3 other && this.Equals(other);
}
[CompilerGenerated]
public bool Equals(Affine2x3 other)
{
return EqualityComparer<float>.Default.Equals(this.<Xx>k__BackingField, other.<Xx>k__BackingField) && EqualityComparer<float>.Default.Equals(this.<Yx>k__BackingField, other.<Yx>k__BackingField) && EqualityComparer<float>.Default.Equals(this.<Xy>k__BackingField, other.<Xy>k__BackingField) && EqualityComparer<float>.Default.Equals(this.<Yy>k__BackingField, other.<Yy>k__BackingField) && EqualityComparer<float>.Default.Equals(this.<Dx>k__BackingField, other.<Dx>k__BackingField) && EqualityComparer<float>.Default.Equals(this.<Dy>k__BackingField, other.<Dy>k__BackingField);
}
[CompilerGenerated]
public void Deconstruct(
out float Xx,
out float Yx,
out float Xy,
out float Yy,
out float Dx,
out float Dy)
{
Xx = this.Xx;
Yx = this.Yx;
Xy = this.Xy;
Yy = this.Yy;
Dx = this.Dx;
Dy = this.Dy;
}
}
When that's not needed this would be then a style question.
Prerequisites
Description
Fixes #462
This PR adds support for COLRv1 and SVG fonts.
A significant rewrite of the rendering API was required to allow the rendering of layers with custom composition and intersection rules.
Summary:
Notes:
I have a local version of ImageSharp.Drawing that supports the breaking changes. Example output is as follows.
COLRv1

SVG
