From 43e80035cc616fb0eabca223a01d7c1c7f65dbdd Mon Sep 17 00:00:00 2001 From: Neko110923 Date: Wed, 10 Dec 2025 01:07:54 +0800 Subject: [PATCH 1/4] chore: add Gson dependency to handling JSON --- build.gradle.kts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index 18ffb270c..877364a0c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -56,6 +56,8 @@ dependencies { implementation(files("libs/steamworks4j-lwjgl3-1.10.0-SNAPSHOT.jar")) // api("com.code-disaster.steamworks4j:steamworks4j:1.10.0-SNAPSHOT") // api("com.code-disaster.steamworks4j:steamworks4j-lwjgl3:1.10.0-SNAPSHOT") + + implementation("com.google.code.gson:gson:2.13.2") // Used for handling JSON } group = "com.aehmttw" From 7122e9b876ed5b462dd17c163460d651a9e785b8 Mon Sep 17 00:00:00 2001 From: Neko110923 Date: Wed, 10 Dec 2025 03:56:56 +0800 Subject: [PATCH 2/4] feat: modified FontRenderer to support font id --- .../java/basewindow/BaseFontRenderer.java | 12 +- src/main/java/lwjglwindow/FontRenderer.java | 109 +++++++++++++----- 2 files changed, 90 insertions(+), 31 deletions(-) diff --git a/src/main/java/basewindow/BaseFontRenderer.java b/src/main/java/basewindow/BaseFontRenderer.java index 0ecf353b1..cc89b06a6 100644 --- a/src/main/java/basewindow/BaseFontRenderer.java +++ b/src/main/java/basewindow/BaseFontRenderer.java @@ -1,5 +1,7 @@ package basewindow; +import lwjglwindow.FontRenderer; + public abstract class BaseFontRenderer { public boolean drawBox = false; @@ -23,5 +25,13 @@ public BaseFontRenderer(BaseWindow h) public abstract double getStringSizeY(double sY, String s); - public abstract void addFont(String imageFile, String chars, int[] charSizes); + public abstract void addFont(String id, String imageFile, String chars, int[] charSizes); + + public abstract void addFont(String id, String imageFile, String chars, int[] charSizes, int hSpace); + + public abstract void setDefaultFont(String id, String imageFile, String chars, int[] charSizes, int hSpace); + + public abstract FontRenderer.FontInfo getFontById(String id); + + public abstract boolean hasFontId(String id); } diff --git a/src/main/java/lwjglwindow/FontRenderer.java b/src/main/java/lwjglwindow/FontRenderer.java index b5e7c3379..41321f79a 100644 --- a/src/main/java/lwjglwindow/FontRenderer.java +++ b/src/main/java/lwjglwindow/FontRenderer.java @@ -12,15 +12,18 @@ public class FontRenderer extends BaseFontRenderer { public static class FontInfo { - public String chars; - public int[] charSizes; - public String image; + public final String id; // Font id + public final String chars; + public final int[] charSizes; + public final String image; public float size = 16; // how many characters fit per horizontal line public int hSpace = 2; // spacing between rows, increase this to 2 for antialiasing to prevent weird artifacts - public Map charIndexMap = new HashMap<>(); + public final Map charIndexMap = new HashMap<>(); - public FontInfo(String image, String chars, int[] charSizes) + public FontInfo(String id, String image, String chars, int[] charSizes, Integer hSpace) { + this.id = id; + if (hSpace != null) this.hSpace = hSpace; this.image = image; this.chars = chars; this.charSizes = charSizes; @@ -30,50 +33,96 @@ public FontInfo(String image, String chars, int[] charSizes) charIndexMap.put(chars.charAt(i), i); } } + + public FontInfo(String id, String image, String chars, int[] charSizes) { + this(id, image, chars, charSizes, null); + } } private final List fontInfos = new ArrayList<>(); - private final FontInfo defaultFont; + private final Map fontById = new HashMap<>(); // Use font id to locate the font + private FontInfo defaultFont; - public FontRenderer(LWJGLWindow h, String defaultFontFile) + public FontRenderer(LWJGLWindow h) { super(h); + } + + /** + * Set the default font. Must be called before any drawing operations. + */ + @Override + public void setDefaultFont(String id, String imageFile, String chars, int[] charSizes, int hSpace) + { + if (defaultFont != null) + { + throw new IllegalStateException("Default font already set!"); + } - defaultFont = new FontInfo(defaultFontFile, - " !\"#$%&'()*+,-./" + - "0123456789:;<=>?" + - "@ABCDEFGHIJKLMNO" + - "PQRSTUVWXYZ[\\]^_" + - "'abcdefghijklmno" + - "pqrstuvwxyz{|}~`" + - "âăîşţàçæèéêëïôœù" + - "úûüÿáíóñ¡¿äöå", - new int[]{ - 3, 2, 4, 5, 5, 6, 5, 2, 3, 3, 4, 5, 1, 5, 1, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 5, 5, 5, 5, - 7, 5, 5, 5, 5, 5, 5, 5, 5, 3, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 3, 5, 3, 5, 5, - 2, 5, 5, 5, 5, 5, 4, 5, 5, 1, 5, 4, 2, 5, 5, 5, - 5, 5, 5, 5, 3, 5, 5, 5, 5, 5, 5, 4, 1, 4, 6, 2, - 5, 5, 5, 5, 3, 5, 5, 7, 5, 5, 5, 5, 3, 5, 7, 5, - 5, 5, 5, 5, 5, 3, 5, 5, 3, 5, 5, 5, 5 - }); - - fontInfos.add(defaultFont); + FontInfo info = new FontInfo(id, imageFile, chars, charSizes, hSpace); + fontInfos.add(info); + fontById.put(id, info); + defaultFont = info; } /** * Add a new font to the renderer. * + * @param id The font ID for lookup. * @param imageFile The image file path. * @param chars The characters to include in the font. * @param charSizes The width of each character in (pixels / 4). */ - public void addFont(String imageFile, String chars, int[] charSizes) + @Override + public void addFont(String id, String imageFile, String chars, int[] charSizes) { - fontInfos.add(new FontInfo(imageFile, chars, charSizes)); + FontInfo info = new FontInfo(id, imageFile, chars, charSizes); + fontInfos.add(info); + fontById.put(id, info); } + /** + * Add a new font to the renderer. + * + * @param id The font ID for lookup. + * @param imageFile The image file path. + * @param chars The characters to include in the font. + * @param charSizes The width of each character in (pixels / 4). + * @param hSpace Spacing between rows + */ + @Override + public void addFont(String id, String imageFile, String chars, int[] charSizes, int hSpace) + { + FontInfo info = new FontInfo(id, imageFile, chars, charSizes, hSpace); + fontInfos.add(info); + fontById.put(id, info); + } + + /** + * Get a font by its ID. + * + * @param id The font ID. + * @return The FontInfo, or null if not found. + */ + @Override + public FontInfo getFontById(String id) + { + return fontById.get(id); + } + + /** + * Check if a font ID exists. + * + * @param id The font ID. + * @return true if the font exists. + */ + @Override + public boolean hasFontId(String id) + { + return fontById.containsKey(id); + } + + @Override public boolean supportsChar(char c) { for (FontInfo font : fontInfos) From 80316b86ee1b722bd04d06b4a6267a16470debfe Mon Sep 17 00:00:00 2001 From: Neko110923 Date: Wed, 10 Dec 2025 04:00:06 +0800 Subject: [PATCH 3/4] feat: font config --- src/main/java/lwjglwindow/LWJGLWindow.java | 232 +++++++++++++++++---- src/main/java/utils/GsonUtils.java | 9 + src/main/resources/fonts/config.json | 88 ++++++++ 3 files changed, 294 insertions(+), 35 deletions(-) create mode 100644 src/main/java/utils/GsonUtils.java create mode 100644 src/main/resources/fonts/config.json diff --git a/src/main/java/lwjglwindow/LWJGLWindow.java b/src/main/java/lwjglwindow/LWJGLWindow.java index 51ccc68a9..1bba62918 100644 --- a/src/main/java/lwjglwindow/LWJGLWindow.java +++ b/src/main/java/lwjglwindow/LWJGLWindow.java @@ -3,6 +3,11 @@ import basewindow.*; import basewindow.transformation.Matrix4; import basewindow.transformation.Transformation; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.stream.JsonReader; import de.matthiasmann.twl.utils.PNGDecoder; import de.matthiasmann.twl.utils.PNGDecoder.Format; import org.lwjgl.BufferUtils; @@ -14,6 +19,7 @@ import org.lwjgl.opengl.*; import org.lwjgl.system.MemoryStack; import tanks.Game; +import utils.GsonUtils; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; @@ -134,42 +140,9 @@ public void run() protected void init() { - this.fontRenderer = new FontRenderer(this, "/fonts/default/font.png"); + this.fontRenderer = new FontRenderer(this); - // Load zh cn font - try - { - int count = 1; - while (true) - { - try (InputStream zhCnFontInputStream = LWJGLWindow.class.getClassLoader().getResourceAsStream("fonts/zh_cn/font_zh_cn_" + count + ".png"); - InputStream zhCnTxtInputStream = LWJGLWindow.class.getClassLoader().getResourceAsStream("fonts/zh_cn/font_zh_cn_" + count + ".txt")) - { - if (zhCnFontInputStream == null) break; - if (zhCnTxtInputStream == null) - { - Game.logger.println("Failed to load zh cn font " + count); - continue; - } - Scanner scanner = new Scanner(Objects.requireNonNull(zhCnTxtInputStream), StandardCharsets.UTF_8.name()); - StringBuilder sb = new StringBuilder(); - while (scanner.hasNextLine()) - { - sb.append(scanner.nextLine()); - } - String chinese_chars = sb.toString(); - int[] chinese_chars_sizes = new int[chinese_chars.length()]; - Arrays.fill(chinese_chars_sizes, 8); - this.fontRenderer.addFont("/fonts/zh_cn/font_zh_cn_" + count + ".png", chinese_chars, chinese_chars_sizes); - count++; - } - } - } - catch (IOException e) - { - e.printStackTrace(Game.logger); - e.printStackTrace(); - } + loadFonts(); GLFWErrorCallback.createPrint(System.err).set(); @@ -262,6 +235,195 @@ else if (action == GLFW_RELEASE) glfwShowWindow(window); } + private void loadFonts() { + try (InputStream fontConfigInputStream = LWJGLWindow.class.getClassLoader().getResourceAsStream("fonts/config.json")) + { + if (fontConfigInputStream != null) + { + Gson gson = GsonUtils.GSON; + JsonReader fontConfigJsonReader = new JsonReader(new InputStreamReader(fontConfigInputStream)); + JsonObject fontConfig = gson.fromJson(fontConfigJsonReader, JsonObject.class); + + JsonObject fontFiles = fontConfig.getAsJsonObject("font_files"); + if (fontFiles == null) + { + Game.logger.println("font config is corrupted. (cannot found font_files object)"); + return; + } + + if (fontFiles.has("default")) + { + loadDefaultFont("default", fontFiles.getAsJsonObject("default")); + } + else + { + Game.logger.println("font config error: 'default' font is required!"); + return; + } + + for (Map.Entry entry : fontFiles.entrySet()) + { + String fontId = entry.getKey(); + if ("default".equals(fontId)) + continue; + + if (!(entry.getValue() instanceof JsonObject)) + { + Game.logger.println("font config warn: font_files['" + fontId + "'] should be JsonObject"); + continue; + } + + loadSingleFont(fontId, (JsonObject) entry.getValue()); + } + } + else + { + Game.logger.println("Couldn't find font config file, this may be a fault"); + } + } + catch (IOException e) + { + e.printStackTrace(Game.logger); + e.printStackTrace(); + } + } + + private void loadDefaultFont(String fontId, JsonObject fontFileObj) { + String path = fontFileObj.get("path").getAsString(); + if (path == null) + { + Game.logger.println("font config error: default font doesn't have 'path' element!"); + return; + } + + Game.logger.println("Loading default font from \"" + path + "\"..."); + + JsonArray chars = fontFileObj.getAsJsonArray("chars"); + if (chars == null) + { + Game.logger.println("font config error: default font doesn't have 'chars' element!"); + return; + } + + StringBuilder finalCharsString = new StringBuilder(); + for (JsonElement element : chars) + { + finalCharsString.append(element.getAsString()); + } + + if (!fontFileObj.has("char_size_type")) + { + Game.logger.println("font config error: char_size_type not exists in default font!"); + return; + } + + String charSizeType = fontFileObj.get("char_size_type").getAsString(); + int[] charSizeArray = new int[finalCharsString.length()]; + + if ("fixed".equals(charSizeType)) + { + if (!fontFileObj.has("char_size")) + { + Game.logger.println("font config error: char_size_type is \"fixed\", but could not found \"char_size\" in default font"); + return; + } + + int charSize = fontFileObj.get("char_size").getAsInt(); + Arrays.fill(charSizeArray, charSize); + } + else if ("array".equals(charSizeType)) + { + if (!fontFileObj.has("char_size_array")) + { + Game.logger.println("font config error: char_size_type is \"array\", but could not found \"char_size_array\" in default font"); + return; + } + + JsonArray charSizeArrayJson = fontFileObj.getAsJsonArray("char_size_array"); + for (int j = 0; j < charSizeArrayJson.size(); j++) + { + charSizeArray[j] = charSizeArrayJson.get(j).getAsInt(); + } + } + else + { + Game.logger.println("font config error: unknown char_size_type '" + charSizeType + "' in default font"); + return; + } + + int hSpace = (fontFileObj.has("h_space") ? fontFileObj.get("h_space").getAsInt() : 2); + + fontRenderer.setDefaultFont(fontId, path, finalCharsString.toString(), charSizeArray, hSpace); + } + + private void loadSingleFont(String fontId, JsonObject fontFileObj) { + String path = fontFileObj.get("path").getAsString(); + if (path == null) + { + Game.logger.println("font config warn: font '" + fontId + "' doesn't have 'path' element, skip..."); + return; + } + + Game.logger.println("Loading font \"" + fontId + "\" from \"" + path + "\"..."); + + JsonArray chars = fontFileObj.getAsJsonArray("chars"); + if (chars == null) + { + Game.logger.println("font config warn: font '" + fontId + "' doesn't have 'chars' element, skip..."); + return; + } + + StringBuilder finalCharsString = new StringBuilder(); + for (JsonElement element : chars) + { + finalCharsString.append(element.getAsString()); + } + + if (!fontFileObj.has("char_size_type")) + { + Game.logger.println("font config warn: char_size_type not exists in font '" + fontId + "', skip..."); + return; + } + + String charSizeType = fontFileObj.get("char_size_type").getAsString(); + int[] charSizeArray = new int[finalCharsString.length()]; + + if ("fixed".equals(charSizeType)) + { + if (!fontFileObj.has("char_size")) + { + Game.logger.println("font config warn: char_size_type is \"fixed\", but could not found \"char_size\" in font '" + fontId + "'"); + return; + } + + int charSize = fontFileObj.get("char_size").getAsInt(); + Arrays.fill(charSizeArray, charSize); + } + else if ("array".equals(charSizeType)) + { + if (!fontFileObj.has("char_size_array")) + { + Game.logger.println("font config warn: char_size_type is \"array\", but could not found \"char_size_array\" in font '" + fontId + "'"); + return; + } + + JsonArray charSizeArrayJson = fontFileObj.getAsJsonArray("char_size_array"); + for (int j = 0; j < charSizeArrayJson.size(); j++) + { + charSizeArray[j] = charSizeArrayJson.get(j).getAsInt(); + } + } + else + { + Game.logger.println("font config warn: unknown char_size_type '" + charSizeType + "' in font '" + fontId + "', skip..."); + return; + } + + int hSpace = (fontFileObj.has("h_space") ? fontFileObj.get("h_space").getAsInt() : 2); + + fontRenderer.addFont(fontId, path, finalCharsString.toString(), charSizeArray, hSpace); + } + protected static String getLogInfo(int obj) { return ARBShaderObjects.glGetInfoLogARB(obj, ARBShaderObjects.glGetObjectParameteriARB(obj, ARBShaderObjects.GL_OBJECT_INFO_LOG_LENGTH_ARB)); diff --git a/src/main/java/utils/GsonUtils.java b/src/main/java/utils/GsonUtils.java new file mode 100644 index 000000000..bec3de84e --- /dev/null +++ b/src/main/java/utils/GsonUtils.java @@ -0,0 +1,9 @@ +package utils; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +public class GsonUtils +{ + public static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); +} diff --git a/src/main/resources/fonts/config.json b/src/main/resources/fonts/config.json new file mode 100644 index 000000000..b4f4ac441 --- /dev/null +++ b/src/main/resources/fonts/config.json @@ -0,0 +1,88 @@ +{ + "font_files": { + "default": { + "path": "/fonts/default/font.png", + "chars": [ + " !\"#$%&'()*+,-./", + "0123456789:;<=>?", + "@ABCDEFGHIJKLMNO", + "PQRSTUVWXYZ[\\]^_", + "'abcdefghijklmno", + "pqrstuvwxyz{|}~`", + "âăîşţàçæèéêëïôœù", + "úûüÿáíóñ¡¿äöå" + ], + "char_size_type": "array", + "char_size_array": [ + 3, 2, 4, 5, 5, 6, 5, 2, 3, 3, 4, 5, 1, 5, 1, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 5, 5, 5, 5, + 7, 5, 5, 5, 5, 5, 5, 5, 5, 3, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 3, 5, 3, 5, 5, + 2, 5, 5, 5, 5, 5, 4, 5, 5, 1, 5, 4, 2, 5, 5, 5, + 5, 5, 5, 5, 3, 5, 5, 5, 5, 5, 5, 4, 1, 4, 6, 2, + 5, 5, 5, 5, 3, 5, 5, 7, 5, 5, 5, 5, 3, 5, 7, 5, + 5, 5, 5, 5, 5, 3, 5, 5, 3, 5, 5, 5, 5 + ] + }, + "zh_cn_1": { + "path": "/fonts/zh_cn/font_zh_cn_1.png", + "chars": [ + ",。?!@#¥%……&*()-+~·《》【】、|'\";:—、简体中文结构基于并添加了更多翻译游戏版本语言", + "坦克十字军东征开始选项关退出帧率内存使用量单人择模式返回随机卡小我的教程时间确定性继续生成新", + "重此到主菜编辑删除名称大背景颜色照明团队物品限制分钟秒将和数" + ], + "char_size_type": "fixed", + "char_size": 8 + }, + "zh_cn_2": { + "path": "/fonts/zh_cn/font_zh_cn_2.png", + "chars": [ + "设置为以禁直射光阴影红绿蓝宽度高盟友敌初硬币商店排列子弹地雷护盾从板普通火焰激反超级闪电冰冻", + "喷器炮治疗束爆炸迷你黑暗保图标最堆叠尺寸价格引线长触发半径冷却活动伤害上一页下破坏方块是否第", + "共后坐力型类效果速次命值提升额外个进行按顺序战斗看能坚持久创建玩自己学习" + ], + "char_size_type": "fixed", + "char_size": 8 + }, + "zh_cn_3": { + "path": "/fonts/zh_cn/font_zh_cn_3.png", + "chars": [ + "如何可键盘控向移或左右这里鼠击摧毁所有获胜些住钮来瞄准空在屏幕同拥墙车放附近棕壁旁边它另靠者被", + "会再试题心之前避包括因们不得过查及剩余利信息栏底部显示网络延迟组闭启户其他家化脱颖而允许义备就", + "绪等待位默认作弊当无纠正较弱适合稳连接强聊天滤潜词汇没输入暂停切换缩" + ], + "char_size_type": "fixed", + "char_size": 8 + }, + "zh_cn_4": { + "path": "/fonts/zh_cn/font_zh_cn_4.png", + "chars": [ + "隐藏全追踪热见槽取消对象播撤销做工具快操伍障碍造擦调整摄像头居属清形解绑目指针替华丽改变面响轨", + "迹路某粒著百比垂步刷减少池耗也决致问三维视场角俯倾斜抗锯齿修复薄缘烁但代需要才注意声音乐载点统", + "计详细据尝花费种固锁与助公平供太流畅验日志证私政策依赖库容功界频衡欢" + ], + "char_size_type": "fixed", + "char_size": 8 + }, + "zh_cn_5": { + "path": "/fonts/zh_cn/font_zh_cn_5.png", + "chars": [ + "迎完吗练假原箭布智两够呈弧眩晕低支援短巨且法阻挡越增仿吹风导范围愤怒打冒险经典失去条途只丢草雪", + "隧道育坑洼堵塞突灌木丛落裹闯箱紫圆顶蛇堡垒幽灵侵冬湖陷阱烤泥潭森林杠狂兄弟婴儿攻暴起享派每都相", + "端口应该非转您址托管朋传想什么送已断好找蜂窝离毫把踢请访码社区现浏览" + ], + "char_size_type": "fixed", + "char_size": 8 + }, + "zh_cn_6": { + "path": "/fonts/zh_cn/font_zh_cn_6.png", + "chars": [ + "账期链纵杆跳那拖橙圈手拉双灰恭喜走测框沉浸杀总死亡鸟瞰哨环填写副坊邀败员错误未检常脑任搜索件理夹", + "辆窗杂资料层询约辨画预倒剪粘贴旋魔棒截材质透佳纪录域处烈幅降撕裂摘滚配药覆盖绕根街逐若占警告止至", + "差很运免顿立即城础虚协议旨年龄段台均宜尊犯感谢欣赏" + ], + "char_size_type": "fixed", + "char_size": 8 + } + } +} From 85b472b388cbc95414ec12222cd4dd1f9a871c57 Mon Sep 17 00:00:00 2001 From: Neko110923 Date: Wed, 10 Dec 2025 14:00:11 +0800 Subject: [PATCH 4/4] fix: BaseFontRenderer shouldn't be use FontRenderer.FontInfo --- src/main/java/basewindow/BaseFontRenderer.java | 4 ---- src/main/java/lwjglwindow/FontRenderer.java | 2 -- 2 files changed, 6 deletions(-) diff --git a/src/main/java/basewindow/BaseFontRenderer.java b/src/main/java/basewindow/BaseFontRenderer.java index cc89b06a6..903307365 100644 --- a/src/main/java/basewindow/BaseFontRenderer.java +++ b/src/main/java/basewindow/BaseFontRenderer.java @@ -30,8 +30,4 @@ public BaseFontRenderer(BaseWindow h) public abstract void addFont(String id, String imageFile, String chars, int[] charSizes, int hSpace); public abstract void setDefaultFont(String id, String imageFile, String chars, int[] charSizes, int hSpace); - - public abstract FontRenderer.FontInfo getFontById(String id); - - public abstract boolean hasFontId(String id); } diff --git a/src/main/java/lwjglwindow/FontRenderer.java b/src/main/java/lwjglwindow/FontRenderer.java index 41321f79a..8a707a923 100644 --- a/src/main/java/lwjglwindow/FontRenderer.java +++ b/src/main/java/lwjglwindow/FontRenderer.java @@ -104,7 +104,6 @@ public void addFont(String id, String imageFile, String chars, int[] charSizes, * @param id The font ID. * @return The FontInfo, or null if not found. */ - @Override public FontInfo getFontById(String id) { return fontById.get(id); @@ -116,7 +115,6 @@ public FontInfo getFontById(String id) * @param id The font ID. * @return true if the font exists. */ - @Override public boolean hasFontId(String id) { return fontById.containsKey(id);