复制LinaVG文本加载部分
This commit is contained in:
parent
bdda16e04b
commit
7486bad390
@ -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() {
|
||||
|
296
src/renderer/core/renderer/renderer_text.cpp
Normal file
296
src/renderer/core/renderer/renderer_text.cpp
Normal 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;
|
||||
}
|
93
src/renderer/core/renderer/renderer_text.h
Normal file
93
src/renderer/core/renderer/renderer_text.h
Normal 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;
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user