PDF 导出
可以将 BlockNote 文档完全在客户端导出为 PDF。导出器由 Typst 排版引擎(编译为 WebAssembly)驱动,并生成可访问的带标签文档;请参见 PDF/UA 一致性。
此功能由 @blocknote/xl-pdf-exporter 提供。xl-
系列包完全开源,但采用了强制开源许可证。商业许可证适用于闭源专有产品,包含在
商业订阅 中。
首先,安装 @blocknote/xl-pdf-exporter 包:
npm install @blocknote/xl-pdf-exporter然后,创建 PDFExporter 类的实例并导出文档:
import {
PDFExporter,
typstDefaultSchemaMappings,
} from "@blocknote/xl-pdf-exporter";
// Create the exporter
const exporter = new PDFExporter(editor.schema, typstDefaultSchemaMappings);
// Export the document; the result carries the PDF as bytes and as a Blob
const result = await exporter.toPDF(editor.document, {
title: "My document",
lang: "en",
});
if (!result.error) {
const url = URL.createObjectURL(result.blob);
// e.g. open or download `url`
}这开箱即用且完全离线:导出结果与编辑器的外观一致,所需的一切(默认字体和编译器本身)都包含在包中。不会从 CDN 获取任何内容。
下面是带有实时 PDF 预览的完整示例:
PDF 自定义
toPDF 接受每次导出的选项,用于设置文档元数据和页面布局。
const result = await exporter.toPDF(editor.document, {
// Document title, shown in the viewer's title bar
title: "My document",
// Document author, written to the PDF metadata
author: "John Doe",
// BCP-47 language tag of the document's natural language
lang: "en",
// Typst paper name, e.g. "a4" (default) or "us-letter"
paper: "a4",
// Page margin as a Typst length
margin: "48pt",
// Raw Typst markup for the running page header / footer, e.g. a
// page counter: "#context counter(page).display()"
header: "My document",
footer: "#context counter(page).display()",
});其余导出选项包括 tryDeclarePdfUA、assets(供调用方提供的页眉/页脚标记使用的额外文件)以及 creationTimestamp(用于生成字节可复现输出的固定 Unix 时间戳)。
如果文档编译失败(例如,文本使用了已加载字体不支持的文字),结果会报告 { error: "compile-failed" } 及编译器诊断信息,而不是抛出异常。
PDF/UA 一致性
生成的 PDF 始终带有 标签:它包含逻辑结构树(标题、段落、列表、表格、带替代文本的图形、链接),屏幕阅读器可以使用它进行导航。此外,只有当文档满足条件时,导出器才会声明 PDF/UA-1 一致性:Typst 会在编译期间验证一致性;不符合要求的文档会以带标签但未声明的形式导出,并在结果中报告违规内容:
const result = await exporter.toPDF(editor.document, {
title: "My document",
lang: "en",
});
if (
!result.error &&
!result.pdfUA.declared &&
result.pdfUA.reason === "nonconforming"
) {
// e.g. "PDF/UA-1 error: the first heading must be of level 1"
console.info(result.pdfUA.violations.map((v) => v.message));
}该文档要满足的一致性要求:
- 标题和语言:在导出选项中传入
title和lang。lang是尝试声明一致性的必要参数:错误的语言声明属于任何验证器都无法捕获的可访问性缺陷,因此不传入它会抛出异常。 - 标题层级:第一个标题必须为 1 级,且层级必须连续(不能从 H1 跳到 H3)。
图像始终会获得 PDF/UA 要求的替代文本,该文本取自标题,文件名作为后备。标题通常是更好的替代文本,因此应鼓励用户添加标题。
在导出选项中传入 tryDeclarePdfUA: false 可以完全跳过验证和声明,例如用于实时预览时,避免浪费验证编译的开销。对于实时预览,还应为每次导出创建新的导出器,因为一个实例会累积其解析的图像资源。
自定义映射 / 自定义 Schema
PDFExporter 构造函数接受 schema 和 mappings 参数。一个 mapping 会将 BlockNote schema 元素转换为 Typst 标记字符串,这些映射与独立的 Typst 导出共用。请参阅其中的自定义映射;关于编写映射的所有内容同样适用于此导出器。
数学和图表区块
数学和图表区块自带 Typst 映射:数学内容会导出为原生 Typst 公式(真实文本,而非图像),图表会导出为嵌入式矢量图形。有关设置,请参阅导出数学公式和导出图表。
字体
默认情况下,导出使用与编辑器匹配的内置字体集:Inter(正文)、Geist Mono(代码)、New Computer Modern Math(数学)和 Noto Color Emoji。若要改用自己的字体,请将字体字节和对应的字体系列名称传递给构造函数:
const exporter = new PDFExporter(editor.schema, typstDefaultSchemaMappings, {
fontFamily: "My Font",
monoFontFamily: "My Mono Font",
// Uint8Array[], or a promise of them
fonts: [myFontBytes, myMonoFontBytes],
// An emoji-capable font; replaces the default independently of `fonts`
emojiFont: myEmojiFontBytes,
});Typst 会根据字体文件本身嵌入的字体系列名称选择字体,因此 fontFamily 选项必须与文件声明的名称匹配。不匹配时,结果的 compileWarnings 中会出现 unknown font family 条目。
要扩展默认字体而不是替换它们(例如添加 CJK 字体作为逐字形后备),请展开导出的默认加载器:
import {
DEFAULT_FONT_FAMILY,
loadDefaultBodyFonts,
PDFExporter,
typstDefaultSchemaMappings,
} from "@blocknote/xl-pdf-exporter";
const exporter = new PDFExporter(editor.schema, typstDefaultSchemaMappings, {
fontFamily: [DEFAULT_FONT_FAMILY, "Noto Sans SC"],
fonts: loadDefaultBodyFonts().then((fonts) => [...fonts, notoSansSCBytes]),
});自托管编译器
Typst 编译器是来自 @blocknote/xl-typst-compiler 的约 25 MB wasm 文件,默认情况下由你的 bundler 作为资源输出。要控制其提供位置(例如设置缓存标头),请传入其 URL 或字节:
import compilerWasmUrl from "@blocknote/xl-typst-compiler/wasm?url";
const exporter = new PDFExporter(editor.schema, typstDefaultSchemaMappings, {
wasm: compilerWasmUrl,
});导出器选项
PDFExporter 构造函数接受一个可选的第三个 options 参数:
const defaultOptions = {
// a function to resolve external resources (e.g. images) in order to avoid
// CORS issues; by default, this calls a BlockNote hosted server-side proxy
resolveFileUrl: corsProxyResolveFileUrl,
// the strings rendered into the exported document (file link texts, error
// placeholders); pass a locale from @blocknote/core/locales (or your
// editor's dictionary) to export in another language
dictionary: locales.en,
// the colors used for highlighting, background colors and font colors
colors: COLORS_DEFAULT, // defaults from @blocknote/core
// font families (DEFAULT_FONT_FAMILY / DEFAULT_MONO_FONT_FAMILY) and the
// font bytes they resolve against - see "Fonts" above
fontFamily: "Inter 18pt",
monoFontFamily: "Geist Mono",
fonts: loadDefaultBodyFonts(),
emojiFont: loadDefaultEmojiFont(),
// base font size in points
fontSize: 12,
// where the compiler wasm loads from - see "Self-hosting the compiler"
wasm: undefined, // the packaged wasm
};导出 Typst 标记
底层 Typst 源代码导出可以独立使用(例如使用你自己的 Typst 工具链,包括在服务端进行编译);请参阅 Typst 导出。
已弃用:react-pdf 导出器
之前版本的 @blocknote/xl-pdf-exporter 使用 react-pdf 导出 PDF,生成不带标签(不可访问)的文档。该导出器已弃用,并将在几个版本后移除;在此之前,它仍可通过 @blocknote/xl-pdf-exporter/react-pdf 子路径保持原样使用:
import {
PDFExporter,
pdfDefaultSchemaMappings,
} from "@blocknote/xl-pdf-exporter/react-pdf";请注意,它的映射是 react-pdf 映射;迁移到新导出器时,自定义区块需要改用 Typst 映射。旧导出器的示例仍位于此处。