// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Numerics; using SixLabors.Fonts.Rendering; namespace SixLabors.ImageSharp.Drawing.Text { /// /// A geometry + paint container for a single glyph, preserving painted layer boundaries. /// public sealed class GlyphPathCollection { private readonly List paths; private readonly ReadOnlyCollection readOnlyPaths; private readonly List layers; private readonly ReadOnlyCollection readOnlyLayers; /// /// Initializes a new instance of the class. /// /// All paths emitted for the glyph in z-order. /// Layer descriptors referring to spans within . internal GlyphPathCollection(List paths, List layers) { Guard.NotNull(paths, nameof(paths)); Guard.NotNull(layers, nameof(layers)); this.paths = paths; this.layers = layers; this.readOnlyPaths = new ReadOnlyCollection(this.paths); this.readOnlyLayers = new ReadOnlyCollection(this.layers); this.Paths = new PathCollection(this.paths); } /// /// Gets the flattened geometry for the glyph (all paths in z-order). /// This is equivalent to concatenating all layer spans. /// public IPathCollection Paths { get; } /// /// Gets a read-only view of all individual paths in z-order. /// public IReadOnlyList PathList => this.readOnlyPaths; /// /// Gets a read-only list of layer descriptors preserving paint, fill rule and path spans. /// public IReadOnlyList Layers => this.readOnlyLayers; /// /// Gets the number of layers. /// public int LayerCount => this.layers.Count; /// /// Gets an axis-aligned bounding box of the entire glyph in device space. /// public RectangleF Bounds => this.Paths.Bounds; /// /// Transforms the glyph using the specified matrix. /// /// The transform matrix. /// /// A new with the matrix applied to it. /// public GlyphPathCollection Transform(Matrix4x4 matrix) { List transformed = new(this.paths.Count); for (int i = 0; i < this.paths.Count; i++) { transformed.Add(this.paths[i].Transform(matrix)); } List transformedLayers = new(this.layers.Count); for (int i = 0; i < this.layers.Count; i++) { transformedLayers.Add(GlyphLayerInfo.Transform(this.layers[i], matrix)); } return new GlyphPathCollection(transformed, transformedLayers); } /// /// Creates a containing only the paths from layers that /// satisfy . Useful to project to monochrome. /// /// A filter deciding whether to keep a layer. /// A new with the selected paths. public PathCollection ToPathCollection(Func? predicate = null) { List kept = []; for (int i = 0; i < this.layers.Count; i++) { GlyphLayerInfo li = this.layers[i]; if (predicate?.Invoke(li) == false) { continue; } int end = li.StartIndex + li.Count; for (int p = li.StartIndex; p < end; p++) { kept.Add(this.paths[p]); } } return new PathCollection(kept); } /// /// Gets a view of a single layer's geometry. /// /// The zero-based layer index. /// A path collection comprising only that layer's span. public PathCollection GetLayerPaths(int layerIndex) { Guard.MustBeLessThan(layerIndex, this.layers.Count, nameof(layerIndex)); GlyphLayerInfo li = this.layers[layerIndex]; List chunk = new(li.Count); int end = li.StartIndex + li.Count; for (int p = li.StartIndex; p < end; p++) { chunk.Add(this.paths[p]); } return new PathCollection(chunk); } /// /// Builder used by glyph renderers to populate a . /// internal sealed class Builder { private readonly List paths = []; private readonly List layers = []; /// /// Adds a completed path to the collection (current z-order position). /// /// The path to add. public void AddPath(IPath path) => this.paths.Add(path); /// /// Adds a layer descriptor pointing at the most recently added paths. /// /// Start index within the path list (inclusive). /// Number of paths belonging to this layer. /// The paint for this layer (may be null for default). /// The fill rule for this layer. /// Optional cached bounds for this layer. /// Optional semantic kind (eg. Decoration). /// /// Thrown if the specified span is out of range of the current path list. /// public void AddLayer( int startIndex, int count, Paint? paint, FillRule fillRule, RectangleF bounds, GlyphLayerKind kind = GlyphLayerKind.Glyph) { if (startIndex < 0 || count < 0 || startIndex + count > this.paths.Count) { throw new ArgumentOutOfRangeException(nameof(count), "Layer span is out of range of the current path list."); } this.layers.Add(new GlyphLayerInfo(startIndex, count, paint, fillRule, bounds, kind)); } /// /// Builds the immutable . /// /// The collection. public GlyphPathCollection Build() => new(this.paths, this.layers); } } }