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
+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