复制LinaVG文本加载部分

This commit is contained in:
Nanako 2024-11-12 18:32:46 +08:00
parent bdda16e04b
commit 7486bad390
3 changed files with 395 additions and 0 deletions

View File

@ -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() {

View File

@ -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;
}

View File

@ -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;
};