diff --git a/example/src/main.cpp b/example/src/main.cpp index c887454..6e51ed4 100644 --- a/example/src/main.cpp +++ b/example/src/main.cpp @@ -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) diff --git a/src/mirage_render/src/font/font_system.cpp b/src/mirage_render/src/font/font_system.cpp index 17103eb..67525a1 100644 --- a/src/mirage_render/src/font/font_system.cpp +++ b/src/mirage_render/src/font/font_system.cpp @@ -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; // 使用的字体 + }; - // 当前行信息 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 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, 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, 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(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(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; } diff --git a/src/mirage_widget/src/widget/leaf_widget/mtext_block.h b/src/mirage_widget/src/widget/leaf_widget/mtext_block.h index 801ab52..c1612b9 100644 --- a/src/mirage_widget/src/widget/leaf_widget/mtext_block.h +++ b/src/mirage_widget/src/widget/leaf_widget/mtext_block.h @@ -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_; };