寻找方案
在使用skia绘制的时候无论如何都无法绘制emoji
手动指定了一个支持emoji的字体
恩。。。手动指定一个中文字体试试
字体比之前更全了,事情开始变得有趣了起来🤔
那么问题应该就在内置字体不全上,已知有些字体支持除了emoji以外的大部分字体,而emobj需要额外的字体支持,而库需要跨平台,那么只能识别emoji然后将符号特殊处理🤯
那么去找unicode的emoji范围规范吧
https://unicode.org/Public/emoji/latest/emoji-test.txt
dump下来常用emoji之后
需要找一个支持的字体,本来google的Noto Color Emoji是个不错的选择,但是吧
skia无法渲染TWT
那只能使用另一套开源但是风格略奇怪的字体openmoji,选用兼容性较好的 OpenMoji-color-colr0_svg
尝试引入
模拟skia的 ParagraphBuilderImpl
和 Paragraph
由 ParagraphBuilderImpl
构建段落布局信息,Paragraph
进行渲染.
// 预先定义字体信息
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);
计算每个字符的位置
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);
}
渲染
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);
}
}
};
成果
看看尺寸
9mb,考虑到是单文件无依赖静态链接,也能接受。
结语
这里尽可能使用最小成本实现了对多端友好(方便跨平台),但是因为裁减了font,会有部分字符显示不全.
HYParagraphBuilderImpl只写了基本的计算,还可以进行超多优化:自动换行、字符/段落间距、超出区域停止渲染,为了最小化代码体积而关闭emoji扩展等
评论 (0)