Skia升级小记m104->m122

本文约 1100 字,阅读需 3 分钟。

最近需要把Skia进行一次升级,首先查看下接口变动,See:

大部分都是接口的小调整,基本没什么难度,但注意m122的一个改动:

SkFontMgr::RefDefault() has been deleted. Clients should instantiate and manage their own SkFontMgrs and use them to explicitly create SkTypefaces

找到这个改动的原因:

是因为一个隐晦的编译问题,从这个链接来看,适配这个修改还是比较麻烦的,总结来说就是:没有统一的平台无关的static接口了,你们自己去实例化(instantiate)吧。

可以看看其他类似框架的处理

Flutter:Replace calls to SkFontMgr::RefDefault by kjlubick · Pull Request #48179 · flutter/engine,感觉之前就已经有所准备了,改成了另一个调用

Chrome:

// skia/ext/font_utils.cc
static sk_sp<SkFontMgr> fontmgr_factory() {
  if (g_fontmgr_override) {
    return sk_ref_sp(g_fontmgr_override);
  }
#if BUILDFLAG(IS_ANDROID)
  return SkFontMgr_New_Android(nullptr);
#elif BUILDFLAG(IS_APPLE)
  return SkFontMgr_New_CoreText(nullptr);
#elif BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
  sk_sp<SkFontConfigInterface> fci(SkFontConfigInterface::RefGlobal());
  return fci ? SkFontMgr_New_FCI(std::move(fci)) : nullptr;
#elif BUILDFLAG(IS_FUCHSIA)
  fuchsia::fonts::ProviderSyncPtr provider;
  base::ComponentContextForProcess()->svc()->Connect(provider.NewRequest());
  return SkFontMgr_New_Fuchsia(std::move(provider));
#elif BUILDFLAG(IS_WIN)
  return SkFontMgr_New_DirectWrite();
#elif defined(SK_FONTMGR_FREETYPE_EMPTY_AVAILABLE)
  return SkFontMgr_New_Custom_Empty();
#else
  return SkFontMgr::RefEmpty();
#endif

这个比较有启发性!其实可以看下Skia的m122的代码是如何适配的。其实可以找到类似的修改:

// skia/tools/fonts/FontToolUtils.cpp
sk_sp<SkFontMgr> TestFontMgr() {
    // ...... skip
#if defined(SK_BUILD_FOR_WIN) && defined(SK_FONTMGR_GDI_AVAILABLE)
        else if (FLAGS_gdi) {
            mgr = SkFontMgr_New_GDI();
        }
#endif
        else {
#if defined(SK_BUILD_FOR_ANDROID) && defined(SK_FONTMGR_ANDROID_AVAILABLE)
            mgr = SkFontMgr_New_Android(nullptr);
#elif defined(SK_BUILD_FOR_WIN) && defined(SK_FONTMGR_DIRECTWRITE_AVAILABLE)
            mgr = SkFontMgr_New_DirectWrite();
#elif defined(SK_FONTMGR_CORETEXT_AVAILABLE) && (defined(SK_BUILD_FOR_IOS) || \
                                                defined(SK_BUILD_FOR_MAC))
            mgr = SkFontMgr_New_CoreText(nullptr);
#elif defined(SK_FONTMGR_FONTCONFIG_AVAILABLE)
            mgr = SkFontMgr_New_FontConfig(nullptr);
#elif defined(SK_FONTMGR_FREETYPE_DIRECTORY_AVAILABLE)
            // In particular, this is used on ChromeOS, which is Linux-like but doesn't have
            // FontConfig.
            mgr = SkFontMgr_New_Custom_Directory(SK_FONT_FILE_PREFIX);
#elif defined(SK_FONTMGR_FREETYPE_EMPTY_AVAILABLE)
            mgr = SkFontMgr_New_Custom_Empty();
#else
            mgr = SkFontMgr::RefEmpty();
#endif
        }
        SkASSERT_RELEASE(mgr);
    });
    return mgr;
}

和Chrome的还是比较类似的,另外还可以看下m104的时候,SkFontMgr::RefDefault是如何实现的?其实是调用了SkFontMgr::Factory(),这个方法是分平台实现的:

