从编辑器导出 PDF
使用 Tiptap 的 @tiptap-pro/extension-export-pdf 将编辑器内容导出为 .pdf 文件。此扩展通过将文档发送到 Tiptap 转换服务进行转换,把 PDF 导出功能集成到你的编辑器中。
如果你更希望在自己的端处理转换,也可以改用 REST API。
安装 PDF 导出扩展
Conversion 扩展发布在 Tiptap 的私有 npm 仓库中。请按照 私有仓库指南 集成这些扩展。
完成后,你就可以安装并导入 Export PDF 扩展包。
npm i @tiptap-pro/extension-export-pdfimport { ExportPdf } from '@tiptap-pro/extension-export-pdf'配置扩展
ExportPdf 扩展可以使用 ExportPdfOptions(object)作为 configure 方法的参数进行配置,包含以下属性:
import { ExportPdf } from '@tiptap-pro/extension-export-pdf'
const editor = new Editor({
extensions: [
// 其他扩展 ...
ExportPdf.configure({
endpoint: 'https://api.tiptap.dev/v2/convert',
token: 'YOUR_TOKEN',
appId: 'YOUR_APP_ID',
styleOverrides: {},
headers: {},
footers: {},
pageSize: { width: '21cm', height: '29.7cm' },
pageMargins: { top: '1cm', bottom: '1cm', left: '1cm', right: '1cm' },
customNodes: [],
onCompleteExport: (result) => {
// 处理导出的 Blob
},
}),
// 其他扩展 ...
],
})| 参数 | 类型 | 描述 | 默认值 |
|---|---|---|---|
endpoint | string | 用于转换的 Tiptap API 端点 | 'https://api.tiptap.dev/v2/convert' |
token | string | 用于身份验证的 Tiptap JWT | '' |
appId | string | 用于身份验证的 Tiptap App ID | '' |
styleOverrides | Record<string, string> | 应用于导出的样式覆盖 | undefined |
headers | HeaderConfig | 导出文档的页眉配置 | undefined |
footers | FooterConfig | 导出文档的页脚配置 | undefined |
pageSize | PageSize | 页面尺寸配置 | undefined |
pageMargins | PageMargins | 页面边距配置 | undefined |
customNodes | CustomNodeDefinition[] | 用于基于 DOCX 转换的自定义节点定义(需要 @tiptap-pro/extension-export-docx) | undefined |
customFonts | CustomFont[] | 为 PDF 导出提供的自定义字体文件(仅限本地部署) | undefined |
onCompleteExport | (result: Blob) => void | 接收导出文件 Blob 的回调 | 若未提供则抛出错误 |
onExportError | (error: Error) => void | 可选回调:当导出流程被拒绝时触发(动态导入失败、DOCX 转换失败、网络错误、非 OK 响应)。如果省略,则通过 console.error 记录错误。 | undefined |
页眉和页脚插槽值
每个页眉和页脚插槽(default、first、even)都接受五种形态中的任意一种;请选择与你的数据相匹配的形式。前三种仅使用 @tiptap-pro/extension-export-pdf 即可;后两种还需要安装 @tiptap-pro/extension-export-docx(它们会走先 DOCX 后 PDF 的导出路径,并且采用懒加载,因此不会影响仅使用 PDF 的消费者)。
| 形态 | 示例 | 说明 |
|---|---|---|
| 纯文本 | 'Company name, 2026' | 无样式的页眉/页脚。 |
| 字符串化的 Tiptap JSONContent | JSON.stringify(myHeaderDoc) | 预先序列化的 Tiptap 节点。保留富文本格式。 |
| Tiptap JSONContent 对象 | { type: 'doc', content: [/* … */] } | 直接传入富文本 Tiptap 节点。无需字符串化。 |
docx Header / Footer 实例 | new Docx.Header({ children: [/* … */] }) | 完整的 DOCX 级别控制。需要 DOCX 扩展。 |
返回 Header / Footer 的异步工厂 | () => convertHeader({ node }) | 在导出时按需构建。典型用法:convertHeader / convertFooter。需要 DOCX 扩展。 |
Tiptap JSONContent 对象必须包含 type 字段。扩展在运行时就是通过这个字段区分 Tiptap 节点和 docx 实例的。不包含 type 且也不是 Header / Footer 实例的对象会被丢弃,并通过 console.warn 提示出问题的值,不会传递给 DOCX 序列化器。
在 TypeScript 中,这些形态会收敛为两个别名(type 要求被编码在 TiptapNodeContent 中):
import type { JSONContent } from '@tiptap/core'
import type { Footer, Header } from 'docx'
type TiptapNodeContent = JSONContent & { type: string }
type HeaderSlotValue = string | TiptapNodeContent | Header | (() => Promise<Header>)
type FooterSlotValue = string | TiptapNodeContent | Footer | (() => Promise<Footer>)HeaderConfig
| 属性 | 类型 | 描述 |
|---|---|---|
evenAndOddHeaders | boolean | 是否为奇数页和偶数页使用不同的页眉。 |
differentFirstPage | boolean | 是否为第一页使用不同的页眉。当为 true 时,第一页使用 first 值而不是 default。 |
default | HeaderSlotValue | 每一页的标准默认页眉;如果启用 evenAndOddHeaders,则为奇数页页眉。 |
first | HeaderSlotValue | 第一页的页眉。仅在 differentFirstPage 为 true 时使用。 |
even | HeaderSlotValue | 偶数页的页眉。仅在 evenAndOddHeaders 为 true 时使用。 |
FooterConfig
| 属性 | 类型 | 描述 |
|---|---|---|
evenAndOddFooters | boolean | 是否为奇数页和偶数页使用不同的页脚。 |
differentFirstPage | boolean | 是否为第一页使用不同的页脚。当为 true 时,第一页使用 first 值而不是 default。 |
default | FooterSlotValue | 每一页的标准默认页脚;如果启用 evenAndOddFooters,则为奇数页页脚。 |
first | FooterSlotValue | 第一页的页脚。仅在 differentFirstPage 为 true 时使用。 |
even | FooterSlotValue | 偶数页的页脚。仅在 evenAndOddFooters 为 true 时使用。 |
PageSize
| 属性 | 类型 | 描述 | 默认值 |
|---|---|---|---|
width | string | 页面宽度。必须是一个正数,后跟有效单位(cm、in、pt、pc、mm、px)。 | "21cm" |
height | string | 页面高度。必须是一个正数,后跟有效单位(cm、in、pt、pc、mm、px)。 | "29.7cm" |
PageMargins
| 属性 | 类型 | 描述 | 默认值 |
|---|---|---|---|
top | string | 页面上边距。可以为负值。必须是一个数字,后跟有效单位(cm、in、pt、pc、mm、px)。 | "1cm" |
bottom | string | 页面下边距。可以为负值。必须是一个数字,后跟有效单位(cm、in、pt、pc、mm、px)。 | "1cm" |
left | string | 页面左边距。必须是一个正数,后跟有效单位(cm、in、pt、pc、mm、px)。 | "1cm" |
right | string | 页面右边距。必须是一个正数,后跟有效单位(cm、in、pt、pc、mm、px)。 | "1cm" |
CustomFont
仅限本地部署
自定义字体仅可用于本地部署。如果你有兴趣在 Tiptap 的云服务中使用自定义 字体,请 联系我们了解 Enterprise 方案。
| 属性 | 类型 | 描述 |
|---|---|---|
url | string | 直接指向 .ttf 或 .woff2 字体文件的 HTTPS URL |
fontFamily | string | 文档中使用的字体族名称 |
导出 PDF 文件
安装该扩展后,你可以使用 exportPdf 命令将编辑器内容导出为 .pdf。
/**
* 通过 Tiptap 转换服务将当前文档导出为 PDF 文件。
*
* @param options - 所有扩展级选项都可以在单次调用时被覆盖
* @example editor.commands.exportPdf({ onCompleteExport: (result) => {} })
*/
exportPdf: (options?: ExportPdfCommandOptions) => ReturnTypeExportPdfCommandOptions 接口继承自 ExportPdfOptions,因此每个配置项都可以在每次命令调用时被覆盖:
| 属性 | 类型 | 描述 |
|---|---|---|
endpoint | string | 为此次导出覆盖 API 端点 |
token | string | 为此次导出覆盖 JWT token |
appId | string | 为此次导出覆盖 App ID |
styleOverrides | Record<string, string> | 为此次导出覆盖样式覆盖配置 |
headers | HeaderConfig | 为此次导出覆盖页眉配置 |
footers | FooterConfig | 为此次导出覆盖页脚配置 |
pageSize | PageSize | 为此次导出覆盖页面尺寸 |
pageMargins | PageMargins | 为此次导出覆盖页边距 |
customNodes | CustomNodeDefinition[] | 为此次导出覆盖自定义节点定义 |
customFonts | CustomFont[] | 为此次导出覆盖自定义字体(仅限本地部署) |
onCompleteExport | (result: Blob) => void | 为此次导出覆盖完成回调 |
onExportError | (error: Error) => void | 为此次导出覆盖错误处理器 |
import { ExportPdf } from '@tiptap-pro/extension-export-pdf'
const editor = new Editor({
extensions: [
// 其他扩展 ...
ExportPdf.configure({
token: 'YOUR_TOKEN',
appId: 'YOUR_APP_ID',
onCompleteExport(result) {
// 下载 PDF 文件
const url = URL.createObjectURL(result)
const a = document.createElement('a')
a.href = url
a.download = 'document.pdf'
a.click()
URL.revokeObjectURL(url)
},
}),
// 其他扩展 ...
],
})
// 使用扩展级回调
editor.chain().exportPdf().run()
// 或为特定导出覆盖选项
editor
.chain()
.exportPdf({
pageSize: { width: '8.5in', height: '11in' },
onCompleteExport(result) {
// 为此次特定导出进行自定义处理
const url = URL.createObjectURL(result)
window.open(url)
},
})
.run()工作原理
当你调用 exportPdf 时,扩展会将编辑器文档序列化为 JSON,并连同任何已配置的选项(样式覆盖、页面布局、页眉、页脚、自定义字体)一起发送到 Tiptap 转换服务。该服务会将文档转换为 PDF,并以二进制 blob 的形式返回。onCompleteExport 回调会直接接收这个 blob,你可以随后按需下载或处理它。错误会作为异常抛出,而不是通过回调传递。
自定义字体
仅限本地部署
自定义字体仅适用于本地部署。如果你有兴趣在 Tiptap 的云服务中使用自定义 字体,请联系我们了解 Enterprise 方案。
PDF 导出支持本地部署中的自定义字体。你可以通过提供一组字体 URL 来配置自定义字体,转换服务会下载这些字体并在生成 PDF 时使用它们。
字体文件必须是 .ttf 或 .woff2 格式,并且可通过 HTTPS 访问。fontFamily 必须与文档文本样式中使用的字体族名称一致。
ExportPdf.configure({
token: 'YOUR_TOKEN',
appId: 'YOUR_APP_ID',
customFonts: [
{
url: 'https://your-cdn.com/fonts/CustomFont-Regular.ttf',
fontFamily: 'Custom Font',
},
{
url: 'https://your-cdn.com/fonts/CustomFont-Bold.ttf',
fontFamily: 'Custom Font',
},
],
onCompleteExport(result) {
// 处理导出的 PDF
},
})你也可以在命令调用时覆盖自定义字体:
editor
.chain()
.exportPdf({
customFonts: [
{
url: 'https://your-cdn.com/fonts/SpecialFont.woff2',
fontFamily: 'Special Font',
},
],
})
.run()预期结果
- 需要认证。 导出会将文档发送到 Convert 服务,该服务需要 JWT(
token)和你的 App ID。请在服务端生成 JWT;切勿在浏览器中嵌入你的密钥。参见 转换安装指南。 - 你可能需要处理的 HTTP 响应:
401(缺少或无效的 JWT)、403(该操作不允许你的应用使用——通常是你的套餐不包含的格式,或在云套餐中使用customFonts)、5xx(临时性服务端错误;请采用退避重试)。 - 自定义字体是 Enterprise / 本地部署功能。 在云套餐中,携带
customFonts的请求会返回403。如需启用,请联系我们了解 Enterprise 方案 或运行本地部署。 - 每个请求都会在响应前端到端处理完成。 非常大的文档可能需要更长时间才能返回;请据此设置你端的请求超时时间。
不要预期
- 从 DOCX → PDF 的往返完全一致。 PDF 是固定布局格式;Word 和 Convert 服务使用不同的排版引擎,因此视觉输出可能会与 Word 的 PDF 打印在细节上有所不同。
- 复杂布局与 Word 像素级一致——尤其是多栏分节、RTL 文本,或依赖 Word 特定排版特性的内容。
- 在生成的 PDF 中进行编辑。 PDF 导出是单向的。若要保留可编辑性,也请导出 DOCX。
支持与限制
| 功能 | 支持 |
|---|---|
| 文本内容 | ✓ 基本文本、间距、标点 |
| 文本格式 | ✓ 加粗、斜体、下划线、删除线、对齐、行高 |
| 块级元素 | ✓ 段落、标题(1–6)、引用块、有序列表和无序列表 |
| 表格 | ✓ 基本结构、表头行、colspan |
| 链接 | ✓ 超链接 |
| 媒体(图片) | ✓ 嵌入图片,保留尺寸 |
| 样式 | ✓ 字体族*、字体颜色、字体大小、背景颜色、行高 |
| 自定义字体 | ✓ 仅限本地部署(.ttf 和 .woff2) |
| 页眉和页脚 | ✓ |
| 数学公式 | ✓ |
| 分页符 | ✓ |
| 分节 | ✗ |
| 脚注和尾注 | ✗ |
| 评论和修订 | ✗ |
| 目录 | ✗ |
| 高级格式 | ✗ 栏、文字方向、表单、宏、嵌入式脚本 |
| 元数据 | ✗ |
| 文本框、形状、SmartArt | ✗ |
- 当使用 Google Fonts(自动可用)或通过
customFonts选项配置的自定义字体(仅限本地部署)时,支持字体族。
如需查看按功能细分的详细说明,请参阅 支持的功能 矩阵。请注意,DOCX 导出扩展 中可用的元素覆盖(paragraphOverrides、textRunOverrides、tableOverrides、tableCellOverrides、imageOverrides)不会传递到 PDF 转换中。