在 DOCX 导出中嵌入自定义字体
Word 只会渲染读者设备上已安装的字体,除非该字体被嵌入到文档本身中。@tiptap-pro/extension-export-docx 可以将内容使用的字体一并嵌入,因此导出的 .docx 在任何地方看起来都一样,即使那些字体未安装也是如此。
有两种方式可以实现:
只会嵌入常规字重
DOCX 字体嵌入会携带每个字体族的常规(直立、400 字重)字形。Word 会基于它合成粗体和斜体,因此你不需要(也无法通过此 API)单独嵌入粗体/斜体字体文件。
手动嵌入
传入一个 fonts 数组,每种字体族一个条目,每个条目包含其原始 TTF 或 OTF 字节。字体会被混淆后写入文档包(根据 OOXML 规范为 word/fonts/font<N>.odttf),并绑定到使用匹配族名称的每个文本片段。
import { ExportDocx } from '@tiptap-pro/extension-export-docx'
// 在浏览器中:获取字体并将其 ArrayBuffer 直接交给导出。
const data = await fetch('/fonts/PlayfairDisplay-Regular.ttf').then((response) =>
response.arrayBuffer(),
)
editor
.chain()
.exportDocx({
fonts: [{ name: 'Playfair Display', data }],
})
.run()在服务器上,则改为将文件读入 Buffer:
import { readFile } from 'node:fs/promises'
import { exportDocx } from '@tiptap-pro/extension-export-docx'
const data = await readFile('./fonts/PlayfairDisplay-Regular.ttf')
await exportDocx({
document: editorJSON,
exportType: 'buffer',
customNodes: [],
styleOverrides: {},
fonts: [{ name: 'Playfair Display', data }],
})DocxFontDefinition
interface DocxFontDefinition {
name: string
data: Buffer | Uint8Array | ArrayBuffer
characterSet?: DocxFontCharacterSet
}| Field | 描述 |
|---|---|
name | 字体族名称。必须与内容中使用的字体名称完全一致:即 textStyle 标记的 fontFamily 的第一个族名(去掉引号,因此 '"Playfair Display", serif' 会解析为 Playfair Display),或者通过 styleOverrides / textRunOverrides 设置的 font。不匹配时会静默回退到替代字体。 |
data | 原始 TTF 或 OTF 字节。接受 Node 的 Buffer、Uint8Array 或 ArrayBuffer(因此在浏览器中可直接使用 await fetch(url).then((r) => r.arrayBuffer()))。这里不接受 WOFF/WOFF2 数据;有关将 WOFF2 转换后的处理,请参见自动嵌入。 |
characterSet | 可选的 Word 字符集代码,写入字体表中的 <w:charset>。普通拉丁字体可留空。请使用重新导出的 CharacterSet 常量作为该值(例如 CharacterSet.GREEK)。 |
自动嵌入
设置 embedFonts: true 后,导出会自动完成:它会收集文档引用的每个字体族,定位每个字体文件,必要时将其转换为可嵌入格式,并将其嵌入,无需为每次导出单独处理字体。
ExportDocx.configure({
embedFonts: true,
// 只有在字体需要 WOFF2 → TTF 转换时才会使用 token
token: 'your-jwt',
endpoint: 'https://api.tiptap.dev/v2/convert', // 可选,这是默认值
onCompleteExport: (blob) => {
/* 下载 blob */
},
})工作原理
- 检测:收集
textStylefontFamily标记、CSS 样式以及运行级样式覆盖中使用的字体族。通用关键字(serif、monospace等)以及你已经通过fonts传入的字体族会被跳过。 - 定位:从页面可读的
@font-face规则中找到每个字体文件,并选择常规字重。如果一个字体族按语言拆分为多个子集(例如 Google Fonts 以及许多自托管方案),则会选择覆盖文档实际使用字符的那个子集:拉丁文档嵌入拉丁字形,西里尔文档嵌入西里尔字形,等等。对于没有可读规则的字体族(通常是 Google Fonts 的<link>的情况,因为其跨域样式表无法被 JavaScript 读取),会回退到 Google Fonts 查询。 - 转换:
TTF/OTF文件会按原样嵌入。WOFF2文件会通过 Convert Service 的POST /fonts/convert端点转换为 TTF(这一步需要具有Convert:Fonts权限的 token)。 - 嵌入:最终得到的字体会像你通过手动
fonts选项传入一样被嵌入。手动提供的字体始终优先于同名的自动解析字体。
要求和凭证
- 仅限浏览器。 自动嵌入会读取
document.styleSheets,因此在服务器环境中不会执行任何操作。请在服务端使用手动fonts选项。 - 只有 WOFF2 转换才需要 token。 自托管的
TTF/OTF字体无需调用 Convert Service 即可嵌入。WOFF2 字体(包括所有从 Google Fonts 解析得到的字体)需要一个具有Convert:Fonts权限的 token,以便 转换端点 可以运行。 - Convert Service ≥ v2.25.0 必须支持你的
endpoint,WOFF2 → TTF 转换才可用。
嵌入绝不会破坏导出
每个字体都会被独立解析。如果某个字体找不到、无法获取或无法转换(或者 WOFF2 字体缺少凭证),导出只会为该字体族记录一条 console.warn,然后继续进行。受影响的文本会像在未嵌入字体时一样,回退到 Word 的字体替换;导出本身始终会成功。
一个字体用于多种脚本
一个 DOCX 会为每个字体族嵌入一个文件。如果在同一文档中,同一个字体族被用于多种脚本的文本(例如拉丁文和西里尔文),则会嵌入覆盖该文本最多内容的子集,而其他脚本中的文本会回退到 Word 的替换。若要为此类字体族嵌入完整覆盖,请提供一个完整的(非子集)字体文件:以单个 @font-face 自托管,或通过手动 fonts 选项传入。仅用于单一脚本的字体族不受影响。
选项
| 参数 | 描述 | 默认值 |
|---|---|---|
embedFonts | 启用自动检测和嵌入。仅限浏览器。 | false |
token | Tiptap Convert JWT,作为 Authorization bearer token 发送。仅在 WOFF2 字体需要转换时必需。 | undefined |
endpoint | 用于 WOFF2 → TTF 转换的 Tiptap Convert REST 端点基础地址。 | https://api.tiptap.dev/v2/convert |
让编辑器与导出结果一致
为了让编辑器预览与嵌入后的输出一致,请在页面中使用 @font-face 规则加载相同字体(这也是自动嵌入读取以定位自托管字体的方式):
@font-face {
font-family: 'Playfair Display';
src: url('/fonts/PlayfairDisplay-Regular.ttf') format('truetype');
font-weight: 400 900;
font-style: normal;
font-display: swap;
}然后使用 fontFamily 标记将该字体应用到内容上;其经过清理的名称是编辑器和导出结果共同绑定的值:
editor.chain().focus().setFontFamily('Playfair Display').run()Convert Service 端点
自动嵌入中的 WOFF2 转换由一个 REST 端点提供支持,你也可以直接调用它:
POST {endpoint}/fonts/convert
- Body:
multipart/form-data,包含一个file字段(字体二进制文件)以及一个可选的fontFamily字段。 - Response: 可嵌入字体,类型为
font/ttf。TTF/OTF 上传会原样返回,WOFF2 上传会转换为 TTF。 - Auth: 与其他转换端点相同的
Authorization: Bearer <token>请求头。该 token 需要Convert:Fonts权限。
可在 Tiptap Cloud 和本地部署环境中使用,前提是 Convert Service v2.25.0。
另请参阅
- 编辑器扩展概览:基础
ExportDocx配置以及完整的选项表。 - CSS 到 DOCX:将你的编辑器 CSS(包括
font-family)映射到 DOCX 样式中。 - 样式:
styleOverrides和textRunOverrides,其中还会提取font用于嵌入。 - REST API:服务端转换端点。