BlockNote Docs特性导出PDF

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 映射。旧导出器的示例仍位于此处。