mirror of
https://github.com/Laupetin/OpenAssetTools.git
synced 2026-09-09 23:47:07 +00:00
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:
co-authored by
hindercanrun
Jan Laupetin
MrIkso
parent
fb4b00398c
commit
3fb8b2bb17
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user