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 <[email protected]>
This commit is contained in:
Ash
2026-07-03 23:15:55 +02:00
committed by GitHub
co-authored by Jan Laupetin
parent 3fb8b2bb17
commit bdc4c19466
8 changed files with 191 additions and 21 deletions
+1 -1
View File
@@ -214,7 +214,7 @@ using `Linker`):
| MapEnts | ✅ | ❌ | |
| GfxWorld | ❌ | ❌ | |
| GfxLightDef | ✅ | ✅ | |
| Font_s | | | |
| Font_s | | | |
| FontIcon | ✅ | ✅ | |
| MenuList | ❌ | ❌ | |
| menuDef_t | ❌ | ❌ | |
+68 -2
View File
@@ -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<std::string>());
else
nlohmann_json_t.firstLetter = jLetter.template get<unsigned>();
jLetter = nlohmann_json_j.at("secondLetter");
if (jLetter.type() == BasicJsonType::value_t::string)
nlohmann_json_t.secondLetter = font::StringToLetter(jLetter.template get<std::string>());
else
nlohmann_json_t.secondLetter = jLetter.template get<unsigned>();
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<std::string> glowMaterial;
std::vector<JsonGlyph> glyphs;
#ifdef FEATURE_T6
std::vector<JsonKerningPair> 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
+93 -13
View File
@@ -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<char>(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<AssetFont>
{
public:
@@ -200,6 +253,10 @@ namespace
bool CreateFontFromJson(const JsonFont& jFont, Font_s& font, AssetRegistration<AssetFont>& registration, AssetCreationContext& context) const
{
font.pixelHeight = static_cast<decltype(Font_s::pixelHeight)>(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<decltype(Font_s::glyphCount)>(jFont.glyphs.size());;
if (font.glyphCount <= 0)
font.glyphCount = static_cast<decltype(Font_s::glyphCount)>(jFont.glyphs.size());
if (font.glyphCount > 0)
{
font.glyphs = m_memory.Alloc<Glyph>(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<Glyph>(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<decltype(Font_s::kerningPairsCount)>(jFont.kerningPairs.size());
if (font.kerningPairsCount > 0)
{
font.kerningPairs = m_memory.Alloc<KerningPairs>(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;
}
+1 -1
View File
@@ -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"
+2 -1
View File
@@ -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<AssetLoaderMapEnts>(memory));
// collection.AddAssetCreator(std::make_unique<AssetLoaderGfxWorld>(memory));
collection.AddAssetCreator(light_def::CreateLoaderT6(memory, searchPath));
// collection.AddAssetCreator(std::make_unique<AssetLoaderFont>(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<AssetLoaderMenuList>(memory));
+23 -1
View File
@@ -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
+1 -1
View File
@@ -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"
+2 -1
View File
@@ -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<map_ents::DumperT6>());
// REGISTER_DUMPER(AssetDumperGfxWorld, m_gfx_world)
RegisterAssetDumper(std::make_unique<light_def::DumperT6>());
// REGISTER_DUMPER(AssetDumperFont, m_font)
RegisterAssetDumper(std::make_unique<font::JsonDumperT6>());
RegisterAssetDumper(font_icon::CreateDumperT6());
// REGISTER_DUMPER(AssetDumperMenuList, m_menu_list)
// REGISTER_DUMPER(AssetDumperMenuDef, m_menu_def)