1. 简介
本规范描述了一套用于网页浏览器的字体枚举 API,浏览器可选择性地允许用户授予对全部可用系统字体集的访问权限。对于每种字体,可以对 SFNT [SFNT] 容器或等效项进行底层(字节级)访问,从而获取完整的字体数据。
Web 开发者过去只能通过启发式信息了解哪些本地字体可用于页面内容样式,通常会在 CSS 中包含复杂的 font-family
列表,用以启发式地控制字体回退。为设计师生成合适的回退方案极为复杂,因而出现了帮助“目测”可能可用本地字体匹配项的工具。
字体枚举能够帮助:
-
提升用户生成内容的样式选择。
-
匹配现有内容声明的字体。
尽管 Web 起源于以文本为主,并且用户代理对高质量排版的支持非常完善,但在某些类型的 Web 应用场景下仍然有限制:
-
系统字体引擎(及浏览器栈)在显示某些字形时可能会有所不同。这种差异通常是必要的,以确保与底层操作系统的一致性(避免网页内容“看起来不对”)。但它也会降低跨平台应用的一致性,比如在需要像素级精确布局和渲染的场景中。
-
设计工具需要访问字体数据,从而以平台无关的方式完成自身布局,以及实现字形矢量滤镜或变换等操作。
-
开发者可能会基于度量或主题提供字体选择界面,或基于度量和其他数据实现自动字体匹配,这些都需要直接访问字体数据。
-
有些字体未获得网络分发许可。例如,Linotype 某些字体的许可仅限于桌面使用。
高品质的设计和图形工具在 Web 平台上一直较难实现,这些工具通常将丰富的排版特性和控制作为核心能力。
本 API 使这些工具能够访问与浏览器布局及光栅化引擎用于绘制文本时一致的底层字体数据。例如用于字形矢量数据的 OpenType glyf 表、用于字形定位的 GPOS 表,以及用于连字和其他字形替换的 GSUB 表。这些信息对于上述工具来说,既能保证输出结果的跨平台性(通过嵌入矢量描述而非码位),也能支持以字体为基础的艺术创作(将字体作为可操作形状的基础)。
2. 目标
该 API 应当:
-
高效枚举所有本地字体且不会阻塞主线程
-
确保用户代理可以返回任何内容。如果浏览器实现偏好,允许只提供内置默认字体集。
-
可在 Workers 环境下使用
-
允许多级隐私保护,例如“受信任”网站可完全访问,“不受信任”场景降级访问
-
在权限 API 中反映本地字体访问状态
-
为字体族和实例(如“bold”“italic”变体)提供唯一标识,包括 PostScript 名称
-
实现内存高效,设计避免数据泄漏与多余拷贝
-
默认通过 Permissions Policy 规范将本地字体数据访问限制为安全上下文且仅限顶层框架
-
按字体名称排序结果列表,减少指纹识别熵位;如 .queryLocalFonts() 返回的可迭代对象按字体名称排序
-
允许访问字体的原始字节数据。多数场景是为仅消耗完整字体文件的现有库提供字体数据,仅提供预解析字体表会导致开发者需要重新组装完整数据 blob 才能使用此类库。
虽然上文目标中提到 Worker 支持,但目前该 API 按规范只暴露于 Window 环境。
3. 示例
本节为非规范性内容。
3.1. 枚举本地字体
该 API 允许脚本枚举本地字体,包括每种字体的相关属性。
showLocalFontsButton. onclick= async function () { try { const array= await self. queryLocalFonts(); array. forEach( font=> { console. log( font. postscriptName); console. log( ` full name: ${ font. fullName} ` ); console. log( ` family: ${ font. family} ` ); console. log( ` style: ${ font. style} ` ); }); } catch ( e) { // 处理错误,例如用户取消了操作。 console. warn( `Local font access not available: ${ e. message} ` ); } };
3.2. 使用本地字体样式化
高级创作工具可以让用户用所有可用本地字体对文本进行样式化。在此场景下,获取本地字体名称能够提供更丰富的选择:
以下代码将可用本地字体填充到下拉选择框,可用于编辑类应用的用户界面。
useLocalFontsButton. onclick= async function () { try { // 查询允许访问的本地字体。 const array= await self. queryLocalFonts(); // 创建用于样式化的元素。 const exampleText= document. createElement( "p" ); exampleText. id= "exampleText" ; exampleText. innerText= "The quick brown fox jumps over the lazy dog" ; exampleText. style. fontFamily= "dynamic-font" ; // 创建字体选择列表及响应处理。 const textStyle= document. createElement( "style" ); const fontSelect= document. createElement( "select" ); fontSelect. onchange= e=> { const postscriptName= fontSelect. value; console. log( "selected:" , postscriptName); // 使用 @font-face src: local 匹配进行样式化的示例。 textStyle. textContent= ` @font-face { font-family: "dynamic-font"; src: local(" ${ postscriptName} "); }` ; }; // 用所有可用字体填充选择列表。 array. forEach( font=> { const option= document. createElement( "option" ); option. text= font. fullName; // postscriptName 可用于 @font-face src: local 对元素样式化。 option. value= font. postscriptName; fontSelect. append( option); }); // 将所有元素添加至页面。 document. body. appendChild( textStyle); document. body. appendChild( exampleText); document. body. appendChild( fontSelect); } catch ( e) { // 处理错误,例如用户取消了操作。 console. warn( `Local font access not available: ${ e. message} ` ); } };
3.3. 访问字体数据
该 API 允许脚本请求字体数据。
这里用枚举来获取本地字体数据,可以解析具体的字体表,也可输入到如 HarfBuzz 或 Freetype 的 WASM 版本:
useLocalFontsButton. onclick= async function () { try { const array= await self. queryLocalFonts(); array. forEach( font=> { // blob() 返回包含字体字节的 Blob。 const bytes= await font. blob(); // 检查前四个字节,SFNT 用于定义格式。 // 规范: https://docs.microsoft.com/en-us/typography/opentype/spec/otff#organization-of-an-opentype-font const sfntVersion= await bytes. slice( 0 , 4 ). text(); let outlineFormat= "UNKNOWN" ; switch ( sfntVersion) { case '\x00\x01\x00\x00' : case 'true' : case 'typ1' : outlineFormat= "truetype" ; break ; case 'OTTO' : outlineFormat= "cff" ; break ; } console. log( ` ${ font. fullName} outline format: ${ outlineFormat} ` ); } } catch ( e) { // 处理错误。可能是权限错误。 console. warn( `Local font access not available: ${ e. message} ` ); } };
更详细解析字体文件,比如枚举包含的全部表现,超出本规范范围。
3.4. 请求特定字体
在某些场景下,Web 应用可能希望请求访问特定字体。例如呈现之前创作内容时需要嵌入字体名称。queryLocalFonts() 方法可接收
postscriptNames 参数,仅返回那些 PostScript 名称完全匹配的字体。
用户代理可能会采用不同的 UI 支持这些操作。例如,如果指纹风险很低,该请求可无需用户授权直接满足。也可能弹出字体选择器,仅显示请求的字体。
// 需要用户激活。 requestFontsButton. onclick= async function () { try { const array= await self. queryLocalFonts({ postscriptNames: [ 'Verdana' , 'Verdana-Bold' , 'Verdana-Italic' ]}); array. forEach( font=> { console. log( `Access granted for ${ font. postscriptName} ` ); }); } catch ( e) { // 处理错误。可能是权限错误。 console. warn( `Local font access not available: ${ e. message} ` ); } };
4. 概念
用户语言是一个有效的 BCP 47 语言标签,代表可能的语言或用户最偏好的语言。[BCP47]
4.1. 字体表示
字体表示是字体的某种具体表现。例如 OpenType、TrueType、位图字体、Type1 字体、SVG 字体以及未来的字体格式。本规范定义字体表示的属性包括:
-
数据字节,即包含字体序列化内容的字节序列。
注意:一个字体表示的数据字节一般应为用户文件系统上的字体文件逐字节一致的内容。用户代理不需要规范化字体数据,因此同一用户在某一操作系统上的字体表示不会因用户代理不同而变化。不规范化有助于实现 Web 应用在内容创作时文本渲染的完整字体保真。
-
PostScript 名称在字体的name 表,nameID = 6 处。如果包含多语言版本,优先取美国英语或首个版本。
这些名称属性参照 [CSS-FONTS-4] 中的属性用法,例如 @font-face、font-family 等。PostScript 名称可作为唯一键(例如指定或匹配内容时),全称和家族名称用于用户可见的字体选择 UI,样式名称可用于更具体选择。
有效的 PostScript 名称是标量值字符串,长度小于 64,只包括 U+0021(!)到 U+007E(~)范围的字符,但排除以下 10 个码位: U+005B ([), U+005D (]), U+0028(左括号), U+0029(右括号), U+007B({), U+007D(}), U+003C(<), U+003E(>), U+002F(/), U+0025(%)。
注意:此要求旨在符合 nameID = 6 在 [OPENTYPE] 中的要求。
4.2. 系统字体
系统字体是由操作系统提供并在系统范围可用的字体。
将系统字体作为字体表示读取,即生成与字体等价的字体表示,需提供全部字体表示属性:
-
数据字节。
-
PostScript 名称(需为有效值)。
-
全称。
-
家族名称。
-
样式名称。
如果无法提供字体表示,则该操作可能失败。
用户代理可使用任何算法为系统字体生成字体表示。实践中,现代操作系统和系统 API 支持以 SFNT [SFNT] 字体文件格式存储的字体,如 OpenType、TrueType、Web Open Font Format 等,满足这些要求,也可以高效枚举字体集合,并同时提供每个字体的常用名称属性。
5. 权限集成
枚举本地字体需要获得授权。
5.1. 权限
本地字体访问 API 是一个默认高级功能,由名称 name
"local-fonts" 标识。
调用 queryLocalFonts()
API 时,用户代理可以呈现字体选择列表、是/否选项,或其他界面方式。用户代理应以合适方式呈现选择结果到权限上。例如,若用户选择了要暴露给站点的字体集且后续 API 调用会返回该集,则权限状态可为
"granted";若会再次提示用户,则权限状态可为 "prompt"。
navigator.permissions API
查询枚举本地字体的权限:
// 此操作仅查询现有权限状态,不会更改它。 const status= await navigator. permissions. query({ name: "local-fonts" }); if ( status. state=== "granted" ) console. log( "permission was granted 👍" ); else if ( status. state=== "prompt" ) console. log( "permission will be requested" ); else console. log( "permission was denied 👎" );
5.2. 权限策略
本规范定义了用字符串 "local-fonts" 标识的策略受控功能。其 默认许可列表为 'self'。
'self'
允许此功能默认在同源嵌套框架中使用,但禁止第三方内容访问。
可以通过在
iframe
元素添加 allow="local-fonts" 属性,选择性允许第三方使用:
也可以通过 HTTP 响应头声明权限策略,完全禁止第一方环境使用:
详情见 [PERMISSIONS-POLICY]。
6. API
6.1. 字体任务源
字体任务源是一个新的通用任务源,用于本规范中排队的所有任务。
6.2. 字体管理器
- await self .
queryLocalFonts()- await self .
queryLocalFonts({postscriptNames: [ ... ] }) - await self .
-
异步查询可用/允许的字体。成功时,返回的 Promise 解析为一组
FontData对象。如果方法调用时文档未处于 临时激活(如响应点击事件),则返回的 Promise 将被拒绝。
用户将被提示授予本地字体访问权限或选择要提供给站点的字体。如果权限未获得,Promise 也会被拒绝。
如果给定
postscriptNames选项,则仅返回 PostScript 名称匹配的字体。
[SecureContext ]partial interface Window {Promise <sequence <FontData >>queryLocalFonts (optional QueryOptions = {}); };options dictionary {QueryOptions sequence <DOMString >; };postscriptNames
queryLocalFonts(options) 方法步骤如下:
-
令 promise 为新的 Promise。
-
令 descriptor 为
PermissionDescriptor,其name设置为"local-fonts"。 -
如果 this 的相关设置对象的来源是 不透明来源,则用 "
SecurityError"DOMException拒绝 promise,并返回 promise。 -
如果 this 的相关全局对象的关联文档未被允许使用名为
"local-fonts"的策略受控功能,则用 "SecurityError"DOMException拒绝 promise,返回。 -
如果 this 的相关全局对象无临时激活,则用 "
SecurityError"DOMException拒绝 promise,返回。 -
否则,并行运行以下步骤:
-
令 system fonts 取 所有系统字体表示。
-
令 selectable fonts 为新列表。
-
遍历 system fonts 中每个字体 representation,执行:
-
令 postscriptName 为 representation 的PostScript 名称。
-
断言:postscriptName 是有效的 PostScript 名称。
-
如果 options[
"postscriptNames"] 存在且不包含 postscriptName,则跳过。
-
-
提示用户选择 selectable fonts 中一个或多个条目,descriptor 和 allowMultiple 设置为 true,令 result 为结果。 用户代理可仅返回全部字体列表,而不用显示选择列表。
-
若 result 为
"denied",则用 "NotAllowedError"DOMException拒绝 promise,并中止。 -
用
postscriptName升序排序 result,并以排序结果存储 result。 -
在字体任务源上队列任务,将 result 解析到 promise。
-
-
返回 promise。
移至 WindowOrWorkerGlobalScope
并理清权限问题。
6.3. FontData
接口
FontData
提供有关字体样式的详细信息。每个 FontData 都有一个对应的
字体表示。
- fontdata .
postscriptName -
字体的 PostScript 名称。例如:“
Arial-Bold”。 - fontdata .
fullName -
字体全名,包括家族和子家族。例如:“
Arial Bold” - fontdata .
family -
字体家族名。对应 CSS 的font-family 属性。例如:“
Arial” - fontdata .
style -
字体风格(或子家族)名称。例如:“
Regular”、“Bold Italic”
[Exposed =Window ]interface {FontData Promise <Blob >blob (); // 名称readonly attribute USVString postscriptName ;readonly attribute USVString fullName ;readonly attribute USVString family ;readonly attribute USVString style ; };
postscriptName getter 步骤如下:
-
令 postscriptName 为此对象的PostScript 名称。
-
断言:postscriptName 是有效的 PostScript 名称。
-
返回 postscriptName。
fullName getter 步骤为返回此对象关联 字体表示的全名。
考虑让 FontData 成为可序列化对象,以便 queryLocalFonts()
的结果可传递给 Workers。
blob() 方法步骤如下:
7. 国际化注意事项
记录除字符串本地化之外的国际化注意事项,例如 https://github.com/WICG/local-font-access/issues/72、https://github.com/WICG/local-font-access/issues/59 等。
7.1. 字体名称
在 OpenType
字体中,`name` 表允许名称(家族、子家族等)拥有多语言字符串,可用平台相关的数字语言标识符,或符合 [BCP47] 的语言标签字符串。例如,一个字体可以为
`en-US` 和 `zh-Hant-HK` 同时定义家族名称字符串。
本 API 提供的 FontData
属性 postscriptName、
fullName、
family、
和 style
都是以字符串形式提供的,根据名称会优先使用美国英语本地化或用户语言本地化,否则回退为第一个本地化版本。
Web 应用如需其它语言的名称,可以直接请求和解析 `name` 表。
是否应为 queryLocalFonts()
方法定义参数来指定字符串的目标语言(例如 {lang: 'zh'}),未找到时回退至 `en-US`?或者提供全部名称的访问,例如以 [BCP47] 语言标签为键的
name 映射?[Issue #69]
8. 无障碍注意事项
当前没有发现该功能会影响无障碍访问。
9. 安全性注意事项
当前没有发现该功能会影响安全性。
10. 隐私注意事项
10.1. 指纹识别
字体数据包括:
-
操作系统发行版自带的字体。
-
系统中安装的特定应用(如办公套件)安装的字体。
-
系统管理员和/或最终用户直接安装的字体。
-
通过字体数据获得的系统中已安装字体的版本号。
这些都可以为区别用户提供若干“熵位”。
用户代理可以在特定场景下进行缓解(如权限被拒绝或在隐私浏览/“无痕”模式下),例如只枚举一组随用户代理提供的固定字体集合。
用户代理还可以允许用户选择通过 API 公开给网站的一组字体。
当字体提供多种本地化名称,用户的区域设定信息可能通过字体名称被暴露。用户代理如暴露区域设定信息,应保证其与
navigator.
所暴露的区域设定一致。
language
10.2. 身份识别
某个组织的用户可能安装了特定字体。例如,“示例公司”的员工可能系统管理员为他们安装了“Example Corporate Typeface”,可通过网站将其识别为该企业员工。
有些服务可根据手写样本生成字体。如果这些字体被命名为包含个人信息(如 "Alice’s Handwriting Font"),则个人信息可能被暴露。当这些信息包含在字体属性中而不仅是字体名称时,用户可能并不知情。
11. 致谢
我们感谢以下人员的贡献:
-
Daniel Nishi、Owen Campbell-Moore 和 Mike Tsao,帮助推动了之前的本地字体访问提案。
-
Figma 的 Evan Wallace、Biru、Leah Cassidy、Katie Gregorio、Morgan Kennedy 和 Noah Levin,耐心梳理了他们创新网页产品的需求。
-
Alex Russell,初稿作者。
-
Olivier Yiptong,提供了 API 的初始实现和迭代。
-
Tab Atkins, Jr. 和 CSS 工作组,提供了可用基类,只需少量扩展即可支持本规范场景。
-
Dominik Röttsches 和 Igor Kopylov,提出了有价值的意见。
-
我们特别感谢已故前编辑 Emil A. Eklund,他于 2020 年离世。Emil 在推动本提案、提供技术指导以及倡导用户和开发者需求方面发挥了重要作用。
再次特别感谢 Tab Atkins, Jr.,他开发并维护了 Bikeshed,即本规范使用的文档编写工具。
同时感谢 Anne van Kesteren、 Chase Phillips、 Domenic Denicola、 Dominik Röttsches、 Igor Kopylov、 Jake Archibald 和 Jeffrey Yasskin 提供了建议、评审和其他反馈。