40 changed files with 457 additions and 873 deletions
@ -1,275 +0,0 @@ |
|||||
using System; |
|
||||
using System.Collections; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Runtime.CompilerServices; |
|
||||
using Avalonia.Utilities; |
|
||||
|
|
||||
namespace Avalonia.Media.TextFormatting |
|
||||
{ |
|
||||
public readonly struct CharacterBufferRange : IReadOnlyList<char> |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Getting an empty character string
|
|
||||
/// </summary>
|
|
||||
public static CharacterBufferRange Empty => new CharacterBufferRange(); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Construct <see cref="CharacterBufferRange"/> from character array
|
|
||||
/// </summary>
|
|
||||
/// <param name="characterArray">character array</param>
|
|
||||
/// <param name="offsetToFirstChar">character buffer offset to the first character</param>
|
|
||||
/// <param name="characterLength">character length</param>
|
|
||||
public CharacterBufferRange( |
|
||||
char[] characterArray, |
|
||||
int offsetToFirstChar, |
|
||||
int characterLength |
|
||||
) |
|
||||
: this( |
|
||||
new CharacterBufferReference(characterArray, offsetToFirstChar), |
|
||||
characterLength |
|
||||
) |
|
||||
{ } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Construct <see cref="CharacterBufferRange"/> from string
|
|
||||
/// </summary>
|
|
||||
/// <param name="characterString">character string</param>
|
|
||||
/// <param name="offsetToFirstChar">character buffer offset to the first character</param>
|
|
||||
/// <param name="characterLength">character length</param>
|
|
||||
public CharacterBufferRange( |
|
||||
string characterString, |
|
||||
int offsetToFirstChar, |
|
||||
int characterLength |
|
||||
) |
|
||||
: this( |
|
||||
new CharacterBufferReference(characterString, offsetToFirstChar), |
|
||||
characterLength |
|
||||
) |
|
||||
{ } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Construct a <see cref="CharacterBufferRange"/> from <see cref="CharacterBufferReference"/>
|
|
||||
/// </summary>
|
|
||||
/// <param name="characterBufferReference">character buffer reference</param>
|
|
||||
/// <param name="characterLength">number of characters</param>
|
|
||||
public CharacterBufferRange( |
|
||||
CharacterBufferReference characterBufferReference, |
|
||||
int characterLength |
|
||||
) |
|
||||
{ |
|
||||
if (characterLength < 0) |
|
||||
{ |
|
||||
throw new ArgumentOutOfRangeException("characterLength", "ParameterCannotBeNegative"); |
|
||||
} |
|
||||
|
|
||||
int maxLength = characterBufferReference.CharacterBuffer.Length > 0 ? |
|
||||
characterBufferReference.CharacterBuffer.Length - characterBufferReference.OffsetToFirstChar : |
|
||||
0; |
|
||||
|
|
||||
if (characterLength > maxLength) |
|
||||
{ |
|
||||
throw new ArgumentOutOfRangeException("characterLength", $"ParameterCannotBeGreaterThan {maxLength}"); |
|
||||
} |
|
||||
|
|
||||
CharacterBufferReference = characterBufferReference; |
|
||||
Length = characterLength; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Construct a <see cref="CharacterBufferRange"/> from part of another <see cref="CharacterBufferRange"/>
|
|
||||
/// </summary>
|
|
||||
internal CharacterBufferRange( |
|
||||
CharacterBufferRange characterBufferRange, |
|
||||
int offsetToFirstChar, |
|
||||
int characterLength |
|
||||
) : |
|
||||
this( |
|
||||
characterBufferRange.CharacterBuffer, |
|
||||
characterBufferRange.OffsetToFirstChar + offsetToFirstChar, |
|
||||
characterLength |
|
||||
) |
|
||||
{ } |
|
||||
|
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Construct a <see cref="CharacterBufferRange"/> from string
|
|
||||
/// </summary>
|
|
||||
internal CharacterBufferRange( |
|
||||
string charString |
|
||||
) : |
|
||||
this( |
|
||||
charString, |
|
||||
0, |
|
||||
charString.Length |
|
||||
) |
|
||||
{ } |
|
||||
|
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Construct <see cref="CharacterBufferRange"/> from memory buffer
|
|
||||
/// </summary>
|
|
||||
internal CharacterBufferRange( |
|
||||
ReadOnlyMemory<char> charBuffer, |
|
||||
int offsetToFirstChar, |
|
||||
int characterLength |
|
||||
) : |
|
||||
this( |
|
||||
new CharacterBufferReference(charBuffer, offsetToFirstChar), |
|
||||
characterLength |
|
||||
) |
|
||||
{ } |
|
||||
|
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Construct a <see cref="CharacterBufferRange"/> by extracting text info from a text run
|
|
||||
/// </summary>
|
|
||||
internal CharacterBufferRange(TextRun textRun) |
|
||||
{ |
|
||||
CharacterBufferReference = textRun.CharacterBufferReference; |
|
||||
Length = textRun.Length; |
|
||||
} |
|
||||
|
|
||||
public char this[int index] |
|
||||
{ |
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|
||||
get |
|
||||
{ |
|
||||
#if DEBUG
|
|
||||
if (index.CompareTo(0) < 0 || index.CompareTo(Length) > 0) |
|
||||
{ |
|
||||
throw new ArgumentOutOfRangeException(nameof(index)); |
|
||||
} |
|
||||
#endif
|
|
||||
return CharacterBuffer.Span[CharacterBufferReference.OffsetToFirstChar + index]; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets a reference to the character buffer
|
|
||||
/// </summary>
|
|
||||
public CharacterBufferReference CharacterBufferReference { get; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets the number of characters in text source character store
|
|
||||
/// </summary>
|
|
||||
public int Length { get; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets a span from the character buffer range
|
|
||||
/// </summary>
|
|
||||
public ReadOnlySpan<char> Span => CharacterBuffer.Span.Slice(OffsetToFirstChar, Length); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets the character memory buffer
|
|
||||
/// </summary>
|
|
||||
internal ReadOnlyMemory<char> CharacterBuffer => CharacterBufferReference.CharacterBuffer; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets the character offset relative to the beginning of buffer to
|
|
||||
/// the first character of the run
|
|
||||
/// </summary>
|
|
||||
internal int OffsetToFirstChar => CharacterBufferReference.OffsetToFirstChar; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Indicate whether the character buffer range is empty
|
|
||||
/// </summary>
|
|
||||
internal bool IsEmpty => CharacterBuffer.Length == 0 || Length <= 0; |
|
||||
|
|
||||
internal CharacterBufferRange Take(int length) |
|
||||
{ |
|
||||
if (IsEmpty) |
|
||||
{ |
|
||||
return this; |
|
||||
} |
|
||||
|
|
||||
if (length > Length) |
|
||||
{ |
|
||||
throw new ArgumentOutOfRangeException(nameof(length)); |
|
||||
} |
|
||||
|
|
||||
return new CharacterBufferRange(CharacterBufferReference, length); |
|
||||
} |
|
||||
|
|
||||
internal CharacterBufferRange Skip(int length) |
|
||||
{ |
|
||||
if (IsEmpty) |
|
||||
{ |
|
||||
return this; |
|
||||
} |
|
||||
|
|
||||
if (length > Length) |
|
||||
{ |
|
||||
throw new ArgumentOutOfRangeException(nameof(length)); |
|
||||
} |
|
||||
|
|
||||
if (length == Length) |
|
||||
{ |
|
||||
return new CharacterBufferRange(new CharacterBufferReference(), 0); |
|
||||
} |
|
||||
|
|
||||
var characterBufferReference = new CharacterBufferReference(CharacterBuffer, OffsetToFirstChar + length); |
|
||||
|
|
||||
return new CharacterBufferRange(characterBufferReference, Length - length); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Compute hash code
|
|
||||
/// </summary>
|
|
||||
public override int GetHashCode() |
|
||||
{ |
|
||||
return CharacterBufferReference.GetHashCode() ^ Length; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Test equality with the input object
|
|
||||
/// </summary>
|
|
||||
/// <param name="obj"> The object to test </param>
|
|
||||
public override bool Equals(object? obj) |
|
||||
{ |
|
||||
if (obj is CharacterBufferRange range) |
|
||||
{ |
|
||||
return Equals(range); |
|
||||
} |
|
||||
|
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Test equality with the input CharacterBufferRange
|
|
||||
/// </summary>
|
|
||||
/// <param name="value"> The CharacterBufferRange value to test </param>
|
|
||||
public bool Equals(CharacterBufferRange value) |
|
||||
{ |
|
||||
return CharacterBufferReference.Equals(value.CharacterBufferReference) |
|
||||
&& Length == value.Length; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Compare two CharacterBufferRange for equality
|
|
||||
/// </summary>
|
|
||||
/// <param name="left">left operand</param>
|
|
||||
/// <param name="right">right operand</param>
|
|
||||
/// <returns>whether or not two operands are equal</returns>
|
|
||||
public static bool operator ==(CharacterBufferRange left, CharacterBufferRange right) |
|
||||
{ |
|
||||
return left.Equals(right); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Compare two CharacterBufferRange for inequality
|
|
||||
/// </summary>
|
|
||||
/// <param name="left">left operand</param>
|
|
||||
/// <param name="right">right operand</param>
|
|
||||
/// <returns>whether or not two operands are equal</returns>
|
|
||||
public static bool operator !=(CharacterBufferRange left, CharacterBufferRange right) |
|
||||
{ |
|
||||
return !(left == right); |
|
||||
} |
|
||||
|
|
||||
int IReadOnlyCollection<char>.Count => Length; |
|
||||
|
|
||||
public IEnumerator<char> GetEnumerator() => new ImmutableReadOnlyListStructEnumerator<char>(this); |
|
||||
|
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); |
|
||||
} |
|
||||
} |
|
||||
@ -1,115 +0,0 @@ |
|||||
using System; |
|
||||
|
|
||||
namespace Avalonia.Media.TextFormatting |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Text character buffer reference
|
|
||||
/// </summary>
|
|
||||
public readonly struct CharacterBufferReference : IEquatable<CharacterBufferReference> |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Construct character buffer reference from character array
|
|
||||
/// </summary>
|
|
||||
/// <param name="characterArray">character array</param>
|
|
||||
/// <param name="offsetToFirstChar">character buffer offset to the first character</param>
|
|
||||
public CharacterBufferReference(char[] characterArray, int offsetToFirstChar = 0) |
|
||||
: this(characterArray.AsMemory(), offsetToFirstChar) |
|
||||
{ } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Construct character buffer reference from string
|
|
||||
/// </summary>
|
|
||||
/// <param name="characterString">character string</param>
|
|
||||
/// <param name="offsetToFirstChar">character buffer offset to the first character</param>
|
|
||||
public CharacterBufferReference(string characterString, int offsetToFirstChar = 0) |
|
||||
: this(characterString.AsMemory(), offsetToFirstChar) |
|
||||
{ } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Construct character buffer reference from memory buffer
|
|
||||
/// </summary>
|
|
||||
internal CharacterBufferReference(ReadOnlyMemory<char> characterBuffer, int offsetToFirstChar = 0) |
|
||||
{ |
|
||||
if (offsetToFirstChar < 0) |
|
||||
{ |
|
||||
throw new ArgumentOutOfRangeException("offsetToFirstChar", "ParameterCannotBeNegative"); |
|
||||
} |
|
||||
|
|
||||
// maximum offset is one less than CharacterBuffer.Count, except that zero is always a valid offset
|
|
||||
// even in the case of an empty or null character buffer
|
|
||||
var maxOffset = characterBuffer.Length == 0 ? 0 : Math.Max(0, characterBuffer.Length - 1); |
|
||||
if (offsetToFirstChar > maxOffset) |
|
||||
{ |
|
||||
throw new ArgumentOutOfRangeException("offsetToFirstChar", $"ParameterCannotBeGreaterThan, {maxOffset}"); |
|
||||
} |
|
||||
|
|
||||
CharacterBuffer = characterBuffer; |
|
||||
OffsetToFirstChar = offsetToFirstChar; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets the character memory buffer
|
|
||||
/// </summary>
|
|
||||
public ReadOnlyMemory<char> CharacterBuffer { get; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Gets the character offset relative to the beginning of buffer to
|
|
||||
/// the first character of the run
|
|
||||
/// </summary>
|
|
||||
public int OffsetToFirstChar { get; } |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Compute hash code
|
|
||||
/// </summary>
|
|
||||
public override int GetHashCode() |
|
||||
{ |
|
||||
return CharacterBuffer.IsEmpty ? 0 : CharacterBuffer.GetHashCode(); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Test equality with the input object
|
|
||||
/// </summary>
|
|
||||
/// <param name="obj"> The object to test. </param>
|
|
||||
public override bool Equals(object? obj) |
|
||||
{ |
|
||||
if (obj is CharacterBufferReference reference) |
|
||||
{ |
|
||||
return Equals(reference); |
|
||||
} |
|
||||
|
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Test equality with the input CharacterBufferReference
|
|
||||
/// </summary>
|
|
||||
/// <param name="value"> The characterBufferReference value to test </param>
|
|
||||
public bool Equals(CharacterBufferReference value) |
|
||||
{ |
|
||||
return CharacterBuffer.Equals(value.CharacterBuffer); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Compare two CharacterBufferReference for equality
|
|
||||
/// </summary>
|
|
||||
/// <param name="left">left operand</param>
|
|
||||
/// <param name="right">right operand</param>
|
|
||||
/// <returns>whether or not two operands are equal</returns>
|
|
||||
public static bool operator ==(CharacterBufferReference left, CharacterBufferReference right) |
|
||||
{ |
|
||||
return left.Equals(right); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Compare two CharacterBufferReference for inequality
|
|
||||
/// </summary>
|
|
||||
/// <param name="left">left operand</param>
|
|
||||
/// <param name="right">right operand</param>
|
|
||||
/// <returns>whether or not two operands are equal</returns>
|
|
||||
public static bool operator !=(CharacterBufferReference left, CharacterBufferReference right) |
|
||||
{ |
|
||||
return !(left == right); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@ -0,0 +1,15 @@ |
|||||
|
using System; |
||||
|
using System.Runtime.InteropServices; |
||||
|
|
||||
|
namespace Avalonia.UnitTests |
||||
|
{ |
||||
|
public static class TextTestHelper |
||||
|
{ |
||||
|
public static int GetStartCharIndex(ReadOnlyMemory<char> text) |
||||
|
{ |
||||
|
if (!MemoryMarshal.TryGetString(text, out _, out var start, out _)) |
||||
|
throw new InvalidOperationException("text memory should have been a string"); |
||||
|
return start; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue