diff --git a/ChatTwo/ChatTwo.csproj b/ChatTwo/ChatTwo.csproj index af5b4f8..9800595 100755 --- a/ChatTwo/ChatTwo.csproj +++ b/ChatTwo/ChatTwo.csproj @@ -48,6 +48,8 @@ + + @@ -55,6 +57,12 @@ + + + + + + diff --git a/ChatTwo/Configuration.cs b/ChatTwo/Configuration.cs index 856e632..768ed2d 100755 --- a/ChatTwo/Configuration.cs +++ b/ChatTwo/Configuration.cs @@ -1,5 +1,6 @@ using ChatTwo.Code; using ChatTwo.Resources; +using ChatTwo.Ui; using Dalamud.Configuration; namespace ChatTwo; @@ -18,7 +19,11 @@ internal class Configuration : IPluginConfiguration { public bool CanMove = true; public bool CanResize = true; public bool ShowTitleBar; + public float FontSize = 17f; + public string GlobalFont = Fonts.GlobalFonts[0].Name; + public string JapaneseFont = Fonts.JapaneseFonts[0].Item1; + public float WindowAlpha = 1f; public Dictionary ChatColours = new(); public List Tabs = new(); @@ -35,6 +40,8 @@ internal class Configuration : IPluginConfiguration { this.CanResize = other.CanResize; this.ShowTitleBar = other.ShowTitleBar; this.FontSize = other.FontSize; + this.GlobalFont = other.GlobalFont; + this.JapaneseFont = other.JapaneseFont; this.WindowAlpha = other.WindowAlpha; this.ChatColours = other.ChatColours.ToDictionary(entry => entry.Key, entry => entry.Value); this.Tabs = other.Tabs.Select(t => t.Clone()).ToList(); diff --git a/ChatTwo/PluginUi.cs b/ChatTwo/PluginUi.cs index 5e29df5..c293343 100755 --- a/ChatTwo/PluginUi.cs +++ b/ChatTwo/PluginUi.cs @@ -79,23 +79,7 @@ internal sealed class PluginUi : IDisposable { builder.AddText("←→↑↓《》■※☀★★☆♥♡ヅツッシ☀☁☂℃℉°♀♂♠♣♦♣♧®©™€$£♯♭♪✓√◎◆◇♦■□〇●△▽▼▲‹›≤≥<«“”─\~Œœ"); builder.BuildRanges(out this._ranges); - var regular = this.GetResource("ChatTwo.fonts.NotoSans-Regular.ttf"); - this._regularFont = ( - GCHandle.Alloc(regular, GCHandleType.Pinned), - regular.Length - ); - - var italic = this.GetResource("ChatTwo.fonts.NotoSans-Italic.ttf"); - this._italicFont = ( - GCHandle.Alloc(italic, GCHandleType.Pinned), - italic.Length - ); - - var jp = this.GetResource("ChatTwo.fonts.NotoSansJP-Regular.otf"); - this._jpFont = ( - GCHandle.Alloc(jp, GCHandleType.Pinned), - jp.Length - ); + this.SetUpUserFonts(); var gameSym = File.ReadAllBytes(Path.Combine(this.Plugin.Interface.DalamudAssetDirectory.FullName, "UIRes", "gamesym.ttf")); this._gameSymFont = ( @@ -132,6 +116,69 @@ internal sealed class PluginUi : IDisposable { this._fontCfgMerge.Destroy(); } + private void SetUpUserFonts() { + FontData? fontData = null; + if (this.Plugin.Config.GlobalFont.StartsWith(Fonts.IncludedIndicator)) { + var globalFont = Fonts.GlobalFonts.FirstOrDefault(font => font.Name == this.Plugin.Config.GlobalFont); + if (globalFont != null) { + fontData = new FontData(this.GetResource(globalFont.ResourcePath), this.GetResource(globalFont.ResourcePathItalic)); + } + } else { + fontData = Fonts.GetFont(this.Plugin.Config.GlobalFont, true); + } + + if (fontData == null) { + PluginLog.Warning("global fallback"); + var globalFont = Fonts.GlobalFonts[0]; + fontData = new FontData(this.GetResource(globalFont.ResourcePath), this.GetResource(globalFont.ResourcePathItalic)); + } + + if (this._regularFont.Item1.IsAllocated) { + this._regularFont.Item1.Free(); + } + + if (this._italicFont.Item1.IsAllocated) { + this._italicFont.Item1.Free(); + } + + this._regularFont = ( + GCHandle.Alloc(fontData.Regular, GCHandleType.Pinned), + fontData.Regular.Length + ); + + this._italicFont = ( + GCHandle.Alloc(fontData.Italic, GCHandleType.Pinned), + fontData.Italic.Length + ); + + FontData? jpFontData = null; + if (this.Plugin.Config.JapaneseFont.StartsWith(Fonts.IncludedIndicator)) { + var jpFont = Fonts.JapaneseFonts.FirstOrDefault(item => item.Item1 == this.Plugin.Config.JapaneseFont); + if (jpFont != default) { + jpFontData = new FontData(this.GetResource(jpFont.Item2), Array.Empty()); + } + } + // else { + // jpFontData = Fonts.GetFont(this.Plugin.Config.JapaneseFont, false, CharacterSet.SHIFTJIS_CHARSET); + // PluginLog.Log($"data.Regular.Length: {jpFontData?.Regular.Length}"); + // } + + if (jpFontData == null) { + PluginLog.Warning("jp fallback"); + var jpFont = Fonts.JapaneseFonts[0]; + jpFontData = new FontData(this.GetResource(jpFont.Item2), Array.Empty()); + } + + if (this._jpFont.Item1.IsAllocated) { + this._jpFont.Item1.Free(); + } + + this._jpFont = ( + GCHandle.Alloc(jpFontData.Regular, GCHandleType.Pinned), + jpFontData.Regular.Length + ); + } + private void Draw() { this.DefaultText = ImGui.GetStyle().Colors[(int) ImGuiCol.Text]; @@ -165,6 +212,8 @@ internal sealed class PluginUi : IDisposable { this.RegularFont = null; this.ItalicFont = null; + this.SetUpUserFonts(); + // load regular noto sans and merge in jp + game icons this.RegularFont = ImGui.GetIO().Fonts.AddFontFromMemoryTTF( this._regularFont.Item1.AddrOfPinnedObject(), diff --git a/ChatTwo/Ui/Fonts.cs b/ChatTwo/Ui/Fonts.cs new file mode 100755 index 0000000..a4b83bc --- /dev/null +++ b/ChatTwo/Ui/Fonts.cs @@ -0,0 +1,119 @@ +using System.Drawing; +using Vanara.PInvoke; + +namespace ChatTwo.Ui; + +internal static class Fonts { + internal const string IncludedIndicator = "Chat 2: "; + + internal static readonly Font[] GlobalFonts = { + new( + $"{IncludedIndicator}Noto Sans", + "ChatTwo.fonts.NotoSans-Regular.ttf", + "ChatTwo.fonts.NotoSans-Italic.ttf" + ), + new( + $"{IncludedIndicator}Noto Serif", + "ChatTwo.fonts.NotoSerif-Regular.ttf", + "ChatTwo.fonts.NotoSerif-Italic.ttf" + ), + new( + $"{IncludedIndicator}Open Sans", + "ChatTwo.fonts.OpenSans-Regular.ttf", + "ChatTwo.fonts.OpenSans-Italic.ttf" + ), + new( + $"{IncludedIndicator}Roboto", + "ChatTwo.fonts.Roboto-Regular.ttf", + "ChatTwo.fonts.Roboto-Italic.ttf" + ), + }; + + internal static readonly (string, string)[] JapaneseFonts = { + ($"{IncludedIndicator}Noto Sans JP", "ChatTwo.fonts.NotoSansJP-Regular.otf"), + // ($"{IncludedIndicator}Noto Serif JP", "ChatTwo.fonts.NotoSerifJP-Regular.otf"), + }; + + internal static List GetJpFonts() { + var fonts = new List(); + using var g = Graphics.FromImage(new Bitmap(1, 1)); + foreach (var (lpelfe, _, fontType) in Gdi32.EnumFontFamiliesEx(g.GetHdc(), CharacterSet.SHIFTJIS_CHARSET)) { + var name = lpelfe.elfEnumLogfontEx.elfLogFont.lfFaceName; + if (name.StartsWith("@")) { + continue; + } + + fonts.Add(name); + } + + return fonts; + } + + internal static unsafe FontData? GetFont(string name, bool withItalic, CharacterSet charset = CharacterSet.ANSI_CHARSET) { + var regularFont = Gdi32.CreateFontIndirect(new LOGFONT { + lfFaceName = name, + lfItalic = false, + lfCharSet = charset, + lfOutPrecision = LogFontOutputPrecision.OUT_TT_ONLY_PRECIS, + }); + + using var g = Graphics.FromImage(new Bitmap(1, 1)); + var hdc = g.GetHdc(); + + byte[]? GetFontData(HGDIOBJ obj) { + Gdi32.SelectObject(hdc, obj); + var size = Gdi32.GetFontData(hdc, pvBuffer: IntPtr.Zero); + var data = new byte[size]; + fixed (byte* p = data) { + var res = Gdi32.GetFontData(hdc, pvBuffer: (IntPtr) p, cjBuffer: size); + Gdi32.DeleteObject(obj); + if (res == Gdi32.GDI_ERROR) { + return null; + } + } + + return data; + } + + var regular = GetFontData(regularFont); + var italic = Array.Empty(); + if (withItalic) { + var italicFont = Gdi32.CreateFontIndirect(new LOGFONT { + lfFaceName = name, + lfItalic = true, + lfCharSet = charset, + lfOutPrecision = LogFontOutputPrecision.OUT_TT_ONLY_PRECIS, + }); + + italic = GetFontData(italicFont); + } + + if (regular == null || italic == null) { + return null; + } + + return new FontData(regular, italic); + } +} + +internal sealed class FontData { + internal byte[] Regular { get; } + internal byte[] Italic { get; } + + internal FontData(byte[] regular, byte[] italic) { + this.Regular = regular; + this.Italic = italic; + } +} + +internal sealed class Font { + internal string Name { get; } + internal string ResourcePath { get; } + internal string ResourcePathItalic { get; } + + internal Font(string name, string resourcePath, string resourcePathItalic) { + this.Name = name; + this.ResourcePath = resourcePath; + this.ResourcePathItalic = resourcePathItalic; + } +} diff --git a/ChatTwo/Ui/Settings.cs b/ChatTwo/Ui/Settings.cs index 04b86e4..08f47e2 100755 --- a/ChatTwo/Ui/Settings.cs +++ b/ChatTwo/Ui/Settings.cs @@ -123,6 +123,8 @@ internal sealed class Settings : IUiComponent { var config = this.Ui.Plugin.Config; var hideChatChanged = this.Mutable.HideChat != this.Ui.Plugin.Config.HideChat; + var fontChanged = this.Mutable.GlobalFont != this.Ui.Plugin.Config.GlobalFont + || this.Mutable.JapaneseFont != this.Ui.Plugin.Config.JapaneseFont; var fontSizeChanged = Math.Abs(this.Mutable.FontSize - this.Ui.Plugin.Config.FontSize) > 0.001; config.UpdateFrom(this.Mutable); @@ -131,7 +133,7 @@ internal sealed class Settings : IUiComponent { this.Ui.Plugin.Store.FilterAllTabs(false); - if (fontSizeChanged) { + if (fontChanged || fontSizeChanged) { this.Ui.Plugin.Interface.UiBuilder.RebuildFonts(); } diff --git a/ChatTwo/Ui/SettingsTabs/Display.cs b/ChatTwo/Ui/SettingsTabs/Display.cs index 91c5da5..a912a13 100755 --- a/ChatTwo/Ui/SettingsTabs/Display.cs +++ b/ChatTwo/Ui/SettingsTabs/Display.cs @@ -1,4 +1,6 @@ -using ChatTwo.Resources; +using System.Drawing; +using System.Drawing.Text; +using ChatTwo.Resources; using ChatTwo.Util; using ImGuiNET; @@ -6,14 +8,32 @@ namespace ChatTwo.Ui.SettingsTabs; internal sealed class Display : ISettingsTab { private Configuration Mutable { get; } + private List Fonts { get; } = new(); + private List JpFonts { get; set; } = new(); public string Name => Language.Options_Display_Tab + "###tabs-display"; internal Display(Configuration mutable) { this.Mutable = mutable; + this.UpdateFonts(); + } + + private void UpdateFonts() { + this.Fonts.Clear(); + + var fonts = new InstalledFontCollection(); + foreach (var font in fonts.Families) { + this.Fonts.Add(font); + } + + this.JpFonts = Ui.Fonts.GetJpFonts(); } public void Draw() { + if (ImGui.IsWindowAppearing()) { + this.UpdateFonts(); + } + ImGuiUtil.OptionCheckbox(ref this.Mutable.HideChat, Language.Options_HideChat_Name, Language.Options_HideChat_Description); ImGuiUtil.OptionCheckbox(ref this.Mutable.HideDuringCutscenes, Language.Options_HideDuringCutscenes_Name, Language.Options_HideDuringCutscenes_Description); ImGuiUtil.OptionCheckbox(ref this.Mutable.NativeItemTooltips, Language.Options_NativeItemTooltips_Name, Language.Options_NativeItemTooltips_Description); @@ -28,6 +48,62 @@ internal sealed class Display : ISettingsTab { ImGuiUtil.OptionCheckbox(ref this.Mutable.ShowNoviceNetwork, Language.Options_ShowNoviceNetwork_Name, Language.Options_ShowNoviceNetwork_Description); + if (ImGui.BeginCombo("Font", this.Mutable.GlobalFont)) { + foreach (var font in Ui.Fonts.GlobalFonts) { + if (ImGui.Selectable(font.Name, this.Mutable.GlobalFont == font.Name)) { + this.Mutable.GlobalFont = font.Name; + } + + if (ImGui.IsWindowAppearing() && this.Mutable.GlobalFont == font.Name) { + ImGui.SetScrollHereY(0.5f); + } + } + + ImGui.Separator(); + + foreach (var family in this.Fonts) { + if (!family.IsStyleAvailable(FontStyle.Italic)) { + continue; + } + + if (ImGui.Selectable(family.Name, this.Mutable.GlobalFont == family.Name)) { + this.Mutable.GlobalFont = family.Name; + } + + if (ImGui.IsWindowAppearing() && this.Mutable.GlobalFont == family.Name) { + ImGui.SetScrollHereY(0.5f); + } + } + + ImGui.EndCombo(); + } + + if (ImGui.BeginCombo("Japanese font", this.Mutable.JapaneseFont)) { + foreach (var (name, _) in Ui.Fonts.JapaneseFonts) { + if (ImGui.Selectable(name, this.Mutable.JapaneseFont == name)) { + this.Mutable.JapaneseFont = name; + } + + if (ImGui.IsWindowAppearing() && this.Mutable.JapaneseFont == name) { + ImGui.SetScrollHereY(0.5f); + } + } + + // ImGui.Separator(); + // + // foreach (var family in this.JpFonts) { + // if (ImGui.Selectable(family, this.Mutable.JapaneseFont == family)) { + // this.Mutable.JapaneseFont = family; + // } + // + // if (ImGui.IsWindowAppearing() && this.Mutable.JapaneseFont == family) { + // ImGui.SetScrollHereY(0.5f); + // } + // } + + ImGui.EndCombo(); + } + ImGui.DragFloat(Language.Options_FontSize_Name, ref this.Mutable.FontSize, .0125f, 12f, 36f, $"{this.Mutable.FontSize:N1}"); if (ImGui.DragFloat(Language.Options_WindowOpacity_Name, ref this.Mutable.WindowAlpha, .0025f, 0f, 1f, $"{this.Mutable.WindowAlpha * 100f:N2}%%")) { switch (this.Mutable.WindowAlpha) { diff --git a/ChatTwo/fonts/NotoSerif-Italic.ttf b/ChatTwo/fonts/NotoSerif-Italic.ttf new file mode 100755 index 0000000..f692967 Binary files /dev/null and b/ChatTwo/fonts/NotoSerif-Italic.ttf differ diff --git a/ChatTwo/fonts/NotoSerif-Regular.ttf b/ChatTwo/fonts/NotoSerif-Regular.ttf new file mode 100755 index 0000000..892423b Binary files /dev/null and b/ChatTwo/fonts/NotoSerif-Regular.ttf differ diff --git a/ChatTwo/fonts/NotoSerifJP-Regular.otf b/ChatTwo/fonts/NotoSerifJP-Regular.otf new file mode 100755 index 0000000..541a86b Binary files /dev/null and b/ChatTwo/fonts/NotoSerifJP-Regular.otf differ diff --git a/ChatTwo/fonts/OpenSans-Italic.ttf b/ChatTwo/fonts/OpenSans-Italic.ttf new file mode 100755 index 0000000..b088474 Binary files /dev/null and b/ChatTwo/fonts/OpenSans-Italic.ttf differ diff --git a/ChatTwo/fonts/OpenSans-Regular.ttf b/ChatTwo/fonts/OpenSans-Regular.ttf new file mode 100755 index 0000000..3a29f26 Binary files /dev/null and b/ChatTwo/fonts/OpenSans-Regular.ttf differ diff --git a/ChatTwo/fonts/Roboto-Italic.ttf b/ChatTwo/fonts/Roboto-Italic.ttf new file mode 100755 index 0000000..c9df607 Binary files /dev/null and b/ChatTwo/fonts/Roboto-Italic.ttf differ diff --git a/ChatTwo/fonts/Roboto-Regular.ttf b/ChatTwo/fonts/Roboto-Regular.ttf new file mode 100755 index 0000000..3d6861b Binary files /dev/null and b/ChatTwo/fonts/Roboto-Regular.ttf differ