mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
Move UtfEnumerator from Dalamud to Lumina (#2111)
* Move UtfEnumerator from Dalamud to Lumina Comes with some trivial cleanups. * Update Lumina to 5.5.0
This commit is contained in:
parent
6d664cc606
commit
d19b7d70d5
10 changed files with 63 additions and 1103 deletions
|
|
@ -27,7 +27,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Lumina" Version="5.4.0" />
|
<PackageReference Include="Lumina" Version="5.5.0" />
|
||||||
<PackageReference Include="Lumina.Excel" Version="7.1.2" />
|
<PackageReference Include="Lumina.Excel" Version="7.1.2" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.333">
|
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.333">
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@
|
||||||
<PackageReference Include="goaaats.Reloaded.Hooks" Version="4.2.0-goat.4" />
|
<PackageReference Include="goaaats.Reloaded.Hooks" Version="4.2.0-goat.4" />
|
||||||
<PackageReference Include="goaaats.Reloaded.Assembler" Version="1.0.14-goat.2" />
|
<PackageReference Include="goaaats.Reloaded.Assembler" Version="1.0.14-goat.2" />
|
||||||
<PackageReference Include="JetBrains.Annotations" Version="2024.2.0" />
|
<PackageReference Include="JetBrains.Annotations" Version="2024.2.0" />
|
||||||
<PackageReference Include="Lumina" Version="5.4.0" />
|
<PackageReference Include="Lumina" Version="5.5.0" />
|
||||||
<PackageReference Include="Lumina.Excel" Version="7.1.2" />
|
<PackageReference Include="Lumina.Excel" Version="7.1.2" />
|
||||||
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="9.0.0-preview.1.24081.5" />
|
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="9.0.0-preview.1.24081.5" />
|
||||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.46-beta">
|
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.46-beta">
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ using Dalamud.Game.Text;
|
||||||
using Dalamud.Game.Text.SeStringHandling;
|
using Dalamud.Game.Text.SeStringHandling;
|
||||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing;
|
|
||||||
using Dalamud.IoC;
|
using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
using Dalamud.Logging.Internal;
|
using Dalamud.Logging.Internal;
|
||||||
|
|
@ -22,12 +21,13 @@ using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
||||||
|
using Lumina.Text;
|
||||||
using Lumina.Text.Payloads;
|
using Lumina.Text.Payloads;
|
||||||
|
using Lumina.Text.ReadOnly;
|
||||||
|
|
||||||
using LinkMacroPayloadType = Lumina.Text.Payloads.LinkMacroPayloadType;
|
using LSeStringBuilder = Lumina.Text.SeStringBuilder;
|
||||||
using LuminaSeStringBuilder = Lumina.Text.SeStringBuilder;
|
using SeString = Dalamud.Game.Text.SeStringHandling.SeString;
|
||||||
using ReadOnlySePayloadType = Lumina.Text.ReadOnly.ReadOnlySePayloadType;
|
using SeStringBuilder = Dalamud.Game.Text.SeStringHandling.SeStringBuilder;
|
||||||
using ReadOnlySeStringSpan = Lumina.Text.ReadOnly.ReadOnlySeStringSpan;
|
|
||||||
|
|
||||||
namespace Dalamud.Game.Gui;
|
namespace Dalamud.Game.Gui;
|
||||||
|
|
||||||
|
|
@ -166,45 +166,51 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void UpdateQueue()
|
public void UpdateQueue()
|
||||||
{
|
{
|
||||||
while (this.chatQueue.Count > 0)
|
if (this.chatQueue.Count == 0)
|
||||||
{
|
return;
|
||||||
var chat = this.chatQueue.Dequeue();
|
|
||||||
var sb = LuminaSeStringBuilder.SharedPool.Get();
|
|
||||||
var rosss = (ReadOnlySeStringSpan)chat.MessageBytes;
|
|
||||||
|
|
||||||
foreach (var payload in rosss)
|
var sb = LSeStringBuilder.SharedPool.Get();
|
||||||
|
Span<byte> namebuf = stackalloc byte[256];
|
||||||
|
using var sender = new Utf8String();
|
||||||
|
using var message = new Utf8String();
|
||||||
|
while (this.chatQueue.TryDequeue(out var chat))
|
||||||
{
|
{
|
||||||
if (payload.Type == ReadOnlySePayloadType.Invalid)
|
sb.Clear();
|
||||||
continue;
|
foreach (var c in UtfEnumerator.From(chat.MessageBytes, UtfEnumeratorFlags.Utf8SeString))
|
||||||
|
|
||||||
if (payload.Type != ReadOnlySePayloadType.Text)
|
|
||||||
{
|
{
|
||||||
sb.Append(payload);
|
if (c.IsSeStringPayload)
|
||||||
continue;
|
sb.Append((ReadOnlySeStringSpan)chat.MessageBytes.AsSpan(c.ByteOffset, c.ByteLength));
|
||||||
}
|
else if (c.Value.IntValue == 0x202F)
|
||||||
|
|
||||||
foreach (var c in UtfEnumerator.From(payload.Body, UtfEnumeratorFlags.Default))
|
|
||||||
{
|
|
||||||
if (c.Value.IntValue == 0x202F)
|
|
||||||
sb.BeginMacro(MacroCode.NonBreakingSpace).EndMacro();
|
sb.BeginMacro(MacroCode.NonBreakingSpace).EndMacro();
|
||||||
else
|
else
|
||||||
sb.Append(c.EffectiveChar);
|
sb.Append(c);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var output = sb.ToArray();
|
if (chat.NameBytes.Length + 1 < namebuf.Length)
|
||||||
LuminaSeStringBuilder.SharedPool.Return(sb);
|
{
|
||||||
|
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());
|
message.SetString(sb.GetViewAsSpan());
|
||||||
var message = Utf8String.FromSequence(output.NullTerminate());
|
|
||||||
|
|
||||||
var targetChannel = chat.Type ?? this.configuration.GeneralChatType;
|
var targetChannel = chat.Type ?? this.configuration.GeneralChatType;
|
||||||
|
|
||||||
this.HandlePrintMessageDetour(RaptureLogModule.Instance(), targetChannel, sender, message, chat.Timestamp, (byte)(chat.Silent ? 1 : 0));
|
this.HandlePrintMessageDetour(
|
||||||
|
RaptureLogModule.Instance(),
|
||||||
sender->Dtor(true);
|
targetChannel,
|
||||||
message->Dtor(true);
|
&sender,
|
||||||
|
&message,
|
||||||
|
chat.Timestamp,
|
||||||
|
(byte)(chat.Silent ? 1 : 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LSeStringBuilder.SharedPool.Return(sb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -282,27 +288,29 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
||||||
|
|
||||||
private void PrintTagged(ReadOnlySpan<byte> message, XivChatType channel, string? tag, ushort? color)
|
private void PrintTagged(ReadOnlySpan<byte> message, XivChatType channel, string? tag, ushort? color)
|
||||||
{
|
{
|
||||||
var builder = new LuminaSeStringBuilder();
|
var sb = LSeStringBuilder.SharedPool.Get();
|
||||||
|
|
||||||
if (!tag.IsNullOrEmpty())
|
if (!tag.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
if (color is not null)
|
if (color is not null)
|
||||||
{
|
{
|
||||||
builder.PushColorType(color.Value);
|
sb.PushColorType(color.Value);
|
||||||
builder.Append($"[{tag}] ");
|
sb.Append($"[{tag}] ");
|
||||||
builder.PopColorType();
|
sb.PopColorType();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
builder.Append($"[{tag}] ");
|
sb.Append($"[{tag}] ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.Print(new XivChatEntry
|
this.Print(new XivChatEntry
|
||||||
{
|
{
|
||||||
MessageBytes = builder.Append((ReadOnlySeStringSpan)message).ToArray(),
|
MessageBytes = sb.Append((ReadOnlySeStringSpan)message).ToArray(),
|
||||||
Type = channel,
|
Type = channel,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
LSeStringBuilder.SharedPool.Return(sb);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InventoryItemCopyDetour(InventoryItem* thisPtr, InventoryItem* otherPtr)
|
private void InventoryItemCopyDetour(InventoryItem* thisPtr, InventoryItem* otherPtr)
|
||||||
|
|
@ -412,7 +420,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
||||||
|
|
||||||
Log.Verbose($"InteractableLinkClicked: {Payload.EmbeddedInfoType.DalamudLink}");
|
Log.Verbose($"InteractableLinkClicked: {Payload.EmbeddedInfoType.DalamudLink}");
|
||||||
|
|
||||||
var sb = LuminaSeStringBuilder.SharedPool.Get();
|
var sb = LSeStringBuilder.SharedPool.Get();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var seStringSpan = new ReadOnlySeStringSpan(linkData->Payload);
|
var seStringSpan = new ReadOnlySeStringSpan(linkData->Payload);
|
||||||
|
|
@ -423,7 +431,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
||||||
sb.Append(payload);
|
sb.Append(payload);
|
||||||
|
|
||||||
if (payload.Type == ReadOnlySePayloadType.Macro &&
|
if (payload.Type == ReadOnlySePayloadType.Macro &&
|
||||||
payload.MacroCode == Lumina.Text.Payloads.MacroCode.Link &&
|
payload.MacroCode == MacroCode.Link &&
|
||||||
payload.TryGetExpression(out var expr1) &&
|
payload.TryGetExpression(out var expr1) &&
|
||||||
expr1.TryGetInt(out var expr1Val) &&
|
expr1.TryGetInt(out var expr1Val) &&
|
||||||
expr1Val == (int)LinkMacroPayloadType.Terminator)
|
expr1Val == (int)LinkMacroPayloadType.Terminator)
|
||||||
|
|
@ -452,7 +460,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
LuminaSeStringBuilder.SharedPool.Return(sb);
|
LSeStringBuilder.SharedPool.Return(sb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -133,9 +133,7 @@ public class SeString
|
||||||
{
|
{
|
||||||
while (stream.Position < len)
|
while (stream.Position < len)
|
||||||
{
|
{
|
||||||
var payload = Payload.Decode(reader);
|
payloads.Add(Payload.Decode(reader));
|
||||||
if (payload != null)
|
|
||||||
payloads.Add(payload);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ using FFXIVClientStructs.FFXIV.Client.UI;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
|
||||||
using Lumina.Excel.Sheets;
|
using Lumina.Excel.Sheets;
|
||||||
|
using Lumina.Text;
|
||||||
using Lumina.Text.Parse;
|
using Lumina.Text.Parse;
|
||||||
using Lumina.Text.Payloads;
|
using Lumina.Text.Payloads;
|
||||||
using Lumina.Text.ReadOnly;
|
using Lumina.Text.ReadOnly;
|
||||||
|
|
@ -66,8 +67,7 @@ internal unsafe class SeStringRenderer : IInternalDisposableService
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private SeStringRenderer(DataManager dm, TargetSigScanner sigScanner)
|
private SeStringRenderer(DataManager dm, TargetSigScanner sigScanner)
|
||||||
{
|
{
|
||||||
this.colorStackSet = new(
|
this.colorStackSet = new(dm.Excel.GetSheet<UIColor>());
|
||||||
dm.Excel.GetSheet<UIColor>() ?? throw new InvalidOperationException("Failed to access UIColor sheet."));
|
|
||||||
this.gfd = dm.GetFile<GfdFile>("common/font/gfdata.gfd")!;
|
this.gfd = dm.GetFile<GfdFile>("common/font/gfdata.gfd")!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
using Lumina.Text;
|
||||||
|
|
||||||
using static Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing.UnicodeEastAsianWidthClass;
|
using static Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing.UnicodeEastAsianWidthClass;
|
||||||
using static Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing.UnicodeGeneralCategory;
|
using static Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing.UnicodeGeneralCategory;
|
||||||
using static Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing.UnicodeLineBreakClass;
|
using static Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing.UnicodeLineBreakClass;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
|
|
||||||
/// <summary>Enumerates a UTF-N byte sequence by codepoint.</summary>
|
|
||||||
[DebuggerDisplay("{Current}/{data.Length} ({flags}, BE={isBigEndian})")]
|
|
||||||
internal ref struct UtfEnumerator
|
|
||||||
{
|
|
||||||
private readonly ReadOnlySpan<byte> data;
|
|
||||||
private readonly UtfEnumeratorFlags flags;
|
|
||||||
private readonly byte numBytesPerUnit;
|
|
||||||
private bool isBigEndian;
|
|
||||||
|
|
||||||
/// <summary>Initializes a new instance of the <see cref="UtfEnumerator"/> struct.</summary>
|
|
||||||
/// <param name="data">UTF-N byte sequence.</param>
|
|
||||||
/// <param name="flags">Enumeration flags.</param>
|
|
||||||
public UtfEnumerator(ReadOnlySpan<byte> 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."),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc cref="IEnumerator.Current"/>
|
|
||||||
public Subsequence Current { get; private set; } = default;
|
|
||||||
|
|
||||||
/// <summary>Creates a new instance of the <see cref="UtfEnumerator"/> struct.</summary>
|
|
||||||
/// <param name="data">UTF-N byte sequence.</param>
|
|
||||||
/// <param name="flags">Enumeration flags.</param>
|
|
||||||
/// <returns>A new enumerator.</returns>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static UtfEnumerator From(ReadOnlySpan<byte> data, UtfEnumeratorFlags flags) => new(data, flags);
|
|
||||||
|
|
||||||
/// <summary>Creates a new instance of the <see cref="UtfEnumerator"/> struct.</summary>
|
|
||||||
/// <param name="data">UTF-N byte sequence.</param>
|
|
||||||
/// <param name="flags">Enumeration flags.</param>
|
|
||||||
/// <returns>A new enumerator.</returns>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static UtfEnumerator From(ReadOnlySpan<char> data, UtfEnumeratorFlags flags) =>
|
|
||||||
new(MemoryMarshal.Cast<char, byte>(data), flags);
|
|
||||||
|
|
||||||
/// <summary>Gets the representative <c>char</c> for a given SeString macro code.</summary>
|
|
||||||
/// <param name="macroCode">The macro code.</param>
|
|
||||||
/// <returns>Representative <c>char</c>, or <see cref="char.MaxValue"/> if none.</returns>
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>Attempts to peek the next item.</summary>
|
|
||||||
/// <param name="nextSubsequence">Retrieved next item.</param>
|
|
||||||
/// <param name="isStillBigEndian">Whether it still should be parsed in big endian.</param>
|
|
||||||
/// <returns><c>true</c> if anything is retrieved.</returns>
|
|
||||||
/// <exception cref="EncoderFallbackException">The sequence is not a fully valid Unicode sequence, and
|
|
||||||
/// <see cref="UtfEnumeratorFlags.ThrowOnFirstError"/> is set.</exception>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc cref="IEnumerator.MoveNext"/>
|
|
||||||
[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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc cref="IEnumerable.GetEnumerator"/>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public UtfEnumerator GetEnumerator() => new(this.data, this.flags);
|
|
||||||
|
|
||||||
/// <summary>A part of a UTF-N sequence containing one codepoint.</summary>
|
|
||||||
[StructLayout(LayoutKind.Explicit, Size = 16)]
|
|
||||||
[DebuggerDisplay("[{ByteOffset}, {ByteLength}] {Value}")]
|
|
||||||
public readonly struct Subsequence : IEquatable<Subsequence>
|
|
||||||
{
|
|
||||||
/// <summary>The codepoint. Valid if <see cref="IsSeStringPayload"/> is <c>false</c>.</summary>
|
|
||||||
[FieldOffset(0)]
|
|
||||||
public readonly UtfValue Value;
|
|
||||||
|
|
||||||
/// <summary>The macro code. Valid if <see cref="IsSeStringPayload"/> is <c>true</c>.</summary>
|
|
||||||
[FieldOffset(0)]
|
|
||||||
public readonly MacroCode MacroCode;
|
|
||||||
|
|
||||||
/// <summary>The offset of this part of a UTF-8 sequence.</summary>
|
|
||||||
[FieldOffset(4)]
|
|
||||||
public readonly int ByteOffset;
|
|
||||||
|
|
||||||
/// <summary>The length of this part of a UTF-8 sequence.</summary>
|
|
||||||
/// <remarks>This may not match <see cref="UtfValue.Length8"/>, if <see cref="BrokenSequence"/> is <c>true</c>.
|
|
||||||
/// </remarks>
|
|
||||||
[FieldOffset(8)]
|
|
||||||
public readonly int ByteLength;
|
|
||||||
|
|
||||||
/// <summary>Whether this part of the UTF-8 sequence is broken.</summary>
|
|
||||||
[FieldOffset(12)]
|
|
||||||
public readonly bool BrokenSequence;
|
|
||||||
|
|
||||||
/// <summary>Whether this part of the SeString sequence is a payload.</summary>
|
|
||||||
[FieldOffset(13)]
|
|
||||||
public readonly bool IsSeStringPayload;
|
|
||||||
|
|
||||||
/// <summary>Storage at byte offset 0, for fast <see cref="Equals(Subsequence)"/> implementation.</summary>
|
|
||||||
[FieldOffset(0)]
|
|
||||||
private readonly ulong storage0;
|
|
||||||
|
|
||||||
/// <summary>Storage at byte offset 8, for fast <see cref="Equals(Subsequence)"/> implementation.</summary>
|
|
||||||
[FieldOffset(8)]
|
|
||||||
private readonly ulong storage1;
|
|
||||||
|
|
||||||
/// <summary>Initializes a new instance of the <see cref="Subsequence"/> struct.</summary>
|
|
||||||
/// <param name="value">The value.</param>
|
|
||||||
/// <param name="byteOffset">The byte offset of this part of a UTF-N sequence.</param>
|
|
||||||
/// <param name="byteLength">The byte length of this part of a UTF-N sequence.</param>
|
|
||||||
/// <param name="brokenSequence">Whether this part of the UTF-N sequence is broken.</param>
|
|
||||||
/// <param name="isSeStringPayload">Whether this part of the SeString sequence is a payload.</param>
|
|
||||||
[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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Gets the effective <c>char</c> value, with invalid or non-representable codepoints replaced.
|
|
||||||
/// </summary>
|
|
||||||
/// <value><see cref="char.MaxValue"/> if the character should not be displayed at all.</value>
|
|
||||||
public char EffectiveChar
|
|
||||||
{
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
get => this.EffectiveInt is var i and >= 0 and < char.MaxValue ? (char)i : char.MaxValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Gets the effective <c>int</c> value, with invalid codepoints replaced.</summary>
|
|
||||||
/// <value><see cref="char.MaxValue"/> if the character should not be displayed at all.</value>
|
|
||||||
public int EffectiveInt =>
|
|
||||||
this.IsSeStringPayload
|
|
||||||
? RepresentativeCharFor(this.MacroCode)
|
|
||||||
: this.BrokenSequence || !this.Value.TryGetRune(out var rune)
|
|
||||||
? 0xFFFD
|
|
||||||
: rune.Value;
|
|
||||||
|
|
||||||
/// <summary>Gets the effective <see cref="Rune"/> value, with invalid codepoints replaced.</summary>
|
|
||||||
/// <value><see cref="char.MaxValue"/> if the character should not be displayed at all.</value>
|
|
||||||
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);
|
|
||||||
|
|
||||||
/// <summary>Creates a new instance of the <see cref="Subsequence"/> struct from a Unicode value.</summary>
|
|
||||||
/// <param name="codepoint">The codepoint.</param>
|
|
||||||
/// <param name="byteOffset">The byte offset of this part of a UTF-N sequence.</param>
|
|
||||||
/// <param name="byteLength">The byte length of this part of a UTF-N sequence.</param>
|
|
||||||
/// <param name="brokenSequence">Whether this part of the UTF-N sequence is broken.</param>
|
|
||||||
/// <returns>A new instance.</returns>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static Subsequence FromUnicode(uint codepoint, int byteOffset, int byteLength, bool brokenSequence) =>
|
|
||||||
new(codepoint, byteOffset, byteLength, brokenSequence, false);
|
|
||||||
|
|
||||||
/// <summary>Creates a new instance of the <see cref="Subsequence"/> struct from a SeString payload.</summary>
|
|
||||||
/// <param name="macroCode">The macro code.</param>
|
|
||||||
/// <param name="byteOffset">The byte offset of this part of a UTF-N sequence.</param>
|
|
||||||
/// <param name="byteLength">The byte length of this part of a UTF-N sequence.</param>
|
|
||||||
/// <returns>A new instance.</returns>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static Subsequence FromPayload(MacroCode macroCode, int byteOffset, int byteLength) =>
|
|
||||||
new((uint)macroCode, byteOffset, byteLength, false, true);
|
|
||||||
|
|
||||||
/// <summary>Tests whether this subsequence contains a valid Unicode codepoint.</summary>
|
|
||||||
/// <returns><c>true</c> if this subsequence contains a valid Unicode codepoint.</returns>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public bool IsValid() => !this.BrokenSequence && Rune.IsValid(this.Value);
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public bool Equals(Subsequence other) => this.storage0 == other.storage0 && this.storage1 == other.storage1;
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public override bool Equals(object? obj) => obj is Subsequence other && this.Equals(other);
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public override int GetHashCode() => HashCode.Combine(this.storage0, this.storage1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
namespace Dalamud.Interface.ImGuiSeStringRenderer.Internal.TextProcessing;
|
|
||||||
|
|
||||||
/// <summary>Flags on enumerating a unicode sequence.</summary>
|
|
||||||
[Flags]
|
|
||||||
internal enum UtfEnumeratorFlags
|
|
||||||
{
|
|
||||||
/// <summary>Use the default configuration of <see cref="Utf8"/> and <see cref="ReplaceErrors"/>.</summary>
|
|
||||||
Default = default,
|
|
||||||
|
|
||||||
/// <summary>Enumerate as UTF-8 (the default.)</summary>
|
|
||||||
Utf8 = Default,
|
|
||||||
|
|
||||||
/// <summary>Enumerate as UTF-8 in a SeString.</summary>
|
|
||||||
Utf8SeString = 1 << 1,
|
|
||||||
|
|
||||||
/// <summary>Enumerate as UTF-16.</summary>
|
|
||||||
Utf16 = 1 << 2,
|
|
||||||
|
|
||||||
/// <summary>Enumerate as UTF-32.</summary>
|
|
||||||
Utf32 = 1 << 3,
|
|
||||||
|
|
||||||
/// <summary>Bitmask for specifying the encoding.</summary>
|
|
||||||
UtfMask = Utf8 | Utf8SeString | Utf16 | Utf32,
|
|
||||||
|
|
||||||
/// <summary>On error, replace to U+FFFD (REPLACEMENT CHARACTER, the default.)</summary>
|
|
||||||
ReplaceErrors = Default,
|
|
||||||
|
|
||||||
/// <summary>On error, drop the invalid byte.</summary>
|
|
||||||
IgnoreErrors = 1 << 4,
|
|
||||||
|
|
||||||
/// <summary>On error, stop the handling.</summary>
|
|
||||||
TerminateOnFirstError = 1 << 5,
|
|
||||||
|
|
||||||
/// <summary>On error, throw an exception.</summary>
|
|
||||||
ThrowOnFirstError = 1 << 6,
|
|
||||||
|
|
||||||
/// <summary>Bitmask for specifying the error handling mode.</summary>
|
|
||||||
ErrorHandlingMask = ReplaceErrors | IgnoreErrors | TerminateOnFirstError | ThrowOnFirstError,
|
|
||||||
|
|
||||||
/// <summary>Use the current system native endianness from <see cref="BitConverter.IsLittleEndian"/>
|
|
||||||
/// (the default.)</summary>
|
|
||||||
NativeEndian = Default,
|
|
||||||
|
|
||||||
/// <summary>Use little endianness.</summary>
|
|
||||||
LittleEndian = 1 << 7,
|
|
||||||
|
|
||||||
/// <summary>Use big endianness.</summary>
|
|
||||||
BigEndian = 1 << 8,
|
|
||||||
|
|
||||||
/// <summary>Bitmask for specifying endianness.</summary>
|
|
||||||
EndiannessMask = NativeEndian | LittleEndian | BigEndian,
|
|
||||||
|
|
||||||
/// <summary>Disrespect byte order mask.</summary>
|
|
||||||
DisrespectByteOrderMask = 1 << 9,
|
|
||||||
|
|
||||||
/// <summary>Yield byte order masks, if it shows up.</summary>
|
|
||||||
YieldByteOrderMask = 1 << 10,
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
|
|
||||||
/// <summary>Represents a single value to be used in a UTF-N byte sequence.</summary>
|
|
||||||
[StructLayout(LayoutKind.Explicit, Size = 4)]
|
|
||||||
[DebuggerDisplay("0x{IntValue,h} ({CharValue})")]
|
|
||||||
internal readonly struct UtfValue : IEquatable<UtfValue>, IComparable<UtfValue>
|
|
||||||
{
|
|
||||||
/// <summary>The unicode codepoint in <c>int</c>, that may not be in a valid range.</summary>
|
|
||||||
[FieldOffset(0)]
|
|
||||||
public readonly int IntValue;
|
|
||||||
|
|
||||||
/// <summary>The unicode codepoint in <c>uint</c>, that may not be in a valid range.</summary>
|
|
||||||
[FieldOffset(0)]
|
|
||||||
public readonly uint UIntValue;
|
|
||||||
|
|
||||||
/// <summary>The high UInt16 value in <c>char</c>, that may have been cut off if outside BMP.</summary>
|
|
||||||
[FieldOffset(0)]
|
|
||||||
public readonly char CharValue;
|
|
||||||
|
|
||||||
/// <summary>Initializes a new instance of the <see cref="UtfValue"/> struct.</summary>
|
|
||||||
/// <param name="value">The raw codepoint value.</param>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public UtfValue(uint value) => this.UIntValue = value;
|
|
||||||
|
|
||||||
/// <inheritdoc cref="UtfValue(uint)"/>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public UtfValue(int value) => this.IntValue = value;
|
|
||||||
|
|
||||||
/// <summary>Gets the length of this codepoint, encoded in UTF-8.</summary>
|
|
||||||
public int Length8
|
|
||||||
{
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
get => GetEncodedLength8(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Gets the length of this codepoint, encoded in UTF-16.</summary>
|
|
||||||
public int Length16
|
|
||||||
{
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
get => GetEncodedLength16(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Gets the short name, if supported.</summary>
|
|
||||||
/// <returns>The buffer containing the short name, or empty if unsupported.</returns>
|
|
||||||
public ReadOnlySpan<char> 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;
|
|
||||||
|
|
||||||
/// <summary>Gets the short name of the codepoint, for some select codepoints.</summary>
|
|
||||||
/// <param name="codepoint">The codepoint.</param>
|
|
||||||
/// <returns>The value.</returns>
|
|
||||||
public static ReadOnlySpan<char> 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,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>Gets the length of the codepoint, when encoded in UTF-8.</summary>
|
|
||||||
/// <param name="codepoint">The codepoint to encode.</param>
|
|
||||||
/// <returns>The length.</returns>
|
|
||||||
[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,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>Gets the length of the codepoint, when encoded in UTF-16.</summary>
|
|
||||||
/// <param name="codepoint">The codepoint to encode.</param>
|
|
||||||
/// <returns>The length.</returns>
|
|
||||||
[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,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <inheritdoc cref="TryDecode8(ReadOnlySpan{byte}, out UtfValue, out int)"/>
|
|
||||||
/// <remarks>Trims <paramref name="source"/> at beginning by <paramref name="length"/>.</remarks>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static bool TryDecode8(ref ReadOnlySpan<byte> source, out UtfValue value, out int length)
|
|
||||||
{
|
|
||||||
var v = TryDecode8(source, out value, out length);
|
|
||||||
source = source[length..];
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Attempts to decode a value from a UTF-8 byte sequence.</summary>
|
|
||||||
/// <param name="source">The span to decode from.</param>
|
|
||||||
/// <param name="value">The decoded value.</param>
|
|
||||||
/// <param name="length">The length of the consumed bytes. <c>1</c> if sequence is broken.</param>
|
|
||||||
/// <returns><c>true</c> if <paramref name="source"/> is successfully decoded.</returns>
|
|
||||||
/// <remarks>Codepoints that results in <c>false</c> from <see cref="Rune.IsValid(int)"/> 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.</remarks>
|
|
||||||
public static unsafe bool TryDecode8(ReadOnlySpan<byte> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc cref="TryDecode16(ReadOnlySpan{byte}, bool, out UtfValue, out int)"/>
|
|
||||||
/// <remarks>Trims <paramref name="source"/> at beginning by <paramref name="length"/>.</remarks>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static bool TryDecode16(ref ReadOnlySpan<byte> source, bool be, out UtfValue value, out int length)
|
|
||||||
{
|
|
||||||
var v = TryDecode16(source, be, out value, out length);
|
|
||||||
source = source[length..];
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Attempts to decode a value from a UTF-16 byte sequence.</summary>
|
|
||||||
/// <param name="source">The span to decode from.</param>
|
|
||||||
/// <param name="be">Whether to use big endian.</param>
|
|
||||||
/// <param name="value">The decoded value.</param>
|
|
||||||
/// <param name="length">The length of the consumed bytes. <c>1</c> if cut short.
|
|
||||||
/// <c>2</c> if sequence is broken.</param>
|
|
||||||
/// <returns><c>true</c> if <paramref name="source"/> is successfully decoded.</returns>
|
|
||||||
/// <remarks>Codepoints that results in <c>false</c> from <see cref="Rune.IsValid(int)"/> 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.</remarks>
|
|
||||||
public static unsafe bool TryDecode16(ReadOnlySpan<byte> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc cref="TryDecode32(ReadOnlySpan{byte}, bool, out UtfValue, out int)"/>
|
|
||||||
/// <remarks>Trims <paramref name="source"/> at beginning by <paramref name="length"/>.</remarks>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static bool TryDecode32(ref ReadOnlySpan<byte> source, bool be, out UtfValue value, out int length)
|
|
||||||
{
|
|
||||||
var v = TryDecode32(source, be, out value, out length);
|
|
||||||
source = source[length..];
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Attempts to decode a value from a UTF-32 byte sequence.</summary>
|
|
||||||
/// <param name="source">The span to decode from.</param>
|
|
||||||
/// <param name="be">Whether to use big endian.</param>
|
|
||||||
/// <param name="value">The decoded value.</param>
|
|
||||||
/// <param name="length">The length of the consumed bytes. <c>1 to 3</c> if cut short.
|
|
||||||
/// <c>4</c> if sequence is broken.</param>
|
|
||||||
/// <returns><c>true</c> if <paramref name="source"/> is successfully decoded.</returns>
|
|
||||||
/// <remarks>Codepoints that results in <c>false</c> from <see cref="Rune.IsValid(int)"/> 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.</remarks>
|
|
||||||
public static bool TryDecode32(ReadOnlySpan<byte> 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Encodes the codepoint to the target in UTF-8.</summary>
|
|
||||||
/// <param name="target">The target stream.</param>
|
|
||||||
/// <param name="codepoint">The codepoint to encode.</param>
|
|
||||||
/// <returns>The length of the encoded data.</returns>
|
|
||||||
/// <remarks>Trims <paramref name="target"/> at beginning by the length.</remarks>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static int Encode8(Stream target, int codepoint)
|
|
||||||
{
|
|
||||||
Span<byte> buf = stackalloc byte[7];
|
|
||||||
Encode8(buf, codepoint, out var length);
|
|
||||||
target.Write(buf[..length]);
|
|
||||||
return length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Encodes the codepoint to the target in UTF-8.</summary>
|
|
||||||
/// <param name="target">The target byte span.</param>
|
|
||||||
/// <param name="codepoint">The codepoint to encode.</param>
|
|
||||||
/// <returns>The length of the encoded data.</returns>
|
|
||||||
/// <remarks>Trims <paramref name="target"/> at beginning by the length.</remarks>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static int Encode8(ref Span<byte> target, int codepoint)
|
|
||||||
{
|
|
||||||
target = Encode8(target, codepoint, out var length);
|
|
||||||
return length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Encodes the codepoint to the target in UTF-8.</summary>
|
|
||||||
/// <param name="target">The optional target byte span.</param>
|
|
||||||
/// <param name="codepoint">The codepoint to encode.</param>
|
|
||||||
/// <param name="length">The length of the encoded data.</param>
|
|
||||||
/// <returns>The remaning region of <paramref name="target"/>.</returns>
|
|
||||||
public static Span<byte> Encode8(Span<byte> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Encodes the codepoint to the target in UTF-16.</summary>
|
|
||||||
/// <param name="target">The target stream.</param>
|
|
||||||
/// <param name="codepoint">The codepoint to encode.</param>
|
|
||||||
/// <param name="be">Whether to use big endian.</param>
|
|
||||||
/// <returns>The length of the encoded data.</returns>
|
|
||||||
/// <remarks>Trims <paramref name="target"/> at beginning by the length.</remarks>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static int Encode16(Stream target, int codepoint, bool be)
|
|
||||||
{
|
|
||||||
Span<byte> buf = stackalloc byte[8];
|
|
||||||
Encode16(buf, codepoint, be, out var length);
|
|
||||||
target.Write(buf[..length]);
|
|
||||||
return length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Encodes the codepoint to the target in UTF-16.</summary>
|
|
||||||
/// <param name="target">The target byte span.</param>
|
|
||||||
/// <param name="codepoint">The codepoint to encode.</param>
|
|
||||||
/// <param name="be">Whether to use big endian.</param>
|
|
||||||
/// <returns>The length of the encoded data.</returns>
|
|
||||||
/// <remarks>Trims <paramref name="target"/> at beginning by the length.</remarks>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static int Encode16(ref Span<byte> target, int codepoint, bool be)
|
|
||||||
{
|
|
||||||
target = Encode16(target, codepoint, be, out var length);
|
|
||||||
return length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Encodes the codepoint to the target in UTF-16.</summary>
|
|
||||||
/// <param name="target">The optional target byte span.</param>
|
|
||||||
/// <param name="codepoint">The codepoint to encode.</param>
|
|
||||||
/// <param name="be">Whether to use big endian.</param>
|
|
||||||
/// <param name="length">The length of the encoded data.</param>
|
|
||||||
/// <returns>The remaning region of <paramref name="target"/>.</returns>
|
|
||||||
public static Span<byte> Encode16(Span<byte> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public int CompareTo(UtfValue other) => this.IntValue.CompareTo(other.IntValue);
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public bool Equals(UtfValue other) => this.IntValue == other.IntValue;
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public override bool Equals(object? obj) => obj is UtfValue other && this.Equals(other);
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public override int GetHashCode() => this.IntValue;
|
|
||||||
|
|
||||||
/// <summary>Attempts to get the corresponding rune.</summary>
|
|
||||||
/// <param name="rune">The retrieved rune.</param>
|
|
||||||
/// <returns><c>true</c> if retrieved.</returns>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public bool TryGetRune(out Rune rune)
|
|
||||||
{
|
|
||||||
if (Rune.IsValid(this.IntValue))
|
|
||||||
{
|
|
||||||
rune = new(this.IntValue);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
rune = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Encodes the codepoint to the target.</summary>
|
|
||||||
/// <param name="target">The target byte span.</param>
|
|
||||||
/// <returns>The remaning region of <paramref name="target"/>.</returns>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public Span<byte> Encode8(Span<byte> target) => Encode8(target, this, out _);
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
|
@ -34,7 +33,7 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget
|
||||||
private static readonly string[] ThemeNames = ["Dark", "Light", "Classic FF", "Clear Blue"];
|
private static readonly string[] ThemeNames = ["Dark", "Light", "Classic FF", "Clear Blue"];
|
||||||
private ImVectorWrapper<byte> testStringBuffer;
|
private ImVectorWrapper<byte> testStringBuffer;
|
||||||
private string testString = string.Empty;
|
private string testString = string.Empty;
|
||||||
private ExcelSheet<Addon> addons;
|
private ExcelSheet<Addon> addons = null!;
|
||||||
private ReadOnlySeString? logkind;
|
private ReadOnlySeString? logkind;
|
||||||
private SeStringDrawParams style;
|
private SeStringDrawParams style;
|
||||||
private bool interactable;
|
private bool interactable;
|
||||||
|
|
@ -241,8 +240,9 @@ internal unsafe class SeStringRendererTestWidget : IDataWindowWidget
|
||||||
|
|
||||||
if (ImGui.Button("Print to Chat Log"))
|
if (ImGui.Button("Print to Chat Log"))
|
||||||
{
|
{
|
||||||
fixed (byte* p = Service<SeStringRenderer>.Get().CompileAndCache(this.testString).Data.Span)
|
Service<ChatGui>.Get().Print(
|
||||||
Service<ChatGui>.Get().Print(Game.Text.SeStringHandling.SeString.Parse(p));
|
Game.Text.SeStringHandling.SeString.Parse(
|
||||||
|
Service<SeStringRenderer>.Get().CompileAndCache(this.testString).Data.Span));
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGuiHelpers.ScaledDummy(3);
|
ImGuiHelpers.ScaledDummy(3);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue