using System; using System.Collections.Generic; using System.Runtime.CompilerServices; namespace Avalonia.Media.TextFormatting.Unicode { public readonly record struct Codepoint { private readonly uint _value; /// /// The replacement codepoint that is used for non supported values. /// public static readonly Codepoint ReplacementCodepoint = new Codepoint('\uFFFD'); public Codepoint(uint value) { _value = value; } /// /// Get the codepoint's value. /// public uint Value => _value; /// /// Gets the . /// public GeneralCategory GeneralCategory => UnicodeData.GetGeneralCategory(_value); /// /// Gets the . /// public Script Script => UnicodeData.GetScript(_value); /// /// Gets the . /// public BidiClass BiDiClass => UnicodeData.GetBiDiClass(_value); /// /// Gets the . /// public BidiPairedBracketType PairedBracketType => UnicodeData.GetBiDiPairedBracketType(_value); /// /// Gets the . /// public LineBreakClass LineBreakClass => UnicodeData.GetLineBreakClass(_value); /// /// Gets the . /// public GraphemeBreakClass GraphemeBreakClass => UnicodeData.GetGraphemeClusterBreak(_value); /// /// Determines whether this is a break char. /// /// /// true if [is break character]; otherwise, false. /// public bool IsBreakChar { get { switch (_value) { case '\u000A': case '\u000B': case '\u000C': case '\u000D': case '\u0085': case '\u2028': case '\u2029': return true; default: return false; } } } /// /// Determines whether this is white space. /// /// /// true if [is whitespace]; otherwise, false. /// public bool IsWhiteSpace { get { switch (GeneralCategory) { case GeneralCategory.Control: case GeneralCategory.NonspacingMark: case GeneralCategory.Format: case GeneralCategory.SpaceSeparator: case GeneralCategory.SpacingMark: return true; } return false; } } /// /// Gets the canonical representation of a given codepoint. /// /// /// The code point to be mapped. /// The mapped canonical code point, or the passed . [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static Codepoint GetCanonicalType(Codepoint codePoint) { if (codePoint._value == 0x3008) { return new Codepoint(0x2329); } if (codePoint._value == 0x3009) { return new Codepoint(0x232A); } return codePoint; } /// /// Gets the codepoint representing the bracket pairing for this instance. /// /// /// When this method returns, contains the codepoint representing the bracket pairing for this instance; /// otherwise, the default value for the type of the parameter. /// This parameter is passed uninitialized. /// . /// if this instance has a bracket pairing; otherwise, [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryGetPairedBracket(out Codepoint codepoint) { if (PairedBracketType == BidiPairedBracketType.None) { codepoint = default; return false; } codepoint = UnicodeData.GetBiDiPairedBracket(_value); return true; } public static implicit operator int(Codepoint codepoint) { return (int)codepoint._value; } public static implicit operator uint(Codepoint codepoint) { return codepoint._value; } /// /// Reads the at specified position. /// /// The buffer to read from. /// The index to read at. /// The count of character that were read. /// public static Codepoint ReadAt(ReadOnlySpan text, int index, out int count) { count = 1; if (index >= text.Length) { return ReplacementCodepoint; } var code = text[index]; ushort hi, low; //# High surrogate if (0xD800 <= code && code <= 0xDBFF) { hi = code; if (index + 1 == text.Length) { return ReplacementCodepoint; } low = text[index + 1]; if (0xDC00 <= low && low <= 0xDFFF) { count = 2; return new Codepoint((uint)((hi - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000)); } return ReplacementCodepoint; } //# Low surrogate if (0xDC00 <= code && code <= 0xDFFF) { if (index == 0) { return ReplacementCodepoint; } hi = text[index - 1]; low = code; if (0xD800 <= hi && hi <= 0xDBFF) { count = 2; return new Codepoint((uint)((hi - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000)); } return ReplacementCodepoint; } return new Codepoint(code); } /// /// Returns if is between /// and , inclusive. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsInRangeInclusive(Codepoint cp, uint lowerBound, uint upperBound) => (cp._value - lowerBound) <= (upperBound - lowerBound); } }