# Local Font Access API

> window.queryLocalFonts() 在 local-fonts 权限之后以 FontData 对象列出设备上安装的字体。本页给出选项、FontData 成员、全部异常，以及带回退的示例。

`window.queryLocalFonts()` 在 `local-fonts` 权限提示之后，兑现为描述用户设备上已安装字体的 `FontData` 数组。每个 `FontData` 暴露 PostScript 名、全名、家族名、样式名，以及返回原始 SFNT 字节的 `blob()` 方法；设计类工具要用用户自己的字体而不是 Web 字体来排版文字时，需要的正是这些数据。

支持仅限桌面 Chromium：Chrome 103，以及基于它的 Edge 与 Opera 版本。Android 上的 Chrome、Firefox、Safari 的任何版本都没有实现（BCD `api.Window.queryLocalFonts`）；该方法带 `[SecureContext]`，在 `localhost` 以外的 `http://` 源上同样不存在。

## 语法

```js
window.queryLocalFonts()
window.queryLocalFonts(options)

fontData.blob()
```

`queryLocalFonts()` 返回 `Promise<sequence<FontData>>`，按 `postscriptName` 升序排列。`blob()` 返回 `Promise<Blob>`，其 `type` 为 `application/octet-stream`。规范写明浏览器不必报告每一个已安装字体，所以结果是用户代理愿意暴露、并经用户在选择器中（如果浏览器显示选择器）筛选过的集合。

## 参数

`queryLocalFonts()` 接受一个可选的 `options` 参数，类型为只有一个成员的 `QueryOptions` 字典。

| 成员 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `postscriptNames` | `sequence<DOMString>` | 否 | 只返回 PostScript 名在列表内的字体，例如 `["Verdana-Bold", "Arial"]`。空列表什么都不返回；不传该成员则返回全部可选字体。匹配是精确的，带空格的 `"Verdana Bold"` 不会匹配任何字体。 |

返回的每个 `FontData` 有四个只读 `USVString` 属性和一个方法。

| 成员 | 类型 | 说明 |
|---|---|---|
| `postscriptName` | `USVString` | PostScript 名，如 `"Arial-Bold"`；也是结果的排序键。 |
| `fullName` | `USVString` | 家族名加子家族名，如 `"Arial Bold"`。 |
| `family` | `USVString` | CSS `font-family` 使用的家族名，如 `"Arial"`。 |
| `style` | `USVString` | 子家族或样式名，如 `"Regular"`、`"Bold Italic"`。 |
| `blob()` | `Promise<Blob>` | 字体文件字节（SFNT 容器：TrueType、OpenType、WOFF 或 WOFF2）。 |

名称以字体 `name` 表的美式英语或用户语言本地化字符串单一返回；需要其他语言的页面要自己从 `blob()` 解析 `name` 表。

## 异常

`queryLocalFonts()` 以下列 `DOMException` 名称拒绝，顺序即规范的检查顺序。

| 异常 | 条件 |
|---|---|
| `SecurityError` | 文档的源是 opaque origin（例如没有 `allow-same-origin` 的沙箱 iframe）；或文档不允许使用 `local-fonts` 这一受策略控制的特性（默认允许列表为 `'self'`）；或调用时没有瞬时用户激活。 |
| `NotAllowedError` | 用户拒绝了 `local-fonts` 权限提示。 |

`blob()` 没有定义任何拒绝情形。Chromium 增加了规范未列出的拒绝：枚举后端缺失时以 `NotSupportedError` 和 `Not yet supported on this platform.` 拒绝，标签页隐藏时以 `SecurityError` 和 `Page needs to be visible.` 拒绝，`DataError` 和 `Font data exceeds memory limit.`，以及其他后端失败时的 `UnknownError`（权限被拒的情形 Chromium 也另有处理，见实测框）。

