// 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 {
///
/// ArraySlice 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 ArraySlice : IEnumerable, IEnumerable
where T : struct
{
private readonly T[] data;
///
/// Initializes a new instance of the struct.
///
/// The underlying data buffer.
public ArraySlice(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 ArraySlice(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 ArraySlice 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 Span 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 ref 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 ref Unsafe.Add(ref b, index);
}
}
///
/// Defines an implicit conversion of a to a
///
public static implicit operator ReadOnlyArraySlice(ArraySlice slice)
=> new(slice.data, slice.Start, slice.Length);
///
/// Defines an implicit conversion of an array to a
///
public static implicit operator ArraySlice(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);
///
/// Fills the contents of this slice with the given value.
///
public void Fill(T value) => this.Span.Fill(value);
///
/// 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 ArraySlice 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(ArraySlice 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.");
}
}
}