using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; namespace ChatTwo.Util; // From Kizer: https://github.com/Soreepeong/Dalamud/blob/feature/log-wordwrap/Dalamud/Interface/Spannables/Internal/GfdFileView.cs public readonly unsafe ref struct GfdFileView { private readonly ReadOnlySpan Span; private readonly bool DirectLookup; /// Initializes a new instance of the struct. /// The data. public GfdFileView(ReadOnlySpan span) { Span = span; if (span.Length < sizeof(GfdHeader)) throw new InvalidDataException($"Not enough space for a {nameof(GfdHeader)}"); if (span.Length < sizeof(GfdHeader) + (Header.Count * sizeof(GfdEntry))) throw new InvalidDataException($"Not enough space for all the {nameof(GfdEntry)}"); var entries = Entries; DirectLookup = true; for (var i = 0; i < entries.Length && DirectLookup; i++) DirectLookup &= i + 1 == entries[i].Id; } /// Gets the header. private ref readonly GfdHeader Header => ref MemoryMarshal.AsRef(Span); /// Gets the entries. private ReadOnlySpan Entries => MemoryMarshal.Cast(Span[sizeof(GfdHeader)..]); /// Attempts to get an entry. /// The icon ID. /// The entry. /// Whether to follow redirects. /// true if found. public bool TryGetEntry(uint iconId, out GfdEntry entry, bool followRedirect = true) { if (iconId == 0) { entry = default; return false; } var entries = Entries; if (DirectLookup) { if (iconId <= entries.Length) { entry = entries[(int)(iconId - 1)]; return !entry.IsEmpty; } entry = default; return false; } var lo = 0; var hi = entries.Length; while (lo <= hi) { var i = lo + ((hi - lo) >> 1); if (entries[i].Id == iconId) { if (followRedirect && entries[i].Redirect != 0) { iconId = entries[i].Redirect; lo = 0; hi = entries.Length; continue; } entry = entries[i]; return !entry.IsEmpty; } if (entries[i].Id < iconId) lo = i + 1; else hi = i - 1; } entry = default; return false; } /// Header of a .gfd file. [StructLayout(LayoutKind.Sequential)] public struct GfdHeader { /// Signature: "gftd0100". public fixed byte Signature[8]; /// Number of entries. public int Count; /// Unused/unknown. public fixed byte Padding[4]; } /// An entry of a .gfd file. [StructLayout(LayoutKind.Sequential, Size = 0x10)] public struct GfdEntry { /// ID of the entry. public ushort Id; /// The left offset of the entry. public ushort Left; /// The top offset of the entry. public ushort Top; /// The width of the entry. public ushort Width; /// The height of the entry. public ushort Height; /// Unknown/unused. public ushort Unk0A; /// The redirected entry, maybe. public ushort Redirect; /// Unknown/unused. public ushort Unk0E; /// Gets a value indicating whether this entry is effectively empty. public bool IsEmpty => Width == 0 || Height == 0; } } internal static class IconUtil { private static byte[]? GfdFile; public static unsafe GfdFileView GfdFileView { get { GfdFile ??= Plugin.DataManager.GetFile("common/font/gfdata.gfd")!.Data; return new GfdFileView(new ReadOnlySpan(Unsafe.AsPointer(ref GfdFile[0]), GfdFile.Length)); } } public static byte[] ImageToRaw(this Image image) { var data = new byte[4 * image.Width * image.Height]; image.CopyPixelDataTo(data); return data; } }