这篇做一个字体实验室:把 OTF 字体放进 rawfile,注册成 InterDisplay,再用 TextStyle.fontFamilies 切换自定义字体、系统字体和中英混排回退链。先看真机动态效果,再看实现。

真机结果是一个完整的字体预览卡片:英文标题使用注册的 Inter 字体,中文和 Emoji 在字体缺字时由 HarmonyOS Sans 接管;页面底部显示当前段落宽度、行数和绘制次数。

先看结果

注册字体

进入首页的“实验 24:FONTLAB 字体与回退实验室”,默认模式是 Inter Display。状态条显示 InterDisplay 已注册,画布中显示 FONT COLLECTION / FALLBACK CHAIN

切换回退链

点击“Fallback Mix”,英文标题继续使用 InterDisplay,中文说明和 Emoji 沿回退链查找系统字体,卡片宽度和换行结果由 Paragraph 重新计算。

暂停和恢复

点击“暂停预览”后画布保留在当前相位,状态变为 PAUSED;点击“继续预览”后光标和进度条继续移动。

准备

  • DevEco Studio,API 26 工程。
  • 一台已连接的 HarmonyOS 真机;本次设备为 HUAWEI Mate 60 Pro,HarmonyOS 7.0。
  • 一份 TTF 或 OTF 字体。本次把 Inter-Regular.otf 放到 entry/src/main/resources/rawfile/
  • 页面入口:entry/src/main/ets/features/graphics2d/CustomFontFallbackPage.ets

官方文本能力说明:FontCollection 支持通过 loadFontSync() 注册字体,注册名需要写入 TextStyle.fontFamilies 才会参与排版。

实施过程

1. 把字体放入 rawfile

工程目录如下:

entry/src/main/resources/rawfile/Inter-Regular.otf

rawfile 会随 HAP 一起打包,运行时不需要读取电脑路径,也不需要网络下载字体。

2. 注册字体别名

页面出现时用 FontCollection 注册别名。这里使用同步接口,注册完成后再启动 Canvas 动画:

const FONT_ALIAS: string = 'InterDisplay';
const collection: text.FontCollection = text.FontCollection.getGlobalInstance();

collection.loadFontSync(FONT_ALIAS, $rawfile('Inter-Regular.otf'));
hilog.info(DOMAIN, TAG,
  'FONT_READY alias=%{public}s rawfile=true fallback=true expectedFps=%{public}d',
  FONT_ALIAS, 30);

如果字体文件不存在,loadFontSync() 会抛出异常,页面状态改为 FONT LOAD ERROR,不会把“已注册”写成成功结果。

3. 设置字体链

三个按钮实际对应三组 fontFamilies

const families: Array<string> = modeIndex === 1
  ? ['HarmonyOS Sans']
  : ['InterDisplay', 'HarmonyOS Sans'];

const paragraphStyle: text.ParagraphStyle = {
  textDirection: text.TextDirection.LTR,
  align: text.TextAlign.LEFT,
  wordBreak: text.WordBreak.BREAK_WORD,
  breakStrategy: text.BreakStrategy.BALANCED,
  maxLines: 4,
  lineSpacing: 12,
  textStyle: {
    color: this.color(0xFFEAF4FF),
    fontSize: 24,
    fontWeight: text.FontWeight.W400,
    fontFamilies: families,
    locale: 'zh-Hans'
  }
};

InterDisplay 没有某个中文字符时,Paragraph 会继续尝试 HarmonyOS Sans。因此英文和中文可以留在一个 Paragraph 中,不需要手工判断每个字符属于哪套字体。

4. 混排标题、中文和数字

标题和正文仍然通过 pushStyle() 分层加入,局部样式共用同一组字体链:

builder.pushStyle({
  color: this.color(0xFF69F4D5),
  fontSize: 38,
  fontWeight: text.FontWeight.W700,
  fontFamilies: families,
  letterSpacing: 1.5,
  locale: 'en-Latn'
});
builder.addText('INTER DISPLAY');
builder.popStyle();

