diff --git a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj index 3f0ed53f4..754124ae1 100644 --- a/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj +++ b/Dalamud.CorePlugin/Dalamud.CorePlugin.csproj @@ -27,7 +27,7 @@ - + diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 79f80ea8c..9399154c8 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -71,7 +71,7 @@ - + diff --git a/Dalamud/Game/Gui/ChatGui.cs b/Dalamud/Game/Gui/ChatGui.cs index 84622c5e8..14346132a 100644 --- a/Dalamud/Game/Gui/ChatGui.cs +++ b/Dalamud/Game/Gui/ChatGui.cs @@ -8,7 +8,6 @@ using Dalamud.Game.Text; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling.Payloads; using Dalamud.Hooking; -using Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing; using Dalamud.IoC; using Dalamud.IoC.Internal; using Dalamud.Logging.Internal; @@ -22,12 +21,13 @@ using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI.Misc; using FFXIVClientStructs.FFXIV.Component.GUI; +using Lumina.Text; using Lumina.Text.Payloads; +using Lumina.Text.ReadOnly; -using LinkMacroPayloadType = Lumina.Text.Payloads.LinkMacroPayloadType; -using LuminaSeStringBuilder = Lumina.Text.SeStringBuilder; -using ReadOnlySePayloadType = Lumina.Text.ReadOnly.ReadOnlySePayloadType; -using ReadOnlySeStringSpan = Lumina.Text.ReadOnly.ReadOnlySeStringSpan; +using LSeStringBuilder = Lumina.Text.SeStringBuilder; +using SeString = Dalamud.Game.Text.SeStringHandling.SeString; +using SeStringBuilder = Dalamud.Game.Text.SeStringHandling.SeStringBuilder; namespace Dalamud.Game.Gui; @@ -166,45 +166,51 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui /// public void UpdateQueue() { - while (this.chatQueue.Count > 0) + if (this.chatQueue.Count == 0) + return; + + var sb = LSeStringBuilder.SharedPool.Get(); + Span namebuf = stackalloc byte[256]; + using var sender = new Utf8String(); + using var message = new Utf8String(); + while (this.chatQueue.TryDequeue(out var chat)) { - var chat = this.chatQueue.Dequeue(); - var sb = LuminaSeStringBuilder.SharedPool.Get(); - var rosss = (ReadOnlySeStringSpan)chat.MessageBytes; - - foreach (var payload in rosss) + sb.Clear(); + foreach (var c in UtfEnumerator.From(chat.MessageBytes, UtfEnumeratorFlags.Utf8SeString)) { - if (payload.Type == ReadOnlySePayloadType.Invalid) - continue; - - if (payload.Type != ReadOnlySePayloadType.Text) - { - sb.Append(payload); - continue; - } - - foreach (var c in UtfEnumerator.From(payload.Body, UtfEnumeratorFlags.Default)) - { - if (c.Value.IntValue == 0x202F) - sb.BeginMacro(MacroCode.NonBreakingSpace).EndMacro(); - else - sb.Append(c.EffectiveChar); - } + if (c.IsSeStringPayload) + sb.Append((ReadOnlySeStringSpan)chat.MessageBytes.AsSpan(c.ByteOffset, c.ByteLength)); + else if (c.Value.IntValue == 0x202F) + sb.BeginMacro(MacroCode.NonBreakingSpace).EndMacro(); + else + sb.Append(c); } - var output = sb.ToArray(); - LuminaSeStringBuilder.SharedPool.Return(sb); + if (chat.NameBytes.Length + 1 < namebuf.Length) + { + chat.NameBytes.AsSpan().CopyTo(namebuf); + namebuf[chat.NameBytes.Length] = 0; + sender.SetString(namebuf); + } + else + { + sender.SetString(chat.NameBytes.NullTerminate()); + } - var sender = Utf8String.FromSequence(chat.NameBytes.NullTerminate()); - var message = Utf8String.FromSequence(output.NullTerminate()); + message.SetString(sb.GetViewAsSpan()); var targetChannel = chat.Type ?? this.configuration.GeneralChatType; - this.HandlePrintMessageDetour(RaptureLogModule.Instance(), targetChannel, sender, message, chat.Timestamp, (byte)(chat.Silent ? 1 : 0)); - - sender->Dtor(true); - message->Dtor(true); + this.HandlePrintMessageDetour( + RaptureLogModule.Instance(), + targetChannel, + &sender, + &message, + chat.Timestamp, + (byte)(chat.Silent ? 1 : 0)); } + + LSeStringBuilder.SharedPool.Return(sb); } /// @@ -282,27 +288,29 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui private void PrintTagged(ReadOnlySpan message, XivChatType channel, string? tag, ushort? color) { - var builder = new LuminaSeStringBuilder(); + var sb = LSeStringBuilder.SharedPool.Get(); if (!tag.IsNullOrEmpty()) { if (color is not null) { - builder.PushColorType(color.Value); - builder.Append($"[{tag}] "); - builder.PopColorType(); + sb.PushColorType(color.Value); + sb.Append($"[{tag}] "); + sb.PopColorType(); } else { - builder.Append($"[{tag}] "); + sb.Append($"[{tag}] "); } } this.Print(new XivChatEntry { - MessageBytes = builder.Append((ReadOnlySeStringSpan)message).ToArray(), + MessageBytes = sb.Append((ReadOnlySeStringSpan)message).ToArray(), Type = channel, }); + + LSeStringBuilder.SharedPool.Return(sb); } private void InventoryItemCopyDetour(InventoryItem* thisPtr, InventoryItem* otherPtr) @@ -412,7 +420,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui Log.Verbose($"InteractableLinkClicked: {Payload.EmbeddedInfoType.DalamudLink}"); - var sb = LuminaSeStringBuilder.SharedPool.Get(); + var sb = LSeStringBuilder.SharedPool.Get(); try { var seStringSpan = new ReadOnlySeStringSpan(linkData->Payload); @@ -423,7 +431,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui sb.Append(payload); if (payload.Type == ReadOnlySePayloadType.Macro && - payload.MacroCode == Lumina.Text.Payloads.MacroCode.Link && + payload.MacroCode == MacroCode.Link && payload.TryGetExpression(out var expr1) && expr1.TryGetInt(out var expr1Val) && expr1Val == (int)LinkMacroPayloadType.Terminator) @@ -452,7 +460,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui } finally { - LuminaSeStringBuilder.SharedPool.Return(sb); + LSeStringBuilder.SharedPool.Return(sb); } } } diff --git a/Dalamud/Game/Text/SeStringHandling/SeString.cs b/Dalamud/Game/Text/SeStringHandling/SeString.cs index baae181e3..7f1955da5 100644 --- a/Dalamud/Game/Text/SeStringHandling/SeString.cs +++ b/Dalamud/Game/Text/SeStringHandling/SeString.cs @@ -133,9 +133,7 @@ public class SeString { while (stream.Position < len) { - var payload = Payload.Decode(reader); - if (payload != null) - payloads.Add(payload); + payloads.Add(Payload.Decode(reader)); } } diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs index 9d62de193..4937e4af0 100644 --- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/SeStringRenderer.cs @@ -19,6 +19,7 @@ using FFXIVClientStructs.FFXIV.Client.UI; using ImGuiNET; using Lumina.Excel.Sheets; +using Lumina.Text; using Lumina.Text.Parse; using Lumina.Text.Payloads; using Lumina.Text.ReadOnly; @@ -66,8 +67,7 @@ internal unsafe class SeStringRenderer : IInternalDisposableService [ServiceManager.ServiceConstructor] private SeStringRenderer(DataManager dm, TargetSigScanner sigScanner) { - this.colorStackSet = new( - dm.Excel.GetSheet() ?? throw new InvalidOperationException("Failed to access UIColor sheet.")); + this.colorStackSet = new(dm.Excel.GetSheet()); this.gfd = dm.GetFile("common/font/gfdata.gfd")!; } diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/LineBreakEnumerator.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/LineBreakEnumerator.cs index fa994bcd2..8b5115369 100644 --- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/LineBreakEnumerator.cs +++ b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/LineBreakEnumerator.cs @@ -2,6 +2,8 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using Lumina.Text; + using static Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing.UnicodeEastAsianWidthClass; using static Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing.UnicodeGeneralCategory; using static Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing.UnicodeLineBreakClass; diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfEnumerator.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfEnumerator.cs deleted file mode 100644 index b73bc85e4..000000000 --- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfEnumerator.cs +++ /dev/null @@ -1,325 +0,0 @@ -using System.Collections; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Text; - -using Lumina.Text; -using Lumina.Text.Payloads; -using Lumina.Text.ReadOnly; - -namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing; - -/// Enumerates a UTF-N byte sequence by codepoint. -[DebuggerDisplay("{Current}/{data.Length} ({flags}, BE={isBigEndian})")] -internal ref struct UtfEnumerator -{ - private readonly ReadOnlySpan data; - private readonly UtfEnumeratorFlags flags; - private readonly byte numBytesPerUnit; - private bool isBigEndian; - - /// Initializes a new instance of the struct. - /// UTF-N byte sequence. - /// Enumeration flags. - public UtfEnumerator(ReadOnlySpan data, UtfEnumeratorFlags flags) - { - this.data = data; - this.flags = flags; - this.numBytesPerUnit = (this.flags & UtfEnumeratorFlags.UtfMask) switch - { - UtfEnumeratorFlags.Utf8 or UtfEnumeratorFlags.Utf8SeString => 1, - UtfEnumeratorFlags.Utf16 => 2, - UtfEnumeratorFlags.Utf32 => 4, - _ => throw new ArgumentOutOfRangeException(nameof(this.flags), this.flags, "Multiple UTF flag specified."), - }; - this.isBigEndian = (flags & UtfEnumeratorFlags.EndiannessMask) switch - { - UtfEnumeratorFlags.NativeEndian => !BitConverter.IsLittleEndian, - UtfEnumeratorFlags.LittleEndian => false, - UtfEnumeratorFlags.BigEndian => true, - _ => throw new ArgumentOutOfRangeException(nameof(flags), flags, "Multiple endianness flag specified."), - }; - } - - /// - public Subsequence Current { get; private set; } = default; - - /// Creates a new instance of the struct. - /// UTF-N byte sequence. - /// Enumeration flags. - /// A new enumerator. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static UtfEnumerator From(ReadOnlySpan data, UtfEnumeratorFlags flags) => new(data, flags); - - /// Creates a new instance of the struct. - /// UTF-N byte sequence. - /// Enumeration flags. - /// A new enumerator. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static UtfEnumerator From(ReadOnlySpan data, UtfEnumeratorFlags flags) => - new(MemoryMarshal.Cast(data), flags); - - /// Gets the representative char for a given SeString macro code. - /// The macro code. - /// Representative char, or if none. - public static char RepresentativeCharFor(MacroCode macroCode) => macroCode switch - { - MacroCode.NewLine => '\u0085', - MacroCode.SoftHyphen => '\u00AD', - MacroCode.NonBreakingSpace => '\u00A0', - MacroCode.Hyphen => '-', - MacroCode.Icon or MacroCode.Icon2 => '\uFFFC', - _ => char.MaxValue, - }; - - /// Attempts to peek the next item. - /// Retrieved next item. - /// Whether it still should be parsed in big endian. - /// true if anything is retrieved. - /// The sequence is not a fully valid Unicode sequence, and - /// is set. - public readonly bool TryPeekNext(out Subsequence nextSubsequence, out bool isStillBigEndian) - { - var offset = this.Current.ByteOffset + this.Current.ByteLength; - isStillBigEndian = this.isBigEndian; - while (true) - { - var subspan = this.data[offset..]; - - if (subspan.IsEmpty) - { - nextSubsequence = default; - return false; - } - - UtfValue value; - int length; - var isBroken = - this.numBytesPerUnit switch - { - 1 => !UtfValue.TryDecode8(subspan, out value, out length), - 2 => !UtfValue.TryDecode16(subspan, isStillBigEndian, out value, out length), - 4 => !UtfValue.TryDecode32(subspan, isStillBigEndian, out value, out length), - _ => throw new InvalidOperationException(), - }; - if (!isBroken && value.IntValue == 0xFFFE) - { - if ((this.flags & UtfEnumeratorFlags.DisrespectByteOrderMask) == 0) - { - isStillBigEndian = !isStillBigEndian; - value = 0xFEFF; - } - - if ((this.flags & UtfEnumeratorFlags.YieldByteOrderMask) == 0) - { - offset += length; - continue; - } - } - - if (isBroken || !Rune.IsValid(value)) - { - switch (this.flags & UtfEnumeratorFlags.ErrorHandlingMask) - { - case UtfEnumeratorFlags.ReplaceErrors: - break; - - case UtfEnumeratorFlags.IgnoreErrors: - offset = Math.Min(offset + this.numBytesPerUnit, this.data.Length); - continue; - - case UtfEnumeratorFlags.ThrowOnFirstError: - if (isBroken) - throw new EncoderFallbackException($"0x{subspan[0]:X02} is not a valid sequence."); - throw new EncoderFallbackException( - $"U+{value.UIntValue:X08} is not a valid unicode codepoint."); - - case UtfEnumeratorFlags.TerminateOnFirstError: - default: - nextSubsequence = default; - return false; - } - } - - if (isBroken) - value = subspan[0]; - - if (value == SeString.StartByte && (this.flags & UtfEnumeratorFlags.Utf8SeString) != 0) - { - var e = new ReadOnlySeStringSpan(subspan).GetEnumerator(); - e.MoveNext(); - switch (this.flags & UtfEnumeratorFlags.ErrorHandlingMask) - { - case var _ when e.Current.Type is ReadOnlySePayloadType.Macro: - nextSubsequence = Subsequence.FromPayload( - e.Current.MacroCode, - offset, - e.Current.EnvelopeByteLength); - return true; - - case UtfEnumeratorFlags.ReplaceErrors: - value = '\uFFFE'; - length = e.Current.EnvelopeByteLength; - isBroken = true; - break; - - case UtfEnumeratorFlags.IgnoreErrors: - offset = Math.Min(offset + e.Current.EnvelopeByteLength, this.data.Length); - continue; - - case UtfEnumeratorFlags.ThrowOnFirstError: - throw new EncoderFallbackException("Invalid SeString payload."); - - case UtfEnumeratorFlags.TerminateOnFirstError: - default: - nextSubsequence = default; - return false; - } - } - - nextSubsequence = Subsequence.FromUnicode(value, offset, length, isBroken); - return true; - } - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool MoveNext() - { - if (!this.TryPeekNext(out var next, out var isStillBigEndian)) - return false; - - this.Current = next; - this.isBigEndian = isStillBigEndian; - return true; - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public UtfEnumerator GetEnumerator() => new(this.data, this.flags); - - /// A part of a UTF-N sequence containing one codepoint. - [StructLayout(LayoutKind.Explicit, Size = 16)] - [DebuggerDisplay("[{ByteOffset}, {ByteLength}] {Value}")] - public readonly struct Subsequence : IEquatable - { - /// The codepoint. Valid if is false. - [FieldOffset(0)] - public readonly UtfValue Value; - - /// The macro code. Valid if is true. - [FieldOffset(0)] - public readonly MacroCode MacroCode; - - /// The offset of this part of a UTF-8 sequence. - [FieldOffset(4)] - public readonly int ByteOffset; - - /// The length of this part of a UTF-8 sequence. - /// This may not match , if is true. - /// - [FieldOffset(8)] - public readonly int ByteLength; - - /// Whether this part of the UTF-8 sequence is broken. - [FieldOffset(12)] - public readonly bool BrokenSequence; - - /// Whether this part of the SeString sequence is a payload. - [FieldOffset(13)] - public readonly bool IsSeStringPayload; - - /// Storage at byte offset 0, for fast implementation. - [FieldOffset(0)] - private readonly ulong storage0; - - /// Storage at byte offset 8, for fast implementation. - [FieldOffset(8)] - private readonly ulong storage1; - - /// Initializes a new instance of the struct. - /// The value. - /// The byte offset of this part of a UTF-N sequence. - /// The byte length of this part of a UTF-N sequence. - /// Whether this part of the UTF-N sequence is broken. - /// Whether this part of the SeString sequence is a payload. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private Subsequence(uint value, int byteOffset, int byteLength, bool brokenSequence, bool isSeStringPayload) - { - this.Value = new(value); - this.ByteOffset = byteOffset; - this.ByteLength = byteLength; - this.BrokenSequence = brokenSequence; - this.IsSeStringPayload = isSeStringPayload; - } - - /// Gets the effective char value, with invalid or non-representable codepoints replaced. - /// - /// if the character should not be displayed at all. - public char EffectiveChar - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this.EffectiveInt is var i and >= 0 and < char.MaxValue ? (char)i : char.MaxValue; - } - - /// Gets the effective int value, with invalid codepoints replaced. - /// if the character should not be displayed at all. - public int EffectiveInt => - this.IsSeStringPayload - ? RepresentativeCharFor(this.MacroCode) - : this.BrokenSequence || !this.Value.TryGetRune(out var rune) - ? 0xFFFD - : rune.Value; - - /// Gets the effective value, with invalid codepoints replaced. - /// if the character should not be displayed at all. - public Rune EffectiveRune - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => new(this.EffectiveInt); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Subsequence left, Subsequence right) => left.Equals(right); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Subsequence left, Subsequence right) => !left.Equals(right); - - /// Creates a new instance of the struct from a Unicode value. - /// The codepoint. - /// The byte offset of this part of a UTF-N sequence. - /// The byte length of this part of a UTF-N sequence. - /// Whether this part of the UTF-N sequence is broken. - /// A new instance. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Subsequence FromUnicode(uint codepoint, int byteOffset, int byteLength, bool brokenSequence) => - new(codepoint, byteOffset, byteLength, brokenSequence, false); - - /// Creates a new instance of the struct from a SeString payload. - /// The macro code. - /// The byte offset of this part of a UTF-N sequence. - /// The byte length of this part of a UTF-N sequence. - /// A new instance. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Subsequence FromPayload(MacroCode macroCode, int byteOffset, int byteLength) => - new((uint)macroCode, byteOffset, byteLength, false, true); - - /// Tests whether this subsequence contains a valid Unicode codepoint. - /// true if this subsequence contains a valid Unicode codepoint. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsValid() => !this.BrokenSequence && Rune.IsValid(this.Value); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Subsequence other) => this.storage0 == other.storage0 && this.storage1 == other.storage1; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override bool Equals(object? obj) => obj is Subsequence other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() => HashCode.Combine(this.storage0, this.storage1); - } -} diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfEnumeratorFlags.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfEnumeratorFlags.cs deleted file mode 100644 index 01380e40c..000000000 --- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfEnumeratorFlags.cs +++ /dev/null @@ -1,58 +0,0 @@ -namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing; - -/// Flags on enumerating a unicode sequence. -[Flags] -internal enum UtfEnumeratorFlags -{ - /// Use the default configuration of and . - Default = default, - - /// Enumerate as UTF-8 (the default.) - Utf8 = Default, - - /// Enumerate as UTF-8 in a SeString. - Utf8SeString = 1 << 1, - - /// Enumerate as UTF-16. - Utf16 = 1 << 2, - - /// Enumerate as UTF-32. - Utf32 = 1 << 3, - - /// Bitmask for specifying the encoding. - UtfMask = Utf8 | Utf8SeString | Utf16 | Utf32, - - /// On error, replace to U+FFFD (REPLACEMENT CHARACTER, the default.) - ReplaceErrors = Default, - - /// On error, drop the invalid byte. - IgnoreErrors = 1 << 4, - - /// On error, stop the handling. - TerminateOnFirstError = 1 << 5, - - /// On error, throw an exception. - ThrowOnFirstError = 1 << 6, - - /// Bitmask for specifying the error handling mode. - ErrorHandlingMask = ReplaceErrors | IgnoreErrors | TerminateOnFirstError | ThrowOnFirstError, - - /// Use the current system native endianness from - /// (the default.) - NativeEndian = Default, - - /// Use little endianness. - LittleEndian = 1 << 7, - - /// Use big endianness. - BigEndian = 1 << 8, - - /// Bitmask for specifying endianness. - EndiannessMask = NativeEndian | LittleEndian | BigEndian, - - /// Disrespect byte order mask. - DisrespectByteOrderMask = 1 << 9, - - /// Yield byte order masks, if it shows up. - YieldByteOrderMask = 1 << 10, -} diff --git a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfValue.cs b/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfValue.cs deleted file mode 100644 index 6930e6ba4..000000000 --- a/Dalamud/Interface/ImGuiSeStringRenderer/Internal/TextProcessing/UtfValue.cs +++ /dev/null @@ -1,665 +0,0 @@ -using System.Buffers.Binary; -using System.Diagnostics; -using System.IO; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Text; - -namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing; - -/// Represents a single value to be used in a UTF-N byte sequence. -[StructLayout(LayoutKind.Explicit, Size = 4)] -[DebuggerDisplay("0x{IntValue,h} ({CharValue})")] -internal readonly struct UtfValue : IEquatable, IComparable -{ - /// The unicode codepoint in int, that may not be in a valid range. - [FieldOffset(0)] - public readonly int IntValue; - - /// The unicode codepoint in uint, that may not be in a valid range. - [FieldOffset(0)] - public readonly uint UIntValue; - - /// The high UInt16 value in char, that may have been cut off if outside BMP. - [FieldOffset(0)] - public readonly char CharValue; - - /// Initializes a new instance of the struct. - /// The raw codepoint value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public UtfValue(uint value) => this.UIntValue = value; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public UtfValue(int value) => this.IntValue = value; - - /// Gets the length of this codepoint, encoded in UTF-8. - public int Length8 - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => GetEncodedLength8(this); - } - - /// Gets the length of this codepoint, encoded in UTF-16. - public int Length16 - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => GetEncodedLength16(this); - } - - /// Gets the short name, if supported. - /// The buffer containing the short name, or empty if unsupported. - public ReadOnlySpan ShortName - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => GetShortName(this); - } - - public static implicit operator uint(UtfValue c) => c.UIntValue; - - public static implicit operator int(UtfValue c) => c.IntValue; - - public static implicit operator UtfValue(byte c) => new(c); - - public static implicit operator UtfValue(sbyte c) => new(c); - - public static implicit operator UtfValue(ushort c) => new(c); - - public static implicit operator UtfValue(short c) => new(c); - - public static implicit operator UtfValue(uint c) => new(c); - - public static implicit operator UtfValue(int c) => new(c); - - public static implicit operator UtfValue(char c) => new(c); - - public static implicit operator UtfValue(Rune c) => new(c.Value); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(UtfValue left, UtfValue right) => left.Equals(right); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(UtfValue left, UtfValue right) => !left.Equals(right); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator <(UtfValue left, UtfValue right) => left.CompareTo(right) < 0; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator >(UtfValue left, UtfValue right) => left.CompareTo(right) > 0; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator <=(UtfValue left, UtfValue right) => left.CompareTo(right) <= 0; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator >=(UtfValue left, UtfValue right) => left.CompareTo(right) >= 0; - - /// Gets the short name of the codepoint, for some select codepoints. - /// The codepoint. - /// The value. - public static ReadOnlySpan GetShortName(int codepoint) => - codepoint switch - { - 0x00 => "NUL", - 0x01 => "SOH", - 0x02 => "STX", - 0x03 => "ETX", - 0x04 => "EOT", - 0x05 => "ENQ", - 0x06 => "ACK", - 0x07 => "BEL", - 0x08 => "BS", - 0x09 => "HT", - 0x0a => "LF", - 0x0b => "VT", - 0x0c => "FF", - 0x0d => "CR", - 0x0e => "SO", - 0x0f => "SI", - - 0x10 => "DLE", - 0x11 => "DC1", - 0x12 => "DC2", - 0x13 => "DC3", - 0x14 => "DC4", - 0x15 => "NAK", - 0x16 => "SYN", - 0x17 => "SOH", - 0x18 => "CAN", - 0x19 => "EOM", - 0x1a => "SUB", - 0x1b => "ESC", - 0x1c => "FS", - 0x1d => "GS", - 0x1e => "RS", - 0x1f => "US", - - 0x80 => "PAD", - 0x81 => "HOP", - 0x82 => "BPH", - 0x83 => "NBH", - 0x84 => "IND", - 0x85 => "NEL", - 0x86 => "SSA", - 0x87 => "ESA", - 0x88 => "HTS", - 0x89 => "HTJ", - 0x8a => "VTS", - 0x8b => "PLD", - 0x8c => "PLU", - 0x8d => "RI", - 0x8e => "SS2", - 0x8f => "SS3", - - 0x90 => "DCS", - 0x91 => "PU1", - 0x92 => "PU2", - 0x93 => "STS", - 0x94 => "CCH", - 0x95 => "MW", - 0x96 => "SPA", - 0x97 => "EPA", - 0x98 => "SOS", - 0x99 => "SGC", - 0x9a => "SCI", - 0x9b => "CSI", - 0x9c => "ST", - 0x9d => "OSC", - 0x9e => "PM", - 0x9f => "APC", - - 0xa0 => "NBSP", - 0xad => "SHY", - - _ => default, - }; - - /// Gets the length of the codepoint, when encoded in UTF-8. - /// The codepoint to encode. - /// The length. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetEncodedLength8(int codepoint) => (uint)codepoint switch - { - < 1u << 7 => 1, - < 1u << 11 => 2, - < 1u << 16 => 3, - < 1u << 21 => 4, - // Not a valid Unicode codepoint anymore below. - < 1u << 26 => 5, - < 1u << 31 => 6, - _ => 7, - }; - - /// Gets the length of the codepoint, when encoded in UTF-16. - /// The codepoint to encode. - /// The length. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetEncodedLength16(int codepoint) => (uint)codepoint switch - { - < 0x10000 => 2, - < 0x10000 + (1 << 20) => 4, - // Not a valid Unicode codepoint anymore below. - < 0x10000 + (1 << 30) => 6, - _ => 8, - }; - - /// - /// Trims at beginning by . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryDecode8(ref ReadOnlySpan source, out UtfValue value, out int length) - { - var v = TryDecode8(source, out value, out length); - source = source[length..]; - return v; - } - - /// Attempts to decode a value from a UTF-8 byte sequence. - /// The span to decode from. - /// The decoded value. - /// The length of the consumed bytes. 1 if sequence is broken. - /// true if is successfully decoded. - /// Codepoints that results in false from can still be returned, - /// including unpaired surrogate characters, or codepoints above U+10FFFFF. This function returns a value only - /// indicating whether the sequence could be decoded into a number, without being too short. - public static unsafe bool TryDecode8(ReadOnlySpan source, out UtfValue value, out int length) - { - if (source.IsEmpty) - { - value = default; - length = 0; - return false; - } - - fixed (byte* ptr = source) - { - if ((ptr[0] & 0x80) == 0) - { - length = 1; - value = ptr[0]; - } - else if ((ptr[0] & 0b11100000) == 0b11000000 && source.Length >= 2 - && ((uint)ptr[1] & 0b11000000) == 0b10000000) - { - length = 2; - value = (((uint)ptr[0] & 0x1F) << 6) | - (((uint)ptr[1] & 0x3F) << 0); - } - else if (((uint)ptr[0] & 0b11110000) == 0b11100000 && source.Length >= 3 - && ((uint)ptr[1] & 0b11000000) == 0b10000000 - && ((uint)ptr[2] & 0b11000000) == 0b10000000) - { - length = 3; - value = (((uint)ptr[0] & 0x0F) << 12) | - (((uint)ptr[1] & 0x3F) << 6) | - (((uint)ptr[2] & 0x3F) << 0); - } - else if (((uint)ptr[0] & 0b11111000) == 0b11110000 && source.Length >= 4 - && ((uint)ptr[1] & 0b11000000) == 0b10000000 - && ((uint)ptr[2] & 0b11000000) == 0b10000000 - && ((uint)ptr[3] & 0b11000000) == 0b10000000) - { - length = 4; - value = (((uint)ptr[0] & 0x07) << 18) | - (((uint)ptr[1] & 0x3F) << 12) | - (((uint)ptr[2] & 0x3F) << 6) | - (((uint)ptr[3] & 0x3F) << 0); - } - else if (((uint)ptr[0] & 0b11111100) == 0b11111000 && source.Length >= 5 - && ((uint)ptr[1] & 0b11000000) == 0b10000000 - && ((uint)ptr[2] & 0b11000000) == 0b10000000 - && ((uint)ptr[3] & 0b11000000) == 0b10000000 - && ((uint)ptr[4] & 0b11000000) == 0b10000000) - { - length = 5; - value = (((uint)ptr[0] & 0x03) << 24) | - (((uint)ptr[1] & 0x3F) << 18) | - (((uint)ptr[2] & 0x3F) << 12) | - (((uint)ptr[3] & 0x3F) << 6) | - (((uint)ptr[4] & 0x3F) << 0); - } - else if (((uint)ptr[0] & 0b11111110) == 0b11111100 && source.Length >= 6 - && ((uint)ptr[1] & 0b11000000) == 0b10000000 - && ((uint)ptr[2] & 0b11000000) == 0b10000000 - && ((uint)ptr[3] & 0b11000000) == 0b10000000 - && ((uint)ptr[4] & 0b11000000) == 0b10000000 - && ((uint)ptr[5] & 0b11000000) == 0b10000000) - { - length = 6; - value = (((uint)ptr[0] & 0x01) << 30) | - (((uint)ptr[1] & 0x3F) << 24) | - (((uint)ptr[2] & 0x3F) << 18) | - (((uint)ptr[3] & 0x3F) << 12) | - (((uint)ptr[4] & 0x3F) << 6) | - (((uint)ptr[5] & 0x3F) << 0); - } - else if (((uint)ptr[0] & 0b11111111) == 0b11111110 && source.Length >= 7 - && ((uint)ptr[1] & 0b11111100) == 0b10000000 - && ((uint)ptr[2] & 0b11000000) == 0b10000000 - && ((uint)ptr[3] & 0b11000000) == 0b10000000 - && ((uint)ptr[4] & 0b11000000) == 0b10000000 - && ((uint)ptr[5] & 0b11000000) == 0b10000000 - && ((uint)ptr[6] & 0b11000000) == 0b10000000) - { - length = 7; - value = (((uint)ptr[1] & 0x03) << 30) | - (((uint)ptr[2] & 0x3F) << 24) | - (((uint)ptr[3] & 0x3F) << 18) | - (((uint)ptr[4] & 0x3F) << 12) | - (((uint)ptr[5] & 0x3F) << 6) | - (((uint)ptr[6] & 0x3F) << 0); - } - else - { - length = 1; - value = default; - return false; - } - - return true; - } - } - - /// - /// Trims at beginning by . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryDecode16(ref ReadOnlySpan source, bool be, out UtfValue value, out int length) - { - var v = TryDecode16(source, be, out value, out length); - source = source[length..]; - return v; - } - - /// Attempts to decode a value from a UTF-16 byte sequence. - /// The span to decode from. - /// Whether to use big endian. - /// The decoded value. - /// The length of the consumed bytes. 1 if cut short. - /// 2 if sequence is broken. - /// true if is successfully decoded. - /// Codepoints that results in false from can still be returned, - /// including unpaired surrogate characters, or codepoints above U+10FFFFF. This function returns a value only - /// indicating whether the sequence could be decoded into a number, without being too short. - public static unsafe bool TryDecode16(ReadOnlySpan source, bool be, out UtfValue value, out int length) - { - if (source.Length < 2) - { - value = default; - length = source.Length; - return false; - } - - fixed (byte* ptr = source) - { - var p16 = (ushort*)ptr; - var val = be == BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(*p16) : *p16; - if (char.IsHighSurrogate((char)val)) - { - var lookahead1 = source.Length >= 4 ? p16[1] : 0; - var lookahead2 = source.Length >= 6 ? p16[2] : 0; - var lookahead3 = source.Length >= 8 ? p16[3] : 0; - if (char.IsLowSurrogate((char)lookahead1)) - { - // Not a valid Unicode codepoint anymore inside the block below. - if (char.IsLowSurrogate((char)lookahead2)) - { - if (char.IsLowSurrogate((char)lookahead3)) - { - value = 0x10000 - + (((val & 0x3) << 30) | - ((lookahead1 & 0x3FF) << 20) | - ((lookahead2 & 0x3FF) << 10) | - ((lookahead3 & 0x3FF) << 0)); - length = 8; - return true; - } - - value = 0x10000 - + (((val & 0x3FF) << 20) | - ((lookahead1 & 0x3FF) << 10) | - ((lookahead2 & 0x3FF) << 0)); - length = 6; - return true; - } - - value = 0x10000 + - (((val & 0x3FF) << 10) | - ((lookahead1 & 0x3FF) << 0)); - length = 4; - return true; - } - } - - // Calls are supposed to handle unpaired surrogates. - value = val; - length = 2; - return true; - } - } - - /// - /// Trims at beginning by . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryDecode32(ref ReadOnlySpan source, bool be, out UtfValue value, out int length) - { - var v = TryDecode32(source, be, out value, out length); - source = source[length..]; - return v; - } - - /// Attempts to decode a value from a UTF-32 byte sequence. - /// The span to decode from. - /// Whether to use big endian. - /// The decoded value. - /// The length of the consumed bytes. 1 to 3 if cut short. - /// 4 if sequence is broken. - /// true if is successfully decoded. - /// Codepoints that results in false from can still be returned, - /// including unpaired surrogate characters, or codepoints above U+10FFFFF. This function returns a value only - /// indicating whether the sequence could be decoded into a number, without being too short. - public static bool TryDecode32(ReadOnlySpan source, bool be, out UtfValue value, out int length) - { - if (source.Length < 4) - { - value = default; - length = source.Length; - return false; - } - - length = 4; - if ((be && BinaryPrimitives.TryReadInt32BigEndian(source, out var i32)) - || (!be && BinaryPrimitives.TryReadInt32LittleEndian(source, out i32))) - { - value = i32; - return true; - } - - value = default; - return false; - } - - /// Encodes the codepoint to the target in UTF-8. - /// The target stream. - /// The codepoint to encode. - /// The length of the encoded data. - /// Trims at beginning by the length. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Encode8(Stream target, int codepoint) - { - Span buf = stackalloc byte[7]; - Encode8(buf, codepoint, out var length); - target.Write(buf[..length]); - return length; - } - - /// Encodes the codepoint to the target in UTF-8. - /// The target byte span. - /// The codepoint to encode. - /// The length of the encoded data. - /// Trims at beginning by the length. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Encode8(ref Span target, int codepoint) - { - target = Encode8(target, codepoint, out var length); - return length; - } - - /// Encodes the codepoint to the target in UTF-8. - /// The optional target byte span. - /// The codepoint to encode. - /// The length of the encoded data. - /// The remaning region of . - public static Span Encode8(Span target, int codepoint, out int length) - { - var value = (uint)codepoint; - length = GetEncodedLength8(codepoint); - if (target.IsEmpty) - return target; - - switch (length) - { - case 1: - target[0] = (byte)value; - return target[1..]; - case 2: - target[0] = (byte)(0xC0 | ((value >> 6) & 0x1F)); - target[1] = (byte)(0x80 | ((value >> 0) & 0x3F)); - return target[2..]; - case 3: - target[0] = (byte)(0xE0 | ((value >> 12) & 0x0F)); - target[1] = (byte)(0x80 | ((value >> 6) & 0x3F)); - target[2] = (byte)(0x80 | ((value >> 0) & 0x3F)); - return target[3..]; - case 4: - target[0] = (byte)(0xF0 | ((value >> 18) & 0x07)); - target[1] = (byte)(0x80 | ((value >> 12) & 0x3F)); - target[2] = (byte)(0x80 | ((value >> 6) & 0x3F)); - target[3] = (byte)(0x80 | ((value >> 0) & 0x3F)); - return target[4..]; - case 5: - target[0] = (byte)(0xF8 | ((value >> 24) & 0x03)); - target[1] = (byte)(0x80 | ((value >> 18) & 0x3F)); - target[2] = (byte)(0x80 | ((value >> 12) & 0x3F)); - target[3] = (byte)(0x80 | ((value >> 6) & 0x3F)); - target[4] = (byte)(0x80 | ((value >> 0) & 0x3F)); - return target[5..]; - case 6: - target[0] = (byte)(0xFC | ((value >> 30) & 0x01)); - target[1] = (byte)(0x80 | ((value >> 24) & 0x3F)); - target[2] = (byte)(0x80 | ((value >> 18) & 0x3F)); - target[3] = (byte)(0x80 | ((value >> 12) & 0x3F)); - target[4] = (byte)(0x80 | ((value >> 6) & 0x3F)); - target[5] = (byte)(0x80 | ((value >> 0) & 0x3F)); - return target[6..]; - case 7: - target[0] = 0xFE; - target[1] = (byte)(0x80 | ((value >> 30) & 0x03)); - target[2] = (byte)(0x80 | ((value >> 24) & 0x3F)); - target[3] = (byte)(0x80 | ((value >> 18) & 0x3F)); - target[4] = (byte)(0x80 | ((value >> 12) & 0x3F)); - target[5] = (byte)(0x80 | ((value >> 6) & 0x3F)); - target[6] = (byte)(0x80 | ((value >> 0) & 0x3F)); - return target[7..]; - default: - Debug.Assert(false, $"{nameof(Length8)} property should have produced all possible cases."); - return target; - } - } - - /// Encodes the codepoint to the target in UTF-16. - /// The target stream. - /// The codepoint to encode. - /// Whether to use big endian. - /// The length of the encoded data. - /// Trims at beginning by the length. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Encode16(Stream target, int codepoint, bool be) - { - Span buf = stackalloc byte[8]; - Encode16(buf, codepoint, be, out var length); - target.Write(buf[..length]); - return length; - } - - /// Encodes the codepoint to the target in UTF-16. - /// The target byte span. - /// The codepoint to encode. - /// Whether to use big endian. - /// The length of the encoded data. - /// Trims at beginning by the length. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Encode16(ref Span target, int codepoint, bool be) - { - target = Encode16(target, codepoint, be, out var length); - return length; - } - - /// Encodes the codepoint to the target in UTF-16. - /// The optional target byte span. - /// The codepoint to encode. - /// Whether to use big endian. - /// The length of the encoded data. - /// The remaning region of . - public static Span Encode16(Span target, int codepoint, bool be, out int length) - { - var value = (uint)codepoint; - length = GetEncodedLength16(codepoint); - if (target.IsEmpty) - return target; - - if (be) - { - switch (length) - { - case 2: - BinaryPrimitives.WriteUInt16BigEndian(target[0..], (ushort)value); - return target[2..]; - case 4: - value -= 0x10000; - BinaryPrimitives.WriteUInt16BigEndian(target[0..], (ushort)(0xD800 | ((value >> 10) & 0x3FF))); - BinaryPrimitives.WriteUInt16BigEndian(target[2..], (ushort)(0xDC00 | ((value >> 00) & 0x3FF))); - return target[4..]; - case 6: - value -= 0x10000; - BinaryPrimitives.WriteUInt16BigEndian(target[0..], (ushort)(0xD800 | ((value >> 20) & 0x3FF))); - BinaryPrimitives.WriteUInt16BigEndian(target[2..], (ushort)(0xDC00 | ((value >> 10) & 0x3FF))); - BinaryPrimitives.WriteUInt16BigEndian(target[4..], (ushort)(0xDC00 | ((value >> 00) & 0x3FF))); - return target[6..]; - case 8: - value -= 0x10000; - BinaryPrimitives.WriteUInt16BigEndian(target[0..], (ushort)(0xD800 | ((value >> 30) & 0x3))); - BinaryPrimitives.WriteUInt16BigEndian(target[2..], (ushort)(0xDC00 | ((value >> 20) & 0x3FF))); - BinaryPrimitives.WriteUInt16BigEndian(target[4..], (ushort)(0xDC00 | ((value >> 10) & 0x3FF))); - BinaryPrimitives.WriteUInt16BigEndian(target[6..], (ushort)(0xDC00 | ((value >> 00) & 0x3FF))); - return target[8..]; - default: - Debug.Assert(false, $"{nameof(Length16)} property should have produced all possible cases."); - return target; - } - } - - switch (length) - { - case 2: - BinaryPrimitives.WriteUInt16LittleEndian(target[0..], (ushort)value); - return target[2..]; - case 4: - value -= 0x10000; - BinaryPrimitives.WriteUInt16LittleEndian(target[0..], (ushort)(0xD800 | ((value >> 10) & 0x3FF))); - BinaryPrimitives.WriteUInt16LittleEndian(target[2..], (ushort)(0xDC00 | ((value >> 00) & 0x3FF))); - return target[4..]; - case 6: - value -= 0x10000; - BinaryPrimitives.WriteUInt16LittleEndian(target[0..], (ushort)(0xD800 | ((value >> 20) & 0x3FF))); - BinaryPrimitives.WriteUInt16LittleEndian(target[2..], (ushort)(0xDC00 | ((value >> 10) & 0x3FF))); - BinaryPrimitives.WriteUInt16LittleEndian(target[4..], (ushort)(0xDC00 | ((value >> 00) & 0x3FF))); - return target[6..]; - case 8: - value -= 0x10000; - BinaryPrimitives.WriteUInt16LittleEndian(target[0..], (ushort)(0xD800 | ((value >> 30) & 0x3))); - BinaryPrimitives.WriteUInt16LittleEndian(target[2..], (ushort)(0xDC00 | ((value >> 20) & 0x3FF))); - BinaryPrimitives.WriteUInt16LittleEndian(target[4..], (ushort)(0xDC00 | ((value >> 10) & 0x3FF))); - BinaryPrimitives.WriteUInt16LittleEndian(target[6..], (ushort)(0xDC00 | ((value >> 00) & 0x3FF))); - return target[8..]; - default: - Debug.Assert(false, $"{nameof(Length16)} property should have produced all possible cases."); - return target; - } - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int CompareTo(UtfValue other) => this.IntValue.CompareTo(other.IntValue); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(UtfValue other) => this.IntValue == other.IntValue; - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override bool Equals(object? obj) => obj is UtfValue other && this.Equals(other); - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetHashCode() => this.IntValue; - - /// Attempts to get the corresponding rune. - /// The retrieved rune. - /// true if retrieved. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryGetRune(out Rune rune) - { - if (Rune.IsValid(this.IntValue)) - { - rune = new(this.IntValue); - return true; - } - - rune = default; - return false; - } - - /// Encodes the codepoint to the target. - /// The target byte span. - /// The remaning region of . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span Encode8(Span target) => Encode8(target, this, out _); -} diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs index f4b76942f..e026a6d2f 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/SeStringRendererTestWidget.cs @@ -1,4 +1,3 @@ -using System.Linq; using System.Numerics; using System.Text; @@ -34,7 +33,7 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget private static readonly string[] ThemeNames = ["Dark", "Light", "Classic FF", "Clear Blue"]; private ImVectorWrapper testStringBuffer; private string testString = string.Empty; - private ExcelSheet addons; + private ExcelSheet addons = null!; private ReadOnlySeString? logkind; private SeStringDrawParams style; private bool interactable; @@ -241,8 +240,9 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget if (ImGui.Button("Print to Chat Log")) { - fixed (byte* p = Service.Get().CompileAndCache(this.testString).Data.Span) - Service.Get().Print(Game.Text.SeStringHandling.SeString.Parse(p)); + Service.Get().Print( + Game.Text.SeStringHandling.SeString.Parse( + Service.Get().CompileAndCache(this.testString).Data.Span)); } ImGuiHelpers.ScaledDummy(3);