From b1356ca5619f12298c094037a7646899b0beb694 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 13 Feb 2026 13:14:13 +0300 Subject: [PATCH 01/15] add freetype library --- .github/workflows/appimage.yml | 2 +- .github/workflows/cmake.yml | 2 +- .github/workflows/macos.yml | 2 +- Dockerfile | 1 + src/CMakeLists.txt | 2 ++ vcpkg.json | 3 ++- 6 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml index 92c9d9fd6..f43254bd3 100644 --- a/.github/workflows/appimage.yml +++ b/.github/workflows/appimage.yml @@ -31,7 +31,7 @@ jobs: sudo apt-get update sudo apt-get install -y build-essential libglfw3-dev libglfw3 libglew-dev libglew2.2 \ libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev libvorbis-dev \ - libcurl4-openssl-dev libgtest-dev cmake squashfs-tools valgrind + libcurl4-openssl-dev libgtest-dev libfreetype6-dev cmake squashfs-tools valgrind # fix luajit paths sudo ln -s /usr/lib/x86_64-linux-gnu/libluajit-5.1.a /usr/lib/x86_64-linux-gnu/liblua5.1.a sudo ln -s /usr/include/luajit-2.1 /usr/include/lua diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 652cc26f9..f41e940e9 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -39,7 +39,7 @@ jobs: # make && make install INSTALL_INC=/usr/include/lua run: | sudo apt-get update - sudo apt-get install libglfw3-dev libglfw3 libglew-dev libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev libvorbis-dev libgtest-dev libcurl4-openssl-dev + sudo apt-get install libglfw3-dev libglfw3 libglew-dev libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev libvorbis-dev libgtest-dev libcurl4-openssl-dev libfreetype6-dev # fix luajit paths sudo ln -s /usr/lib/x86_64-linux-gnu/libluajit-5.1.a /usr/lib/x86_64-linux-gnu/liblua-5.1.a sudo ln -s /usr/include/luajit-2.1 /usr/include/lua diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 9fdd8d0b3..c3f5156f9 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -23,7 +23,7 @@ jobs: - name: Install dependencies from brew run: | - brew install glfw3 glew libpng openal-soft luajit libvorbis skypjack/entt/entt googletest glm + brew install glfw3 glew libpng openal-soft luajit libvorbis skypjack/entt/entt googletest glm freetype - name: Configure run: | diff --git a/Dockerfile b/Dockerfile index e067b92d1..da90324fc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,6 +25,7 @@ RUN apt-get update && apt-get install --no-install-recommends -y \ libluajit-5.1-dev \ libvorbis-dev \ libcurl4-openssl-dev \ + libfreetype6-dev \ ca-certificates \ wget \ && rm -rf /var/lib/apt/lists/* diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 226ac8c25..d6f4c5b74 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -19,6 +19,7 @@ find_package(ZLIB REQUIRED) find_package(PNG REQUIRED) find_package(CURL REQUIRED) find_package(glfw3 REQUIRED) +find_package(Freetype REQUIRED) if(NOT APPLE) find_package(EnTT REQUIRED) endif() @@ -75,6 +76,7 @@ target_link_libraries( Vorbis::vorbis Vorbis::vorbisfile luajit::luajit + Freetype::Freetype PUBLIC glm::glm # Need public for src/delegates.hpp, which including to # main.cpp ) diff --git a/vcpkg.json b/vcpkg.json index 17145c4a0..4f56dc6c2 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -13,6 +13,7 @@ "libvorbis", "entt", "gtest", - "curl" + "curl", + "freetype" ] } From 1abe15dd1d6c7b00ae618fe5c0725dd58aed8120 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 13 Feb 2026 13:29:04 +0300 Subject: [PATCH 02/15] update AppImageBuilder.yml --- dev/AppImageBuilder.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/dev/AppImageBuilder.yml b/dev/AppImageBuilder.yml index 16fcf57a7..2321ca43d 100644 --- a/dev/AppImageBuilder.yml +++ b/dev/AppImageBuilder.yml @@ -31,6 +31,7 @@ AppDir: - libvorbis0a - libvorbisfile3 - libluajit-5.1-2 + - libfreetype6 exclude: - hicolor-icon-theme - sound-theme-freedesktop From d65f3cb1d52e7c47b82e775766f4feae19d92cfb Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 13 Feb 2026 13:57:36 +0300 Subject: [PATCH 03/15] update AppImageBuilder.yml --- dev/AppImageBuilder.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/dev/AppImageBuilder.yml b/dev/AppImageBuilder.yml index 2321ca43d..bbd250cbb 100644 --- a/dev/AppImageBuilder.yml +++ b/dev/AppImageBuilder.yml @@ -46,6 +46,7 @@ AppDir: - kwin-style-breeze - kde-style-breeze - plasma-integration + - libllvm15 files: exclude: - usr/share/man From 0b61a337debaa7fde73830fc5dcd543bf41ae64b Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 13 Feb 2026 14:11:35 +0300 Subject: [PATCH 04/15] update AppImageBuilder.yml --- dev/AppImageBuilder.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dev/AppImageBuilder.yml b/dev/AppImageBuilder.yml index bbd250cbb..b2c7f09ec 100644 --- a/dev/AppImageBuilder.yml +++ b/dev/AppImageBuilder.yml @@ -47,6 +47,8 @@ AppDir: - kde-style-breeze - plasma-integration - libllvm15 + - libicu70 + - libxml2 files: exclude: - usr/share/man From 2c813446389f134457b9d77dd71ea9c9758d7208 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 13 Feb 2026 15:11:27 +0300 Subject: [PATCH 05/15] test library --- src/coders/vector_fonts.cpp | 34 ++++++++++++++++++++++++++++++++++ src/coders/vector_fonts.hpp | 5 +++++ 2 files changed, 39 insertions(+) create mode 100644 src/coders/vector_fonts.cpp create mode 100644 src/coders/vector_fonts.hpp diff --git a/src/coders/vector_fonts.cpp b/src/coders/vector_fonts.cpp new file mode 100644 index 000000000..a3773a4e5 --- /dev/null +++ b/src/coders/vector_fonts.cpp @@ -0,0 +1,34 @@ +#include "vector_fonts.hpp" + +#include "io/io.hpp" + +#include +#include +#include FT_FREETYPE_H + +static FT_Library library; + +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)) + ); + } +} + +static FT_Face load_ft_face(const io::path& path) { + auto bytes = io::read_bytes_buffer(path); + + 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)) + ); + } + + return face; +} diff --git a/src/coders/vector_fonts.hpp b/src/coders/vector_fonts.hpp new file mode 100644 index 000000000..4e093d593 --- /dev/null +++ b/src/coders/vector_fonts.hpp @@ -0,0 +1,5 @@ +#pragma once + +namespace vector_fonts { + void initialize(); +} From 11c3f1c5eb79bc780be1df351a96e63340210400 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 15 Feb 2026 20:27:34 +0300 Subject: [PATCH 06/15] 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; }; From 8d7c995684c7ac24666fc72345c45cd671fc42d6 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 15 Feb 2026 20:52:46 +0300 Subject: [PATCH 07/15] cleanup --- src/coders/vector_fonts.cpp | 62 +++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/src/coders/vector_fonts.cpp b/src/coders/vector_fonts.cpp index ca64b6929..05e2420de 100644 --- a/src/coders/vector_fonts.cpp +++ b/src/coders/vector_fonts.cpp @@ -26,7 +26,8 @@ static const char* get_ft_error_message(FT_Error 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(FT_Face face, util::Buffer buffer) + : face(std::move(face)), buffer(std::move(buffer)) { } ~FTFontFile() { @@ -49,33 +50,20 @@ namespace { ); } - 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; + int dstSize = size * 16; + ImageData canvas(ImageFormat::RGBA8888, dstSize, dstSize); - if (FT_Error error = FT_Load_Char(face, codepoint, FT_LOAD_RENDER)) { - logger.warning() << get_ft_error_message(error); + int srcWidth = size; + int srcHeight = size; + ImageData bitmapDst(ImageFormat::RGBA8888, srcWidth, srcHeight); + + for (int pageid = 0; pageid < pagesCount; pageid++) { + for (int c = 0; c < 256; c++) { + if (!renderGlyph(pageid << 8 | c, bitmapDst)) { 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); + + canvas.blit(bitmapDst, (c % 16) * size, (c / 16) * size); glyphs.push_back(Font::Glyph { face->glyph->bitmap_top - size, static_cast(face->glyph->advance.x >> 6) @@ -84,11 +72,33 @@ namespace { canvas.flipY(); pages.push_back(Texture::from(&canvas)); } - return std::make_unique(std::move(pages), std::move(glyphs), size, size / 2); + return std::make_unique( + std::move(pages), std::move(glyphs), size, size / 2 + ); } private: FT_Face face; util::Buffer buffer; + + bool renderGlyph(int codepoint, ImageData& bitmapDst) { + int width = bitmapDst.getWidth(); + if (FT_Error error = FT_Load_Char(face, codepoint, FT_LOAD_RENDER)) { + logger.warning() << get_ft_error_message(error); + return false; + } + const FT_Bitmap& bitmap = face->glyph->bitmap; + 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 * width + col) * 4 + 0] = 255; + dstData[(row * width + col) * 4 + 1] = 255; + dstData[(row * width + col) * 4 + 2] = 255; + dstData[(row * width + col) * 4 + 3] = value; + } + } + return true; + } }; } From 93dbf34d15739f6db7f2c91471e15748679fed66 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 15 Feb 2026 21:16:39 +0300 Subject: [PATCH 08/15] perform digital exorcism --- src/coders/vector_fonts.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/coders/vector_fonts.cpp b/src/coders/vector_fonts.cpp index 05e2420de..f838237f7 100644 --- a/src/coders/vector_fonts.cpp +++ b/src/coders/vector_fonts.cpp @@ -88,6 +88,7 @@ namespace { } const FT_Bitmap& bitmap = face->glyph->bitmap; auto dstData = bitmapDst.getData(); + std::memset(dstData, 0, bitmapDst.getDataSize()); 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]; From ae54ac9975507cd57e9c7e14a15ad782acb57c40 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 15 Feb 2026 23:48:10 +0300 Subject: [PATCH 09/15] feat: non-monospaced fonts support --- src/coders/vector_fonts.cpp | 6 ++-- src/graphics/core/Font.cpp | 32 ++++++++++++++++---- src/graphics/core/Font.hpp | 4 +-- src/graphics/core/FontMetics.hpp | 8 +++++ src/graphics/core/FontMetrics.cpp | 5 ++++ src/graphics/ui/elements/Label.cpp | 44 +++++++++++++++------------- src/graphics/ui/elements/Label.hpp | 3 +- src/graphics/ui/elements/TextBox.cpp | 34 ++++++++++++--------- 8 files changed, 90 insertions(+), 46 deletions(-) create mode 100644 src/graphics/core/FontMetrics.cpp diff --git a/src/coders/vector_fonts.cpp b/src/coders/vector_fonts.cpp index f838237f7..076067662 100644 --- a/src/coders/vector_fonts.cpp +++ b/src/coders/vector_fonts.cpp @@ -82,6 +82,8 @@ namespace { bool renderGlyph(int codepoint, ImageData& bitmapDst) { int width = bitmapDst.getWidth(); + int height = bitmapDst.getHeight(); + if (FT_Error error = FT_Load_Char(face, codepoint, FT_LOAD_RENDER)) { logger.warning() << get_ft_error_message(error); return false; @@ -89,8 +91,8 @@ namespace { const FT_Bitmap& bitmap = face->glyph->bitmap; auto dstData = bitmapDst.getData(); std::memset(dstData, 0, bitmapDst.getDataSize()); - for (int row = 0; row < bitmap.rows; row++) { - for (int col = 0; col < bitmap.width; col++) { + for (int row = 0; row < std::min(bitmap.rows, height); row++) { + for (int col = 0; col < std::min(bitmap.width, width); col++) { uint8_t value = bitmap.buffer[row * bitmap.pitch + col]; dstData[(row * width + col) * 4 + 0] = 255; dstData[(row * width + col) * 4 + 1] = 255; diff --git a/src/graphics/core/Font.cpp b/src/graphics/core/Font.cpp index d9be091a7..2b37d0db7 100644 --- a/src/graphics/core/Font.cpp +++ b/src/graphics/core/Font.cpp @@ -18,8 +18,8 @@ Font::Font( int yoffset ) : lineHeight(lineHeight), - glyphInterval(lineHeight / 2), yoffset(yoffset), + glyphInterval(lineHeight / 2), pages(std::move(pages)), glyphs(std::move(glyphs)) { } @@ -48,7 +48,22 @@ 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; + auto font = this->font.has_value() ? this->font->lock() : nullptr; + if (font == nullptr) { + return std::min(text.length() - offset, length) * _glyphInterval; + } + int totalWidth = 0; + for (int i = offset; i < offset + length && i < text.length(); i++) { + auto codepoint = text[i]; + if (!font->isPrintableChar(codepoint)) { + totalWidth += _glyphInterval; + } else if (auto glyph = font->getGlyph(codepoint)) { + totalWidth += glyph->xAdvance; + } else { + totalWidth += _glyphInterval; + } + } + return totalWidth; } int Font::calcWidth(std::wstring_view text, size_t length) const { @@ -109,7 +124,6 @@ static inline void draw_glyph( } else { color = style.color; } - batch.sprite( pos + right * (offset.x + i) + up * offset.y, @@ -143,10 +157,12 @@ static inline void draw_text( uint page = 0; uint next = MAX_CODEPAGES; - int x = 0; + float x = 0; int y = 0; bool hasLines = false; + float baseAdvance = glm::length(right); + do { for (size_t i = 0; i < text.length(); i++) { uint c = text[i]; @@ -162,8 +178,10 @@ static inline void draw_text( continue; } int yOffset = 0; + float advance = baseAdvance; if (auto glyph = font.getGlyph(c)) { yOffset = glyph->yOffset; + advance = glyph->xAdvance; } uint charpage = c >> 8; if (charpage == page){ @@ -175,7 +193,7 @@ static inline void draw_text( else if (charpage > page && charpage < next){ next = charpage; } - x++; + x += advance / baseAdvance; } page = next; next = MAX_CODEPAGES; @@ -263,6 +281,10 @@ std::unique_ptr Font::createBitmapFont( int res = pages.at(0)->getHeight() / 16; std::vector> textures; std::vector glyphs(textures.size() * 256); + for (auto& glyph : glyphs) { + glyph.yOffset = 0; + glyph.xAdvance = res / 2; + } for (auto& page : pages) { if (page == nullptr) { textures.emplace_back(nullptr); diff --git a/src/graphics/core/Font.hpp b/src/graphics/core/Font.hpp index 697aae7b6..244e2e8a3 100644 --- a/src/graphics/core/Font.hpp +++ b/src/graphics/core/Font.hpp @@ -76,9 +76,9 @@ public: const Texture* getPage(int page) const; FontMetrics getMetrics() const { - return {lineHeight, yoffset, glyphInterval}; + return {std::nullopt, lineHeight, yoffset, glyphInterval}; } - + const Glyph* getGlyph(int codepoint) const { return (codepoint < 0 || codepoint >= glyphs.size()) ? nullptr diff --git a/src/graphics/core/FontMetics.hpp b/src/graphics/core/FontMetics.hpp index d61864499..a835a495b 100644 --- a/src/graphics/core/FontMetics.hpp +++ b/src/graphics/core/FontMetics.hpp @@ -1,11 +1,19 @@ #pragma once +#include #include +#include + +class Font; struct FontMetrics { + std::optional> font; + int lineHeight; int yoffset; int _glyphInterval = 8; + ~FontMetrics(); + int calcWidth(std::wstring_view text, size_t offset=0, size_t length=-1) const; }; diff --git a/src/graphics/core/FontMetrics.cpp b/src/graphics/core/FontMetrics.cpp new file mode 100644 index 000000000..4727faed0 --- /dev/null +++ b/src/graphics/core/FontMetrics.cpp @@ -0,0 +1,5 @@ +#include "FontMetics.hpp" + +#include "Font.hpp" + +FontMetrics::~FontMetrics() = default; diff --git a/src/graphics/ui/elements/Label.cpp b/src/graphics/ui/elements/Label.cpp index 374c088a0..9387e91c5 100644 --- a/src/graphics/ui/elements/Label.cpp +++ b/src/graphics/ui/elements/Label.cpp @@ -11,11 +11,11 @@ using namespace gui; -void LabelCache::prepare(std::ptrdiff_t fontId, FontMetrics metrics, size_t wrapWidth) { - if (fontId != this->fontId) { +void LabelCache::prepare(const std::shared_ptr& font, FontMetrics metrics, size_t wrapWidth) { + if (!this->metrics.font.has_value() || font.get() != this->metrics.font.value().lock().get()) { resetFlag = true; - this->fontId = fontId; this->metrics = metrics; + this->metrics.font = font; } if (wrapWidth != this->wrapWidth) { resetFlag = true; @@ -42,7 +42,8 @@ void LabelCache::update(std::wstring_view text, bool multiline, bool wrap) { lines.clear(); lines.push_back(LineScheme {0, false}); - if (fontId == 0) { + auto font = metrics.font.has_value() ? metrics.font.value().lock() : nullptr; + if (font == nullptr) { wrap = false; } @@ -61,26 +62,27 @@ void LabelCache::update(std::wstring_view text, bool multiline, bool wrap) { } } } - if (fontId != 0) { - int maxWidth = 0; - for (int i = 0; i < lines.size() - 1; i++) { - const auto& next = lines[i + 1]; - const auto& cur = lines[i]; - maxWidth = std::max( - metrics.calcWidth( - text.substr(cur.offset, next.offset - cur.offset) - ), - maxWidth - ); - } + if (font == nullptr) { + return; + } + int maxWidth = 0; + for (int i = 0; i < lines.size() - 1; i++) { + const auto& next = lines[i + 1]; + const auto& cur = lines[i]; maxWidth = std::max( metrics.calcWidth( - text.substr(lines[lines.size() - 1].offset) + text.substr(cur.offset, next.offset - cur.offset) ), maxWidth ); - multilineWidth = maxWidth; } + maxWidth = std::max( + metrics.calcWidth( + text.substr(lines[lines.size() - 1].offset) + ), + maxWidth + ); + multilineWidth = maxWidth; } } @@ -136,7 +138,7 @@ void Label::setText(std::wstring text) { this->text = std::move(text); cache.update(this->text, multiline, textWrap); - if (cache.fontId != 0 && autoresize) { + if (cache.metrics.font.has_value() && !cache.metrics.font->expired() && autoresize) { setSize(calcSize()); } } @@ -203,9 +205,9 @@ uint Label::getLinesNumber() const { void Label::draw(const DrawContext& pctx, const Assets& assets) { auto batch = pctx.getBatch2D(); - auto font = assets.get(fontName); + auto font = assets.getShared(fontName); cache.prepare( - reinterpret_cast(font), + font, font->getMetrics(), static_cast(glm::abs(getSize().x)) ); diff --git a/src/graphics/ui/elements/Label.hpp b/src/graphics/ui/elements/Label.hpp index 07f9e91ce..aace3ba94 100644 --- a/src/graphics/ui/elements/Label.hpp +++ b/src/graphics/ui/elements/Label.hpp @@ -15,7 +15,6 @@ namespace gui { }; struct LabelCache { - ptrdiff_t fontId = 0; FontMetrics metrics; std::vector lines; @@ -24,7 +23,7 @@ namespace gui { size_t wrapWidth = -1; int multilineWidth = 0; - void prepare(std::ptrdiff_t fontId, FontMetrics metrics, size_t wrapWidth); + void prepare(const std::shared_ptr& font, FontMetrics metrics, size_t wrapWidth); void update(std::wstring_view text, bool multiline, bool wrap); size_t getTextLineOffset(size_t line) const; diff --git a/src/graphics/ui/elements/TextBox.cpp b/src/graphics/ui/elements/TextBox.cpp index 26160e126..fb8dbd9c8 100644 --- a/src/graphics/ui/elements/TextBox.cpp +++ b/src/graphics/ui/elements/TextBox.cpp @@ -252,10 +252,12 @@ void TextBox::draw(const DrawContext& pctx, const Assets& assets) { float time = gui.getWindow().time(); + // drawing caret here if (isFocused() && editable && static_cast((time - caretLastMove) * 2) % 2 == 0) { uint line = label->getLineByTextIndex(caret); + auto linestart = label->getTextLineOffset(line); uint lcaret = caret - label->getTextLineOffset(line); - int width = rawTextCache.metrics.calcWidth(input, 0, lcaret); + int width = rawTextCache.metrics.calcWidth(input.substr(linestart), 0, lcaret); batch->rect( lcoord.x + width, @@ -264,6 +266,7 @@ void TextBox::draw(const DrawContext& pctx, const Assets& assets) { lineHeight ); } + // drawing selection if (selectionStart != selectionEnd) { auto selectionCtx = subctx.sub(batch); selectionCtx.setBlendMode(BlendMode::addition); @@ -349,12 +352,8 @@ void TextBox::draw(const DrawContext& pctx, const Assets& assets) { } void TextBox::drawBackground(const DrawContext& pctx, const Assets& assets) { - auto font = assets.get(label->getFontName()); - rawTextCache.prepare( - reinterpret_cast(font), - font->getMetrics(), - label->getSize().x - ); + auto font = assets.getShared(label->getFontName()); + rawTextCache.prepare(font, font->getMetrics(), label->getSize().x); glm::vec2 pos = calcPos(); @@ -384,8 +383,11 @@ void TextBox::drawBackground(const DrawContext& pctx, const Assets& assets) { } void TextBox::refreshLabel() { + if (!rawTextCache.metrics.font.has_value()) { + return; + } rawTextCache.prepare( - rawTextCache.fontId, + rawTextCache.metrics.font.value().lock(), rawTextCache.metrics, static_cast(getSize().x) ); @@ -425,7 +427,7 @@ void TextBox::refreshLabel() { lineNumbersLabel->setColor(glm::vec4(1, 1, 1, 0.25f)); } - if (autoresize && rawTextCache.fontId) { + if (autoresize) { auto size = getSize(); int newy = glm::min( static_cast(parent->getSize().y), @@ -443,7 +445,7 @@ void TextBox::refreshLabel() { } } - if (multiline && rawTextCache.fontId) { + if (multiline) { setScrollable(true); uint height = label->getLinesNumber() * rawTextCache.metrics.lineHeight * label->getLineInterval(); @@ -665,7 +667,7 @@ size_t TextBox::normalizeIndex(int index) { /// @param y screen Y position /// @return non-normalized character index int TextBox::calcIndexAt(int x, int y) const { - if (rawTextCache.fontId == 0) return 0; + if (!rawTextCache.metrics.font.has_value()) return 0; const auto& labelText = label->getText(); glm::vec2 lcoord = label->calcPos(); uint line = label->getLineByYOffset(y - lcoord.y); @@ -682,7 +684,7 @@ int TextBox::calcIndexAt(int x, int y) const { } int TextBox::getLineYOffset(int line) const { - if (rawTextCache.fontId == 0) return 0; + if (!rawTextCache.metrics.font.has_value()) return 0; return label->getLineYOffset(line); } @@ -1205,12 +1207,16 @@ size_t TextBox::getCaret() const { void TextBox::setCaret(size_t position) { const auto& labelText = label->getText(); caret = std::min(static_cast(position), input.length()); - if (rawTextCache.fontId == 0) { + + auto font = rawTextCache.metrics.font.has_value() + ? rawTextCache.metrics.font->lock() + : nullptr; + if (font == nullptr) { return; } int width = label->getSize().x; - rawTextCache.prepare(rawTextCache.fontId, rawTextCache.metrics, width); + rawTextCache.prepare(font, rawTextCache.metrics, width); rawTextCache.update(input, multiline, label->isTextWrapping()); caretLastMove = gui.getWindow().time(); From 381cb77d08c0a0dc5ccab9a36fad4f4ec23ca716 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 16 Feb 2026 00:51:51 +0300 Subject: [PATCH 10/15] cleanup --- src/coders/vector_fonts.cpp | 63 +++++++++++++++++++++++-------------- src/constants.hpp | 2 ++ 2 files changed, 42 insertions(+), 23 deletions(-) diff --git a/src/coders/vector_fonts.cpp b/src/coders/vector_fonts.cpp index 076067662..b308d5943 100644 --- a/src/coders/vector_fonts.cpp +++ b/src/coders/vector_fonts.cpp @@ -5,6 +5,7 @@ #include "graphics/core/ImageData.hpp" #include "graphics/core/Texture.hpp" #include "graphics/core/Font.hpp" +#include "constants.hpp" #include #include @@ -38,11 +39,6 @@ namespace { } 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: " + @@ -50,26 +46,15 @@ namespace { ); } + std::vector> pages; + std::vector glyphs; + int dstSize = size * 16; ImageData canvas(ImageFormat::RGBA8888, dstSize, dstSize); + ImageData bitmapDst(ImageFormat::RGBA8888, size, size); - int srcWidth = size; - int srcHeight = size; - ImageData bitmapDst(ImageFormat::RGBA8888, srcWidth, srcHeight); - - for (int pageid = 0; pageid < pagesCount; pageid++) { - for (int c = 0; c < 256; c++) { - if (!renderGlyph(pageid << 8 | c, bitmapDst)) { - continue; - } - - canvas.blit(bitmapDst, (c % 16) * size, (c / 16) * size); - glyphs.push_back(Font::Glyph { - face->glyph->bitmap_top - size, - static_cast(face->glyph->advance.x >> 6) - }); - } - canvas.flipY(); + for (int pageid = 0; pageid < FONT_PAGES; pageid++) { + renderPage(pageid, canvas, bitmapDst, glyphs); pages.push_back(Texture::from(&canvas)); } return std::make_unique( @@ -88,9 +73,12 @@ namespace { logger.warning() << get_ft_error_message(error); return false; } - const FT_Bitmap& bitmap = face->glyph->bitmap; auto dstData = bitmapDst.getData(); std::memset(dstData, 0, bitmapDst.getDataSize()); + if (face->glyph->bitmap.width == 0) { + return false; + } + const FT_Bitmap& bitmap = face->glyph->bitmap; for (int row = 0; row < std::min(bitmap.rows, height); row++) { for (int col = 0; col < std::min(bitmap.width, width); col++) { uint8_t value = bitmap.buffer[row * bitmap.pitch + col]; @@ -102,6 +90,35 @@ namespace { } return true; } + + void renderPage( + int pageid, + ImageData& canvas, + ImageData& bitmapDst, + std::vector& glyphs + ) { + int size = canvas.getWidth() / 16; + for (int c = 0; c < 256; c++) { + int codepoint = pageid << 8 | c; + if (!renderGlyph(codepoint, bitmapDst)) { + glyphs.push_back(Font::Glyph {0, size / 2}); + continue; + } + + canvas.blit(bitmapDst, (c % 16) * size, (c / 16) * size); + + Font::Glyph glyph { + face->glyph->bitmap_top - size, + static_cast(face->glyph->advance.x >> 6) + }; + if (codepoint < glyphs.size()) { + glyphs[codepoint] = std::move(glyph); + } else { + glyphs.push_back(std::move(glyph)); + } + } + canvas.flipY(); + } }; } diff --git a/src/constants.hpp b/src/constants.hpp index 72e397397..d57f73669 100644 --- a/src/constants.hpp +++ b/src/constants.hpp @@ -65,6 +65,8 @@ inline constexpr int ATLAS_EXTRUSION = 2; inline constexpr int DEFAULT_FONT_SIZE = 16; +inline constexpr int FONT_PAGES = 5; + inline const std::string SHADERS_FOLDER = "shaders"; inline const std::string TEXTURES_FOLDER = "textures"; inline const std::string FONTS_FOLDER = "fonts"; From 47809b4024dd7cff33a4be167cc8fbc1334101f6 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 16 Feb 2026 01:42:48 +0300 Subject: [PATCH 11/15] feat: rendering pages on demand --- src/assets/assetload_funcs.cpp | 1 + src/coders/vector_fonts.cpp | 46 ++++++++++++++++----- src/coders/vector_fonts.hpp | 10 ++++- src/constants.hpp | 2 +- src/graphics/core/Font.cpp | 47 +++++++++++++++++----- src/graphics/core/Font.hpp | 29 +++++++------ src/graphics/render/TextsRenderer.cpp | 2 +- src/graphics/ui/elements/InventoryView.cpp | 6 +-- src/graphics/ui/elements/InventoryView.hpp | 2 +- 9 files changed, 106 insertions(+), 39 deletions(-) diff --git a/src/assets/assetload_funcs.cpp b/src/assets/assetload_funcs.cpp index 8a39d3934..09f244c34 100644 --- a/src/assets/assetload_funcs.cpp +++ b/src/assets/assetload_funcs.cpp @@ -208,6 +208,7 @@ assetload::postfunc assetload::font( logger.info() << "loading vector font " << util::quote(filename); return [=](auto assets) { auto font = vector_fonts::load_font(paths.find(filename).string()); + assets->store(font, filename); assets->store(font->createInstance(cfg ? cfg->size : 16), name); }; } diff --git a/src/coders/vector_fonts.cpp b/src/coders/vector_fonts.cpp index b308d5943..ad7706e6b 100644 --- a/src/coders/vector_fonts.cpp +++ b/src/coders/vector_fonts.cpp @@ -25,7 +25,8 @@ static const char* get_ft_error_message(FT_Error error) { } namespace { - class FTFontFile : public vector_fonts::FontFile { + class FTFontFile : public vector_fonts::FontFile, + public std::enable_shared_from_this { public: FTFontFile(FT_Face face, util::Buffer buffer) : face(std::move(face)), buffer(std::move(buffer)) { @@ -47,20 +48,42 @@ namespace { } std::vector> pages; - std::vector glyphs; + std::vector glyphs; int dstSize = size * 16; ImageData canvas(ImageFormat::RGBA8888, dstSize, dstSize); ImageData bitmapDst(ImageFormat::RGBA8888, size, size); - for (int pageid = 0; pageid < FONT_PAGES; pageid++) { + for (int pageid = 0; pageid < DEFAULT_PRE_RENDER_FONT_PAGES; pageid++) { renderPage(pageid, canvas, bitmapDst, glyphs); pages.push_back(Texture::from(&canvas)); } return std::make_unique( - std::move(pages), std::move(glyphs), size, size / 2 + std::move(pages), + std::move(glyphs), + size, + size / 2, + shared_from_this() ); } + + std::unique_ptr renderPage( + int pageid, std::vector& glyphs, int size + ) override { + 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)) + ); + } + + int dstSize = size * 16; + ImageData canvas(ImageFormat::RGBA8888, dstSize, dstSize); + ImageData bitmapDst(ImageFormat::RGBA8888, size, size); + + renderPage(pageid, canvas, bitmapDst, glyphs); + return Texture::from(&canvas); + } private: FT_Face face; util::Buffer buffer; @@ -95,19 +118,19 @@ namespace { int pageid, ImageData& canvas, ImageData& bitmapDst, - std::vector& glyphs + std::vector& glyphs ) { int size = canvas.getWidth() / 16; for (int c = 0; c < 256; c++) { int codepoint = pageid << 8 | c; if (!renderGlyph(codepoint, bitmapDst)) { - glyphs.push_back(Font::Glyph {0, size / 2}); + glyphs.push_back(Glyph {0, size / 2}); continue; } canvas.blit(bitmapDst, (c % 16) * size, (c / 16) * size); - Font::Glyph glyph { + Glyph glyph { face->glyph->bitmap_top - size, static_cast(face->glyph->advance.x >> 6) }; @@ -133,11 +156,14 @@ void vector_fonts::initialize() { void vector_fonts::finalize() { if (FT_Error error = FT_Done_FreeType(library)) { - logger.error() << "error on FT_Done_FreeType: " << get_ft_error_message(error); + logger.error() << "error on FT_Done_FreeType: " + << get_ft_error_message(error); } } -std::unique_ptr vector_fonts::load_font(const std::string& filename) { +std::shared_ptr vector_fonts::load_font( + const std::string& filename +) { auto bytes = io::read_bytes_buffer(io::path(filename)); FT_Face face; @@ -150,5 +176,5 @@ std::unique_ptr vector_fonts::load_font(const std::strin ); } - return std::make_unique(std::move(face), std::move(bytes)); + return std::make_shared(std::move(face), std::move(bytes)); } diff --git a/src/coders/vector_fonts.hpp b/src/coders/vector_fonts.hpp index de73323c6..a28f3c5bf 100644 --- a/src/coders/vector_fonts.hpp +++ b/src/coders/vector_fonts.hpp @@ -2,8 +2,12 @@ #include #include +#include class Font; +struct Glyph; + +class Texture; namespace vector_fonts { class FontFile { @@ -11,10 +15,14 @@ namespace vector_fonts { virtual ~FontFile() {} virtual std::unique_ptr createInstance(int size) = 0; + + virtual std::unique_ptr renderPage( + int pageid, std::vector& glyphs, int size + ) = 0; }; void initialize(); void finalize(); - std::unique_ptr load_font(const std::string& filename); + std::shared_ptr load_font(const std::string& filename); } diff --git a/src/constants.hpp b/src/constants.hpp index d57f73669..501c5f62e 100644 --- a/src/constants.hpp +++ b/src/constants.hpp @@ -65,7 +65,7 @@ inline constexpr int ATLAS_EXTRUSION = 2; inline constexpr int DEFAULT_FONT_SIZE = 16; -inline constexpr int FONT_PAGES = 5; +inline constexpr int DEFAULT_PRE_RENDER_FONT_PAGES = 1; // must be at least 1 inline const std::string SHADERS_FOLDER = "shaders"; inline const std::string TEXTURES_FOLDER = "textures"; diff --git a/src/graphics/core/Font.cpp b/src/graphics/core/Font.cpp index 2b37d0db7..3f0b4b69b 100644 --- a/src/graphics/core/Font.cpp +++ b/src/graphics/core/Font.cpp @@ -1,11 +1,13 @@ #include "Font.hpp" +#include "Batch2D.hpp" +#include "Batch3D.hpp" +#include "coders/vector_fonts.hpp" +#include "Texture.hpp" +#include "window/Camera.hpp" + #include #include -#include "Texture.hpp" -#include "Batch2D.hpp" -#include "Batch3D.hpp" -#include "window/Camera.hpp" inline constexpr uint GLYPH_SIZE = 16; inline constexpr uint MAX_CODEPAGES = 10000; // idk ho many codepages unicode has @@ -15,13 +17,15 @@ Font::Font( std::vector> pages, std::vector glyphs, int lineHeight, - int yoffset + int yoffset, + std::optional> fontFile ) : lineHeight(lineHeight), yoffset(yoffset), glyphInterval(lineHeight / 2), pages(std::move(pages)), - glyphs(std::move(glyphs)) { + glyphs(std::move(glyphs)), + fontFile(std::move(fontFile)) { } Font::~Font() = default; @@ -139,7 +143,7 @@ static inline void draw_glyph( template static inline void draw_text( - const Font& font, + Font& font, Batch& batch, std::wstring_view text, const glm::vec3& pos, @@ -244,7 +248,7 @@ void Font::draw( const FontStylesScheme* styles, size_t styleMapOffset, float scale -) const { +) { draw_text( *this, batch, text, glm::vec3(x, y, 0), @@ -264,7 +268,7 @@ void Font::draw( const glm::vec3& pos, const glm::vec3& right, const glm::vec3& up -) const { +) { draw_text( *this, batch, text, pos, right * static_cast(glyphInterval), @@ -296,3 +300,28 @@ std::unique_ptr Font::createBitmapFont( } return std::make_unique(std::move(textures), std::move(glyphs), res, 4); } + +const Glyph* Font::getGlyph(int codepoint) { + if (codepoint < 0) { + return nullptr; + } + if (codepoint < glyphs.size()) { + return &glyphs.at(codepoint); + } + if (!this->fontFile.has_value() || this->fontFile->expired()) { + return nullptr; + } + int codepage = codepoint >> 8; + if (codepage >= 1024) { + return nullptr; + } + if (glyphs.size() < (codepage << 8)) { + glyphs.resize(codepage << 8); + } + auto fontFile = this->fontFile->lock(); + if (pages.size() <= codepage) { + pages.resize(codepage); + } + pages.push_back(fontFile->renderPage(codepage, glyphs, lineHeight)); + return &glyphs.at(codepoint); +} diff --git a/src/graphics/core/Font.hpp b/src/graphics/core/Font.hpp index 244e2e8a3..e23c45ba8 100644 --- a/src/graphics/core/Font.hpp +++ b/src/graphics/core/Font.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "typedefs.hpp" @@ -18,18 +19,23 @@ class ImageData; class Font; +namespace vector_fonts { + class FontFile; +} + +struct Glyph { + int yOffset; + int xAdvance; +}; + class Font { public: - struct Glyph { - int yOffset; - int xAdvance; - }; - Font( std::vector> pages, std::vector glyphs, int lineHeight, - int yoffset + int yoffset, + std::optional> fontFile = std::nullopt ); ~Font(); @@ -61,7 +67,7 @@ public: const FontStylesScheme* styles, size_t styleMapOffset, float scale = 1 - ) const; + ); void draw( Batch3D& batch, @@ -71,7 +77,7 @@ public: const glm::vec3& pos, const glm::vec3& right={1, 0, 0}, const glm::vec3& up={0, 1, 0} - ) const; + ); const Texture* getPage(int page) const; @@ -79,11 +85,7 @@ public: return {std::nullopt, lineHeight, yoffset, glyphInterval}; } - const Glyph* getGlyph(int codepoint) const { - return (codepoint < 0 || codepoint >= glyphs.size()) - ? nullptr - : &glyphs.at(codepoint); - } + const Glyph* getGlyph(int codepoint); static std::unique_ptr createBitmapFont( std::vector> pages @@ -94,4 +96,5 @@ private: int glyphInterval; std::vector> pages; std::vector glyphs; + std::optional> fontFile; }; diff --git a/src/graphics/render/TextsRenderer.cpp b/src/graphics/render/TextsRenderer.cpp index 4b905e138..0f9d32285 100644 --- a/src/graphics/render/TextsRenderer.cpp +++ b/src/graphics/render/TextsRenderer.cpp @@ -45,7 +45,7 @@ void TextsRenderer::renderNote( } opacity = preset.xrayOpacity; } - const auto& font = assets.require(FONT_DEFAULT); + auto& font = assets.require(FONT_DEFAULT); glm::vec3 xvec = note.getAxisX(); glm::vec3 yvec = note.getAxisY(); diff --git a/src/graphics/ui/elements/InventoryView.cpp b/src/graphics/ui/elements/InventoryView.cpp index 816f53474..49c8bd3b9 100644 --- a/src/graphics/ui/elements/InventoryView.cpp +++ b/src/graphics/ui/elements/InventoryView.cpp @@ -276,13 +276,13 @@ void SlotView::draw(const DrawContext& pctx, const Assets& assets) { drawItemIcon(batch, stack, item, assets, tint, pos); if (stack.getCount() > 1 || stack.getFields() != nullptr) { - const auto& font = assets.require(FONT_DEFAULT); + auto& font = assets.require(FONT_DEFAULT); drawItemInfo(batch, stack, item, font, pos); } } static void draw_shaded_text( - Batch2D& batch, const Font& font, const std::wstring& text, int x, int y + Batch2D& batch, Font& font, const std::wstring& text, int x, int y ) { batch.setColor({0, 0, 0, 1.0f}); font.draw(batch, text, x + 1, y + 1, nullptr, 0); @@ -294,7 +294,7 @@ void SlotView::drawItemInfo( Batch2D& batch, const ItemStack& stack, const ItemDef& item, - const Font& font, + Font& font, const glm::vec2& pos ) { const int SLOT_SIZE = InventoryView::SLOT_SIZE; diff --git a/src/graphics/ui/elements/InventoryView.hpp b/src/graphics/ui/elements/InventoryView.hpp index 9275a2daa..b77e7d684 100644 --- a/src/graphics/ui/elements/InventoryView.hpp +++ b/src/graphics/ui/elements/InventoryView.hpp @@ -81,7 +81,7 @@ namespace gui { Batch2D& batch, const ItemStack& stack, const ItemDef& item, - const Font& font, + Font& font, const glm::vec2& pos ); From 32f3f762a088ad184969e00b305ce8efe2e0e70e Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 16 Feb 2026 02:12:23 +0300 Subject: [PATCH 12/15] feat: shared FontFile --- src/assets/assetload_funcs.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/assets/assetload_funcs.cpp b/src/assets/assetload_funcs.cpp index 09f244c34..6a301ec62 100644 --- a/src/assets/assetload_funcs.cpp +++ b/src/assets/assetload_funcs.cpp @@ -206,10 +206,15 @@ assetload::postfunc assetload::font( 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, filename); - assets->store(font->createInstance(cfg ? cfg->size : 16), name); + return [=](Assets* assets) { + using FontFile = vector_fonts::FontFile; + FontFile* fontFile; + if ((fontFile = assets->get(filename)) == nullptr) { + auto fontFilePtr = vector_fonts::load_font(paths.find(filename).string()); + fontFile = fontFilePtr.get(); + assets->store(fontFilePtr, filename); + } + assets->store(fontFile->createInstance(cfg ? cfg->size : 16), name); }; } From 55056223953cc8ac13e1adfda315a9db6bd4efa0 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 16 Feb 2026 02:30:20 +0300 Subject: [PATCH 13/15] add freetype library installation to README.md --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1221148c3..93e597fb6 100644 --- a/README.md +++ b/README.md @@ -30,13 +30,13 @@ sudo make install ```sh su - -apt-get install entt-devel libglfw3-devel libGLEW-devel libglm-devel libpng-devel libvorbis-devel libopenal-devel libluajit-devel libstdc++13-devel-static libcurl-devel +apt-get install entt-devel libglfw3-devel libGLEW-devel libglm-devel libpng-devel libvorbis-devel libopenal-devel libluajit-devel libstdc++13-devel-static libcurl-devel libfreetype-devel ``` #### Debian based distros ```sh -sudo apt install libglfw3 libglfw3-dev libglew-dev libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev libvorbis-dev libcurl4-openssl-dev +sudo apt install libglfw3 libglfw3-dev libglew-dev libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev libvorbis-dev libcurl4-openssl-dev libfreetype6-dev ``` > [!TIP] @@ -50,7 +50,7 @@ sudo apt install libglfw3 libglfw3-dev libglew-dev libglm-dev libpng-dev libopen #### RHEL based distros ```sh -sudo dnf install glfw-devel glew-devel glm-devel libpng-devel libvorbis-devel openal-soft-devel luajit-devel libcurl-devel +sudo dnf install glfw-devel glew-devel glm-devel libpng-devel libvorbis-devel openal-soft-devel luajit-devel libcurl-devel libfreetype-devel ``` #### Arch based distros @@ -58,13 +58,13 @@ sudo dnf install glfw-devel glew-devel glm-devel libpng-devel libvorbis-devel op If you use X11: ```sh -sudo pacman -S glfw-x11 glew glm libpng libvorbis openal luajit libcurl +sudo pacman -S glfw-x11 glew glm libpng libvorbis openal luajit libcurl freetype2 ``` If you use Wayland: ```sh -sudo pacman -S glfw-wayland glew glm libpng libvorbis openal luajit libcurl +sudo pacman -S glfw-wayland glew glm libpng libvorbis openal luajit libcurl freetype2 ``` And install EnTT: @@ -94,7 +94,7 @@ cmake --build . --parallel ### Install libraries ```sh -brew install glfw3 glew glm libpng libvorbis lua luajit libcurl openal-soft skypjack/entt/entt +brew install glfw3 glew glm libpng libvorbis lua luajit libcurl openal-soft skypjack/entt/entt freetype ``` > [!TIP] From b0d9a162ca7c560dadbfabdb1ffbb92eec4b092c Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 16 Feb 2026 02:54:54 +0300 Subject: [PATCH 14/15] add 'font' xml property to label, button, bindbox, select, textbox --- src/graphics/ui/elements/Button.cpp | 4 ++++ src/graphics/ui/elements/Button.hpp | 2 ++ src/graphics/ui/elements/InputBindBox.cpp | 4 ++++ src/graphics/ui/elements/InputBindBox.hpp | 2 ++ src/graphics/ui/elements/SelectBox.cpp | 4 ++++ src/graphics/ui/elements/SelectBox.hpp | 2 ++ src/graphics/ui/elements/TextBox.cpp | 4 ++++ src/graphics/ui/elements/TextBox.hpp | 2 ++ src/graphics/ui/gui_xml.cpp | 27 +++++++++++++++++++++++ 9 files changed, 51 insertions(+) diff --git a/src/graphics/ui/elements/Button.cpp b/src/graphics/ui/elements/Button.cpp index c951e6c3d..96ca620b9 100644 --- a/src/graphics/ui/elements/Button.cpp +++ b/src/graphics/ui/elements/Button.cpp @@ -91,6 +91,10 @@ void Button::setTextAlign(Align align) { } } +std::shared_ptr