// 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.TrueType.Glyphs; namespace SixLabors.Fonts.Tables.General.Colr { /// /// Supplies painted glyphs for COLR v0 fonts. /// Flattens paint graphs into a linear stream and emits a . /// internal sealed class ColrV0GlyphSource : ColrGlyphSourceBase { /// /// Cache of previously resolved painted glyphs keyed by glyph ID. /// private readonly ConcurrentDictionary cachedGlyphs = []; /// /// 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. public ColrV0GlyphSource(ColrTable colr, CpalTable? cpal, Func glyphLoader) : base(colr, cpal, glyphLoader) { } /// public override bool TryGetPaintedGlyph(ushort glyphId, out PaintedGlyph glyph, out PaintedCanvasMetadata canvas) { (PaintedGlyph Glyph, PaintedCanvasMetadata Canvas) result = this.cachedGlyphs.GetOrAdd(glyphId, id => { if (this.Colr.TryGetColrV0Layers(id, out Span resolved)) { List layers = new(resolved.Length); for (int i = 0; i < resolved.Length; i++) { LayerRecord 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: attach composite mode to leaves. List leafPaints = []; PaintSolid paint = new() { PaletteIndex = rl.PaletteIndex, Alpha = 1, Format = 2 }; FlattenPaint(paint, Matrix3x2.Identity, CompositeMode.SrcOver, this.Cpal, this.Colr, null, leafPaints); // Emit one layer per leaf paint. for (int p = 0; p < leafPaints.Count; p++) { // Unlike COLR v1, COLR v0 leaves have no transform so we can reuse the same path. Rendering.Paint leaf = leafPaints[p]; layers.Add(new PaintedLayer(leaf, FillRule.NonZero, leaf.Transform, null, 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; } } }