From 7d97d99f58fb232e44e2ea9d0ee36e5b2b25b40c Mon Sep 17 00:00:00 2001
From: Nanako <469449812@qq.com>
Date: Tue, 12 Nov 2024 18:32:46 +0800
Subject: [PATCH] =?UTF-8?q?=E5=A4=8D=E5=88=B6LinaVG=E6=96=87=E6=9C=AC?=
 =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E9=83=A8=E5=88=86?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/renderer/core/renderer/renderer.cpp      |   6 +
 src/renderer/core/renderer/renderer_text.cpp | 296 +++++++++++++++++++
 src/renderer/core/renderer/renderer_text.h   |  93 ++++++
 3 files changed, 395 insertions(+)
 create mode 100644 src/renderer/core/renderer/renderer_text.cpp
 create mode 100644 src/renderer/core/renderer/renderer_text.h

diff --git a/src/renderer/core/renderer/renderer.cpp b/src/renderer/core/renderer/renderer.cpp
index e957018..f27fd15 100644
--- a/src/renderer/core/renderer/renderer.cpp
+++ b/src/renderer/core/renderer/renderer.cpp
@@ -6,6 +6,7 @@
 #include "core/window/renderer_window.h"
 #define STB_IMAGE_IMPLEMENTATION
 #define STBI_FAILURE_USERMSG
+#include "renderer_text.h"
 #include "stb_image.h"
 
 #if DX_BACKEND
