using Hexa.NET.ImGui; namespace SDL3_TestingSuite.SDL3; public struct FontMetrics { public ushort UnitsPerEm; public short TypoAscender; public short TypoDescender; public short TypoLineGap; public short WinAscent; public short WinDescent; public int TypoLineHeight; public int WinLineHeight; } public static class FontFind { extension(ImGuiIOPtr io) { public unsafe ImFontPtr AddFont(string fontPath) { try { if (fontPath == null || io.Handle == null) return null; FileInfo info = new FileInfo(fontPath); if (!info.Exists || info.Length <= 0) return null; FontMetrics metrics = FontFind.ReadFontMetrics(fontPath); float size = FontFind.GetRecommendedPixelSize(metrics); ImFontConfigPtr config = ImGui.ImFontConfig(); config.FontLoaderFlags |= (uint)ImGuiFreeTypeLoaderFlags.LoadColor; ImFontPtr font = io.Fonts.AddFontFromFileTTF(fontPath, size, config); Program.Logger.Log("Added font: " + Path.GetFileName(fontPath)); return font; } catch (Exception e) { Console.WriteLine(e); } return null; } public unsafe bool RemoveFont(string? fontName) { if (fontName == null || io.Handle == null) return false; bool flag = false; ImVector fonts = io.Fonts.Fonts; List toRemove = new List(); for (int i = 0; i < fonts.Size; i++) { ImFontPtr font = fonts[i]; if (font.GetDebugNameS() == fontName) { toRemove.Add(font); flag = true; } } foreach (ImFontPtr font in toRemove) { io.Fonts.RemoveFont(font); Program.Logger.Log("Removed font: " + fontName); } return flag; } } public static FontMetrics ReadFontMetrics(string fontPath) { byte[] data = File.ReadAllBytes(fontPath); ushort numTables = ReadU16Be(4); const int tableDir = 12; int headOffset = 0; int os2Offset = 0; for (int i = 0; i < numTables; i++) { int entry = tableDir + i * 16; string tag = ReadTag(entry); uint offset = ReadU32Be(entry + 8); if (tag == "head") headOffset = (int)offset; if (tag == "OS/2") os2Offset = (int)offset; } FontMetrics metrics = new FontMetrics(); metrics.UnitsPerEm = ReadU16Be(headOffset + 18); if (os2Offset != 0) { metrics.TypoAscender = ReadS16Be(os2Offset + 68); metrics.TypoDescender = ReadS16Be(os2Offset + 70); metrics.TypoLineGap = ReadS16Be(os2Offset + 72); metrics.WinAscent = ReadS16Be(os2Offset + 74); metrics.WinDescent = ReadS16Be(os2Offset + 76); } metrics.TypoLineHeight = metrics.TypoAscender - metrics.TypoDescender + metrics.TypoLineGap; metrics.WinLineHeight = metrics.WinAscent + metrics.WinDescent; return metrics; ushort ReadU16Be(int o) { return (ushort)((data[o] << 8) | data[o + 1]); } short ReadS16Be(int o) { return (short)ReadU16Be(o); } uint ReadU32Be(int o) { return (uint)((data[o] << 24) | (data[o + 1] << 16) | (data[o + 2] << 8) | data[o + 3]); } string ReadTag(int o) { char c1 = (char)data[o]; char c2 = (char)data[o + 1]; char c3 = (char)data[o + 2]; char c4 = (char)data[o + 3]; return new string(new char[] { c1, c2, c3, c4 }); } } public static int GetRecommendedPixelSize(FontMetrics metrics) { if (metrics.UnitsPerEm == 0 || metrics.TypoLineHeight == 0) return 16; const float targetLineHeightPx = 18.0f; float scale = targetLineHeightPx * metrics.UnitsPerEm / metrics.TypoLineHeight; int size = (int)MathF.Round(scale); if (size < 10) size = 10; if (size > 72) size = 72; return size; } }