Loading... # 寻找方案 在使用skia绘制的时候无论如何都无法绘制emoji ![image.png](https://blog.hyiy.top/usr/uploads/2024/08/3350851909.png) 手动指定了一个支持emoji的字体 ![image.png](https://blog.hyiy.top/usr/uploads/2024/08/1908348939.png) 恩。。。手动指定一个中文字体试试 ![image.png](https://blog.hyiy.top/usr/uploads/2024/08/367455795.png) 字体比之前更全了,事情开始变得有趣了起来🤔 那么问题应该就在内置字体不全上,已知有些字体支持除了emoji以外的大部分字体,而emobj需要额外的字体支持,而库需要跨平台,那么只能识别emoji然后将符号特殊处理🤯 那么去找unicode的emoji范围规范吧 https://unicode.org/Public/emoji/latest/emoji-test.txt dump下来常用emoji之后 ![image.png](https://blog.hyiy.top/usr/uploads/2024/09/570911914.png) 需要找一个支持的字体,本来google的[Noto Color Emoji](https://fonts.google.com/noto/specimen/Noto+Color+Emoji)是个不错的选择,但是吧 skia无法渲染TWT 那只能使用另一套开源但是风格略奇怪的字体[openmoji](https://github.com/hfg-gmuend/openmoji),选用兼容性较好的 `OpenMoji-color-colr0_svg` # 尝试引入 模拟skia的 `ParagraphBuilderImpl` 和 `Paragraph` 由 `ParagraphBuilderImpl`构建段落布局信息,`Paragraph`进行渲染. ```cpp // 预先定义字体信息 SkFontMetrics font_metrics{}; Font->getMetrics(&font_metrics); auto emoji_font = SkFont(g_app.EmojiTypeface->makeClone(SkFontArguments{}), Font->getSize()); SkFontMetrics emoji_font_metrics{}; emoji_font.getMetrics(&emoji_font_metrics); auto utils_font = SkFont(g_app.UtilsTypeface->makeClone(SkFontArguments{}), Font->getSize()); SkFontMetrics utils_font_metrics{}; utils_font.getMetrics(&utils_font_metrics); ``` ### 计算每个字符的位置 ```cpp for (auto textLayout: TextCache) { HYParagraph::LineLayout lineLayout; if (textLayout.empty()) { textLayout = U" "; } // 计算每个字符 textLayout.forEachUtf8CharBoundary([&](const char8_t *data, size_t start, size_t len, char32_t c) -> int { // 取出每个符号进行测量 HYString tests(c); auto tests_c = c; switch (c) { case U'\n': { // 也许暂时不需要? PrintError("未处理的换行符!!!"); } break; case U' ': { // 空格,一般会无法计算,使用a进行代替计算. tests = U"1"; tests_c = U' '; len = tests.size(); } break; default: break; } HYParagraph::CharacterLayout characterLayout; SkRect ts; SkScalar char_scalar; auto GlyphID = Font->unicharToGlyph(c); if (GlyphID == 0) { // 测量失败,测试特殊字符 GlyphID = utils_font.unicharToGlyph(c); if (GlyphID != 0) { char_scalar = utils_font.measureText(tests.c_str(), len, SkTextEncoding::kUTF8, &ts); characterLayout.glyphType = HYParagraph::CharacterGlyphType::Utils; } else { // 测量失败,测试emoji GlyphID = emoji_font.unicharToGlyph(c); if (GlyphID != 0) { char_scalar = emoji_font.measureText(tests.c_str(), len, SkTextEncoding::kUTF8, &ts); characterLayout.glyphType = HYParagraph::CharacterGlyphType::Emoji; } else { tests = U'☐'; tests_c = U'☐'; len = tests.size(); char_scalar = Font->measureText(tests.c_str(), len, SkTextEncoding::kUTF8, &ts); } } } else { char_scalar = Font->measureText(tests.c_str(), len, SkTextEncoding::kUTF8, &ts); } PrintDebug("{} {} {}x{} GlyphID:{}", tests.c_str(), char_scalar, ts.width(), ts.height(), GlyphID); characterLayout.metrics.fAscent = ts.top(); characterLayout.metrics.fDescent = ts.bottom(); characterLayout.metrics.fLeading = std::max(font_metrics.fLeading, emoji_font_metrics.fLeading); characterLayout.metrics.fMaxHeight = fabs(lineLayout.metrics.fAscent) + fabs(lineLayout.metrics.fDescent) + lineLayout.metrics.fLeading; lineLayout.metrics.fAscent = std::min(ts.top(), lineLayout.metrics.fAscent); lineLayout.metrics.fDescent = std::max(ts.bottom(), lineLayout.metrics.fDescent); lineLayout.metrics.fLeading = std::max(std::max(font_metrics.fLeading, emoji_font_metrics.fLeading), lineLayout.metrics.fLeading); characterLayout.len = len; characterLayout.value = tests_c; characterLayout.text = tests_c; characterLayout.rect = { .x = addLen, .y = addHeight, .width = char_scalar, .height = ts.height(), }; // 偏移 addLen += char_scalar; lineLayout.characterLayouts.emplace_back(characterLayout); return 0; }); lineLayout.metrics.fMaxHeight = std::fabs(lineLayout.metrics.fAscent) + std::fabs(lineLayout.metrics.fDescent) + std::fabs(lineLayout.metrics.fLeading); addHeight += lineLayout.metrics.fMaxHeight + LineSpacing; addLen = 0; layouts->emplace_back(lineLayout); } ``` ### 渲染 ```cpp void HYParagraph::Canvas(CanvasPtr canvas, PaintPtr paint, const HYRectf &rect, const HYPointf &offset) { auto emoji_font = SkFont(g_app.EmojiTypeface->makeClone(SkFontArguments{}), font->getSize()); auto utils_font = SkFont(g_app.UtilsTypeface->makeClone(SkFontArguments{}), font->getSize()); for (auto &line: *lineLayouts) { for (auto &word: line.characterLayouts) { SkFont *df = font; if (word.glyphType == HYParagraph::CharacterGlyphType::Emoji) { df = &emoji_font; } else if (word.glyphType == HYParagraph::CharacterGlyphType::Utils) { df = &utils_font; } canvas->drawString(word.text.c_str(), offset.x + word.rect.x, offset.y + word.rect.y - line.metrics.fAscent, *df, *paint); } } }; ``` ### 成果 ![image.png](https://blog.hyiy.top/usr/uploads/2024/09/3526877044.png) 看看尺寸 ![image.png](https://blog.hyiy.top/usr/uploads/2024/09/1823125626.png) 9mb,考虑到是单文件无依赖静态链接,也能接受。 # 结语 这里尽可能使用最小成本实现了对多端友好(方便跨平台),但是因为裁减了font,会有部分字符显示不全. HYParagraphBuilderImpl只写了基本的计算,还可以进行超多优化:自动换行、字符/段落间距、超出区域停止渲染,为了最小化代码体积而关闭emoji扩展等 ### 相关项目 [HYGUI](https://github.com/huiyicc/hygui) 最后修改:2024 年 09 月 13 日 © 允许规范转载 赞 如果觉得我的文章对你有用,请随意赞赏