Web 优化之:定制 Flutter Engine 字体回滚策略

介绍

Flutter Web 目前(3.8.0-16.0.pre.21)采用的字体回滚策略是,若当前使用使用的字体不包含需要渲染的文字,则回滚到后备字体。默认的后备字体为 Roboto,另外还有通过网络加载的 Noto Fonts 系列后备字体。

由于中文字体文件体积较大,Flutter Web 默认使用的字体文件 Roboto 又不含中文字体,因此在渲染中文时会回滚到网络后备字体(大概 8M)。常见的优化方式是通过字体提取工具提取出需要的字体,从而减小字体文件体积。问题正是出自这里,字体提取工具提取出的字体文件并不包含所有字体,因此在渲染时可能出现因缺少某个单一文字便回滚到网络后备字体的情况,这近乎直接致使字体提取带来的优化付之东流。

本文主要讲述如何对字体回滚策略进行修改,在字体文件缺少某些文字字体时在 Console 及终端打印相应警告提醒开发者对字体文件进行补充,从而避免回滚到网络后备字体。

实践

  1. 创建工程目录
1
2
mkdir -p $HOME/Projects/flutter_engine
cd $HOME/Projects/flutter_engine
  1. 设置网络代理
1
2
git config --global http.proxy http://192.168.1.136:10809
git config --global https.proxy http://192.168.1.136:10809
1
2
3
4
5
6
cat >$PWD/.boto.cfg<EOF
[Boto]
proxy=192.168.1.136
proxy_port=10809
EFO
export NO_AUTH_BOTO_CONFIG=$PWD/.boto.cfg
  1. 安装 depot_tools 工具
1
2
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git tools/depot_tools
export PATH="$PATH:$PWD/tools/depot_tools"
  1. 下载 Flutter Engine 源码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
cat >$PWD/.gclient<EOF
solutions = [
  {
    "managed": False,
    "name": "src/flutter",
    "url": "git@github.com:flutter/engine.git",
    "custom_deps": {},
    "deps_file": "DEPS",
    "safesync_url": "",
    "custom_vars": {
      "download_emsdk": True,
    },
  },
]
EFO
1
gclient sync

在 Windows 上,gclient sync 由于这个问题必须以管理员身份运行。另外,避免中断此脚本(gclient sync),因为这样做会使存储库处于不一致的状态,清理起来很乏味。

  1. 切换 Flutter Engine 版本

这一步主要是找到并将 Engine 切换至与当前 Flutter 对应的版本。可以通过 flutter --version 查看当前 Flutter 版本,例如以下输出:

1
2
3
4
Flutter 3.8.0-16.0.pre.21 • channel master • https://github.com/flutter/flutter.git
Framework • revision 56e1bddc59 (3 days ago) • 2023-02-23 12:12:11 -0500
Engine • revision 19cf8e363f
Tools • Dart 3.0.0 (build 3.0.0-266.0.dev) • DevTools 2.22.1

上述输出中,Engine 版本为 19cf8e363f,因此我们需要将 Engine 切换至此版本:

1
2
cd src/flutter
git reset --hard 19cf8e363f

如果后续遇到很离奇的问题,可以尝试执行 gclient sync --with_tags 重新同步代码。

  1. 设置环境变量
1
export PATH="$PATH:$PWD/src/flutter/lib/web_ui/dev"
  1. 修改源码

修改当前目录下 src/flutter/lib/web_ui/lib/src/engine/canvaskit/font_fallbacks.dart 文件的第 214 行。

findFontsForMissingCodeunits(codeUnits); 替换为如下代码:

1
2
3
4
5
// 此行为新增代码
printWarning('contains unsupported code units: '
  '$codeUnits (${String.fromCharCodes(codeUnits)})');

findFontsForMissingCodeunits(codeUnits);

随着 Flutter Engine 版本的更新,此处的行号可能会发生变化,因此请以实际情况为准。此处提供的是 3.8.0-16.0.pre.21 版本的行号。

  1. 编译引擎
1
felt build
  1. 运行测试
1
2
cd path/to/some/app
flutter --local-web-sdk=wasm_release --local-engine-src-path=$HOME/Projects/flutter_engine/src run -d chrome --web-hostname 0.0.0.0 --web-port 9000 --web-renderer canvaskit

注意将 path/to/some/app 替换你的 Flutter Web 项目的实际路径。

演示

从下面演示图片中可以看出,通过修改字体回滚策略部分的代码,可以实现在 Console 中打印文字缺少字体的警告信息。

此处缺少的字体的文字为中文符号 ,编码为 65306

image

补全字体文件后,再次运行测试,可以看到 Console 中不再有警告信息。

image

番外

由于原理相同,本文提及的方法通过简单修改也可实现诸如缺少字体时通过 API 进行上报或强行阻止字体回滚等操作。通过 API 上报缺失字体的文字,配合后端可实现自动补充并更新字体文件的功能,可以最小化开发者的工作负担。

另外,在此提供一个 Git Patch 文件,可直接应用于 Flutter Engine 3.8.0-16.0.pre.21 源码,以实现本文所述的功能。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
From a44f4c0239f1575a910477d0cae5a73d669f7e34 Mon Sep 17 00:00:00 2001
From: zeronumber <main@woini.men>
Date: Sun, 26 Feb 2023 23:07:41 +0800
Subject: [PATCH] [web] add unsupported code units warning

---
 lib/web_ui/lib/src/engine/canvaskit/font_fallbacks.dart | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/lib/web_ui/lib/src/engine/canvaskit/font_fallbacks.dart b/lib/web_ui/lib/src/engine/canvaskit/font_fallbacks.dart
index 90c2fb31d1..9734cc859f 100644
--- a/lib/web_ui/lib/src/engine/canvaskit/font_fallbacks.dart
+++ b/lib/web_ui/lib/src/engine/canvaskit/font_fallbacks.dart
@@ -210,6 +210,10 @@ class FontFallbackData {
         codeUnits.removeAt(i);
       }
     }
+
+    printWarning('contains unsupported code units: '
+        '$codeUnits (${String.fromCharCodes(codeUnits)})');
+
     findFontsForMissingCodeunits(codeUnits);
   }
 
-- 
2.34.1

参考资料