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 +#include + +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 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(); + + for (auto& [glyph, character] : in_font->glyphs) { + const auto& sz = character.size.cast(); + 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(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 +#include FT_FREETYPE_H +#include + +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 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 glyphs; + std::unordered_map 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 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 update_function; + std::vector 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 atlases; + std::function update_function; +};