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.
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 []; |
There was a problem hiding this comment.
[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.
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.
Should this rather be called PaintedCanvasMetadata ?
There was a problem hiding this comment.
Yeah, I'll change that.
| namespace SixLabors.Fonts.Tables.General.Colr; | ||
|
|
||
| // Affine matrices used by PaintTransform variants | ||
| internal readonly struct Affine2x3 |
There was a problem hiding this comment.
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.
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.
There was a problem hiding this comment.
I don't like using record structs because of all the extra stuff generated and I like to be in tight control of when I do require comparison operators. In this case we don't need comparison operators here, just a shell to hold the type structure.
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
