diff --git a/docs/SupportedAssetTypes.md b/docs/SupportedAssetTypes.md index f57ef1b12..6f3731d8a 100644 --- a/docs/SupportedAssetTypes.md +++ b/docs/SupportedAssetTypes.md @@ -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 | ✅ | ✅ | | diff --git a/src/Common/Game/IW3/IW3_Assets.h b/src/Common/Game/IW3/IW3_Assets.h index f0f21f0b5..011804c3c 100644 --- a/src/Common/Game/IW3/IW3_Assets.h +++ b/src/Common/Game/IW3/IW3_Assets.h @@ -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; diff --git a/src/Common/Game/IW4/IW4_Assets.h b/src/Common/Game/IW4/IW4_Assets.h index b7f5b4091..c2092686c 100644 --- a/src/Common/Game/IW4/IW4_Assets.h +++ b/src/Common/Game/IW4/IW4_Assets.h @@ -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; diff --git a/src/Common/Game/T4/T4_Assets.h b/src/Common/Game/T4/T4_Assets.h index 21e75c91a..0418adb14 100644 --- a/src/Common/Game/T4/T4_Assets.h +++ b/src/Common/Game/T4/T4_Assets.h @@ -2711,9 +2711,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; diff --git a/src/Common/Game/T5/T5_Assets.h b/src/Common/Game/T5/T5_Assets.h index 3cc91b732..4ced8c9e3 100644 --- a/src/Common/Game/T5/T5_Assets.h +++ b/src/Common/Game/T5/T5_Assets.h @@ -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; diff --git a/src/Common/Game/T6/T6_Assets.h b/src/Common/Game/T6/T6_Assets.h index 5fb616686..85672c3ab 100644 --- a/src/Common/Game/T6/T6_Assets.h +++ b/src/Common/Game/T6/T6_Assets.h @@ -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; diff --git a/src/ObjCommon/Font/FontCommon.cpp b/src/ObjCommon/Font/FontCommon.cpp new file mode 100644 index 000000000..e7aab9adb --- /dev/null +++ b/src/ObjCommon/Font/FontCommon.cpp @@ -0,0 +1,107 @@ +#include "FontCommon.h" + +#include + +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(letter)); + + if (letter < 0x800) + { + // UTF-8 => 2 bytes + std::string s(2, '\0'); + s[0] = static_cast(0xC0 | (letter >> 6u)); + s[1] = static_cast(0x80 | (letter & 0x3F)); + + return s; + } + + // UTF-8 => 3 bytes + std::string s(3, '\0'); + s[0] = static_cast(0xE0 | (letter >> 12u)); + s[1] = static_cast(0x80 | ((letter >> 6u) & 0x3F)); + s[2] = static_cast(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(str[0]); + + // UTF-8 => 1 byte + if ((firstByte & 0x80) == 0) + return firstByte; + + if (strSize < 2) + return 0; + const auto secondByte = static_cast(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(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 diff --git a/src/ObjCommon/Font/FontCommon.h b/src/ObjCommon/Font/FontCommon.h new file mode 100644 index 000000000..99767a79d --- /dev/null +++ b/src/ObjCommon/Font/FontCommon.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +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 diff --git a/src/ObjCommon/Font/JsonFont.h.template b/src/ObjCommon/Font/JsonFont.h.template new file mode 100644 index 000000000..cd5e4b25c --- /dev/null +++ b/src/ObjCommon/Font/JsonFont.h.template @@ -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 +#include +#include +#include + +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()); + else + nlohmann_json_t.letter = jLetter.template get(); + + 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 glowMaterial; + std::vector glyphs; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonFont, pixelHeight, material, glowMaterial, glyphs); +} // namespace GAME diff --git a/src/ObjLoading/Font/FontLoader.cpp.template b/src/ObjLoading/Font/FontLoader.cpp.template new file mode 100644 index 000000000..1a60cca32 --- /dev/null +++ b/src/ObjLoading/Font/FontLoader.cpp.template @@ -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 +#include +#include + +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 + bool AssignIntegerField(FieldType& field, const ValueType value, const Font_s& font, const char* fieldName) + { + const auto minValue = static_cast(std::numeric_limits::min()); + const auto maxValue = static_cast(std::numeric_limits::max()); + + if (value < minValue || value > maxValue) + { + PrintError(font, std::format("{} value {} is outside allowed range {}..{}", fieldName, value, minValue, maxValue)); + return false; + } + + field = static_cast(value); + + return true; + } + + bool CreateMaterialDependency(const std::string& materialName, + Material*& material, + AssetRegistration& registration, + AssetCreationContext& context, + const Font_s& font, + const char* fieldName) + { + auto* materialDependency = context.LoadDependency(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(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(requiredGlyphLetter)); + return false; + } + } + + return true; + } + + class FontLoader final : public AssetCreator + { + 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->fontName = m_memory.Dup(assetName.c_str()); + AssetRegistration 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(); + 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& registration, AssetCreationContext& context) const + { + font.pixelHeight = static_cast(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::max(); + if (jFont.glyphs.size() > static_cast(MAX_GLYPH_COUNT)) + { + PrintError(font, std::format("glyph count {} exceeds maximum {}", jFont.glyphs.size(), MAX_GLYPH_COUNT)); + return false; + } + + font.glyphCount = static_cast(jFont.glyphs.size());; + if (font.glyphCount <= 0) + { + 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; + + return true; + } + + MemoryManager& m_memory; + ISearchPath& m_search_path; + }; +} // namespace + +namespace font +{ +#set CREATE_LOADER_METHOD "CreateLoader" + GAME + std::unique_ptr> CREATE_LOADER_METHOD(MemoryManager& memory, ISearchPath& searchPath) + { + return std::make_unique(memory, searchPath); + } +} // namespace font diff --git a/src/ObjLoading/Font/FontLoader.h.template b/src/ObjLoading/Font/FontLoader.h.template new file mode 100644 index 000000000..f7e92c608 --- /dev/null +++ b/src/ObjLoading/Font/FontLoader.h.template @@ -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 + +namespace font +{ +#set CREATE_LOADER_METHOD "CreateLoader" + GAME + std::unique_ptr> CREATE_LOADER_METHOD(MemoryManager& memory, ISearchPath& searchPath); +} // namespace font diff --git a/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp b/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp index 2af4cc84e..70681bb5e 100644 --- a/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp +++ b/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp @@ -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" @@ -116,7 +117,7 @@ namespace // collection.AddAssetCreator(std::make_unique(memory)); // collection.AddAssetCreator(std::make_unique(memory)); collection.AddAssetCreator(light_def::CreateLoaderIW3(memory, searchPath)); - // collection.AddAssetCreator(std::make_unique(memory)); + collection.AddAssetCreator(font::CreateLoaderIW3(memory, searchPath)); // collection.AddAssetCreator(std::make_unique(memory)); // collection.AddAssetCreator(std::make_unique(memory)); collection.AddAssetCreator(localize::CreateLoaderIW3(memory, searchPath, zone)); diff --git a/src/ObjLoading/Game/IW4/ObjLoaderIW4.cpp b/src/ObjLoading/Game/IW4/ObjLoaderIW4.cpp index cb92ce6b9..e38a06fec 100644 --- a/src/ObjLoading/Game/IW4/ObjLoaderIW4.cpp +++ b/src/ObjLoading/Game/IW4/ObjLoaderIW4.cpp @@ -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(memory)); // collection.AddAssetCreator(std::make_unique(memory)); collection.AddAssetCreator(light_def::CreateLoaderIW4(memory, searchPath)); - // collection.AddAssetCreator(std::make_unique(memory)); + collection.AddAssetCreator(font::CreateLoaderIW4(memory, searchPath)); collection.AddAssetCreator(menu::CreateMenuListLoaderIW4(memory, searchPath)); // collection.AddAssetCreator(std::make_unique(memory)); collection.AddAssetCreator(localize::CreateLoaderIW4(memory, searchPath, zone)); diff --git a/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp b/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp index f9d406a1c..330e7a986 100644 --- a/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp +++ b/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp @@ -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(memory)); // collection.AddAssetCreator(std::make_unique(memory)); collection.AddAssetCreator(light_def::CreateLoaderIW5(memory, searchPath)); - // collection.AddAssetCreator(std::make_unique(memory)); + collection.AddAssetCreator(font::CreateLoaderIW5(memory, searchPath)); collection.AddAssetCreator(menu::CreateMenuListLoaderIW5(memory, searchPath)); // collection.AddAssetCreator(std::make_unique(memory)); collection.AddAssetCreator(localize::CreateLoaderIW5(memory, searchPath, zone)); diff --git a/src/ObjLoading/Game/T4/ObjLoaderT4.cpp b/src/ObjLoading/Game/T4/ObjLoaderT4.cpp index 080fdf684..6f6d016be 100644 --- a/src/ObjLoading/Game/T4/ObjLoaderT4.cpp +++ b/src/ObjLoading/Game/T4/ObjLoaderT4.cpp @@ -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/XAnim/XAnimLoaderT4.h" #include "Localize/AssetLoaderLocalizeT4.h" @@ -87,6 +88,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)); } } // namespace diff --git a/src/ObjLoading/Game/T5/ObjLoaderT5.cpp b/src/ObjLoading/Game/T5/ObjLoaderT5.cpp index 6508efe5c..2b4d66b35 100644 --- a/src/ObjLoading/Game/T5/ObjLoaderT5.cpp +++ b/src/ObjLoading/Game/T5/ObjLoaderT5.cpp @@ -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(memory)); // collection.AddAssetCreator(std::make_unique(memory)); collection.AddAssetCreator(light_def::CreateLoaderT5(memory, searchPath)); - // collection.AddAssetCreator(std::make_unique(memory)); + collection.AddAssetCreator(font::CreateLoaderT5(memory, searchPath)); // collection.AddAssetCreator(std::make_unique(memory)); // collection.AddAssetCreator(std::make_unique(memory)); collection.AddAssetCreator(localize::CreateLoaderT5(memory, searchPath, zone)); diff --git a/src/ObjWriting/Font/FontDumper.cpp.template b/src/ObjWriting/Font/FontDumper.cpp.template new file mode 100644 index 000000000..74963d887 --- /dev/null +++ b/src/ObjWriting/Font/FontDumper.cpp.template @@ -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 +#include + +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& asset) + { + const auto assetFile = context.OpenAssetFile(GetJsonFileNameForAssetName(asset.m_name)); + + if (!assetFile) + return; + + Dumper dumper(*assetFile); + dumper.Dump(*asset.Asset()); + } +} // namespace font diff --git a/src/ObjWriting/Font/FontDumper.h.template b/src/ObjWriting/Font/FontDumper.h.template new file mode 100644 index 000000000..0c5ab02a1 --- /dev/null +++ b/src/ObjWriting/Font/FontDumper.h.template @@ -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 + { + protected: + void DumpAsset(AssetDumpingContext& context, const XAssetInfo& asset) override; + }; +} // namespace font diff --git a/src/ObjWriting/Game/IW3/ObjWriterIW3.cpp b/src/ObjWriting/Game/IW3/ObjWriterIW3.cpp index ee2bc480c..3e2fc691b 100644 --- a/src/ObjWriting/Game/IW3/ObjWriterIW3.cpp +++ b/src/ObjWriting/Game/IW3/ObjWriterIW3.cpp @@ -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" @@ -40,7 +41,7 @@ void ObjWriter::RegisterAssetDumpers(AssetDumpingContext& context) RegisterAssetDumper(std::make_unique()); // REGISTER_DUMPER(AssetDumperGfxWorld) RegisterAssetDumper(std::make_unique()); - // REGISTER_DUMPER(AssetDumperFont_s) + RegisterAssetDumper(std::make_unique()); // REGISTER_DUMPER(AssetDumperMenuList) // REGISTER_DUMPER(AssetDumpermenuDef_t) RegisterAssetDumper(std::make_unique()); diff --git a/src/ObjWriting/Game/IW4/ObjWriterIW4.cpp b/src/ObjWriting/Game/IW4/ObjWriterIW4.cpp index b31fab57d..b38c1d487 100644 --- a/src/ObjWriting/Game/IW4/ObjWriterIW4.cpp +++ b/src/ObjWriting/Game/IW4/ObjWriterIW4.cpp @@ -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()); - // REGISTER_DUMPER(AssetDumperFont_s) + RegisterAssetDumper(std::make_unique()); RegisterAssetDumper(std::make_unique()); RegisterAssetDumper(std::make_unique()); RegisterAssetDumper(std::make_unique()); diff --git a/src/ObjWriting/Game/IW5/ObjWriterIW5.cpp b/src/ObjWriting/Game/IW5/ObjWriterIW5.cpp index 9027f820c..c58d7afc8 100644 --- a/src/ObjWriting/Game/IW5/ObjWriterIW5.cpp +++ b/src/ObjWriting/Game/IW5/ObjWriterIW5.cpp @@ -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()); - // REGISTER_DUMPER(AssetDumperFont_s) + RegisterAssetDumper(std::make_unique()); RegisterAssetDumper(std::make_unique()); RegisterAssetDumper(std::make_unique()); RegisterAssetDumper(std::make_unique()); diff --git a/src/ObjWriting/Game/T4/ObjWriterT4.cpp b/src/ObjWriting/Game/T4/ObjWriterT4.cpp index 95b236591..e5ced9304 100644 --- a/src/ObjWriting/Game/T4/ObjWriterT4.cpp +++ b/src/ObjWriting/Game/T4/ObjWriterT4.cpp @@ -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" @@ -16,6 +17,7 @@ void ObjWriter::RegisterAssetDumpers(AssetDumpingContext& context) RegisterAssetDumper(std::make_unique()); RegisterAssetDumper(std::make_unique()); RegisterAssetDumper(std::make_unique()); + RegisterAssetDumper(std::make_unique()); RegisterAssetDumper(std::make_unique()); RegisterAssetDumper(std::make_unique()); RegisterAssetDumper(std::make_unique()); diff --git a/src/ObjWriting/Game/T5/ObjWriterT5.cpp b/src/ObjWriting/Game/T5/ObjWriterT5.cpp index 52fab971a..0623274fd 100644 --- a/src/ObjWriting/Game/T5/ObjWriterT5.cpp +++ b/src/ObjWriting/Game/T5/ObjWriterT5.cpp @@ -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()); - // REGISTER_DUMPER(AssetDumperFont, m_font) + RegisterAssetDumper(std::make_unique()); // REGISTER_DUMPER(AssetDumperMenuList, m_menu_list) // REGISTER_DUMPER(AssetDumperMenuDef, m_menu_def) RegisterAssetDumper(std::make_unique()); diff --git a/src/Utils/Json/JsonExtension.h b/src/Utils/Json/JsonExtension.h index b7040d92b..5b9b4c295 100644 --- a/src/Utils/Json/JsonExtension.h +++ b/src/Utils/Json/JsonExtension.h @@ -53,17 +53,21 @@ namespace nlohmann } } // namespace nlohmann +#define NLOHMANN_TO_JSON_METHOD(Type) \ + template::value, int> = 0> \ + inline void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) + +#define NLOHMANN_FROM_JSON_METHOD(Type) \ + template::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::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::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__)) \ }