【HarmonyOS 7新能力|043】可变字体工程封装:把接入逻辑放进可维护的分层结构

可变字体可以在一份字体资源中提供连续字重、宽度或光学尺寸变化,让标题、正文和交互状态获得更细腻的视觉层级。但如果页面直接填写轴值,多语言字形缺失、无障碍缩放、主题切换和设备差异会迅速把样式逻辑打散。工程化目标不是让每个组件"会调字体",而是建立语义样式到字体能力的稳定映射。
说明:文中的
VariableFontPort、轴值配置和代码均为教学抽象,并非 HarmonyOS SDK 的真实接口。可用字体格式、轴能力、注册方式与 ArkUI 属性请以当前官方文档和目标 SDK 为准。
1. 先用语义角色定义排版
页面应该声明标题、正文、标签、数字强调等角色,而不是到处复制字号、字重和字体名。语义角色保持产品层级一致,字体实现可以随语言和设备替换。
ts
export type TextRole = 'display' | 'title' | 'body' | 'label' | 'number'
export interface TypographyRequest {
role: TextRole
locale: string
scale: number
emphasis: 'normal' | 'strong'
availableWidth: number
}
scale 来自可访问性设置,不能为了防止溢出而被页面静默忽略。
2. 四层结构隔离字体细节

业务页面使用语义令牌;排版策略层计算字号、行高和变体意图;字体能力适配层完成注册、轴探测和样式映射;字体与样式仓库维护资源、元数据、语言覆盖与缓存。
ts
export interface VariableFontPort {
inspect(fontId: string): Promise<FontCapability>
resolve(request: FontResolveRequest): Promise<ResolvedFont>
}
export interface TypographyRepository {
getToken(role: TextRole, locale: string): Promise<TypographyToken>
getFallbackChain(locale: string): Promise<readonly string[]>
}
页面不持有字体文件路径,仓库不依赖具体组件,平台变化留在适配器。
3. 注册后先探测能力
同名字体在不同版本中可能包含不同轴。使用前读取支持轴、范围和默认值,生成能力快照;不存在的轴不应强行设置。
ts
export interface VariationAxis {
tag: string
min: number
max: number
defaultValue: number
}
export interface FontCapability {
fontId: string
axes: readonly VariationAxis[]
supportedLocales: readonly string[]
version: string
}
能力快照按字体版本缓存,资源升级后失效。字体解析失败时立即进入回退链。
4. 轴值计算必须归一化
设计令牌可以表达 0 到 1 的视觉强度,适配层再映射到实际轴范围。这样设计系统不依赖某一字体的原始数值。
ts
export function mapAxis(normalized: number, axis: VariationAxis): number {
const value = Math.max(0, Math.min(1, normalized))
return axis.min + (axis.max - axis.min) * value
}
export function axisOrDefault(capability: FontCapability, tag: string, value: number): number {
const axis = capability.axes.find(item => item.tag === tag)
return axis ? mapAxis(value, axis) : 0
}
实际轴标签和语义必须来自字体元数据,不自行假设所有字体都支持相同能力。
5. 可变字体排版流程

流程依次完成字体注册、能力探测、语言识别、轴值计算、样式应用和视觉校验。字体缺失、轴不支持或文本溢出进入回退路径,然后重新计算布局,而不是保留错误样式。
ts
export type FontResolution =
| { status: 'resolved'; font: ResolvedFont }
| { status: 'fallback'; font: ResolvedFont; reason: string }
| { status: 'system'; reason: string }
UI 只消费最终结果,诊断信息则用于定位为何发生回退。
6. 多语言按覆盖范围选择字体
一段文本可能同时包含中文、拉丁字母、数字和符号。语言标签只是第一步,还要确认字体对实际字符有覆盖。缺失字形时按脚本或字符簇使用回退,避免整段突然变成不一致字体。
ts
export interface ScriptRun {
text: string
script: 'Hans' | 'Hant' | 'Latin' | 'Kana' | 'Hangul' | 'Other'
direction: 'ltr' | 'rtl'
}
function selectFamily(run: ScriptRun, chain: readonly FontDescriptor[]): FontDescriptor {
return chain.find(font => font.supports(run.script, run.text)) ?? systemFallback(run.script)
}
复杂脚本的分段和塑形应交给平台能力,不用简单字符循环替代正式排版引擎。
7. 字重变化服务于层级而非装饰
连续字重最适合表达标题层级、选中状态或轻微强调。动画变化需要限制范围和时长,不能让正文持续呼吸式变化影响阅读。
ts
export interface WeightIntent {
base: number
emphasized: number
pressed: number
disabled: number
}
function resolveWeight(intent: WeightIntent, state: ComponentState): number {
if (state.disabled) return intent.disabled
if (state.pressed) return intent.pressed
if (state.selected) return intent.emphasized
return intent.base
}
状态令牌集中维护,所有同类组件得到一致反馈。
8. 无障碍缩放需要重新布局
字体放大不仅改变字号,还影响行高、换行和容器高度。排版策略先应用用户缩放,再根据可用宽度决定行数;重要文本不能用缩小字体来塞进固定框。
ts
export interface TextLayoutPolicy {
minLines: number
maxLines?: number
overflow: 'wrap' | 'ellipsis'
allowContainerGrowth: boolean
}
export function scaledSize(base: number, userScale: number): number {
return base * Math.max(1, userScale)
}
按钮、列表行和弹窗在大字体下都要真机检查,操作区域必须仍然可达。
9. 响应式排版不是等比缩放
手机、平板和 2in1 的阅读距离与列宽不同。宽屏时正文不应无限拉长,而应限制阅读列宽、调整留白;小窗则优先换行和减少装饰,不牺牲正文可读性。
ts
export interface LayoutTypographyContext {
widthClass: 'compact' | 'medium' | 'expanded'
windowWidth: number
userScale: number
locale: string
}
策略根据宽度等级选择令牌集,不在组件中写多个零散断点。
10. 缓存以能力和样式键为边界
字体解析和样式构造可以缓存,但键必须包含字体版本、语言、主题、缩放和语义角色。否则主题或语言切换后可能复用陈旧结果。
ts
export function typographyCacheKey(input: FontResolveRequest): string {
return [input.fontVersion, input.locale, input.theme, input.role,
input.scale, input.emphasis].join('|')
}
缓存只保存解析结果,不长期复制字体大对象。内存告警时允许清理并重新解析。
11. 失败回退必须保持内容可读
字体文件损坏、注册失败或轴设置不支持时,优先回退到覆盖当前语言的系统字体。轴值越界则裁剪到范围,文本仍溢出时允许容器增长、换行或省略非关键文本。
ts
export interface TypographyFailure {
code: 'FONT_MISSING' | 'AXIS_UNSUPPORTED' | 'GLYPH_MISSING' | 'OVERFLOW'
fontId?: string
locale: string
role: TextRole
}
日志不记录完整用户文本,只记录角色、语言、字体版本和错误码。
12. 验收清单与总结
验收覆盖简体中文、繁体中文、英文、日文、韩文及混排,检查缺字、方向、标点、数字和换行;覆盖普通与大字体、深浅主题、手机小窗、平板和 2in1;测试字体缺失、轴不支持与缓存失效。视觉检查关注正文对比度、行长、行高和交互文本是否截断。
可变字体工程化的重点,是把字体轴从页面参数提升为设计系统能力。业务使用语义角色,策略层处理语言和缩放,适配层尊重真实字体轴,仓库提供回退与缓存。即使特定字体或轴不可用,内容仍能正确、清晰地呈现,才是可靠的多语言排版体系。