// 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);
}
}
}