From 11c3f1c5eb79bc780be1df351a96e63340210400 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 15 Feb 2026 20:27:34 +0300 Subject: [PATCH] add vector fonts support --- src/assets/AssetsLoader.cpp | 6 ++ src/assets/AssetsLoader.hpp | 9 ++- src/assets/assetload_funcs.cpp | 28 ++++----- src/coders/vector_fonts.cpp | 102 +++++++++++++++++++++++++++++-- src/coders/vector_fonts.hpp | 15 +++++ src/constants.hpp | 2 + src/engine/Engine.cpp | 6 ++ src/graphics/core/Font.cpp | 41 +++++++++++-- src/graphics/core/Font.hpp | 35 +++++++++-- src/graphics/core/FontMetics.hpp | 2 +- 10 files changed, 212 insertions(+), 34 deletions(-) diff --git a/src/assets/AssetsLoader.cpp b/src/assets/AssetsLoader.cpp index 4d943ae95..774fa1a00 100644 --- a/src/assets/AssetsLoader.cpp +++ b/src/assets/AssetsLoader.cpp @@ -187,6 +187,12 @@ void AssetsLoader::processPreload( config = std::make_shared(squashed); break; } + case AssetType::FONT: { + int size = DEFAULT_FONT_SIZE; + map.at("size").get(size); + config = std::make_unique(size); + break; + } default: break; } diff --git a/src/assets/AssetsLoader.hpp b/src/assets/AssetsLoader.hpp index 3d1704545..e237e01e9 100644 --- a/src/assets/AssetsLoader.hpp +++ b/src/assets/AssetsLoader.hpp @@ -33,8 +33,7 @@ struct LayoutCfg : AssetCfg { gui::GUI* gui; scriptenv env; - LayoutCfg(gui::GUI* gui, scriptenv env) : gui(gui), env(std::move(env)) { - } + LayoutCfg(gui::GUI* gui, scriptenv env) : gui(gui), env(std::move(env)) {} }; struct SoundCfg : AssetCfg { @@ -43,6 +42,12 @@ struct SoundCfg : AssetCfg { SoundCfg(bool keepPCM) : keepPCM(keepPCM) {} }; +struct FontCfg : AssetCfg { + int size; + + FontCfg(int size) : size(size) {} +}; + enum class AtlasType { ATLAS, SEPARATE }; diff --git a/src/assets/assetload_funcs.cpp b/src/assets/assetload_funcs.cpp index 4bd32d5d0..8a39d3934 100644 --- a/src/assets/assetload_funcs.cpp +++ b/src/assets/assetload_funcs.cpp @@ -13,6 +13,7 @@ #include "coders/obj.hpp" #include "coders/vcm.hpp" #include "coders/vec3.hpp" +#include "coders/vector_fonts.hpp" #include "constants.hpp" #include "debug/Logger.hpp" #include "engine/EnginePaths.hpp" @@ -199,8 +200,18 @@ assetload::postfunc assetload::font( const ResPaths& paths, const std::string& filename, const std::string& name, - const std::shared_ptr& + const std::shared_ptr& config ) { + auto cfg = std::dynamic_pointer_cast(config); + auto ext = fs::path(filename).extension().string(); + if (ext == ".ttf" || ext == ".otf") { + logger.info() << "loading vector font " << util::quote(filename); + return [=](auto assets) { + auto font = vector_fonts::load_font(paths.find(filename).string()); + assets->store(font->createInstance(cfg ? cfg->size : 16), name); + }; + } + auto pages = std::make_shared>>(); for (size_t i = 0; i <= 1024; i++) { std::string pagefile = filename + "_" + std::to_string(i) + ".png"; @@ -214,20 +225,7 @@ assetload::postfunc assetload::font( } } return [=](auto assets) { - int res = pages->at(0)->getHeight() / 16; - std::vector> textures; - for (auto& page : *pages) { - if (page == nullptr) { - textures.emplace_back(nullptr); - } else { - auto texture = Texture::from(page.get()); - texture->setMipMapping(false, true); - textures.emplace_back(std::move(texture)); - } - } - assets->store( - std::make_unique(std::move(textures), res, 4), name - ); + assets->store(Font::createBitmapFont(std::move(*pages)), name); }; } diff --git a/src/coders/vector_fonts.cpp b/src/coders/vector_fonts.cpp index a3773a4e5..ca64b6929 100644 --- a/src/coders/vector_fonts.cpp +++ b/src/coders/vector_fonts.cpp @@ -1,34 +1,124 @@ #include "vector_fonts.hpp" #include "io/io.hpp" +#include "debug/Logger.hpp" +#include "graphics/core/ImageData.hpp" +#include "graphics/core/Texture.hpp" +#include "graphics/core/Font.hpp" #include #include #include FT_FREETYPE_H +static debug::Logger logger("vector_fonts"); + static FT_Library library; +static const char* get_ft_error_message(FT_Error error) { + #undef FTERRORS_H_ + #define FT_ERRORDEF( e, v, s ) case v: return s; + #define FT_ERROR_START_LIST switch (error) { + #define FT_ERROR_END_LIST } + #include FT_ERRORS_H + return "unknown error"; +} + +namespace { + class FTFontFile : public vector_fonts::FontFile { + public: + FTFontFile(FT_Face face, util::Buffer buffer) : face(std::move(face)), buffer(std::move(buffer)) { + } + + ~FTFontFile() { + if (FT_Error error = FT_Done_Face(face)) { + logger.error() + << "error on FT_Done_Face: " << get_ft_error_message(error); + } + } + + std::unique_ptr createInstance(int size) override { + int pagesCount = 5; + + std::vector> pages; + std::vector glyphs; + + if (FT_Error error = FT_Set_Pixel_Sizes(face, 0, size)) { + throw std::runtime_error( + "FT_Set_Pixel_Sizes error: " + + std::string(get_ft_error_message(error)) + ); + } + + for (int pageid = 0; pageid < pagesCount; pageid++) { + int dstSize = size * 16; + ImageData canvas(ImageFormat::RGBA8888, dstSize, dstSize); + for (int c = 0; c < 256; c++) { + int srcWidth = size; + int srcHeight = size; + ImageData bitmapDst(ImageFormat::RGBA8888, srcWidth, srcHeight); + int codepoint = pageid << 8 | c; + + if (FT_Error error = FT_Load_Char(face, codepoint, FT_LOAD_RENDER)) { + logger.warning() << get_ft_error_message(error); + continue; + } + FT_Bitmap& bitmap = face->glyph->bitmap; + if (codepoint < 1000) + logger.info() << codepoint << ": " << bitmap.width << "x" << bitmap.rows << " " << size; + auto dstData = bitmapDst.getData(); + for (int row = 0; row < bitmap.rows; row++) { + for (int col = 0; col < bitmap.width; col++) { + uint8_t value = bitmap.buffer[row * bitmap.pitch + col]; + dstData[(row * srcWidth + col) * 4 + 0] = 255; + dstData[(row * srcWidth + col) * 4 + 1] = 255; + dstData[(row * srcWidth + col) * 4 + 2] = 255; + dstData[(row * srcWidth + col) * 4 + 3] = value; + } + } + canvas.blit(bitmapDst, (int)(c % 16) * size, (int)(c / 16) * size); + glyphs.push_back(Font::Glyph { + face->glyph->bitmap_top - size, + static_cast(face->glyph->advance.x >> 6) + }); + } + canvas.flipY(); + pages.push_back(Texture::from(&canvas)); + } + return std::make_unique(std::move(pages), std::move(glyphs), size, size / 2); + } + private: + FT_Face face; + util::Buffer buffer; + }; +} + void vector_fonts::initialize() { if (FT_Error error = FT_Init_FreeType(&library)) { throw std::runtime_error( "could not initialize freetype: " + - std::string(FT_Error_String(error)) + std::string(get_ft_error_message(error)) ); } } -static FT_Face load_ft_face(const io::path& path) { - auto bytes = io::read_bytes_buffer(path); +void vector_fonts::finalize() { + if (FT_Error error = FT_Done_FreeType(library)) { + logger.error() << "error on FT_Done_FreeType: " << get_ft_error_message(error); + } +} + +std::unique_ptr vector_fonts::load_font(const std::string& filename) { + auto bytes = io::read_bytes_buffer(io::path(filename)); FT_Face face; if (FT_Error error = FT_New_Memory_Face(library, bytes.data(), bytes.size(), 0, &face)) { throw std::runtime_error( - "could not load font '" + path.string() + - "': " + std::string(FT_Error_String(error)) + "could not load font '" + filename + + "': " + std::string(get_ft_error_message(error)) ); } - return face; + return std::make_unique(std::move(face), std::move(bytes)); } diff --git a/src/coders/vector_fonts.hpp b/src/coders/vector_fonts.hpp index 4e093d593..de73323c6 100644 --- a/src/coders/vector_fonts.hpp +++ b/src/coders/vector_fonts.hpp @@ -1,5 +1,20 @@ #pragma once +#include +#include + +class Font; + namespace vector_fonts { + class FontFile { + public: + virtual ~FontFile() {} + + virtual std::unique_ptr createInstance(int size) = 0; + }; + void initialize(); + void finalize(); + + std::unique_ptr load_font(const std::string& filename); } diff --git a/src/constants.hpp b/src/constants.hpp index 1ae27a545..72e397397 100644 --- a/src/constants.hpp +++ b/src/constants.hpp @@ -63,6 +63,8 @@ inline constexpr int TRANSLUCENT_BLOCKS_SORT_INTERVAL = 8; inline constexpr int ATLAS_EXTRUSION = 2; +inline constexpr int DEFAULT_FONT_SIZE = 16; + inline const std::string SHADERS_FOLDER = "shaders"; inline const std::string TEXTURES_FOLDER = "textures"; inline const std::string FONTS_FOLDER = "fonts"; diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index e793224a9..89a345ff6 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -9,6 +9,7 @@ #include "coders/commons.hpp" #include "coders/GLSLExtension.hpp" #include "coders/toml.hpp" +#include "coders/vector_fonts.hpp" #include "content/ContentControl.hpp" #include "core_defs.hpp" #include "debug/Logger.hpp" @@ -167,6 +168,8 @@ void Engine::initialize(CoreParameters coreParameters) { settings.audio ); + vector_fonts::initialize(); + if (settings.ui.language.get() == "auto") { settings.ui.language.set( langs::locale_by_envlocale(platform::detect_locale()) @@ -342,6 +345,9 @@ void Engine::close() { gui.reset(); logger.info() << "gui finished"; } + if (!isHeadless()) { + vector_fonts::finalize(); + } audio::close(); debuggingServer.reset(); network.reset(); diff --git a/src/graphics/core/Font.cpp b/src/graphics/core/Font.cpp index 73718b4fa..d9be091a7 100644 --- a/src/graphics/core/Font.cpp +++ b/src/graphics/core/Font.cpp @@ -11,8 +11,17 @@ inline constexpr uint GLYPH_SIZE = 16; inline constexpr uint MAX_CODEPAGES = 10000; // idk ho many codepages unicode has inline constexpr glm::vec4 SHADOW_TINT(0.0f, 0.0f, 0.0f, 1.0f); -Font::Font(std::vector> pages, int lineHeight, int yoffset) - : lineHeight(lineHeight), yoffset(yoffset), pages(std::move(pages)) { +Font::Font( + std::vector> pages, + std::vector glyphs, + int lineHeight, + int yoffset +) + : lineHeight(lineHeight), + glyphInterval(lineHeight / 2), + yoffset(yoffset), + pages(std::move(pages)), + glyphs(std::move(glyphs)) { } Font::~Font() = default; @@ -39,7 +48,7 @@ bool Font::isPrintableChar(uint codepoint) const { } int FontMetrics::calcWidth(std::wstring_view text, size_t offset, size_t length) const { - return std::min(text.length() - offset, length) * glyphInterval; + return std::min(text.length() - offset, length) * _glyphInterval; } int Font::calcWidth(std::wstring_view text, size_t length) const { @@ -71,7 +80,7 @@ static inline void draw_glyph( batch.sprite( pos.x + (offset.x + i / (right.x/glyphInterval/2.0f)) * right.x, - pos.y + offset.y * right.y, + pos.y + offset.y * up.y, right.x / glyphInterval, up.y, -0.15f * style.italic, @@ -152,11 +161,15 @@ static inline void draw_text( x++; continue; } + int yOffset = 0; + if (auto glyph = font.getGlyph(c)) { + yOffset = glyph->yOffset; + } uint charpage = c >> 8; if (charpage == page){ batch.texture(font.getPage(charpage)); draw_glyph( - batch, pos, glm::vec2(x, y), c, right, up, interval, style + batch, pos, glm::vec2(x, y - yOffset / static_cast(font.getLineHeight())), c, right, up, interval, style ); } else if (charpage > page && charpage < next){ @@ -243,3 +256,21 @@ void Font::draw( styleMapOffset ); } + +std::unique_ptr Font::createBitmapFont( + std::vector> pages +) { + int res = pages.at(0)->getHeight() / 16; + std::vector> textures; + std::vector glyphs(textures.size() * 256); + for (auto& page : pages) { + if (page == nullptr) { + textures.emplace_back(nullptr); + } else { + auto texture = Texture::from(page.get()); + texture->setMipMapping(false, true); + textures.emplace_back(std::move(texture)); + } + } + return std::make_unique(std::move(textures), std::move(glyphs), res, 4); +} diff --git a/src/graphics/core/Font.hpp b/src/graphics/core/Font.hpp index f30335be3..697aae7b6 100644 --- a/src/graphics/core/Font.hpp +++ b/src/graphics/core/Font.hpp @@ -14,14 +14,23 @@ class Texture; class Batch2D; class Batch3D; class Camera; +class ImageData; + +class Font; class Font { - int lineHeight; - int yoffset; - int glyphInterval = 8; - std::vector> pages; public: - Font(std::vector> pages, int lineHeight, int yoffset); + struct Glyph { + int yOffset; + int xAdvance; + }; + + Font( + std::vector> pages, + std::vector glyphs, + int lineHeight, + int yoffset + ); ~Font(); int getLineHeight() const; @@ -69,4 +78,20 @@ public: FontMetrics getMetrics() const { return {lineHeight, yoffset, glyphInterval}; } + + const Glyph* getGlyph(int codepoint) const { + return (codepoint < 0 || codepoint >= glyphs.size()) + ? nullptr + : &glyphs.at(codepoint); + } + + static std::unique_ptr createBitmapFont( + std::vector> pages + ); +private: + int lineHeight; + int yoffset; + int glyphInterval; + std::vector> pages; + std::vector glyphs; }; diff --git a/src/graphics/core/FontMetics.hpp b/src/graphics/core/FontMetics.hpp index d58a4fd98..d61864499 100644 --- a/src/graphics/core/FontMetics.hpp +++ b/src/graphics/core/FontMetics.hpp @@ -5,7 +5,7 @@ struct FontMetrics { int lineHeight; int yoffset; - int glyphInterval = 8; + int _glyphInterval = 8; int calcWidth(std::wstring_view text, size_t offset=0, size_t length=-1) const; };