// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Runtime.CompilerServices;
namespace SixLabors.Fonts.Tables.Cff {
///
/// A ref struct stack implementation that uses a pooled span to store the data.
///
/// The type of elements in the stack.
internal ref struct RefStack
where T : unmanaged
{
private const int MaxLength = 0X7FFFFFC7;
private Buffer buffer;
private Span stack;
private bool isDisposed;
///
/// Initializes a new instance of the struct with the specified initial capacity.
///
/// The initial capacity of the stack. Values less than 1 default to 4.
public RefStack(int capacity)
{
if (capacity < 1)
{
capacity = 4;
}
this.buffer = new Buffer(capacity);
this.stack = this.buffer.GetSpan();
this.isDisposed = false;
this.Length = 0;
}
///
/// Gets the number of elements currently in the stack.
///
public int Length { get; private set; }
///
/// Gets or sets the element at the specified index in the stack.
///
/// The zero-based index of the element.
/// The element at the specified index.
public T this[int index]
{
readonly get
{
if ((uint)index >= (uint)this.Length)
{
ThrowForOutOfRange();
}
return this.stack[index];
}
set
{
if ((uint)index >= (uint)this.Length)
{
this.Push(value);
return;
}
this.stack[index] = value;
}
}
///
/// Adds an item to the stack.
///
/// The item to add.
public void Push(T value)
{
if ((uint)this.Length < (uint)this.stack.Length)
{
this.stack[this.Length++] = value;
}
else
{
int capacity = this.stack.Length * 2;
if ((uint)capacity > MaxLength)
{
capacity = MaxLength;
}
var newBuffer = new Buffer(capacity);
Span newStack = newBuffer.GetSpan();
this.stack.CopyTo(newStack);
this.buffer.Dispose();
this.buffer = newBuffer;
this.stack = newStack;
this.stack[this.Length++] = value;
}
}
///
/// Removes the first element of the stack.
///
/// The element.
public T Shift()
{
int newSize = this.Length - 1;
if (newSize < 0)
{
ThrowForEmptyStack();
}
T item = this.stack[0];
this.stack = this.stack.Slice(1);
this.Length = newSize;
return item;
}
///
/// Removes the last element of the stack.
///
/// The element.
public T Pop()
{
int newSize = this.Length - 1;
if (newSize < 0)
{
ThrowForEmptyStack();
}
this.Length = newSize;
return this.stack[newSize];
}
///
/// Clears the current stack.
///
public void Clear()
{
this.Length = 0;
this.stack = this.buffer.GetSpan();
}
///
/// Releases the pooled buffer used by this stack.
///
public void Dispose()
{
if (this.isDisposed)
{
return;
}
this.buffer.Dispose();
this.isDisposed = true;
}
///
/// Throws an for an out-of-range index access.
///
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowForOutOfRange()
=> throw new InvalidOperationException("Index must be greater or equal to zero or less than the stack length.");
///
/// Throws an when attempting to pop or shift from an empty stack.
///
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowForEmptyStack() => throw new InvalidOperationException("Empty stack!");
}
}