// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Numerics;
using SixLabors.Fonts.Rendering;
using SixLabors.Fonts.Tables.AdvancedTypographic.Variations;
using SixLabors.Fonts.Tables.TrueType.Glyphs;
namespace SixLabors.Fonts.Tables.General.Colr {
///
/// Supplies painted glyphs for COLR v1 fonts.
/// Flattens paint graphs into a linear stream and emits a .
///
internal sealed class ColrV1GlyphSource : ColrGlyphSourceBase
{
///
/// Cache of previously resolved painted glyphs keyed by glyph ID.
///
private readonly ConcurrentDictionary cachedGlyphs = [];
///
/// The glyph variation processor for variable fonts, or for static fonts.
///
private readonly GlyphVariationProcessor? processor;
///
/// Initializes a new instance of the class.
///
/// The COLR table.
/// The CPAL table, or null if not present.
/// Delegate that loads a glyph outline for the given glyph id.
/// The glyph variation processor for variable fonts, or null.
public ColrV1GlyphSource(ColrTable colr, CpalTable? cpal, Func glyphLoader, GlyphVariationProcessor? processor = null)
: base(colr, cpal, glyphLoader)
=> this.processor = processor;
///
public override bool TryGetPaintedGlyph(ushort glyphId, out PaintedGlyph glyph, out PaintedCanvasMetadata canvas)
{
(PaintedGlyph Glyph, PaintedCanvasMetadata Canvas) result = this.cachedGlyphs.GetOrAdd(glyphId, _ =>
{
if (this.Colr.TryGetColrV1Layers(glyphId, this.processor, out List? resolved))
{
List layers = new(resolved.Count);
for (int i = 0; i < resolved.Count; i++)
{
ResolvedGlyphLayer rl = resolved[i];
GlyphVector? gv = this.GlyphLoader(rl.GlyphId);
if (gv is null || !gv.Value.HasValue())
{
continue;
}
// Build geometry once for this layer.
List path = BuildPath(gv.Value);
// Flatten paint graph: accumulate wrapper transforms; attach composite mode to leaves.
List leafPaints = [];
FlattenPaint(rl.Paint, rl.PaintTransform, rl.CompositeMode, this.Cpal, this.Colr, this.processor, leafPaints);
// Emit one layer per leaf paint.
Bounds? clip = rl.ClipBox;
for (int p = 0; p < leafPaints.Count; p++)
{
Rendering.Paint leaf = leafPaints[p];
layers.Add(new PaintedLayer(leaf, FillRule.NonZero, rl.GlyphTransform, clip, path));
}
}
if (layers.Count > 0)
{
// Canvas viewBox in Y-up; renderer downstream decides orientation via flag.
PaintedGlyph glyph = new(layers);
PaintedCanvasMetadata canvas = new(FontRectangle.Empty, isYDown: false, rootTransform: Matrix3x2.Identity);
return (glyph, canvas);
}
}
return (default, default);
});
glyph = result.Glyph;
canvas = result.Canvas;
return result.Glyph.Layers.Count > 0;
}
}
}