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