feat: font loading and dumping (#866)

* feat: font loading and dumping

* chore: make font properties unsigned if possible

* chore: use merge_patch to dump json font

* chore: font material is required

* chore: move static methods from font loader and dumper to anonymous namespace

* chore: make sure loaded fonts have their glyphs sorted

* chore: add macros for json NLOHMANN_TO_JSON_METHOD and NLOHMANN_FROM_JSON_METHOD

* feat: encode printable letters as string in json

Co-authored-by: hindercanrun <[email protected]>

* chore: omit redundant glyph count from jsons

---------

Co-authored-by: Jan Laupetin <[email protected]>
Co-authored-by: hindercanrun <[email protected]>
Co-authored-by: MrIkso <[email protected]>
This commit is contained in:
mo
2026-07-03 19:26:12 +02:00
committed by GitHub
co-authored by Jan Laupetin hindercanrun MrIkso
parent fb4b00398c
commit 3fb8b2bb17
24 changed files with 659 additions and 35 deletions
+5 -5
View File
@@ -28,7 +28,7 @@ using `Linker`):
| MapEnts | ✅ | ❌ | |
| GfxWorld | ❌ | ❌ | |
| GfxLightDef | ✅ | ✅ | |
| Font_s | | | |
| Font_s | | | |
| MenuList | ❌ | ❌ | |
| menuDef_t | ❌ | ❌ | |
| LocalizeEntry | ✅ | ✅ | |
@@ -63,7 +63,7 @@ using `Linker`):
| FxWorld | ❌ | ❌ | |
| GfxWorld | ❌ | ❌ | |
| GfxLightDef | ✅ | ✅ | |
| Font_s | | | |
| Font_s | | | |
| MenuList | ✅ | ✅ | The output is decompiled. The result will not be the same as the input. |
| menuDef_t | ✅ | ✅ | See menulist. |
| LocalizeEntry | ✅ | ✅ | |
@@ -105,7 +105,7 @@ using `Linker`):
| FxWorld | ❌ | ❌ | |
| GfxWorld | ❌ | ❌ | |
| GfxLightDef | ✅ | ✅ | |
| Font_s | | | |
| Font_s | | | |
| MenuList | ✅ | ✅ | The output is decompiled. The result will not be the same as the input. |
| menuDef_t | ✅ | ✅ | See menulist. |
| LocalizeEntry | ✅ | ✅ | |
@@ -145,7 +145,7 @@ using `Linker`):
| MapEnts | ✅ | ✅ | |
| GfxWorld | ❌ | ❌ | |
| GfxLightDef | ❌ | ❌ | |
| Font_s | | | |
| Font_s | | | |
| MenuList | ❌ | ❌ | |
| menuDef_t | ❌ | ❌ | |
| LocalizeEntry | ✅ | ✅ | |
@@ -177,7 +177,7 @@ using `Linker`):
| MapEnts | ❌ | ❌ | |
| GfxWorld | ❌ | ❌ | |
| GfxLightDef | ✅ | ✅ | |
| Font_s | | | |
| Font_s | | | |
| MenuList | ❌ | ❌ | |
| menuDef_t | ❌ | ❌ | |
| LocalizeEntry | ✅ | ✅ | |
+3 -3
View File
@@ -2455,9 +2455,9 @@ namespace IW3
uint16_t letter;
char x0;
char y0;
char dx;
char pixelWidth;
char pixelHeight;
unsigned char dx;
unsigned char pixelWidth;
unsigned char pixelHeight;
float s0;
float t0;
float s1;
+3 -3
View File
@@ -2676,9 +2676,9 @@ namespace IW4
uint16_t letter;
char x0;
char y0;
char dx;
char pixelWidth;
char pixelHeight;
unsigned char dx;
unsigned char pixelWidth;
unsigned char pixelHeight;
float s0;
float t0;
float s1;
+3 -3
View File
@@ -2713,9 +2713,9 @@ namespace T4
uint16_t letter;
char x0;
char y0;
char dx;
char pixelWidth;
char pixelHeight;
unsigned char dx;
unsigned char pixelWidth;
unsigned char pixelHeight;
float s0;
float t0;
float s1;
+3 -3
View File
@@ -3310,9 +3310,9 @@ namespace T5
uint16_t letter;
char x0;
char y0;
char dx;
char pixelWidth;
char pixelHeight;
unsigned char dx;
unsigned char pixelWidth;
unsigned char pixelHeight;
float s0;
float t0;
float s1;
+3 -3
View File
@@ -3997,9 +3997,9 @@ namespace T6
uint16_t letter;
char x0;
char y0;
char dx;
char pixelWidth;
char pixelHeight;
unsigned char dx;
unsigned char pixelWidth;
unsigned char pixelHeight;
float s0;
float t0;
float s1;
+107
View File
@@ -0,0 +1,107 @@
#include "FontCommon.h"
#include <filesystem>
namespace fs = std::filesystem;
namespace font
{
std::string GetJsonFileNameForAssetName(const std::string& assetName)
{
fs::path path(assetName);
path.replace_extension(".json");
return path.string();
}
bool IsPrintableLetter(const unsigned letter)
{
// Control characters
if (letter < 0x20)
return false;
// Printable ascii characters
if (letter < 0x7F)
return true;
// There are characters after this point that are printable as well
// But they don't seem to play a role in cod fonts
if (letter > 0xFF)
return false;
switch (letter)
{
case 0x7F:
case 0x81:
case 0x8D:
case 0x8F:
case 0x90:
case 0x9D:
case 0xA0:
case 0xAD:
return false;
default:
return true;
}
}
std::string LetterToString(const unsigned letter)
{
// UTF-8 => 1 byte
if (letter < 0x80)
return std::string(1, static_cast<char>(letter));
if (letter < 0x800)
{
// UTF-8 => 2 bytes
std::string s(2, '\0');
s[0] = static_cast<char>(0xC0 | (letter >> 6u));
s[1] = static_cast<char>(0x80 | (letter & 0x3F));
return s;
}
// UTF-8 => 3 bytes
std::string s(3, '\0');
s[0] = static_cast<char>(0xE0 | (letter >> 12u));
s[1] = static_cast<char>(0x80 | ((letter >> 6u) & 0x3F));
s[2] = static_cast<char>(0x80 | (letter & 0x3F));
return s;
}
unsigned StringToLetter(const std::string& str)
{
const auto strSize = str.size();
if (strSize < 1)
return 0;
const auto firstByte = static_cast<unsigned char>(str[0]);
// UTF-8 => 1 byte
if ((firstByte & 0x80) == 0)
return firstByte;
if (strSize < 2)
return 0;
const auto secondByte = static_cast<unsigned char>(str[1]);
if ((firstByte & 0xE0) == 0xC0 && (secondByte & 0xC0) == 0x80)
{
// UTF-8 => 2 bytes
return ((firstByte & 0x1F) << 6u) | (secondByte & 0x3F);
}
if (strSize < 3)
return 0;
const auto thirdByte = static_cast<unsigned char>(str[2]);
if ((firstByte & 0xF0) == 0xE0 && (secondByte & 0xC0) == 0x80 && (thirdByte & 0xC0) == 0x80)
{
// UTF-8 => 3 bytes
return ((firstByte & 0x0F) << 12u) | ((secondByte & 0x3F) << 6u) | (thirdByte & 0x3F);
}
// Unsupported
return 0;
}
} // namespace font
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include <string>
namespace font
{
std::string GetJsonFileNameForAssetName(const std::string& assetName);
bool IsPrintableLetter(unsigned letter);
std::string LetterToString(unsigned letter);
unsigned StringToLetter(const std::string& str);
} // namespace font
+67
View File
@@ -0,0 +1,67 @@
#options GAME (IW3, IW4, IW5, T4, T5)
#filename "Game/" + GAME + "/Font/JsonFont" + GAME + ".h"
// This file was templated.
// See JsonFont.h.template.
// Do not modify, changes will be lost.
#pragma once
#include "Json/JsonExtension.h"
#include "Font/FontCommon.h"
#include <nlohmann/json.hpp>
#include <optional>
#include <string>
#include <vector>
namespace GAME
{
class JsonGlyph
{
public:
unsigned letter;
int x0;
int y0;
unsigned dx;
unsigned pixelWidth;
unsigned pixelHeight;
float s0;
float t0;
float s1;
float t1;
};
NLOHMANN_TO_JSON_METHOD(JsonGlyph)
{
if (font::IsPrintableLetter(nlohmann_json_t.letter))
extended_to_json("letter", nlohmann_json_j, font::LetterToString(nlohmann_json_t.letter));
else
extended_to_json("letter", nlohmann_json_j, nlohmann_json_t.letter);
NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(EXTEND_JSON_TO, x0, y0, dx, pixelWidth, pixelHeight, s0, t0, s1, t1))
}
NLOHMANN_FROM_JSON_METHOD(JsonGlyph)
{
const auto jLetter = nlohmann_json_j.at("letter");
if (jLetter.type() == BasicJsonType::value_t::string)
nlohmann_json_t.letter = font::StringToLetter(jLetter.template get<std::string>());
else
nlohmann_json_t.letter = jLetter.template get<unsigned>();
NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(EXTEND_JSON_FROM, x0, y0, dx, pixelWidth, pixelHeight, s0, t0, s1, t1))
}
class JsonFont
{
public:
unsigned pixelHeight;
std::string material;
std::optional<std::string> glowMaterial;
std::vector<JsonGlyph> glyphs;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(JsonFont, pixelHeight, material, glowMaterial, glyphs);
} // namespace GAME
+254
View File
@@ -0,0 +1,254 @@
#options GAME (IW3, IW4, IW5, T4, T5)
#filename "Game/" + GAME + "/Font/FontLoader" + GAME + ".cpp"
#if GAME == "IW3"
#define GAME_LOWER "iw3"
#elif GAME == "IW4"
#define GAME_LOWER "iw4"
#elif GAME == "IW5"
#define GAME_LOWER "iw5"
#elif GAME == "T4"
#define GAME_LOWER "t4"
#elif GAME == "T5"
#define GAME_LOWER "t5"
#endif
// This file was templated.
// See FontLoader.cpp.template.
// Do not modify, changes will be lost.
#set LOADER_HEADER "\"FontLoader" + GAME + ".h\""
#include LOADER_HEADER
#include "Font/FontCommon.h"
#set JSON_HEADER "\"Game/" + GAME + "/Font/JsonFont" + GAME + ".h\""
#include JSON_HEADER
#include "Utils/Logging/Log.h"
#include <format>
#include <limits>
#include <nlohmann/json.hpp>
using namespace nlohmann;
using namespace GAME;
namespace
{
void PrintError(const Font_s& font, const std::string& message)
{
con::error("Cannot load font \"{}\": {}", font.fontName, message);
}
template<typename FieldType, typename ValueType>
bool AssignIntegerField(FieldType& field, const ValueType value, const Font_s& font, const char* fieldName)
{
const auto minValue = static_cast<ValueType>(std::numeric_limits<FieldType>::min());
const auto maxValue = static_cast<ValueType>(std::numeric_limits<FieldType>::max());
if (value < minValue || value > maxValue)
{
PrintError(font, std::format("{} value {} is outside allowed range {}..{}", fieldName, value, minValue, maxValue));
return false;
}
field = static_cast<FieldType>(value);
return true;
}
bool CreateMaterialDependency(const std::string& materialName,
Material*& material,
AssetRegistration<AssetFont>& registration,
AssetCreationContext& context,
const Font_s& font,
const char* fieldName)
{
auto* materialDependency = context.LoadDependency<AssetMaterial>(materialName);
if (!materialDependency)
{
PrintError(font, std::format("Could not find {} material \"{}\"", fieldName, materialName));
return false;
}
registration.AddDependency(materialDependency);
material = materialDependency->Asset();
return true;
}
bool CreateGlyphFromJson(const JsonGlyph& jGlyph, Glyph& glyph, const Font_s& font)
{
if (!AssignIntegerField(glyph.letter, jGlyph.letter, font, "glyph.letter"))
return false;
if (!AssignIntegerField(glyph.x0, jGlyph.x0, font, "glyph.x0"))
return false;
if (!AssignIntegerField(glyph.y0, jGlyph.y0, font, "glyph.y0"))
return false;
if (!AssignIntegerField(glyph.dx, jGlyph.dx, font, "glyph.dx"))
return false;
if (!AssignIntegerField(glyph.pixelWidth, jGlyph.pixelWidth, font, "glyph.pixelWidth"))
return false;
if (!AssignIntegerField(glyph.pixelHeight, jGlyph.pixelHeight, font, "glyph.pixelHeight"))
return false;
glyph.s0 = jGlyph.s0;
glyph.t0 = jGlyph.t0;
glyph.s1 = jGlyph.s1;
glyph.t1 = jGlyph.t1;
return true;
}
constexpr auto REQUIRED_GLYPH_START = 0x20;
constexpr auto REQUIRED_GLYPH_END_INCLUSIVE = 0x7F;
constexpr unsigned REQUIRED_GLYPH_COUNT = REQUIRED_GLYPH_END_INCLUSIVE + 1 - REQUIRED_GLYPH_START;
constexpr bool IsRequiredGlyph(const unsigned letter)
{
return letter >= REQUIRED_GLYPH_START && letter <= REQUIRED_GLYPH_END_INCLUSIVE;
}
void SortGlyphs(const Font_s& font)
{
std::sort(&font.glyphs[0], &font.glyphs[font.glyphCount], [](const Glyph& a, const Glyph& b)
{
const auto aRequired = IsRequiredGlyph(a.letter);
const auto bRequired = IsRequiredGlyph(b.letter);
if (aRequired != bRequired)
return aRequired;
return a.letter < b.letter;
});
}
bool EnsureFontContainsAllRequiredGlyphs(const Font_s& font)
{
if (static_cast<unsigned>(font.glyphCount) < REQUIRED_GLYPH_COUNT)
{
con::error("Font {} must contain all {} required letters", font.fontName, REQUIRED_GLYPH_COUNT);
return false;
}
for (unsigned i = 0; i < REQUIRED_GLYPH_COUNT; i++)
{
const unsigned requiredGlyphLetter = REQUIRED_GLYPH_START + i;
if (font.glyphs[i].letter != requiredGlyphLetter)
{
con::error("Font {} is missing required letter {} ('{}')", font.fontName, requiredGlyphLetter, static_cast<char>(requiredGlyphLetter));
return false;
}
}
return true;
}
class FontLoader final : public AssetCreator<AssetFont>
{
public:
FontLoader(MemoryManager& memory, ISearchPath& searchPath)
: m_memory(memory),
m_search_path(searchPath)
{
}
AssetCreationResult CreateAsset(const std::string& assetName, AssetCreationContext& context) override
{
const auto file = m_search_path.Open(font::GetJsonFileNameForAssetName(assetName));
if (!file.IsOpen())
return AssetCreationResult::NoAction();
auto* font = m_memory.Alloc<Font_s>();
font->fontName = m_memory.Dup(assetName.c_str());
AssetRegistration<AssetFont> registration(assetName, font);
try
{
const auto jRoot = json::parse(*file.m_stream);
std::string type;
unsigned version;
jRoot.at("_type").get_to(type);
jRoot.at("_version").get_to(version);
if (type != "font" || version != 1u)
{
con::error(R"(Tried to load font "{}" but did not find expected type font of version 1)", assetName);
return AssetCreationResult::Failure();
}
std::string game;
jRoot.at("_game").get_to(game);
if (game != GAME_LOWER)
{
con::error(R"(Tried to load font "{}" but "_game" did not have expected value {})", assetName, GAME_LOWER);
return AssetCreationResult::Failure();
}
const auto jFont = jRoot.get<JsonFont>();
if (CreateFontFromJson(jFont, *font, registration, context))
return AssetCreationResult::Success(context.AddAsset(std::move(registration)));
}
catch (const json::exception& e)
{
con::error("Failed to parse json of font: {}", e.what());
}
return AssetCreationResult::Failure();
}
private:
bool CreateFontFromJson(const JsonFont& jFont, Font_s& font, AssetRegistration<AssetFont>& registration, AssetCreationContext& context) const
{
font.pixelHeight = static_cast<decltype(Font_s::pixelHeight)>(jFont.pixelHeight);
if (!CreateMaterialDependency(jFont.material, font.material, registration, context, font, "font"))
return false;
if (jFont.glowMaterial)
{
if (!CreateMaterialDependency(*jFont.glowMaterial, font.glowMaterial, registration, context, font, "glow"))
return false;
}
constexpr auto MAX_GLYPH_COUNT = std::numeric_limits<decltype(Font_s::glyphCount)>::max();
if (jFont.glyphs.size() > static_cast<size_t>(MAX_GLYPH_COUNT))
{
PrintError(font, std::format("glyph count {} exceeds maximum {}", jFont.glyphs.size(), MAX_GLYPH_COUNT));
return false;
}
font.glyphCount = static_cast<decltype(Font_s::glyphCount)>(jFont.glyphs.size());;
if (font.glyphCount <= 0)
{
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;
return true;
}
MemoryManager& m_memory;
ISearchPath& m_search_path;
};
} // namespace
namespace font
{
#set CREATE_LOADER_METHOD "CreateLoader" + GAME
std::unique_ptr<AssetCreator<AssetFont>> CREATE_LOADER_METHOD(MemoryManager& memory, ISearchPath& searchPath)
{
return std::make_unique<FontLoader>(memory, searchPath);
}
} // namespace font
+23
View File
@@ -0,0 +1,23 @@
#options GAME (IW3, IW4, IW5, T4, T5)
#filename "Game/" + GAME + "/Font/FontLoader" + GAME + ".h"
// This file was templated.
// See FontLoader.h.template.
// Do not modify, changes will be lost.
#pragma once
#include "Asset/IAssetCreator.h"
#set GAME_HEADER "\"Game/" + GAME + "/" + GAME + ".h\""
#include GAME_HEADER
#include "SearchPath/ISearchPath.h"
#include "Utils/MemoryManager.h"
#include <memory>
namespace font
{
#set CREATE_LOADER_METHOD "CreateLoader" + GAME
std::unique_ptr<AssetCreator<GAME::AssetFont>> CREATE_LOADER_METHOD(MemoryManager& memory, ISearchPath& searchPath);
} // namespace font
+2 -1
View File
@@ -2,6 +2,7 @@
#include "Asset/GlobalAssetPoolsLoader.h"
#include "Game/IW3/AssetMarkerIW3.h"
#include "Game/IW3/Font/FontLoaderIW3.h"
#include "Game/IW3/GameIW3.h"
#include "Game/IW3/IW3.h"
#include "Game/IW3/Image/ImageLoaderEmbeddedIW3.h"
@@ -119,7 +120,7 @@ namespace
// collection.AddAssetCreator(std::make_unique<AssetLoaderMapEnts>(memory));
// collection.AddAssetCreator(std::make_unique<AssetLoaderGfxWorld>(memory));
collection.AddAssetCreator(light_def::CreateLoaderIW3(memory, searchPath));
// collection.AddAssetCreator(std::make_unique<AssetLoaderFont>(memory));
collection.AddAssetCreator(font::CreateLoaderIW3(memory, searchPath));
// collection.AddAssetCreator(std::make_unique<AssetLoaderMenuList>(memory));
// collection.AddAssetCreator(std::make_unique<AssetLoaderMenu>(memory));
collection.AddAssetCreator(localize::CreateLoaderIW3(memory, searchPath, zone));
+2 -1
View File
@@ -2,6 +2,7 @@
#include "Asset/GlobalAssetPoolsLoader.h"
#include "Game/IW4/AssetMarkerIW4.h"
#include "Game/IW4/Font/FontLoaderIW4.h"
#include "Game/IW4/GameIW4.h"
#include "Game/IW4/IW4.h"
#include "Game/IW4/Image/ImageLoaderEmbeddedIW4.h"
@@ -146,7 +147,7 @@ namespace
// collection.AddAssetCreator(std::make_unique<AssetLoaderFxWorld>(memory));
// collection.AddAssetCreator(std::make_unique<AssetLoaderGfxWorld>(memory));
collection.AddAssetCreator(light_def::CreateLoaderIW4(memory, searchPath));
// collection.AddAssetCreator(std::make_unique<AssetLoaderFont>(memory));
collection.AddAssetCreator(font::CreateLoaderIW4(memory, searchPath));
collection.AddAssetCreator(menu::CreateMenuListLoaderIW4(memory, searchPath));
// collection.AddAssetCreator(std::make_unique<AssetLoaderMenu>(memory));
collection.AddAssetCreator(localize::CreateLoaderIW4(memory, searchPath, zone));
+2 -1
View File
@@ -2,6 +2,7 @@
#include "Asset/GlobalAssetPoolsLoader.h"
#include "Game/IW5/AssetMarkerIW5.h"
#include "Game/IW5/Font/FontLoaderIW5.h"
#include "Game/IW5/GameIW5.h"
#include "Game/IW5/IW5.h"
#include "Game/IW5/Image/ImageLoaderEmbeddedIW5.h"
@@ -154,7 +155,7 @@ namespace
// collection.AddAssetCreator(std::make_unique<AssetLoaderFxWorld>(memory));
// collection.AddAssetCreator(std::make_unique<AssetLoaderGfxWorld>(memory));
collection.AddAssetCreator(light_def::CreateLoaderIW5(memory, searchPath));
// collection.AddAssetCreator(std::make_unique<AssetLoaderFont>(memory));
collection.AddAssetCreator(font::CreateLoaderIW5(memory, searchPath));
collection.AddAssetCreator(menu::CreateMenuListLoaderIW5(memory, searchPath));
// collection.AddAssetCreator(std::make_unique<AssetLoaderMenu>(memory));
collection.AddAssetCreator(localize::CreateLoaderIW5(memory, searchPath, zone));
+2
View File
@@ -2,6 +2,7 @@
#include "Asset/GlobalAssetPoolsLoader.h"
#include "Game/T4/AssetMarkerT4.h"
#include "Game/T4/Font/FontLoaderT4.h"
#include "Game/T4/T4.h"
#include "Game/T4/Weapon/AccuracyGraphLoaderT4.h"
#include "Game/T4/XAnim/XAnimLoaderT4.h"
@@ -93,6 +94,7 @@ namespace
auto& memory = zone.Memory();
collection.AddAssetCreator(xanim::CreateLoaderT4(memory, searchPath, zone));
collection.AddAssetCreator(font::CreateLoaderT4(memory, searchPath));
collection.AddAssetCreator(localize::CreateLoaderT4(memory, searchPath, zone));
collection.AddAssetCreator(map_ents::CreateLoaderT4(memory, searchPath));
collection.AddAssetCreator(raw_file::CreateLoaderT4(memory, searchPath));
+2 -1
View File
@@ -2,6 +2,7 @@
#include "Asset/GlobalAssetPoolsLoader.h"
#include "Game/T5/AssetMarkerT5.h"
#include "Game/T5/Font/FontLoaderT5.h"
#include "Game/T5/GameT5.h"
#include "Game/T5/Image/ImageLoaderEmbeddedT5.h"
#include "Game/T5/Image/ImageLoaderExternalT5.h"
@@ -132,7 +133,7 @@ namespace
// collection.AddAssetCreator(std::make_unique<AssetLoaderMapEnts>(memory));
// collection.AddAssetCreator(std::make_unique<AssetLoaderGfxWorld>(memory));
collection.AddAssetCreator(light_def::CreateLoaderT5(memory, searchPath));
// collection.AddAssetCreator(std::make_unique<AssetLoaderFont>(memory));
collection.AddAssetCreator(font::CreateLoaderT5(memory, searchPath));
// collection.AddAssetCreator(std::make_unique<AssetLoaderMenuList>(memory));
// collection.AddAssetCreator(std::make_unique<AssetLoaderMenu>(memory));
collection.AddAssetCreator(localize::CreateLoaderT5(memory, searchPath, zone));
+119
View File
@@ -0,0 +1,119 @@
#options GAME (IW3, IW4, IW5, T4, T5)
#filename "Game/" + GAME + "/Font/FontDumper" + GAME + ".cpp"
#if GAME == "IW3"
#define GAME_LOWER "iw3"
#elif GAME == "IW4"
#define GAME_LOWER "iw4"
#elif GAME == "IW5"
#define GAME_LOWER "iw5"
#elif GAME == "T4"
#define GAME_LOWER "t4"
#elif GAME == "T5"
#define GAME_LOWER "t5"
#endif
// This file was templated.
// See FontDumper.cpp.template.
// Do not modify, changes will be lost.
#set DUMPER_HEADER "\"FontDumper" + GAME + ".h\""
#include DUMPER_HEADER
#include "Font/FontCommon.h"
#set JSON_HEADER "\"Game/" + GAME + "/Font/JsonFont" + GAME + ".h\""
#include JSON_HEADER
#include <iomanip>
#include <nlohmann/json.hpp>
using namespace nlohmann;
using namespace GAME;
#set CLASS_NAME "JsonDumper" + GAME
namespace
{
const char* AssetName(const char* input)
{
if (input && input[0] == ',')
return &input[1];
return input;
}
void CreateJsonGlyph(JsonGlyph& jGlyph, const Glyph& glyph)
{
jGlyph.letter = glyph.letter;
jGlyph.x0 = glyph.x0;
jGlyph.y0 = glyph.y0;
jGlyph.dx = glyph.dx;
jGlyph.pixelWidth = glyph.pixelWidth;
jGlyph.pixelHeight = glyph.pixelHeight;
jGlyph.s0 = glyph.s0;
jGlyph.t0 = glyph.t0;
jGlyph.s1 = glyph.s1;
jGlyph.t1 = glyph.t1;
}
void CreateJsonFont(JsonFont& jFont, const Font_s& font)
{
jFont.pixelHeight = font.pixelHeight;
if (font.material && font.material->info.name)
jFont.material = AssetName(font.material->info.name);
if (font.glowMaterial && font.glowMaterial->info.name)
jFont.glowMaterial = AssetName(font.glowMaterial->info.name);
if (font.glyphs == nullptr || font.glyphCount <= 0)
return;
jFont.glyphs.resize(font.glyphCount);
for (auto i = 0; i < font.glyphCount; i++)
CreateJsonGlyph(jFont.glyphs[i], font.glyphs[i]);
}
class Dumper
{
public:
explicit Dumper(std::ostream& stream)
: m_stream(stream)
{
}
void Dump(const Font_s& font) const
{
JsonFont jFont;
CreateJsonFont(jFont, font);
ordered_json jRoot;
jRoot["$schema"] = "http://openassettools.dev/schema/font.v1.json";
jRoot["_type"] = "font";
jRoot["_version"] = 1;
jRoot["_game"] = GAME_LOWER;
jRoot.merge_patch(jFont);
m_stream << std::setw(4) << jRoot << "\n";
}
private:
std::ostream& m_stream;
};
} // namespace
namespace font
{
void CLASS_NAME::DumpAsset(AssetDumpingContext& context, const XAssetInfo<AssetFont::Type>& asset)
{
const auto assetFile = context.OpenAssetFile(GetJsonFileNameForAssetName(asset.m_name));
if (!assetFile)
return;
Dumper dumper(*assetFile);
dumper.Dump(*asset.Asset());
}
} // namespace font
+25
View File
@@ -0,0 +1,25 @@
#options GAME (IW3, IW4, IW5, T4, T5)
#filename "Game/" + GAME + "/Font/FontDumper" + GAME + ".h"
// This file was templated.
// See FontDumper.h.template.
// Do not modify, changes will be lost.
#pragma once
#include "Dumping/AbstractAssetDumper.h"
#include "Dumping/AssetDumpingContext.h"
#set GAME_HEADER "\"Game/" + GAME + "/" + GAME + ".h\""
#include GAME_HEADER
#set CLASS_NAME "JsonDumper" + GAME
namespace font
{
class CLASS_NAME final : public AbstractAssetDumper<GAME::AssetFont>
{
protected:
void DumpAsset(AssetDumpingContext& context, const XAssetInfo<GAME::AssetFont::Type>& asset) override;
};
} // namespace font
+2 -1
View File
@@ -1,5 +1,6 @@
#include "ObjWriterIW3.h"
#include "Game/IW3/Font/FontDumperIW3.h"
#include "Game/IW3/Image/ImageDumperIW3.h"
#include "Game/IW3/Material/MaterialJsonDumperIW3.h"
#include "Game/IW3/Techset/TechsetDumperIW3.h"
@@ -41,7 +42,7 @@ void ObjWriter::RegisterAssetDumpers(AssetDumpingContext& context)
RegisterAssetDumper(std::make_unique<map_ents::DumperIW3>());
// REGISTER_DUMPER(AssetDumperGfxWorld)
RegisterAssetDumper(std::make_unique<light_def::DumperIW3>());
// REGISTER_DUMPER(AssetDumperFont_s)
RegisterAssetDumper(std::make_unique<font::JsonDumperIW3>());
// REGISTER_DUMPER(AssetDumperMenuList)
// REGISTER_DUMPER(AssetDumpermenuDef_t)
RegisterAssetDumper(std::make_unique<localize::DumperIW3>());
+2 -1
View File
@@ -1,5 +1,6 @@
#include "ObjWriterIW4.h"
#include "Game/IW4/Font/FontDumperIW4.h"
#include "Game/IW4/Image/ImageDumperIW4.h"
#include "Game/IW4/Material/MaterialJsonDumperIW4.h"
#include "Game/IW4/Techset/PixelShaderDumperIW4.h"
@@ -58,7 +59,7 @@ void ObjWriter::RegisterAssetDumpers(AssetDumpingContext& context)
// REGISTER_DUMPER(AssetDumperFxWorld)
// REGISTER_DUMPER(AssetDumperGfxWorld)
RegisterAssetDumper(std::make_unique<light_def::DumperIW4>());
// REGISTER_DUMPER(AssetDumperFont_s)
RegisterAssetDumper(std::make_unique<font::JsonDumperIW4>());
RegisterAssetDumper(std::make_unique<menu::MenuListDumperIW4>());
RegisterAssetDumper(std::make_unique<menu::MenuDumperIW4>());
RegisterAssetDumper(std::make_unique<localize::DumperIW4>());
+2 -1
View File
@@ -1,6 +1,7 @@
#include "ObjWriterIW5.h"
#include "Game/IW4/XAnim/XAnimDumperIW4.h"
#include "Game/IW5/Font/FontDumperIW5.h"
#include "Game/IW5/Image/ImageDumperIW5.h"
#include "Game/IW5/Material/MaterialJsonDumperIW5.h"
#include "Game/IW5/Techset/PixelShaderDumperIW5.h"
@@ -53,7 +54,7 @@ void ObjWriter::RegisterAssetDumpers(AssetDumpingContext& context)
// REGISTER_DUMPER(AssetDumperFxWorld)
// REGISTER_DUMPER(AssetDumperGfxWorld)
RegisterAssetDumper(std::make_unique<light_def::DumperIW5>());
// REGISTER_DUMPER(AssetDumperFont_s)
RegisterAssetDumper(std::make_unique<font::JsonDumperIW5>());
RegisterAssetDumper(std::make_unique<menu::MenuListDumperIW5>());
RegisterAssetDumper(std::make_unique<menu::MenuDumperIW5>());
RegisterAssetDumper(std::make_unique<localize::DumperIW5>());
+2
View File
@@ -1,5 +1,6 @@
#include "ObjWriterT4.h"
#include "Game/T4/Font/FontDumperT4.h"
#include "Game/T4/Image/ImageDumperT4.h"
#include "Game/T4/XAnim/XAnimDumperT4.h"
#include "Game/T4/XModel/XModelDumperT4.h"
@@ -19,6 +20,7 @@ void ObjWriter::RegisterAssetDumpers(AssetDumpingContext& context)
RegisterAssetDumper(std::make_unique<image::DumperT4>());
RegisterAssetDumper(std::make_unique<sound::LoadedSoundDumperT4>());
RegisterAssetDumper(std::make_unique<map_ents::DumperT4>());
RegisterAssetDumper(std::make_unique<font::JsonDumperT4>());
RegisterAssetDumper(std::make_unique<localize::DumperT4>());
RegisterAssetDumper(std::make_unique<weapon::DumperT4>());
RegisterAssetDumper(std::make_unique<raw_file::DumperT4>());
+2 -1
View File
@@ -1,5 +1,6 @@
#include "ObjWriterT5.h"
#include "Game/T5/Font/FontDumperT5.h"
#include "Game/T5/Image/ImageDumperT5.h"
#include "Game/T5/Material/MaterialJsonDumperT5.h"
#include "Game/T5/Techset/TechsetDumperT5.h"
@@ -39,7 +40,7 @@ void ObjWriter::RegisterAssetDumpers(AssetDumpingContext& context)
// REGISTER_DUMPER(AssetDumperMapEnts, m_map_ents)
// REGISTER_DUMPER(AssetDumperGfxWorld, m_gfx_world)
RegisterAssetDumper(std::make_unique<light_def::DumperT5>());
// REGISTER_DUMPER(AssetDumperFont, m_font)
RegisterAssetDumper(std::make_unique<font::JsonDumperT5>());
// REGISTER_DUMPER(AssetDumperMenuList, m_menu_list)
// REGISTER_DUMPER(AssetDumperMenuDef, m_menu_def)
RegisterAssetDumper(std::make_unique<localize::DumperT5>());
+11 -7
View File
@@ -53,17 +53,21 @@ namespace nlohmann
}
} // namespace nlohmann
#define NLOHMANN_TO_JSON_METHOD(Type) \
template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \
inline void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t)
#define NLOHMANN_FROM_JSON_METHOD(Type) \
template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \
inline void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t)
#define EXTEND_JSON_TO(v1) extended_to_json(#v1, nlohmann_json_j, nlohmann_json_t.v1);
#define EXTEND_JSON_FROM(v1) extended_from_json(#v1, nlohmann_json_j, nlohmann_json_t.v1);
#define NLOHMANN_DEFINE_TYPE_EXTENSION(Type, ...) \
template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \
inline void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) \
{ \
NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(EXTEND_JSON_TO, __VA_ARGS__)) \
} \
template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \
inline void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) \
NLOHMANN_TO_JSON_METHOD(Type){NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(EXTEND_JSON_TO, __VA_ARGS__))} \
\
NLOHMANN_FROM_JSON_METHOD(Type) \
{ \
NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(EXTEND_JSON_FROM, __VA_ARGS__)) \
}