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