@@ -85,6 +86,9 @@ renderer* aorii::get_renderer_raw() {
 }
 
 bool aorii::create_renderer(renderer_api api) {
+    if (!aorii_text::init_freetype())
+        return false;
+    aorii_text::load_font("C:/Windows/Fonts/simsun.ttc", false, 48);
     begin_time = std::chrono::high_resolution_clock::now();
     if (s_renderer) return true;
     switch (api) {
@@ -118,6 +122,8 @@ void aorii::destroy_renderer() {
     s_renderer->destroy();
     delete s_renderer;
     s_renderer = nullptr;
+
+    aorii_text::destroy_freetype();
 }
 
 void aorii::update() {
diff --git a/src/renderer/core/renderer/renderer_text.cpp b/src/renderer/core/renderer/renderer_text.cpp
new file mode 100644
index 0000000..b8ca2b9
--- /dev/null
+++ b/src/renderer/core/renderer/renderer_text.cpp
@@ -0,0 +1,296 @@
+#include "renderer_text.h"
+
+#include <utility>
+#include <spdlog/spdlog.h>
+
+text_font::~text_font() {
+    for (auto& [glyph, character] : glyphs) {
+        std::free(character.buffer);
+    }
+    glyphs.clear();
+    assert(atlas == nullptr);
+}
+
+text_atlas::text_atlas(const Eigen::Vector2i& in_size, std::function<void(text_atlas* atlas)> in_update_function) {
+    update_function = std::move(in_update_function);
+    size = in_size;
+    data = new uint8_t[size.x() * size.y()];
+    memset(data, 0, size.x() * size.y());
+    slices.emplace_back(0, size.y());
+}
+
+text_atlas::~text_atlas() {
+    delete[] data;
+    slices.clear();
+}
+
+bool text_atlas::add_font(text_font* in_font) {
+    if (in_font->atlas_rect_height > size.y()) {
+        spdlog::error("字体超过图集大小! 通过配置增加最大图集大小");
+        return false;
+    }
+
+    in_font->atlas = this;
+    uint32_t best_slice_diff = size.y();
+    slice* best_slice = nullptr;
+
+    for (auto& slice : slices) {
+        if (slice.height < in_font->atlas_rect_height) {
+            continue;
+        }
+
+        const uint32_t diff = slice.height - in_font->atlas_rect_height;
+        if (diff < best_slice_diff) {
+            best_slice_diff = diff;
+            best_slice = &slice;
+        }
+    }
+
+    if (best_slice == nullptr) {
+        spdlog::error("无法找到合适的切片");
+        return false;
+    }
+
+    in_font->atlas_rect_pos = best_slice->pos;
+
+    uint32_t start_x = 0;
+    uint32_t start_y = best_slice->pos;
+    uint32_t max_height = 0;
+    const Eigen::Vector2f& temp_size = size.cast<float>();
+
+    for (auto& [glyph, character] : in_font->glyphs) {
+        const auto& sz = character.size.cast<uint32_t>();
+        if (start_x + sz.x() >= size.x()) {
+            start_x = 0;
+            start_y += max_height;
+            max_height = 0;
+        }
+
+        const auto uv1x = start_x / temp_size.x();
+        const auto uv1y = start_y / temp_size.y();
+        const auto uv2x = (start_x + character.size.x()) / temp_size.x();
+        const auto uv2y = start_y / temp_size.y();
+        const auto uv3y = (start_y + character.size.y()) / temp_size.y();
+        const auto uv4y = (start_y + character.size.y()) / temp_size.y();
+        const Eigen::Vector2f uv1{uv1x, uv1y};
+        const Eigen::Vector2f uv2{uv2x, uv2y};
+        const Eigen::Vector2f uv3{uv2x, uv3y};
+        const Eigen::Vector2f uv4{uv1x, uv4y};
+        character.uv12 = {uv1.x(), uv1.y(), uv2.x(), uv2.y()};
+        character.uv34 = {uv3.x(), uv3.y(), uv4.x(), uv4.y()};
+
+        uint32_t start_offset = start_y * size.x() + start_x;
+        const auto width = sz.x();
+        for (uint32_t row = 0; row < sz.y(); row++) {
+            std::memcpy(data + start_offset, &character.buffer[row * width], width);
+            start_offset += size.x();
+        }
+
+        max_height = std::max(max_height, sz.y());
+        start_x += sz.x() + 1;
+    }
+
+    if (best_slice->height > in_font->atlas_rect_height) {
+        slices.emplace_back(best_slice->pos + in_font->atlas_rect_height, best_slice->height - in_font->atlas_rect_height);
+    }
+
+    auto it = std::ranges::find_if(slices, [best_slice](const slice& s) {
+        return s.pos == best_slice->pos && s.height == best_slice->height;
+    });
+    slices.erase(it);
+    update_function(this);
+    return true;
+}
+
+void text_atlas::remove_font(uint32_t pos, uint32_t height) {
+    slices.emplace_back(pos, height);
+
+    const auto start = pos * size.x();
+    memset(data + start, 0, height * size.x());
+    update_function(this);
+}
+
+aorii_text::~aorii_text() {
+    for (auto* atlas : atlases)
+        delete atlas;
+}
+
+bool aorii_text::init_freetype() {
+    if (FT_Init_FreeType(&library)) {
+        spdlog::error("无法初始化FreeType");
+        return false;
+    }
+    return true;
+}
+
+void aorii_text::destroy_freetype() {
+    FT_Done_FreeType(library);
+}
+
+text_font* aorii_text::load_font(const std::string& in_file, bool load_as_sdf, int size, glyph_encoding* custom_ranges,
+                                 int32_t custom_ranges_size, bool use_kerning_if_available) {
+    FT_Face face;
+    if (FT_New_Face(library, in_file.c_str(), 0, &face)) {
+        spdlog::error("无法加载字体: {}", in_file);
+        return nullptr;
+    }
+    return setup_font(face, load_as_sdf, size, custom_ranges, custom_ranges_size, use_kerning_if_available);
+}
+
+text_font* aorii_text::setup_font(FT_Face& in_face, bool load_as_sdf, int size, glyph_encoding* custom_ranges,
+    int32_t custom_ranges_size, bool use_kerning_if_available) {
+    FT_Error err = FT_Set_Pixel_Sizes(in_face, 0, size);
+    if (err) {
+        spdlog::error("无法设置字体大小: {}", size);
+        return nullptr;
+    }
+    err = FT_Select_Charmap(in_face, FT_ENCODING_UNICODE);
+    if (err) {
+        spdlog::error("无法选择字体编码");
+        return nullptr;
+    }
+
+    text_font* font = new text_font();
+    font->size = size;
+    font->is_sdf = load_as_sdf;
+    font->supports_unicode = custom_ranges != nullptr;
+    font->supports_kerning = use_kerning_if_available && FT_HAS_KERNING(in_face) != 0;
+    font->new_line_height = in_face->size->metrics.height / 64.0f;
+
+    auto& character_map = font->glyphs;
+    auto slot = in_face->glyph;
+
+    uint32_t size_ctr_x = 0;
+    uint32_t size_ctr_y = 0;
+
+    auto set_sizes = [&](uint32_t c) {
+        const auto i = FT_Get_Char_Index(in_face, c);
+
+        if (i == 0) return true;
+        err = FT_Load_Glyph(in_face, i, FT_LOAD_DEFAULT);
+        text_character& ch = character_map[c];
+
+        if (err) {
+            spdlog::error("无法加载字形: {}", c);
+            return false;
+        }
+
+        if (load_as_sdf)
+            err = FT_Render_Glyph(slot, FT_RENDER_MODE_SDF);
+        else
+            err = FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL);
+
+        if (err) {
+            spdlog::error("无法渲染字形: {}", c);
+            return false;
+        }
+
+        const uint32_t glyph_width = slot->bitmap.width;
+        const uint32_t glyph_rows = slot->bitmap.rows;
+
+        const auto buffer_size = glyph_width * glyph_rows;
+
+        if (slot->bitmap.buffer) {
+            ch.buffer = static_cast<uint8_t*>(std::malloc(buffer_size));
+            if (ch.buffer) {
+                memcpy(ch.buffer, slot->bitmap.buffer, buffer_size);
+            }
+        }
+
+        ch.size = {glyph_width, glyph_rows};
+        ch.bearing = {slot->bitmap_left, slot->bitmap_top};
+        ch.advance = {slot->advance.x >> 6, slot->advance.y >> 6};
+
+        size_ctr_y = std::max(size_ctr_y, glyph_rows);
+        if (size_ctr_x + glyph_width >= max_font_atlas_size) {
+            size_ctr_x = 0;
+            font->atlas_rect_height += size_ctr_y + 1;
+        }
+
+        size_ctr_x += glyph_width + 1;
+        return true;
+    };
+
+    auto store_kerning = [&](uint32_t first, uint32_t second) {
+        auto i = FT_Get_Char_Index(in_face, first);
+        auto j = FT_Get_Char_Index(in_face, second);
+
+        FT_Vector delta;
+        err = FT_Get_Kerning(in_face, i, j, FT_KERNING_DEFAULT, &delta);
+
+        if (err) {
+            spdlog::error("无法获取字距: {} {}", first, second);
+        }
+
+        font->kerning_table[first].x_advances[second] = delta.x;
+    };
+
+    for (uint32_t c = 32; c < 128; c++) {
+        set_sizes(c);
+        if (font->supports_kerning) {
+            for (uint32_t a = 32; a < c; a++) {
+                store_kerning(a, c);
+            }
+        }
+    }
+
+    bool use_custom_ranges = custom_ranges_size != 0;
+    if (custom_ranges_size % 2 == 1) {
+        use_custom_ranges = false;
+        if (custom_ranges) {
+            spdlog::error("自定义范围大小必须是2的倍数");
+        }
+    }
+
+    if (use_custom_ranges) {
+        int32_t index = 0;
+        const int32_t custom_range_count = custom_ranges_size / 2;
+        for (int32_t i = 0; i < custom_range_count; i++) {
+            if (custom_ranges[index] == custom_ranges[index + 1]) {
+                set_sizes(custom_ranges[index]);
+            } else {
+                for (uint32_t c = custom_ranges[index]; c < custom_ranges[index + 1]; c++) {
+                    set_sizes(c);
+                }
+            }
+            index += 2;
+        }
+    }
+
+    font->atlas_rect_height += size_ctr_y + 1;
+    font->space_advance = character_map[' '].advance.x();
+
+    err = FT_Done_Face(in_face);
+    if (err) {
+        spdlog::error("无法释放字体");
+    }
+    return font;
+}
+
+void aorii_text::add_font_to_atlas(text_font* in_font) {
+    text_atlas* found_atlas = nullptr;
+    for (auto& atlas : atlases) {
+        if (atlas->add_font(in_font)) {
+            found_atlas = atlas;
+            break;
+        }
+    }
+    if (found_atlas)
+        return;
+
+    auto* new_atlas = new text_atlas({max_font_atlas_size, max_font_atlas_size}, update_function);
+
+    if (!new_atlas->add_font(in_font)) {
+        spdlog::error("无法添加字体到图集");
+        delete new_atlas;
+        return;
+    }
+    atlases.push_back(new_atlas);
+}
+
+void aorii_text::remove_font_from_atlas(text_font* in_font) {
+    if (in_font->atlas)
+        return;
+    in_font->atlas->remove_font(in_font->atlas_rect_pos, in_font->atlas_rect_height);
+    in_font->atlas = nullptr;
+}
diff --git a/src/renderer/core/renderer/renderer_text.h b/src/renderer/core/renderer/renderer_text.h
new file mode 100644
index 0000000..aa2f110
--- /dev/null
+++ b/src/renderer/core/renderer/renderer_text.h
@@ -0,0 +1,93 @@
+#pragma once
+// from LinaVG
+
+#include <freetype2/ft2build.h>
+#include FT_FREETYPE_H
+#include <Eigen/Eigen>
+
+class text_atlas;
+using glyph_encoding = FT_ULong;
+
+struct text_character {
+    Eigen::Vector4f uv12;
+    Eigen::Vector4f uv34;
+    Eigen::Vector2f size;
+    Eigen::Vector2f bearing;
+    Eigen::Vector2f advance;
+    float ascent;
+    float descent;
+
+    uint8_t* buffer = nullptr;
+};
+
+struct kerning_information {
+    std::unordered_map<unsigned long, unsigned long> x_advances;
+};
+
+class text_font {
+public:
+    ~text_font();
+
+    int32_t size = 0;
+    float new_line_height = 0.0f;
+    float space_advance = 0.0f;
+    bool supports_unicode = false;
+    bool is_sdf = false;
+    bool supports_kerning = false;
+    uint32_t atlas_rect_height = 0;
+    uint32_t atlas_rect_pos = 0;
+    text_atlas* atlas = nullptr;
+
+    std::unordered_map<glyph_encoding, text_character> glyphs;
+    std::unordered_map<uint32_t, kerning_information> kerning_table;
+};
+
+class text_atlas {
+public:
+    struct slice {
+        slice(const uint32_t pos, const uint32_t height) : pos(pos), height(height) {};
+        uint32_t pos = 0;
+        uint32_t height = 0;
+    };
+
+    text_atlas(const Eigen::Vector2i& in_size, std::function<void(text_atlas* atlas)> in_update_function);
+    ~text_atlas();
+
+    bool add_font(text_font* in_font);
+    void remove_font(uint32_t pos, uint32_t height);
+
+    [[nodiscard]] const auto& get_size() const { return size; }
+    [[nodiscard]] auto get_data() const { return data; }
+private:
+    std::function<void(text_atlas* atlas)> update_function;
+    std::vector<slice> slices;
+    Eigen::Vector2i size;
+    uint8_t* data;
+};
+
+class aorii_text {
+public:
+    ~aorii_text();
+
+    static bool init_freetype();
+    static void destroy_freetype();
+
+    static text_font* load_font(const std::string& in_file, bool load_as_sdf, int size = 48, glyph_encoding* custom_ranges = nullptr, int32_t custom_ranges_size = 0, bool use_kerning_if_available = true);
+    static text_font* setup_font(FT_Face& in_face, bool load_as_sdf, int size, glyph_encoding* custom_ranges, int32_t custom_ranges_size, bool use_kerning_if_available);
+    void add_font_to_atlas(text_font* in_font);
+    void remove_font_from_atlas(text_font* in_font);
+
+    inline static float global_aa_multiplier = 1.0f;
+    inline static float miter_limit = 150.f;
+    inline static uint32_t max_font_atlas_size = 768;
+    inline static int32_t gc_collect_interval = 600;
+    inline static int32_t default_buffer_reserve = 50;
+    inline static bool text_caching_enabled = false;
+    inline static int32_t text_cache_reserve = 300;
+    inline static int32_t text_cache_expire_interval = 3000;
+private:
+    inline static FT_Library library = nullptr;
+
+    std::vector<text_atlas*> atlases;
+    std::function<void(text_atlas*)> update_function;
+};