// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using System; using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace SixLabors.Fonts { /// /// ReadOnlyArraySlice represents a contiguous region of arbitrary memory similar /// to and though constrained /// to arrays. /// Unlike , it is not a byref-like type. /// /// The type of item contained in the slice. internal readonly struct ReadOnlyArraySlice : IEnumerable where T : struct { private readonly T[] data; /// /// Initializes a new instance of the struct. /// /// The underlying data buffer. public ReadOnlyArraySlice(T[] data) : this(data, 0, data.Length) { } /// /// Initializes a new instance of the struct. /// /// The underlying data buffer. /// The offset position in the underlying buffer this slice was created from. /// The number of items in the slice. public ReadOnlyArraySlice(T[] data, int start, int length) { DebugGuard.MustBeGreaterThanOrEqualTo(start, 0, nameof(start)); DebugGuard.MustBeLessThanOrEqualTo(length, data.Length, nameof(length)); DebugGuard.MustBeLessThanOrEqualTo(start + length, data.Length, nameof(this.data)); this.data = data; this.Start = start; this.Length = length; } /// /// Gets an empty /// public static ReadOnlyArraySlice Empty => new(Array.Empty()); /// /// Gets the offset position in the underlying buffer this slice was created from. /// public int Start { get; } /// /// Gets the number of items in the slice. /// public int Length { get; } /// /// Gets a representing this slice. /// public ReadOnlySpan Span { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new(this.data, this.Start, this.Length); } /// /// Returns a reference to specified element of the slice. /// /// The index of the element to return. /// The . /// /// Thrown when index less than 0 or index greater than or equal to . /// public readonly T this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { DebugGuard.MustBeBetweenOrEqualTo(index, 0, this.Length, nameof(index)); ref T b = ref MemoryMarshal.GetReference(this.Span); return Unsafe.Add(ref b, index); } } /// /// Defines an implicit conversion of an array to a /// /// The input array. public static implicit operator ReadOnlyArraySlice(T[] array) => new(array, 0, array.Length); /// /// Copies the contents of this slice into destination span. If the source /// and destinations overlap, this method behaves as if the original values in /// a temporary location before the destination is overwritten. /// /// The slice to copy items into. /// /// Thrown when the destination slice is shorter than the source Span. /// public void CopyTo(ArraySlice destination) => this.Span.CopyTo(destination.Span); /// /// Forms a slice out of the given slice, beginning at 'start', of given length /// /// The index at which to begin this slice. /// The desired length for the slice (exclusive). /// /// Thrown when the specified or end index is not in range (<0 or >Length). /// public ReadOnlyArraySlice Slice(int start, int length) => new(this.data, start, length); /// public IEnumerator GetEnumerator() => new Enumerator(this); /// IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this); public struct Enumerator : IEnumerator { private readonly T[]? array; private readonly int start; private readonly int end; // cache Start + Length, since it's a little slow private int current; internal Enumerator(ReadOnlyArraySlice slice) { DebugGuard.NotNull(slice.data, nameof(slice.data)); DebugGuard.MustBeGreaterThanOrEqualTo(slice.Start, 0, nameof(slice.Start)); DebugGuard.MustBeGreaterThanOrEqualTo(slice.Length, 0, nameof(slice.Length)); DebugGuard.MustBeLessThanOrEqualTo( slice.Start + slice.Length, slice.data.Length, nameof(slice.data.Length)); this.array = slice.data; this.start = slice.Start; this.end = slice.Start + slice.Length; this.current = slice.Start - 1; } /// public readonly T Current { get { if (this.current < this.start) { ThrowEnumNotStarted(); } if (this.current >= this.end) { ThrowEnumEnded(); } return this.array![this.current]; } } object? IEnumerator.Current => this.Current; /// public bool MoveNext() { if (this.current < this.end) { this.current++; return this.current < this.end; } return false; } /// void IEnumerator.Reset() => this.current = this.start - 1; public readonly void Dispose() { } private static void ThrowEnumNotStarted() => throw new InvalidOperationException("Enumeration has not started. Call MoveNext."); private static void ThrowEnumEnded() => throw new InvalidOperationException("Enumeration already finished."); } } }