优化文本布局算法

This commit is contained in:
Nanako 2025-04-22 01:45:02 +08:00
parent cc37be615c
commit d8551a96ca
3 changed files with 170 additions and 110 deletions

View File

@ -57,30 +57,30 @@ int main(int argc, char* argv[]) {
ss << "version: " << version << "\n";
ss << "author: " << author << "\n";
ss << "description: " << description << "\n";
ss << "license: " << license << "\n";
ss << "license: " << license;
// const char*转换为std::u32string
// const auto& config_info_str = utf8::utf8to32(ss.str());
const auto& config_info_str = utf8::utf8to32(ss.str());
// text_block->set_text(U"Hello, World! 你好,世界!\n换行测试1111测试测试测试测试,测试测试😀🐵🙏 😃🐵🙏");
const auto& window = mwindow::create({ 800, 600 }, L"Hello, World!");
window->set_content(
mnew(mv_box)
[
// mslot(mv_box)
// .horizontal_alignment(horizontal_alignment_t::left)
// + mnew(mbutton)
// [
// mslot(mbutton)
// .margin({ 10 })
// .visibility(visibility_t::visible)
// [
// mnew(mtext_block,
// .text(config_info_str)
// .font_size(24)
// )
// ]
// ],
mslot(mv_box)
.horizontal_alignment(horizontal_alignment_t::left)
+ mnew(mbutton)
[
mslot(mbutton)
.margin({ 10 })
.visibility(visibility_t::visible)
[
mnew(mtext_block,
.text(config_info_str)
.font_size(24)
)
]
],
mslot(mv_box)
.horizontal_alignment(horizontal_alignment_t::right)

View File

@ -118,154 +118,214 @@ text_layout_t font_manager::layout_text(
float line_spacing) {
text_layout_t layout;
// 使用指定字体或主字体
const auto& primary_font = in_font ? in_font : get_primary_font();
assert(primary_font && "No valid font available");
primary_font->set_font_size(font_size);
// 初始化布局变量
float cursor_x = 0.0f; // 当前光标X位置
float cursor_y = 0.0f; // 当前光标Y位置
float width = 0.0f; // 总布局宽度
float height = 0.0f; // 总布局高度
uint32_t prev_glyph_id = 0; // 上一个字形ID用于字距调整
float cursor_y = 0.0f;
float total_width = 0.0f;
float total_height = 0.0f;
// 临时存储待处理字形的信息
struct pending_glyph_t {
uint32_t glyph_index; // 字形索引
uint32_t prev_glyph_id_for_kerning; // 用于字距调整的上一个字形ID
float start_x; // 该字形开始绘制的相对X坐标应用字距调整前
glyph_shaped_t metrics; // 字形度量信息
atlas_region_t region; // 字形在图集中的区域
bool is_emoji; // 是否为表情符号
std::shared_ptr<font_face_interface> font; // 使用的字体
};
// 当前行信息
struct line_info {
float height = 0.0f; // 行总高度
float ascent = 0.0f; // 最大上升距离
float descent = 0.0f; // 最大下降距离
float line_width = 0.0f; // 行宽度
bool has_content = false; // 标记行是否有内容
float max_ascent = 0.0f;
float max_descent = 0.0f; // 正值
float max_line_height = 0.0f;
float current_width = 0.0f;
bool has_content = false;
std::vector<pending_glyph_t> glyphs;
};
line_info current_line;
float line_cursor_x = 0.0f;
uint32_t prev_glyph_id = 0;
/**
* @brief
*/
auto finish_line = [&] {
if (current_line.has_content) {
// 更新总体尺寸
width = std::max(width, current_line.line_width);
height += current_line.height * line_spacing;
std::map<std::shared_ptr<font_face_interface>, font_v_metrics_t> font_metrics_cache;
// 移到下一行
cursor_y += current_line.height * line_spacing;
cursor_x = 0.0f;
// 重置行信息
current_line = line_info{};
}
};
// 设置字体大小
// 设置所有可能用到的字体大小
for (const auto& font : fonts_ | std::views::values) {
font->set_font_size(font_size);
if (font)
font->set_font_size(font_size);
}
if (primary_font) {
primary_font->set_font_size(font_size);
}
std::map<std::shared_ptr<font_face_interface>, font_v_metrics_t> font_metrics;
/**
* @brief
*/
auto finish_line = [&] {
// --- 处理空行 ---
if (!current_line.has_content) {
if (primary_font) {
// 需要参考字体确定空行高度
if (!font_metrics_cache.contains(primary_font)) {
font_metrics_cache[primary_font] = primary_font->get_metrics();
}
// 如果主字体有效,则增加由主字体确定的行高
if (font_metrics_cache.contains(primary_font)) {
total_height += font_metrics_cache[primary_font].line_height * line_spacing;
cursor_y = total_height;
}
}
// 重置行信息 (即使是空行也要重置)
current_line = line_info{};
line_cursor_x = 0.0f;
prev_glyph_id = 0;
return; // 空行处理完毕
}
// --- 计算行垂直居中的基线 ---
// **1. 计算该行分配的总高度**
const float actual_line_height = current_line.max_line_height * line_spacing;
// **2. 计算该行内容的实际高度**
const float content_height = current_line.max_ascent + current_line.max_descent;
// **3. 计算垂直居中对齐的基线 Y 坐标**
// 目标是将 content_height 放在 actual_line_height 的中间。
// 行空间的中心点 Y = cursor_y + actual_line_height / 2.0f
// 内容空间的中心点相对于基线的偏移 = (max_ascent - max_descent) / 2.0f
// 所以 baseline_y + (max_ascent - max_descent) / 2.0f = cursor_y + actual_line_height / 2.0f
// baseline_y = cursor_y + (actual_line_height - max_ascent + max_descent) / 2.0f
// (注意:这里假设 ascent 为正, descent 为正)
const float baseline_y = cursor_y + current_line.max_ascent + (actual_line_height - content_height) / 2.f;
// --- 处理当前行的所有字形,计算最终位置 ---
for (const auto& pending_glyph : current_line.glyphs) {
const auto& metrics = pending_glyph.metrics;
const auto& region = pending_glyph.region;
// 计算Y轴差异 (确保从位图数据的实际顶部开始绘制)
const float size_y_diff = metrics.rect.size().y() - static_cast<float>(region.rect.size().y());
// 计算最终字形绘制坐标 (使用新的 baseline_y)
float final_x = pending_glyph.start_x + metrics.offset.x();
// **final_y = baseline_y + 字形相对于基线的偏移 + 高度差异**
float final_y = baseline_y + metrics.offset.y() + size_y_diff;
// 添加字形位置信息到最终布局
auto& glyph_position = layout.glyphs.emplace_back();
glyph_position.is_emoji = pending_glyph.is_emoji;
glyph_position.glyph_index = pending_glyph.glyph_index;
glyph_position.position = { final_x, final_y };
glyph_position.size = metrics.rect.size();
glyph_position.region = region;
}
// --- 更新总体尺寸和下一行位置 ---
total_width = std::max(total_width, current_line.current_width);
// **总高度增加的是该行分配的高度**
total_height += actual_line_height;
// 移到下一行的顶部
cursor_y = total_height;
line_cursor_x = 0.0f;
// 重置行信息和上一个字形ID
current_line = line_info{};
prev_glyph_id = 0;
};
/**
* @brief
*
* @param c Unicode字符
* @brief
* ( finish_line 使)
*/
auto process_character = [&](const char32_t c) {
// 处理换行符
if (c == U'\n') {
finish_line();
return;
}
// 检查是否是表情符号
const bool is_emoji = emoji_detector::is_emoji(c);
// 查找能够渲染此字符的字体
uint32_t glyph_index = 0;
const auto& using_font = get_font_for_code_point(primary_font, c, &glyph_index);
const bool is_emoji = emoji_detector::is_emoji(c);
uint32_t glyph_index = 0;
const auto& using_font = get_font_for_code_point(primary_font, c, &glyph_index);
if (glyph_index == 0) {
return; // 如果没有找到支持的字体,跳过该字符
prev_glyph_id = 0;
return;
}
if (!font_metrics.contains(using_font)) {
font_metrics[using_font] = using_font->get_metrics();
if (!font_metrics_cache.contains(using_font)) {
// 确保获取度量前字体大小已设置 (已在循环开始前完成)
font_metrics_cache[using_font] = using_font->get_metrics();
}
const auto& current_font_metrics = font_metrics_cache[using_font];
// 获取当前字体的度量信息
const auto& current_metrics = font_metrics[using_font];
// 获取字形度量信息(形状、尺寸、间距等)
const auto& glyph_metrics = using_font->shape_glyph(glyph_index);
const auto& region_opt = get_or_create_glyph_by_index(glyph_metrics.glyph_index, using_font, font_size,
is_emoji);
// 获取或创建字形在字形图集中的区域
const auto& region = get_or_create_glyph_by_index(glyph_metrics.glyph_index, using_font, font_size, is_emoji);
if (!region_opt) {
prev_glyph_id = 0;
return;
}
const auto& region = *region_opt;
if (!region) {
return; // 如果无法获取或创建字形区域,跳过该字符
float kerning = 0.0f;
if (prev_glyph_id != 0) {
kerning = using_font->get_kerning(prev_glyph_id, glyph_index);
}
// 检查是否需要自动换行
float estimated_next_x = line_cursor_x + kerning + glyph_metrics.advance.x();
// 检查自动换行
if (max_width > 0 &&
cursor_x + glyph_metrics.advance.x() > max_width &&
estimated_next_x > max_width &&
current_line.has_content) {
finish_line();
// !! 继续处理当前字符 c 在新行上 !!
kerning = 0.0f; // 新行首字符无 kerning
// line_cursor_x 和 prev_glyph_id 已在 finish_line 中重置为 0
}
// 标记当前行有内容
current_line.has_content = true;
// 更新当前行的度量信息
current_line.ascent = std::max(current_line.ascent, current_metrics.ascent);
current_line.descent = std::max(current_line.descent, std::abs(current_metrics.descent));
current_line.height = std::max(current_line.height, current_metrics.line_height);
// **更新当前行的最大度量信息** (保持不变)
current_line.max_ascent = std::max(current_line.max_ascent, current_font_metrics.ascent);
// **确保 descent 是正值**
current_line.max_descent = std::max(current_line.max_descent, std::abs(current_font_metrics.descent));
current_line.max_line_height = std::max(current_line.max_line_height, current_font_metrics.line_height);
// 计算基线位置 - 基于当前行的上升距离
const auto baseline = cursor_y + current_line.ascent;
// 计算Y轴差异(字形位图高度与区域高度)
const auto size_y_diff = glyph_metrics.rect.size().y() - static_cast<float>(region->rect.size().y());
line_cursor_x += kerning;
// 计算字形绘制坐标
auto x = cursor_x + glyph_metrics.offset.x();
auto y = baseline + glyph_metrics.offset.y() + size_y_diff;
pending_glyph_t pending_glyph;
pending_glyph.glyph_index = glyph_index;
pending_glyph.prev_glyph_id_for_kerning = prev_glyph_id;
pending_glyph.start_x = line_cursor_x;
pending_glyph.metrics = glyph_metrics;
pending_glyph.region = region;
pending_glyph.is_emoji = is_emoji;
pending_glyph.font = using_font;
// 添加字形位置信息到布局
auto& glyph_position = layout.glyphs.emplace_back();
glyph_position.is_emoji = is_emoji;
glyph_position.glyph_index = glyph_index;
glyph_position.position = { x, y };
glyph_position.size = glyph_metrics.rect.size();
glyph_position.region = *region;
current_line.glyphs.push_back(pending_glyph);
// 更新当前行宽度
current_line.line_width += glyph_metrics.advance.x();
// 更新行宽度
current_line.current_width = line_cursor_x + glyph_metrics.advance.x();
// 更新光标位置
cursor_x += glyph_metrics.advance.x();
// 应用字距调整kerning
if (prev_glyph_id != 0) {
cursor_x += using_font->get_kerning(prev_glyph_id, glyph_index);
}
// 更新光标 X 位置
line_cursor_x += glyph_metrics.advance.x();
// 更新上一个字形索引
prev_glyph_id = glyph_index;
};
// 处理每个字符
for (const auto& c : text) {
// --- 主处理循环 ---
for (const char32_t c : text) {
process_character(c);
}
// 考虑最后一行的下降部分
height -= current_line.descent;
// 处理最后一行
// --- 处理文本末尾的最后一行 ---
finish_line();
// 设置文本布局的总体尺寸
layout.total_size = { width, height };
// --- 设置最终布局尺寸 ---
layout.total_size = { total_width, total_height };
return layout;
}

View File

@ -55,7 +55,7 @@ private:
std::u32string text_;
text_layout_t layout_{};
float font_size_ = .0f;
float line_spacing_ = 1.2f;
float line_spacing_ = 1.f;
float max_width_ = 0.0f;
std::shared_ptr<font_face_interface> font_;
};