优化文本布局算法
This commit is contained in:
parent
cc37be615c
commit
d8551a96ca
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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_;
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user