[HYGUI开发] emoji绘制问题的解决

[HYGUI开发] emoji绘制问题的解决

huiyi
2024-09-13 / 0 评论 / 517 阅读 / 正在检测是否收录...

寻找方案

在使用skia绘制的时候无论如何都无法绘制emoji

image.png

手动指定了一个支持emoji的字体

image.png

恩。。。手动指定一个中文字体试试

image.png

字体比之前更全了,事情开始变得有趣了起来🤔

那么问题应该就在内置字体不全上,已知有些字体支持除了emoji以外的大部分字体,而emobj需要额外的字体支持,而库需要跨平台,那么只能识别emoji然后将符号特殊处理🤯

那么去找unicode的emoji范围规范吧

https://unicode.org/Public/emoji/latest/emoji-test.txt

dump下来常用emoji之后

image.png

需要找一个支持的字体,本来google的Noto Color Emoji是个不错的选择,但是吧

skia无法渲染TWT

那只能使用另一套开源但是风格略奇怪的字体openmoji,选用兼容性较好的 OpenMoji-color-colr0_svg

尝试引入

模拟skia的 ParagraphBuilderImplParagraph

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);
    }
  }
};

成果

image.png

看看尺寸
image.png

9mb,考虑到是单文件无依赖静态链接,也能接受。

结语

这里尽可能使用最小成本实现了对多端友好(方便跨平台),但是因为裁减了font,会有部分字符显示不全.
HYParagraphBuilderImpl只写了基本的计算,还可以进行超多优化:自动换行、字符/段落间距、超出区域停止渲染,为了最小化代码体积而关闭emoji扩展等

相关项目

HYGUI

0

评论 (0)

取消