$ grep -rI "SkFontMgr::Factory()" # grep in m104
./gn/skia.gni:  # skia_fontmgr_factory should define SkFontMgr::Factory()
./src/core/SkFontMgr.cpp:                                                        : SkFontMgr::Factory();
./src/ports/SkFontMgr_android_factory.cpp:sk_sp<SkFontMgr> SkFontMgr::Factory() {
./src/ports/SkFontMgr_FontConfigInterface_factory.cpp:sk_sp<SkFontMgr> SkFontMgr::Factory() {
./src/ports/SkFontMgr_custom_empty_factory.cpp:sk_sp<SkFontMgr> SkFontMgr::Factory() {
./src/ports/SkFontMgr_mac_ct_factory.cpp:sk_sp<SkFontMgr> SkFontMgr::Factory() {
./src/ports/SkFontMgr_custom_directory_factory.cpp:sk_sp<SkFontMgr> SkFontMgr::Factory() {
./src/ports/SkFontMgr_empty_factory.cpp:sk_sp<SkFontMgr> SkFontMgr::Factory() {
./src/ports/SkFontMgr_fontconfig_factory.cpp:sk_sp<SkFontMgr> SkFontMgr::Factory() {
./src/ports/SkFontMgr_custom_embedded_factory.cpp:sk_sp<SkFontMgr> SkFontMgr::Factory() {
./src/ports/SkFontMgr_win_dw_factory.cpp:sk_sp<SkFontMgr> SkFontMgr::Factory() {

以Android为例,是通过skia_enable_fontmgr_android控制的。那么,修改的思路就比较明确了,其实就是自己去分平台调用接口。

需要注意的是,include是不区分平台的,所以如果某个接口你调用了但Skia构建时没有开启,就可能出现Symbol Undefined错误。

另外遇到一个比较离谱的问题,就是Web平台适配时,发现SkFontMgr_New_Custom_Embedded接口没有对外暴露,之前还通过Factory接口暴露了:

$ grep -rI "SkFontMgr_New_Custom_Embedded"
./skia-m122/src/ports/SkFontMgr_custom_embedded.cpp:sk_sp<SkFontMgr> SkFontMgr_New_Custom_Embedded(const SkEmbeddedResourceHeader* header) {
./skia-m104/src/ports/SkFontMgr_custom_embedded.cpp:sk_sp<SkFontMgr> SkFontMgr_New_Custom_Embedded(const SkEmbeddedResourceHeader* header) {
./skia-m104/src/ports/SkFontMgr_custom_embedded_factory.cpp:sk_sp<SkFontMgr> SkFontMgr_New_Custom_Embedded(const SkEmbeddedResourceHeader* header);
./skia-m104/src/ports/SkFontMgr_custom_embedded_factory.cpp:    return SkFontMgr_New_Custom_Embedded(&SK_EMBEDDED_FONTS);

不过这个接口的参数本身不复杂,可以自己实现一个方法声明如下:

struct SkEmbeddedResource { const uint8_t* data; size_t size; };
struct SkEmbeddedResourceHeader { const SkEmbeddedResource* entries; int count; };
extern "C" const SkEmbeddedResourceHeader SK_EMBEDDED_FONTS;
sk_sp<SkFontMgr> SkFontMgr_New_Custom_Embedded(const SkEmbeddedResourceHeader* header);

再去调用,就能编译并链接过了。实际执行的是SkFontMgr_custom_embedded.cpp内部的实现,这样SkEmbeddedResourceHeader实际就被定义了三次,第3处就是生成的字体文件本身:

$ tail NotoSansSC-Regular.otf.cc
0x54,0xa1,0x61,0x19,0xbf,0xb,0xa7,0x8c,0xa9,0x1b,0xf7,0xc0,0x6,0x87,0xb,0xec,0x95,0xf7,0x6,0x96,0xef,0x93,0x8a,0xb,0xf7,0x7,0x9f,0x8a,0xd8,0x18,0xfb,0xc,
0xb,0x0,0x0,0x0,};
static const size_t resource0_size = 8482020;
struct SkEmbeddedResource { const uint8_t* d; const size_t s; };
static const SkEmbeddedResource header[] = {
  { resource0, resource0_size },
};
static const int header_count = 1;
struct SkEmbeddedHeader {const SkEmbeddedResource* e; const int c;};
extern "C" const SkEmbeddedHeader SK_EMBEDDED_FONTS = { header, header_count };

还是要提个Isssue,让Skia赶紧把这个问题修复了。

总阅读量次。