复制LinaVG文本加载部分
This commit is contained in:
parent
ffcdc3b9f5
commit
7d97d99f58
@ -6,6 +6,7 @@
|
|||||||
#include "core/window/renderer_window.h"
|
#include "core/window/renderer_window.h"
|
||||||
#define STB_IMAGE_IMPLEMENTATION
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
#define STBI_FAILURE_USERMSG
|
#define STBI_FAILURE_USERMSG
|
||||||
|
#include "renderer_text.h"
|
||||||
#include "stb_image.h"
|
#include "stb_image.h"
|
||||||
|
|
||||||
#if DX_BACKEND
|
#if DX_BACKEND
|
||||||
@ -85,6 +86,9 @@ renderer* aorii::get_renderer_raw() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool aorii::create_renderer(renderer_api api) {
|
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();
|
begin_time = std::chrono::high_resolution_clock::now();
|
||||||
if (s_renderer) return true;
|
if (s_renderer) return true;
|
||||||
switch (api) {
|
switch (api) {
|
||||||
@ -118,6 +122,8 @@ void aorii::destroy_renderer() {
|
|||||||
s_renderer->destroy();
|
s_renderer->destroy();
|
||||||
delete s_renderer;
|
delete s_renderer;
|
||||||
s_renderer = nullptr;
|
s_renderer = nullptr;
|
||||||
|
|
||||||
|
aorii_text::destroy_freetype();
|
||||||
}
|
}
|
||||||
|
|
||||||
void aorii::update() {
|
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