builder.pushStyle({
  color: this.color(0xFFD6E4F4),
  fontSize: 23,
  fontFamilies: families,
  locale: 'zh-Hans'
});
builder.addText('\n标题使用注册字体,中文与 Emoji 交给回退链。');
builder.addText('\nHarmonyOS 7  ·  API 26  ·  2026');
builder.popStyle();

5. 在 RenderNode 中测量并绘制

每次 RenderNode.draw() 都重新布局当前宽度,再将 Paragraph 绘制到 Canvas,并把真实宽度和行数同步到页面:

paragraph.layoutSync(contentWidth);
paragraph.paint(canvas, x, y);

const width: number = paragraph.getMaxWidth();
const lines: number = paragraph.getLineCount();
this.metrics = `宽度 ${Math.round(width)} px  ·  ${lines} 行`;

真机默认模式记录 宽度 963 px · 4 行;切换回退链后仍然保持 4 行,但段落中英文字符的字形来源发生变化。

6. 卸载字体

字体不能在页面退出后一直挂在全局集合中。退出时先取消 Animator、释放 RenderNode,再卸载别名:

private releaseAll(reason: string): void {
  if (this.released) {
    return;
  }
  this.released = true;
  this.animator?.cancel();
  this.controller.release();
  if (this.loaded) {
    text.FontCollection.getGlobalInstance().unloadFontSync(FONT_ALIAS);
    this.loaded = false;
  }
  hilog.info(DOMAIN, TAG, 'FONT_RELEASE reason=%{public}s', reason);
}

退出后的首页结果:

遇到的情况与处理

自定义字体注册成功但没有生效

只调用 loadFontSync() 不够,Paragraph 的 TextStyle.fontFamilies 还必须写入同一个别名。本实验把 InterDisplay 放在字体链第一位,英文标题即可看到注册字体效果。

中文显示成方框

Inter-Regular 只覆盖拉丁字符,缺少中文时不能把它作为唯一字体。将 HarmonyOS Sans 放在第二位后,中文和 Emoji 能继续由系统字体绘制,这就是本实验的回退模式。

重进页面后字体状态异常

FontCollection 是全局集合,页面退出不卸载会影响下一次实验。现在进入时注册、退出时 unloadFontSync(),每次重新进入都从干净状态开始。

真机 Hilog

包名、进程号和时间戳已删除,只保留本次验证的关键字段:

FONT_LAB_ENTER modes=inter,system,fallback expectedFps=30 permissionRequired=false
FONT_READY alias=InterDisplay rawfile=true fallback=true expectedFps=30
FONT_DRAW mode=0 drawCount=30 width=1174 paragraphWidth=963 lines=4 alias=InterDisplay
FONT_MODE mode=2 name=Fallback Mix
FONT_PAUSE mode=2
FONT_RESUME mode=2
FONT_RELEASE reason=EXIT_BUTTON

FONT_READY 证明 rawfile 注册成功,FONT_MODE 证明回退模式切换成功,FONT_PAUSE/RESUME 与页面状态截图对应,FONT_RELEASE 证明退出时完成清理。

验证结果

验证项真机结果
rawfile 注册InterDisplay 注册成功
英文标题使用 InterDisplay 绘制
中文与 EmojiHarmonyOS Sans 回退绘制
段落布局paragraphWidth=963,4 行
动画控制暂停后保持,恢复后继续
资源释放退出日志记录 FONT_RELEASE,字体别名卸载

本次文件

entry/src/main/ets/features/graphics2d/CustomFontFallbackPage.ets
entry/src/main/ets/pages/Index.ets
entry/src/main/resources/rawfile/Inter-Regular.otf
articles/第二十二篇-ArkGraphics 2D自定义字体实战/
docs/qa/fontlab-22/动态原始帧/
articles/第二十二篇-ArkGraphics 2D自定义字体实战/真机验证日志.txt

动态 GIF 使用 24 帧真机连续画面制作,规格为 720×610、8 FPS、3 秒;静态图保留完整手机界面,便于核对模式、暂停状态和实时数据。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