:::observed
在 Chrome 中，从定时器而不是点击调用 `queryLocalFonts()` 会以 `SecurityError: User activation is required.` 拒绝；被 Permissions Policy 拦截时抛出 `SecurityError: Access to the feature "local-fonts" is disallowed by Permissions Policy`。权限气泡在「This site would like to:」标题下列出 `Use the fonts on your computer so you can create high-fidelity content`（英文界面）。用户点击 Block 时，Chrome 并不像规范要求的那样以 `NotAllowedError` 拒绝：`font_access.cc` 把 Promise 兑现为空数组（`// Return an empty font list if user has denied the permission request.`），所以页面无法区分「被拒绝」与「设备上没有可选字体」。来源：[`font_access.cc`](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/modules/font_access/font_access.cc)（chromium.googlesource.com）与 [`permissions_strings.grdp`](https://chromium.googlesource.com/chromium/src/+/main/components/permissions_strings.grdp)（chromium.googlesource.com）。
:::

## 示例

两个示例都在 `click` 处理函数中运行，先检测 `window` 上的 `queryLocalFonts`，API 缺失或用户拒绝时回退到页面自带的字体。

### 用设备字体填充字体选择器，并带一份内置回退列表

回退列表就是页面已经加载的 Web 字体，这样在 Firefox、Safari 或移动端 Chrome 中选择器也不会为空。鉴于上文所述的拒绝行为，Chrome 返回空结果时按同样方式处理。

```js
const BUNDLED = ['Inter', 'Source Serif 4', 'JetBrains Mono'];

async function listFontFamilies() {
  if (!('queryLocalFonts' in window)) return BUNDLED;

  try {
    const fonts = await window.queryLocalFonts();
    if (fonts.length === 0) return BUNDLED; // Chrome 中被拒绝，或没有可选字体
    return [...new Set(fonts.map((f) => f.family))];
  } catch (err) {
    if (err.name === 'SecurityError' || err.name === 'NotAllowedError') return BUNDLED;
    throw err;
  }
}

document.querySelector('#pick-fonts').addEventListener('click', async () => {
  const select = document.querySelector('#font-family');
  select.replaceChildren(...(await listFontFamilies()).map((name) => new Option(name)));
});
```

按 `family` 去重是必要的，因为一个家族的每种字重和样式都是单独一个 `FontData`：`"Arial"`、`"Arial-Bold"`、`"Arial-Italic"`、`"Arial-BoldItalicMT"` 是共享家族名 `"Arial"` 的四个条目。

### 渲染文档前确认某个特定字体是否存在

`postscriptNames` 把提示和结果都收窄到你关心的字体。字体缺失时改用文档内嵌的 Web 字体渲染，并告知用户版面可能有差异。

```js
async function hasLocalFont(postscriptName) {
  if (!('queryLocalFonts' in window)) return false;
  try {
    const matches = await window.queryLocalFonts({ postscriptNames: [postscriptName] });
    return matches.length === 1;
  } catch {
    return false; // 没有手势、被策略拦截，或用户拒绝
  }
}

document.querySelector('#open-document').addEventListener('click', async () => {
  const useLocal = await hasLocalFont('Verdana-Bold');
  document.body.style.fontFamily = useLocal ? '"Verdana"' : '"Verdana Web", sans-serif';
  document.querySelector('#font-note').hidden = useLocal;
});
```

只有当你必须自己读取字形轮廓或 `name` 表时（canvas 文本编辑器、字体检查器）才需要 `blob()`；CSS 按家族名引用已安装字体并不需要它。

## 另请参阅

- [File System Access API：读写本地文件](/zh/reference/capabilities/file-system-access/)，另一个每次调用都要用户手势的桌面 Chromium 文件级能力
- [字体与图片优化：font-display、picture 与 AVIF/WebP](/zh/reference/performance/fonts-images/)，Web 字体回退路径
- [桌面上的 PWA](/zh/reference/platforms/desktop/)
- [Local Font Access API: queryLocalFonts() method](https://wicg.github.io/local-font-access/#dom-window-querylocalfonts)（wicg.github.io）
- [Local Font Access API: local-fonts permission](https://wicg.github.io/local-font-access/#permissiondef-local-fonts)（wicg.github.io）
- [Window: queryLocalFonts() browser compatibility](https://developer.mozilla.org/en-US/docs/Web/API/Window/queryLocalFonts#browser_compatibility)（developer.mozilla.org）