using Dalamud.Interface.GameFonts; using Dalamud.Interface.ManagedFontAtlas; using Dalamud.Interface.Utility; using HSUI.Config; using HSUI.Interface.GeneralElements; using Dalamud.Bindings.ImGui; using System; using System.Collections.Generic; using System.IO; using System.Linq; namespace HSUI.Helpers { public class FontScope : IDisposable { private readonly IFontHandle? _handle; public FontScope(IFontHandle? handle) { _handle = handle; _handle?.Push(); } public void Dispose() { _handle?.Pop(); GC.SuppressFinalize(this); } } public class FontsManager : IDisposable { #region Singleton private FontsManager(string basePath) { DefaultFontsPath = Path.GetDirectoryName(basePath) + "\\Media\\Fonts\\"; } public static void Initialize(string basePath) { Instance = new FontsManager(basePath); } public static FontsManager Instance { get; private set; } = null!; private FontsConfig? _config; public void LoadConfig() { if (_config != null) { return; } _config = ConfigurationManager.Instance.GetConfigObject(); ConfigurationManager.Instance.ResetEvent += OnConfigReset; } private void OnConfigReset(ConfigurationManager sender) { _config = sender.GetConfigObject(); } ~FontsManager() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected void Dispose(bool disposing) { if (!disposing) { return; } ConfigurationManager.Instance.ResetEvent -= OnConfigReset; Instance = null!; } #endregion public readonly string DefaultFontsPath; public bool DefaultFontBuilt { get; private set; } public IFontHandle? DefaultFont { get; private set; } = null!; private List _fonts = new List(); public IReadOnlyCollection Fonts => _fonts.AsReadOnly(); public FontScope PushDefaultFont() { if (DefaultFontBuilt && DefaultFont != null) { return new FontScope(DefaultFont); } return new FontScope(null); } public FontScope PushFont(string? fontId) { if (fontId == null || _config == null || !_config.Fonts.ContainsKey(fontId)) { return new FontScope(null); } var index = _config.Fonts.IndexOfKey(fontId); if (index < 0 || index >= _fonts.Count) { return new FontScope(null); } return new FontScope(_fonts[index]); } public void ClearFonts() { foreach (IFontHandle font in _fonts) { font.Dispose(); } _fonts.Clear(); } public unsafe void BuildFonts() { ClearFonts(); DefaultFontBuilt = false; FontsConfig config = ConfigurationManager.Instance.GetConfigObject(); ImGuiIOPtr io = ImGui.GetIO(); ushort[]? ranges = GetCharacterRanges(config, io); foreach (KeyValuePair fontData in config.Fonts) { bool isGameFont = config.GameFontMap.ContainsValue(fontData.Value.Name); string path = DefaultFontsPath + fontData.Value.Name + ".ttf"; if (!File.Exists(path)) { path = config.ValidatedFontsPath + fontData.Value.Name + ".ttf"; if (!File.Exists(path) && !isGameFont) { continue; } } try { IFontHandle font; if (isGameFont) { GameFontFamily fontFamily = (GameFontFamily)Enum.Parse( typeof(GameFontFamily), config.GameFontMap.FirstOrDefault(x => x.Value == fontData.Value.Name).Key ); GameFontStyle style = new GameFontStyle(fontFamily, fontData.Value.Size); font = Plugin.UiBuilder.FontAtlas.NewGameFontHandle(style); } else { font = Plugin.UiBuilder.FontAtlas.NewDelegateFontHandle ( e => e.OnPreBuild ( tk => tk.AddFontFromFile ( path, new SafeFontConfig { SizePx = fontData.Value.Size, GlyphRanges = ranges } ) ) ); } _fonts.Add(font); // save default font if (fontData.Key == FontsConfig.DefaultBigFontKey) { DefaultFont = font; DefaultFontBuilt = true; } } catch (Exception ex) { Plugin.Logger.Error($"Error loading font from path {path}:\n{ex.Message}"); } } } private unsafe ushort[]? GetCharacterRanges(FontsConfig config, ImGuiIOPtr io) { if (!config.SupportChineseCharacters && !config.SupportKoreanCharacters && !config.SupportCyrillicCharacters) { return null; } var builder = new ImFontGlyphRangesBuilderPtr(ImGuiNative.ImFontGlyphRangesBuilder()); if (config.SupportChineseCharacters) { // GetGlyphRangesChineseFull() includes Default + Hiragana, Katakana, Half-Width, Selection of 1946 Ideographs // https://skia.googlesource.com/external/github.com/ocornut/imgui/+/v1.53/extra_fonts/README.txt builder.AddRanges(io.Fonts.GetGlyphRangesChineseFull()); } if (config.SupportKoreanCharacters) { builder.AddRanges(io.Fonts.GetGlyphRangesKorean()); } if (config.SupportCyrillicCharacters) { builder.AddRanges(io.Fonts.GetGlyphRangesCyrillic()); } return builder.BuildRangesToArray(); } } }