diff --git a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj
index ff59d5e8..44d5cc7c 100644
--- a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj
+++ b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj
@@ -44,8 +44,8 @@
-
-
+
+
diff --git a/src/ImageSharp.Drawing/Processing/RichTextGlyphRenderer.cs b/src/ImageSharp.Drawing/Processing/RichTextGlyphRenderer.cs
index 34a78a40..3073a590 100644
--- a/src/ImageSharp.Drawing/Processing/RichTextGlyphRenderer.cs
+++ b/src/ImageSharp.Drawing/Processing/RichTextGlyphRenderer.cs
@@ -1218,6 +1218,14 @@ internal struct GlyphRenderData
///
public Pen? PenReference { get; init; }
+ ///
+ /// Gets the color palette selection the glyph's colors were resolved with, or
+ /// when the glyph resolves no palette colors. The selection
+ /// changes the cached layer paints, so palette variants of one glyph must occupy
+ /// separate cache entries.
+ ///
+ public FontPalette? FontPalette { get; init; }
+
///
/// Determines whether two instances are equal.
///
@@ -1269,7 +1277,8 @@ public static CacheKey FromParameters(
TextAttributes = parameters.TextRun.TextAttributes,
TextDecorations = parameters.TextRun.TextDecorations,
Size = size,
- PenReference = penReference
+ PenReference = penReference,
+ FontPalette = parameters.FontPalette
};
///
@@ -1291,7 +1300,8 @@ public bool Equals(CacheKey other)
this.TextAttributes == other.TextAttributes &&
this.TextDecorations == other.TextDecorations &&
this.Size.Equals(other.Size) &&
- ReferenceEquals(this.PenReference, other.PenReference);
+ ReferenceEquals(this.PenReference, other.PenReference) &&
+ Equals(this.FontPalette, other.FontPalette);
///
public override int GetHashCode()
@@ -1312,6 +1322,7 @@ public override int GetHashCode()
hash.Add(this.TextDecorations);
hash.Add(this.Size);
hash.Add(this.PenReference is null ? 0 : RuntimeHelpers.GetHashCode(this.PenReference));
+ hash.Add(this.FontPalette);
return hash.ToHashCode();
}
}
diff --git a/src/ImageSharp.Drawing/Processing/RichTextOptions.cs b/src/ImageSharp.Drawing/Processing/RichTextOptions.cs
index 3d12a78c..a57c6ee9 100644
--- a/src/ImageSharp.Drawing/Processing/RichTextOptions.cs
+++ b/src/ImageSharp.Drawing/Processing/RichTextOptions.cs
@@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
using SixLabors.Fonts;
+using SixLabors.Fonts.Tables.AdvancedTypographic;
namespace SixLabors.ImageSharp.Drawing.Processing;
@@ -27,12 +28,17 @@ public RichTextOptions(RichTextOptions options)
: base(options)
{
// Copy each run into a fresh instance so later mutation of the source runs
- // cannot leak into this options instance (and vice versa).
+ // cannot leak into this options instance (and vice versa). Every property of
+ // RichTextRun and its TextRun base must appear here: a missing property
+ // silently resets to its default on the clone DrawText renders from.
List runs = new(options.TextRuns.Count);
foreach (RichTextRun run in options.TextRuns)
{
runs.Add(new RichTextRun()
{
+ // Brushes, pens, fonts, and palettes copy by reference: each is immutable
+ // once constructed (FontPalette snapshots its overrides in its own
+ // constructor), so a shared reference cannot leak later mutation.
Brush = run.Brush,
Pen = run.Pen,
StrikeoutPen = run.StrikeoutPen,
@@ -41,8 +47,17 @@ public RichTextOptions(RichTextOptions options)
Start = run.Start,
End = run.End,
Font = run.Font,
+ FontWeight = run.FontWeight,
+ Script = run.Script,
+ Culture = run.Culture,
+
+ // The feature tag list is the one caller-owned mutable collection on a
+ // run; the read-only interface is only a view, so isolation needs a copy.
+ FeatureTags = run.FeatureTags is null ? null : new List(run.FeatureTags),
TextAttributes = run.TextAttributes,
TextDecorations = run.TextDecorations,
+ ColorFontSupport = run.ColorFontSupport,
+ FontPalette = run.FontPalette,
Placeholder = run.Placeholder
});
}
diff --git a/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.Text.cs b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.Text.cs
index 36daefa5..fab0e52a 100644
--- a/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.Text.cs
+++ b/tests/ImageSharp.Drawing.Tests/Processing/DrawingCanvasTests.Text.cs
@@ -107,6 +107,7 @@ public void DrawGlyphById_Inter_OverlappingContours_NoHoles(TestImagePro
TextDecorations.None,
LayoutMode.HorizontalTopBottom,
ColorFontSupport.None,
+ null,
out FontGlyphMetrics metrics))
{
continue;
@@ -160,6 +161,7 @@ public void DrawGlyphById_Inter_EvenOddCanvasState_NoHoles(TestImageProv
TextDecorations.None,
LayoutMode.HorizontalTopBottom,
ColorFontSupport.None,
+ null,
out FontGlyphMetrics metrics))
{
continue;
@@ -306,6 +308,7 @@ public void DrawPositionedGlyphs_Inter_MatchesGlyphByIdLoop(TestImagePro
TextDecorations.None,
LayoutMode.HorizontalTopBottom,
ColorFontSupport.None,
+ null,
out FontGlyphMetrics metrics))
{
continue;
@@ -820,6 +823,7 @@ private static void DrawGlyphs(DrawingCanvas canvas, string text
TextDecorations.None,
LayoutMode.HorizontalTopBottom,
ColorFontSupport.None,
+ null,
out FontGlyphMetrics metrics))
{
continue;