From bdc4c1946607fb5cc39961a0338d612f3f92ddb9 Mon Sep 17 00:00:00 2001 From: Ash <109132519+uhhashe@users.noreply.github.com> Date: Sat, 4 Jul 2026 05:15:55 +0800 Subject: [PATCH] feat: T6 font_s dumper (#850) * feat: T6 font dumper * feat: implement templated loader and writer for fonts in t6 --------- Co-authored-by: Jan Laupetin --- docs/SupportedAssetTypes.md | 2 +- src/ObjCommon/Font/JsonFont.h.template | 70 ++++++++++++- src/ObjLoading/Font/FontLoader.cpp.template | 106 +++++++++++++++++--- src/ObjLoading/Font/FontLoader.h.template | 2 +- src/ObjLoading/Game/T6/ObjLoaderT6.cpp | 3 +- src/ObjWriting/Font/FontDumper.cpp.template | 24 ++++- src/ObjWriting/Font/FontDumper.h.template | 2 +- src/ObjWriting/Game/T6/ObjWriterT6.cpp | 3 +- 8 files changed, 191 insertions(+), 21 deletions(-) diff --git a/docs/SupportedAssetTypes.md b/docs/SupportedAssetTypes.md index f931019c..efbbc607 100644 --- a/docs/SupportedAssetTypes.md +++ b/docs/SupportedAssetTypes.md @@ -214,7 +214,7 @@ using `Linker`): | MapEnts | ✅ | ❌ | | | GfxWorld | ❌ | ❌ | | | GfxLightDef | ✅ | ✅ | | -| Font_s | ❌ | ❌ | | +| Font_s | ✅ | ✅ | | | FontIcon | ✅ | ✅ | | | MenuList | ❌ | ❌ | | | menuDef_t | ❌ | ❌ | | diff --git a/src/ObjCommon/Font/JsonFont.h.template b/src/ObjCommon/Font/JsonFont.h.template index cd5e4b25..a7248cb1 100644 --- a/src/ObjCommon/Font/JsonFont.h.template +++ b/src/ObjCommon/Font/JsonFont.h.template @@ -1,7 +1,11 @@ -#options GAME (IW3, IW4, IW5, T4, T5) +#options GAME (IW3, IW4, IW5, T4, T5, T6) #filename "Game/" + GAME + "/Font/JsonFont" + GAME + ".h" +#if GAME == "T6" +#define FEATURE_T6 +#endif + // This file was templated. // See JsonFont.h.template. // Do not modify, changes will be lost. @@ -54,14 +58,76 @@ namespace GAME NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(EXTEND_JSON_FROM, x0, y0, dx, pixelWidth, pixelHeight, s0, t0, s1, t1)) } +#ifdef FEATURE_T6 + class JsonKerningPair + { + public: + unsigned firstLetter; + unsigned secondLetter; + int kernAmount; + }; + + NLOHMANN_TO_JSON_METHOD(JsonKerningPair) + { + if (font::IsPrintableLetter(nlohmann_json_t.firstLetter)) + extended_to_json("firstLetter", nlohmann_json_j, font::LetterToString(nlohmann_json_t.firstLetter)); + else + extended_to_json("firstLetter", nlohmann_json_j, nlohmann_json_t.firstLetter); + + if (font::IsPrintableLetter(nlohmann_json_t.secondLetter)) + extended_to_json("secondLetter", nlohmann_json_j, font::LetterToString(nlohmann_json_t.secondLetter)); + else + extended_to_json("secondLetter", nlohmann_json_j, nlohmann_json_t.secondLetter); + + NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(EXTEND_JSON_TO, kernAmount)) + } + + NLOHMANN_FROM_JSON_METHOD(JsonKerningPair) + { + auto jLetter = nlohmann_json_j.at("firstLetter"); + if (jLetter.type() == BasicJsonType::value_t::string) + nlohmann_json_t.firstLetter = font::StringToLetter(jLetter.template get()); + else + nlohmann_json_t.firstLetter = jLetter.template get(); + + jLetter = nlohmann_json_j.at("secondLetter"); + if (jLetter.type() == BasicJsonType::value_t::string) + nlohmann_json_t.secondLetter = font::StringToLetter(jLetter.template get()); + else + nlohmann_json_t.secondLetter = jLetter.template get(); + + NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(EXTEND_JSON_FROM, kernAmount)) + } +#endif + class JsonFont { public: unsigned pixelHeight; +#ifdef FEATURE_T6 + bool isScalingAllowed; +#endif std::string material; std::optional glowMaterial; std::vector glyphs; +#ifdef FEATURE_T6 + std::vector kerningPairs; +#endif }; - NLOHMANN_DEFINE_TYPE_EXTENSION(JsonFont, pixelHeight, material, glowMaterial, glyphs); + NLOHMANN_DEFINE_TYPE_EXTENSION( + JsonFont, + pixelHeight, +#ifdef FEATURE_T6 + isScalingAllowed, +#endif + material, + glowMaterial, +#ifdef FEATURE_T6 + glyphs, + kerningPairs +#else + glyphs +#endif + ); } // namespace GAME diff --git a/src/ObjLoading/Font/FontLoader.cpp.template b/src/ObjLoading/Font/FontLoader.cpp.template index 1a60cca3..979420d8 100644 --- a/src/ObjLoading/Font/FontLoader.cpp.template +++ b/src/ObjLoading/Font/FontLoader.cpp.template @@ -1,4 +1,4 @@ -#options GAME (IW3, IW4, IW5, T4, T5) +#options GAME (IW3, IW4, IW5, T4, T5, T6) #filename "Game/" + GAME + "/Font/FontLoader" + GAME + ".cpp" @@ -12,6 +12,9 @@ #define GAME_LOWER "t4" #elif GAME == "T5" #define GAME_LOWER "t5" +#elif GAME == "T6" +#define GAME_LOWER "t6" +#define FEATURE_T6 #endif // This file was templated. @@ -142,6 +145,56 @@ namespace return true; } +#ifdef FEATURE_T6 + bool CreateKerningPairFromJson(const JsonKerningPair& jKerningPair, KerningPairs& kerningPair, const Font_s& font) + { + if (!AssignIntegerField(kerningPair.wFirst, jKerningPair.firstLetter, font, "kerningPair.firstLetter")) + return false; + if (!AssignIntegerField(kerningPair.wSecond, jKerningPair.secondLetter, font, "kerningPair.secondLetter")) + return false; + if (!AssignIntegerField(kerningPair.iKernAmount, jKerningPair.kernAmount, font, "kerningPair.kernAmount")) + return false; + + return true; + } + + void SortKerningPairs(const Font_s& font) + { + std::sort(&font.kerningPairs[0], &font.kerningPairs[font.kerningPairsCount], [](const KerningPairs& a, const KerningPairs& b) + { + if (a.wFirst != b.wFirst) + return a.wFirst < b.wFirst; + + return a.wSecond < b.wSecond; + }); + } + + bool EnsureFontContainsKerningPairLetter(const Font_s& font, const unsigned letter) + { + for (auto i = 0; i < font.glyphCount; i++) + { + const auto& glyph = font.glyphs[i]; + if (glyph.letter == letter) + return true; + } + + con::error("Font {} has kerning pair with letter {} ('{}') but no glyph for it", font.fontName, letter, static_cast(letter)); + return false; + } + + bool EnsureFontContainsGlyphsOfKerningPairs(const Font_s& font) + { + for (auto i = 0; i < font.kerningPairsCount; i++) + { + const auto& kerningPair = font.kerningPairs[i]; + if (!EnsureFontContainsKerningPairLetter(font, kerningPair.wFirst) || !EnsureFontContainsKerningPairLetter(font, kerningPair.wSecond)) + return false; + } + + return true; + } +#endif + class FontLoader final : public AssetCreator { public: @@ -200,6 +253,10 @@ namespace bool CreateFontFromJson(const JsonFont& jFont, Font_s& font, AssetRegistration& registration, AssetCreationContext& context) const { font.pixelHeight = static_cast(jFont.pixelHeight); + +#ifdef FEATURE_T6 + font.isScalingAllowed = jFont.isScalingAllowed; +#endif if (!CreateMaterialDependency(jFont.material, font.material, registration, context, font, "font")) return false; @@ -217,25 +274,48 @@ namespace return false; } - font.glyphCount = static_cast(jFont.glyphs.size());; - if (font.glyphCount <= 0) + font.glyphCount = static_cast(jFont.glyphs.size()); + if (font.glyphCount > 0) + { + font.glyphs = m_memory.Alloc(font.glyphCount); + for (auto i = 0; i < font.glyphCount; i++) + { + if (!CreateGlyphFromJson(jFont.glyphs[i], font.glyphs[i], font)) + return false; + } + + SortGlyphs(font); + } + else { font.glyphs = nullptr; - return true; } - - font.glyphs = m_memory.Alloc(font.glyphCount); - for (auto i = 0; i < font.glyphCount; i++) - { - if (!CreateGlyphFromJson(jFont.glyphs[i], font.glyphs[i], font)) - return false; - } - - SortGlyphs(font); if (!EnsureFontContainsAllRequiredGlyphs(font)) return false; +#ifdef FEATURE_T6 + font.kerningPairsCount = static_cast(jFont.kerningPairs.size()); + if (font.kerningPairsCount > 0) + { + font.kerningPairs = m_memory.Alloc(font.kerningPairsCount); + for (auto i = 0; i < font.kerningPairsCount; i++) + { + if (!CreateKerningPairFromJson(jFont.kerningPairs[i], font.kerningPairs[i], font)) + return false; + } + + SortKerningPairs(font); + } + else + { + font.kerningPairs = nullptr; + } + + if (!EnsureFontContainsGlyphsOfKerningPairs(font)) + return false; +#endif + return true; } diff --git a/src/ObjLoading/Font/FontLoader.h.template b/src/ObjLoading/Font/FontLoader.h.template index f7e92c60..3cd80ed1 100644 --- a/src/ObjLoading/Font/FontLoader.h.template +++ b/src/ObjLoading/Font/FontLoader.h.template @@ -1,4 +1,4 @@ -#options GAME (IW3, IW4, IW5, T4, T5) +#options GAME (IW3, IW4, IW5, T4, T5, T6) #filename "Game/" + GAME + "/Font/FontLoader" + GAME + ".h" diff --git a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp index 9d448942..70eb451b 100644 --- a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp +++ b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp @@ -5,6 +5,7 @@ #include "FontIcon/JsonLoaderFontIconT6.h" #include "Game/T6/AssetMarkerT6.h" #include "Game/T6/CommonT6.h" +#include "Game/T6/Font/FontLoaderT6.h" #include "Game/T6/GameT6.h" #include "Game/T6/Image/ImageLoaderEmbeddedT6.h" #include "Game/T6/Image/ImageLoaderExternalT6.h" @@ -404,7 +405,7 @@ namespace T6 // collection.AddAssetCreator(std::make_unique(memory)); // collection.AddAssetCreator(std::make_unique(memory)); collection.AddAssetCreator(light_def::CreateLoaderT6(memory, searchPath)); - // collection.AddAssetCreator(std::make_unique(memory)); + collection.AddAssetCreator(font::CreateLoaderT6(memory, searchPath)); collection.AddAssetCreator(font_icon::CreateCsvLoaderT6(memory, searchPath)); collection.AddAssetCreator(font_icon::CreateJsonLoaderT6(memory, searchPath)); // collection.AddAssetCreator(std::make_unique(memory)); diff --git a/src/ObjWriting/Font/FontDumper.cpp.template b/src/ObjWriting/Font/FontDumper.cpp.template index 74963d88..be9572db 100644 --- a/src/ObjWriting/Font/FontDumper.cpp.template +++ b/src/ObjWriting/Font/FontDumper.cpp.template @@ -1,4 +1,4 @@ -#options GAME (IW3, IW4, IW5, T4, T5) +#options GAME (IW3, IW4, IW5, T4, T5, T6) #filename "Game/" + GAME + "/Font/FontDumper" + GAME + ".cpp" @@ -12,6 +12,9 @@ #define GAME_LOWER "t4" #elif GAME == "T5" #define GAME_LOWER "t5" +#elif GAME == "T6" +#define FEATURE_T6 +#define GAME_LOWER "t6" #endif // This file was templated. @@ -57,9 +60,22 @@ namespace jGlyph.t1 = glyph.t1; } +#ifdef FEATURE_T6 + void CreateJsonKerningPair(JsonKerningPair& jKerningPair, const KerningPairs& kerningPair) + { + jKerningPair.firstLetter = kerningPair.wFirst; + jKerningPair.secondLetter = kerningPair.wSecond; + jKerningPair.kernAmount = kerningPair.iKernAmount; + } +#endif + void CreateJsonFont(JsonFont& jFont, const Font_s& font) { jFont.pixelHeight = font.pixelHeight; + +#ifdef FEATURE_T6 + jFont.isScalingAllowed = font.isScalingAllowed; +#endif if (font.material && font.material->info.name) jFont.material = AssetName(font.material->info.name); @@ -73,6 +89,12 @@ namespace jFont.glyphs.resize(font.glyphCount); for (auto i = 0; i < font.glyphCount; i++) CreateJsonGlyph(jFont.glyphs[i], font.glyphs[i]); + +#ifdef FEATURE_T6 + jFont.kerningPairs.resize(font.kerningPairsCount); + for (auto i = 0; i < font.kerningPairsCount; i++) + CreateJsonKerningPair(jFont.kerningPairs[i], font.kerningPairs[i]); +#endif } class Dumper diff --git a/src/ObjWriting/Font/FontDumper.h.template b/src/ObjWriting/Font/FontDumper.h.template index 0c5ab02a..cb7bee59 100644 --- a/src/ObjWriting/Font/FontDumper.h.template +++ b/src/ObjWriting/Font/FontDumper.h.template @@ -1,4 +1,4 @@ -#options GAME (IW3, IW4, IW5, T4, T5) +#options GAME (IW3, IW4, IW5, T4, T5, T6) #filename "Game/" + GAME + "/Font/FontDumper" + GAME + ".h" diff --git a/src/ObjWriting/Game/T6/ObjWriterT6.cpp b/src/ObjWriting/Game/T6/ObjWriterT6.cpp index 30157e52..ae264e1f 100644 --- a/src/ObjWriting/Game/T6/ObjWriterT6.cpp +++ b/src/ObjWriting/Game/T6/ObjWriterT6.cpp @@ -1,6 +1,7 @@ #include "ObjWriterT6.h" #include "FontIcon/FontIconDumperT6.h" +#include "Game/T6/Font/FontDumperT6.h" #include "Game/T6/Image/ImageDumperT6.h" #include "Game/T6/Material/MaterialJsonDumperT6.h" #include "Game/T6/Techset/TechsetDumperT6.h" @@ -55,7 +56,7 @@ void ObjWriter::RegisterAssetDumpers(AssetDumpingContext& context) RegisterAssetDumper(std::make_unique()); // REGISTER_DUMPER(AssetDumperGfxWorld, m_gfx_world) RegisterAssetDumper(std::make_unique()); - // REGISTER_DUMPER(AssetDumperFont, m_font) + RegisterAssetDumper(std::make_unique()); RegisterAssetDumper(font_icon::CreateDumperT6()); // REGISTER_DUMPER(AssetDumperMenuList, m_menu_list) // REGISTER_DUMPER(AssetDumperMenuDef, m_menu_def)