diff --git a/docs/.gitignore b/docs/.gitignore
index c12953bff8..b5f41ae2c3 100644
--- a/docs/.gitignore
+++ b/docs/.gitignore
@@ -30,3 +30,7 @@ next-env.d.ts
/content/examples/*/*
/components/example/generated/
sqlite.db
+
+# Auto-generated by Next.js postinstall (agent rules); not repo content.
+/AGENTS.md
+/CLAUDE.md
diff --git a/docs/content/docs/features/blocks/math.mdx b/docs/content/docs/features/blocks/math.mdx
index 83378c6aa6..d8f2a7ae60 100644
--- a/docs/content/docs/features/blocks/math.mdx
+++ b/docs/content/docs/features/blocks/math.mdx
@@ -142,10 +142,6 @@ covered; a valid formula using an uncovered command fails the export loudly
(rather than silently exporting something else), while invalid LaTeX renders
the error placeholder as in other formats.
-The deprecated [react-pdf exporter](/docs/features/export/pdf#deprecated-the-react-pdf-exporter)'s
-mappings remain available from `@blocknote/math-block/pdf-exporter` during its
-deprecation window.
-
### Email
With the [email exporter](/docs/features/export/email), math exports as images with the LaTeX source as the alt text: math blocks are rasterized to PNG in the browser (and embedded as SVG elsewhere), inline math is always embedded as SVG:
diff --git a/docs/content/docs/features/export/pdf.mdx b/docs/content/docs/features/export/pdf.mdx
index 1d096961da..fc577af1ca 100644
--- a/docs/content/docs/features/export/pdf.mdx
+++ b/docs/content/docs/features/export/pdf.mdx
@@ -51,10 +51,10 @@ This works out of the box, fully offline: exports match the editor's look,
and everything needed (the default fonts and the compiler itself) ships
inside the package. Nothing is fetched from a CDN.
-See the [full example](/examples/interoperability/converting-blocks-to-pdf-ua)
+See the [full example](/examples/interoperability/converting-blocks-to-pdf)
with a live PDF preview below:
-
+
### Customizing the PDF
@@ -253,4 +253,5 @@ import {
Note that its mappings are react-pdf mappings; when migrating to the new
exporter, custom blocks need a [Typst mapping](#custom-mappings--custom-schemas)
-instead.
+instead. The old exporter's example lives on at
+[converting-blocks-to-pdf-react-pdf-deprecated](/examples/interoperability/converting-blocks-to-pdf-react-pdf-deprecated).
diff --git a/docs/package.json b/docs/package.json
index 9e4e282da9..76294747a1 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -52,7 +52,6 @@
"@polar-sh/sdk": "^0.42.2",
"@react-email/components": "^1.0.4",
"@react-email/render": "^2.0.4",
- "@react-pdf/math": "^2.0.1",
"@react-pdf/renderer": "^4.5.1",
"@sentry/nextjs": "^10.34.0",
"@shikijs/core": "^4.4.3",
diff --git a/examples/05-interoperability/05-converting-blocks-to-pdf/.bnexample.json b/examples/05-interoperability/05-converting-blocks-to-pdf/.bnexample.json
index abf12f3d8e..0a39b03402 100644
--- a/examples/05-interoperability/05-converting-blocks-to-pdf/.bnexample.json
+++ b/examples/05-interoperability/05-converting-blocks-to-pdf/.bnexample.json
@@ -2,16 +2,14 @@
"playground": true,
"docs": true,
"author": "yousefed",
- "tags": ["Interoperability"],
+ "tags": ["Interoperability", "Accessibility"],
"sharedTestDocument": true,
"dependencies": {
- "@blocknote/diagram-block": "latest",
- "@blocknote/math-block": "latest",
- "@blocknote/xl-multi-column": "latest",
"@blocknote/xl-pdf-exporter": "latest",
- "@react-pdf/math": "^2.0.1",
- "@react-pdf/renderer": "^4.5.1",
- "mathjax-full": "^3.2.2"
+ "@blocknote/xl-multi-column": "latest",
+ "@blocknote/math-block": "latest",
+ "@blocknote/diagram-block": "latest",
+ "@blocknote/xl-typst-compiler": "latest"
},
"pro": true
}
diff --git a/examples/05-interoperability/05-converting-blocks-to-pdf/README.md b/examples/05-interoperability/05-converting-blocks-to-pdf/README.md
index c147d90bb4..44c5f3cf2f 100644
--- a/examples/05-interoperability/05-converting-blocks-to-pdf/README.md
+++ b/examples/05-interoperability/05-converting-blocks-to-pdf/README.md
@@ -1,11 +1,13 @@
-# Exporting documents to PDF (react-pdf, deprecated)
+# Exporting documents to PDF (PDF/UA)
-> **Deprecated:** this example uses the react-pdf based exporter
-> (`@blocknote/xl-pdf-exporter/react-pdf`), which is deprecated and will be
-> removed after a few releases. Use the Typst-based `PDFExporter` instead -
-> see the "Exporting documents to tagged PDF (PDF/UA)" example - which
-> produces accessible, tagged PDF/UA-1 output.
+This example exports the current document to an **accessible, tagged PDF/UA-1**
+file using the Typst-powered `@blocknote/xl-pdf-exporter`. Unlike a plain PDF,
+a tagged PDF carries a logical structure tree (headings, paragraphs, lists,
+tables, figures with alt text, links) that screen readers can navigate.
-This example exports the current document (all blocks) as a PDF file and downloads it to your computer.
+**Try it out:** Edit the document — the PDF preview updates live. Click
+"Download" to save it, then verify it with a tool like
+[veraPDF](https://verapdf.org/) (`--flavour ua1`) or the Acrobat Tags panel.
-**Try it out:** Edit the document and click "Download .pdf" at the top to download the PDF file.
+> The first export downloads the Typst compiler (wasm) and fonts, so it may take
+> a moment. Images render as tagged placeholder figures for now.
diff --git a/examples/05-interoperability/05-converting-blocks-to-pdf/index.html b/examples/05-interoperability/05-converting-blocks-to-pdf/index.html
index 4d59fc32bf..3aa02420e0 100644
--- a/examples/05-interoperability/05-converting-blocks-to-pdf/index.html
+++ b/examples/05-interoperability/05-converting-blocks-to-pdf/index.html
@@ -2,7 +2,7 @@
- Exporting documents to PDF (react-pdf, deprecated)
+ Exporting documents to PDF (PDF/UA)
diff --git a/examples/05-interoperability/05-converting-blocks-to-pdf/package.json b/examples/05-interoperability/05-converting-blocks-to-pdf/package.json
index 30c28d0430..dc44362e3f 100644
--- a/examples/05-interoperability/05-converting-blocks-to-pdf/package.json
+++ b/examples/05-interoperability/05-converting-blocks-to-pdf/package.json
@@ -20,13 +20,11 @@
"@mantine/hooks": "^9.0.2",
"react": "^19.2.3",
"react-dom": "^19.2.3",
- "@blocknote/diagram-block": "latest",
- "@blocknote/math-block": "latest",
- "@blocknote/xl-multi-column": "latest",
"@blocknote/xl-pdf-exporter": "latest",
- "@react-pdf/math": "^2.0.1",
- "@react-pdf/renderer": "^4.5.1",
- "mathjax-full": "^3.2.2"
+ "@blocknote/xl-multi-column": "latest",
+ "@blocknote/math-block": "latest",
+ "@blocknote/diagram-block": "latest",
+ "@blocknote/xl-typst-compiler": "latest"
},
"devDependencies": {
"@types/react": "^19.2.3",
diff --git a/examples/05-interoperability/05-converting-blocks-to-pdf/src/App.tsx b/examples/05-interoperability/05-converting-blocks-to-pdf/src/App.tsx
index c60aa61749..bfce34f37a 100644
--- a/examples/05-interoperability/05-converting-blocks-to-pdf/src/App.tsx
+++ b/examples/05-interoperability/05-converting-blocks-to-pdf/src/App.tsx
@@ -1,5 +1,6 @@
import { testDocumentBlocks } from "./testDocumentBlocks";
import {
+ Block,
BlockNoteSchema,
combineByGroup,
withPageBreak,
@@ -10,45 +11,126 @@ import * as locales from "@blocknote/core/locales";
import { BlockNoteView } from "@blocknote/mantine";
import "@blocknote/mantine/style.css";
import { createReactDiagramBlockSpec } from "@blocknote/diagram-block";
+import { diagramBlockMapping } from "@blocknote/diagram-block/typst-exporter";
import {
createReactInlineMathSpec,
createReactMathBlockSpec,
} from "@blocknote/math-block";
+import {
+ inlineMathMapping,
+ mathBlockMapping,
+} from "@blocknote/math-block/typst-exporter";
import {
SuggestionMenuController,
getDefaultReactSlashMenuItems,
getPageBreakReactSlashMenuItems,
useCreateBlockNote,
} from "@blocknote/react";
+import {
+ PDFExporter,
+ typstDefaultSchemaMappings,
+} from "@blocknote/xl-pdf-exporter";
import {
getMultiColumnSlashMenuItems,
- multiColumnDropCursor,
locales as multiColumnLocales,
+ multiColumnDropCursor,
withMultiColumn,
} from "@blocknote/xl-multi-column";
-import {
- PDFExporter,
- pdfDefaultSchemaMappings,
-} from "@blocknote/xl-pdf-exporter/react-pdf";
-import { diagramBlockMapping } from "@blocknote/diagram-block/pdf-exporter";
-import {
- inlineMathMapping,
- mathBlockMapping,
-} from "@blocknote/math-block/pdf-exporter";
-import { pdf, PDFViewer } from "@react-pdf/renderer";
-import { JSX, useEffect, useMemo, useReducer, useState } from "react";
+// Bundle the Typst compiler wasm explicitly (it would otherwise load from
+// the package's own files - also CDN-free - but an explicit URL keeps the
+// bundling visible in this example).
+// Fonts need no setup: the exporter's bundled defaults (Inter, Geist Mono,
+// math, emoji - matching the editor) load lazily from the package.
+import compilerWasmUrl from "@blocknote/xl-typst-compiler/wasm?url";
+import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import "./styles.css";
-export default function App() {
- // Stores the editor's contents as JSX for download and displaying the PDF
- // using ReactPDF's `PDFViewer` component.
- const [pdfDocument, setPDFDocument] = useState();
- const [renders, forceRerender] = useReducer((s) => s + 1, 0);
+/**
+ * Exports the given document to a PDF/UA object URL, re-exporting whenever
+ * `blocks` changes.
+ *
+ * The effect-with-cleanup idiom keeps only the newest result: when a newer
+ * version (or unmount) invalidates the effect, the cleanup marks the running
+ * export stale and its result is dropped. Overlapping exports are *safe* -
+ * the exporter serializes its shared compile stage internally - but like any
+ * async calls they may complete out of call order, and which result to
+ * display is this component's concern, not the exporter's.
+ */
+function usePdfUA(
+ makeExporter: () => PDFExporter,
+ blocks: Block[],
+) {
+ const [pdfUrl, setPdfUrl] = useState();
+ const [status, setStatus] = useState<
+ "loading" | "ready" | "unclaimed" | "error"
+ >("loading");
+
+ useEffect(() => {
+ let stale = false;
+ setStatus("loading");
+ void (async () => {
+ try {
+ const result = await makeExporter().toPDF(blocks, {
+ title: "BlockNote document",
+ lang: "en",
+ });
+ if (stale) {
+ return;
+ }
+ // A document that fails to compile (e.g. text no supplied font
+ // covers) is an expected outcome, reported in the result.
+ if (result.error) {
+ // eslint-disable-next-line no-console
+ console.error(
+ "PDF export failed:",
+ result.compileErrors.map((d) => d.message).join("; "),
+ );
+ setStatus("error");
+ return;
+ }
+ // A nonconforming document (e.g. one not starting with an H1) still
+ // exports - tagged but without the PDF/UA-1 claim; surface why, and
+ // show a distinct status instead of claiming conformance.
+ if (!result.pdfUA.declared && result.pdfUA.reason === "nonconforming") {
+ // eslint-disable-next-line no-console
+ console.info(
+ "Exported without PDF/UA-1 declaration:",
+ result.pdfUA.violations.map((v) => v.message).join("; "),
+ );
+ }
+ setPdfUrl(URL.createObjectURL(result.blob));
+ setStatus(result.pdfUA.declared ? "ready" : "unclaimed");
+ } catch (e) {
+ if (stale) {
+ return;
+ }
+ // eslint-disable-next-line no-console
+ console.error(e);
+ setStatus("error");
+ }
+ })();
+ return () => {
+ stale = true;
+ };
+ }, [makeExporter, blocks]);
+
+ // Each object URL is revoked when replaced by the next one (and the last
+ // one on unmount).
+ useEffect(() => {
+ return () => {
+ if (pdfUrl) {
+ URL.revokeObjectURL(pdfUrl);
+ }
+ };
+ }, [pdfUrl]);
+
+ return { pdfUrl, status };
+}
- // Creates a new editor instance.
+export default function App() {
+ // Creates a new editor instance with support for page breaks.
const editor = useCreateBlockNote({
- // Adds support for page breaks & multi-column blocks.
// Adds support for math & diagram blocks.
schema: withMultiColumn(withPageBreak(BlockNoteSchema.create())).extend({
blockSpecs: {
@@ -64,14 +146,12 @@ export default function App() {
...locales.en,
multi_column: multiColumnLocales.en,
},
- // Adds support for advanced table features.
tables: {
splitCells: true,
cellBackgroundColor: true,
cellTextColor: true,
headers: true,
},
- // Sets initial editor content.
initialContent: [
...testDocumentBlocks,
// The math & diagram blocks aren't part of the shared test document,
@@ -105,7 +185,7 @@ export default function App() {
],
});
- // Additional Slash Menu items for page breaks and multi-column blocks.
+ // Additional Slash Menu items for page breaks.
const getSlashMenuItems = useMemo(
() => async (query: string) =>
filterSuggestionItems(
@@ -119,54 +199,71 @@ export default function App() {
[editor],
);
- // Exports the editor document to PDF whenever it changes.
- const onChange = async () => {
- const exporter = new PDFExporter(editor.schema, {
- ...pdfDefaultSchemaMappings,
- blockMapping: {
- ...pdfDefaultSchemaMappings.blockMapping,
- // Embeds diagrams as images instead of their Mermaid source.
- diagram: diagramBlockMapping,
- // Renders math blocks as formulas instead of their LaTeX source.
- mathBlock: mathBlockMapping,
- },
- inlineContentMapping: {
- ...pdfDefaultSchemaMappings.inlineContentMapping,
- // Renders inline math as formula images instead of its LaTeX source.
- math: inlineMathMapping,
- },
- });
- const pdfDocument = await exporter.toReactPDFDocument(editor.document);
- setPDFDocument(pdfDocument);
- forceRerender();
- };
+ // A fresh exporter per export: its asset registry is append-only for the
+ // exporter's lifetime, so reusing one across re-exports would accumulate
+ // every image/diagram variant it has ever rendered.
+ const makeExporter = useCallback(
+ () =>
+ new PDFExporter(
+ editor.schema,
+ {
+ ...typstDefaultSchemaMappings,
+ blockMapping: {
+ ...typstDefaultSchemaMappings.blockMapping,
+ // Renders math blocks as native Typst equations, and diagrams as
+ // embedded images - both carrying alt text for PDF/UA.
+ mathBlock: mathBlockMapping,
+ diagram: diagramBlockMapping,
+ },
+ inlineContentMapping: {
+ ...typstDefaultSchemaMappings.inlineContentMapping,
+ math: inlineMathMapping,
+ },
+ },
+ {
+ // The bundled compiler wasm (see the import above) - engine setup
+ // belongs to the exporter, per-document facts go to toPDF.
+ wasm: compilerWasmUrl,
+ },
+ ),
+ [editor],
+ );
- // Exports the inital editor document to PDF.
- useEffect(() => {
- void onChange();
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
+ // The document snapshot driving the export - the export effect depends on
+ // the data it exports. Updated debounced: reading `editor.document`
+ // converts the whole document to blocks, so it shouldn't run (and the
+ // export shouldn't restart) on every keystroke.
+ const [blocks, setBlocks] = useState(() => editor.document);
+ const { pdfUrl, status } = usePdfUA(makeExporter, blocks);
- // Downloads the PDF.
- const onDownloadClick = async () => {
- const blob = await pdf(pdfDocument).toBlob();
+ const debounceTimer = useRef>(undefined);
+ useEffect(() => () => clearTimeout(debounceTimer.current), []);
+ const onChange = () => {
+ clearTimeout(debounceTimer.current);
+ debounceTimer.current = setTimeout(() => setBlocks(editor.document), 600);
+ };
+ const onDownloadClick = () => {
+ if (!pdfUrl) {
+ return;
+ }
const link = document.createElement("a");
- link.href = window.URL.createObjectURL(blob);
- link.download = "My Document (blocknote export).pdf";
+ link.href = pdfUrl;
+ link.download = "blocknote (pdf-ua).pdf";
document.body.appendChild(link);
- link.dispatchEvent(
- new MouseEvent("click", {
- bubbles: true,
- cancelable: true,
- view: window,
- }),
- );
+ link.click();
link.remove();
- window.URL.revokeObjectURL(link.href);
};
- // Renders the editor instance and PDF view.
+ const label =
+ status === "loading"
+ ? "Generating…"
+ : status === "error"
+ ? "Export failed (see console)"
+ : status === "unclaimed"
+ ? "Tagged PDF, no UA-1 claim (see console)"
+ : "✓ Tagged PDF/UA-1";
+
return (
@@ -182,15 +279,24 @@ export default function App() {
- PDF Output
-
+ {label}
+
Download
-
+
-
- {pdfDocument}
-
+ {pdfUrl ? (
+
+ ) : null}
diff --git a/examples/05-interoperability/05-converting-blocks-to-pdf/src/styles.css b/examples/05-interoperability/05-converting-blocks-to-pdf/src/styles.css
index 87b4331486..369287272e 100644
--- a/examples/05-interoperability/05-converting-blocks-to-pdf/src/styles.css
+++ b/examples/05-interoperability/05-converting-blocks-to-pdf/src/styles.css
@@ -32,7 +32,13 @@
margin-inline: 16px;
}
+/* A real (keyboard-focusable), restyled to match the label text. */
.view-label-download {
+ background: none;
+ border: none;
+ color: inherit;
+ font: inherit;
+ padding: 0;
cursor: pointer;
text-decoration: underline;
}
diff --git a/examples/05-interoperability/11-converting-blocks-to-pdf-ua/.bnexample.json b/examples/05-interoperability/11-converting-blocks-to-pdf-react-pdf-deprecated/.bnexample.json
similarity index 56%
rename from examples/05-interoperability/11-converting-blocks-to-pdf-ua/.bnexample.json
rename to examples/05-interoperability/11-converting-blocks-to-pdf-react-pdf-deprecated/.bnexample.json
index 0a39b03402..4def42681f 100644
--- a/examples/05-interoperability/11-converting-blocks-to-pdf-ua/.bnexample.json
+++ b/examples/05-interoperability/11-converting-blocks-to-pdf-react-pdf-deprecated/.bnexample.json
@@ -2,14 +2,12 @@
"playground": true,
"docs": true,
"author": "yousefed",
- "tags": ["Interoperability", "Accessibility"],
+ "tags": ["Interoperability"],
"sharedTestDocument": true,
"dependencies": {
- "@blocknote/xl-pdf-exporter": "latest",
"@blocknote/xl-multi-column": "latest",
- "@blocknote/math-block": "latest",
- "@blocknote/diagram-block": "latest",
- "@blocknote/xl-typst-compiler": "latest"
+ "@blocknote/xl-pdf-exporter": "latest",
+ "@react-pdf/renderer": "^4.5.1"
},
"pro": true
}
diff --git a/examples/05-interoperability/11-converting-blocks-to-pdf-react-pdf-deprecated/README.md b/examples/05-interoperability/11-converting-blocks-to-pdf-react-pdf-deprecated/README.md
new file mode 100644
index 0000000000..3c6efad22b
--- /dev/null
+++ b/examples/05-interoperability/11-converting-blocks-to-pdf-react-pdf-deprecated/README.md
@@ -0,0 +1,11 @@
+# Exporting documents to PDF (react-pdf, deprecated)
+
+> **Deprecated:** this example uses the react-pdf based exporter
+> (`@blocknote/xl-pdf-exporter/react-pdf`), which is deprecated and will be
+> removed after a few releases. Use the Typst-based `PDFExporter` instead -
+> see the "Exporting documents to PDF (PDF/UA)" example - which
+> produces accessible, tagged PDF/UA-1 output.
+
+This example exports the current document (all blocks) as a PDF file and downloads it to your computer.
+
+**Try it out:** Edit the document and click "Download .pdf" at the top to download the PDF file.
diff --git a/examples/05-interoperability/11-converting-blocks-to-pdf-ua/index.html b/examples/05-interoperability/11-converting-blocks-to-pdf-react-pdf-deprecated/index.html
similarity index 82%
rename from examples/05-interoperability/11-converting-blocks-to-pdf-ua/index.html
rename to examples/05-interoperability/11-converting-blocks-to-pdf-react-pdf-deprecated/index.html
index 46d4ed40aa..4d59fc32bf 100644
--- a/examples/05-interoperability/11-converting-blocks-to-pdf-ua/index.html
+++ b/examples/05-interoperability/11-converting-blocks-to-pdf-react-pdf-deprecated/index.html
@@ -2,7 +2,7 @@
- Exporting documents to tagged PDF (PDF/UA)
+ Exporting documents to PDF (react-pdf, deprecated)
diff --git a/examples/05-interoperability/11-converting-blocks-to-pdf-ua/main.tsx b/examples/05-interoperability/11-converting-blocks-to-pdf-react-pdf-deprecated/main.tsx
similarity index 100%
rename from examples/05-interoperability/11-converting-blocks-to-pdf-ua/main.tsx
rename to examples/05-interoperability/11-converting-blocks-to-pdf-react-pdf-deprecated/main.tsx
diff --git a/examples/05-interoperability/11-converting-blocks-to-pdf-ua/package.json b/examples/05-interoperability/11-converting-blocks-to-pdf-react-pdf-deprecated/package.json
similarity index 86%
rename from examples/05-interoperability/11-converting-blocks-to-pdf-ua/package.json
rename to examples/05-interoperability/11-converting-blocks-to-pdf-react-pdf-deprecated/package.json
index 515f5b808e..13404b0a65 100644
--- a/examples/05-interoperability/11-converting-blocks-to-pdf-ua/package.json
+++ b/examples/05-interoperability/11-converting-blocks-to-pdf-react-pdf-deprecated/package.json
@@ -1,5 +1,5 @@
{
- "name": "@blocknote/example-interoperability-converting-blocks-to-pdf-ua",
+ "name": "@blocknote/example-interoperability-converting-blocks-to-pdf-react-pdf-deprecated",
"description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY",
"type": "module",
"private": true,
@@ -20,11 +20,9 @@
"@mantine/hooks": "^9.0.2",
"react": "^19.2.3",
"react-dom": "^19.2.3",
- "@blocknote/xl-pdf-exporter": "latest",
"@blocknote/xl-multi-column": "latest",
- "@blocknote/math-block": "latest",
- "@blocknote/diagram-block": "latest",
- "@blocknote/xl-typst-compiler": "latest"
+ "@blocknote/xl-pdf-exporter": "latest",
+ "@react-pdf/renderer": "^4.5.1"
},
"devDependencies": {
"@types/react": "^19.2.3",
diff --git a/examples/05-interoperability/11-converting-blocks-to-pdf-react-pdf-deprecated/src/App.tsx b/examples/05-interoperability/11-converting-blocks-to-pdf-react-pdf-deprecated/src/App.tsx
new file mode 100644
index 0000000000..c22946e9fd
--- /dev/null
+++ b/examples/05-interoperability/11-converting-blocks-to-pdf-react-pdf-deprecated/src/App.tsx
@@ -0,0 +1,135 @@
+import { testDocumentBlocks } from "./testDocumentBlocks";
+import {
+ BlockNoteSchema,
+ combineByGroup,
+ withPageBreak,
+} from "@blocknote/core";
+import { filterSuggestionItems } from "@blocknote/core/extensions";
+import "@blocknote/core/fonts/inter.css";
+import * as locales from "@blocknote/core/locales";
+import { BlockNoteView } from "@blocknote/mantine";
+import "@blocknote/mantine/style.css";
+import {
+ SuggestionMenuController,
+ getDefaultReactSlashMenuItems,
+ getPageBreakReactSlashMenuItems,
+ useCreateBlockNote,
+} from "@blocknote/react";
+import {
+ getMultiColumnSlashMenuItems,
+ multiColumnDropCursor,
+ locales as multiColumnLocales,
+ withMultiColumn,
+} from "@blocknote/xl-multi-column";
+import {
+ PDFExporter,
+ pdfDefaultSchemaMappings,
+} from "@blocknote/xl-pdf-exporter/react-pdf";
+import { pdf, PDFViewer } from "@react-pdf/renderer";
+import { JSX, useEffect, useMemo, useReducer, useState } from "react";
+
+import "./styles.css";
+
+export default function App() {
+ // Stores the editor's contents as JSX for download and displaying the PDF
+ // using ReactPDF's `PDFViewer` component.
+ const [pdfDocument, setPDFDocument] = useState();
+ const [renders, forceRerender] = useReducer((s) => s + 1, 0);
+
+ // Creates a new editor instance.
+ const editor = useCreateBlockNote({
+ // Adds support for page breaks & multi-column blocks.
+ schema: withMultiColumn(withPageBreak(BlockNoteSchema.create())),
+ dropCursor: multiColumnDropCursor,
+ dictionary: {
+ ...locales.en,
+ multi_column: multiColumnLocales.en,
+ },
+ // Adds support for advanced table features.
+ tables: {
+ splitCells: true,
+ cellBackgroundColor: true,
+ cellTextColor: true,
+ headers: true,
+ },
+ // Sets initial editor content.
+ initialContent: testDocumentBlocks,
+ });
+
+ // Additional Slash Menu items for page breaks and multi-column blocks.
+ const getSlashMenuItems = useMemo(
+ () => async (query: string) =>
+ filterSuggestionItems(
+ combineByGroup(
+ getDefaultReactSlashMenuItems(editor),
+ getPageBreakReactSlashMenuItems(editor),
+ getMultiColumnSlashMenuItems(editor),
+ ),
+ query,
+ ),
+ [editor],
+ );
+
+ // Exports the editor document to PDF whenever it changes.
+ const onChange = async () => {
+ const exporter = new PDFExporter(editor.schema, pdfDefaultSchemaMappings);
+ const pdfDocument = await exporter.toReactPDFDocument(editor.document);
+ setPDFDocument(pdfDocument);
+ forceRerender();
+ };
+
+ // Exports the inital editor document to PDF.
+ useEffect(() => {
+ void onChange();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ // Downloads the PDF.
+ const onDownloadClick = async () => {
+ const blob = await pdf(pdfDocument).toBlob();
+
+ const link = document.createElement("a");
+ link.href = window.URL.createObjectURL(blob);
+ link.download = "My Document (blocknote export).pdf";
+ document.body.appendChild(link);
+ link.dispatchEvent(
+ new MouseEvent("click", {
+ bubbles: true,
+ cancelable: true,
+ view: window,
+ }),
+ );
+ link.remove();
+ window.URL.revokeObjectURL(link.href);
+ };
+
+ // Renders the editor instance and PDF view.
+ return (
+
+
+
Editor Input
+
+
+
+
+
+
+
+
+ PDF Output
+
+ Download
+
+
+
+
+
+ );
+}
diff --git a/examples/05-interoperability/11-converting-blocks-to-pdf-ua/src/styles.css b/examples/05-interoperability/11-converting-blocks-to-pdf-react-pdf-deprecated/src/styles.css
similarity index 84%
rename from examples/05-interoperability/11-converting-blocks-to-pdf-ua/src/styles.css
rename to examples/05-interoperability/11-converting-blocks-to-pdf-react-pdf-deprecated/src/styles.css
index 369287272e..87b4331486 100644
--- a/examples/05-interoperability/11-converting-blocks-to-pdf-ua/src/styles.css
+++ b/examples/05-interoperability/11-converting-blocks-to-pdf-react-pdf-deprecated/src/styles.css
@@ -32,13 +32,7 @@
margin-inline: 16px;
}
-/* A real (keyboard-focusable), restyled to match the label text. */
.view-label-download {
- background: none;
- border: none;
- color: inherit;
- font: inherit;
- padding: 0;
cursor: pointer;
text-decoration: underline;
}
diff --git a/examples/05-interoperability/11-converting-blocks-to-pdf-ua/src/testDocumentBlocks.ts b/examples/05-interoperability/11-converting-blocks-to-pdf-react-pdf-deprecated/src/testDocumentBlocks.ts
similarity index 100%
rename from examples/05-interoperability/11-converting-blocks-to-pdf-ua/src/testDocumentBlocks.ts
rename to examples/05-interoperability/11-converting-blocks-to-pdf-react-pdf-deprecated/src/testDocumentBlocks.ts
diff --git a/examples/05-interoperability/05-converting-blocks-to-pdf/src/vite-env.d.ts b/examples/05-interoperability/11-converting-blocks-to-pdf-react-pdf-deprecated/src/vite-env.d.ts
similarity index 100%
rename from examples/05-interoperability/05-converting-blocks-to-pdf/src/vite-env.d.ts
rename to examples/05-interoperability/11-converting-blocks-to-pdf-react-pdf-deprecated/src/vite-env.d.ts
diff --git a/examples/05-interoperability/11-converting-blocks-to-pdf-ua/tsconfig.json b/examples/05-interoperability/11-converting-blocks-to-pdf-react-pdf-deprecated/tsconfig.json
similarity index 100%
rename from examples/05-interoperability/11-converting-blocks-to-pdf-ua/tsconfig.json
rename to examples/05-interoperability/11-converting-blocks-to-pdf-react-pdf-deprecated/tsconfig.json
diff --git a/examples/05-interoperability/11-converting-blocks-to-pdf-ua/vite-env.d.ts b/examples/05-interoperability/11-converting-blocks-to-pdf-react-pdf-deprecated/vite-env.d.ts
similarity index 100%
rename from examples/05-interoperability/11-converting-blocks-to-pdf-ua/vite-env.d.ts
rename to examples/05-interoperability/11-converting-blocks-to-pdf-react-pdf-deprecated/vite-env.d.ts
diff --git a/examples/05-interoperability/11-converting-blocks-to-pdf-ua/vite.config.ts b/examples/05-interoperability/11-converting-blocks-to-pdf-react-pdf-deprecated/vite.config.ts
similarity index 100%
rename from examples/05-interoperability/11-converting-blocks-to-pdf-ua/vite.config.ts
rename to examples/05-interoperability/11-converting-blocks-to-pdf-react-pdf-deprecated/vite.config.ts
diff --git a/examples/05-interoperability/11-converting-blocks-to-pdf-ua/README.md b/examples/05-interoperability/11-converting-blocks-to-pdf-ua/README.md
deleted file mode 100644
index 91e9df6ef2..0000000000
--- a/examples/05-interoperability/11-converting-blocks-to-pdf-ua/README.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# Exporting documents to tagged PDF (PDF/UA)
-
-This example exports the current document to an **accessible, tagged PDF/UA-1**
-file using the Typst-powered `@blocknote/xl-pdf-exporter`. Unlike a plain PDF,
-a tagged PDF carries a logical structure tree (headings, paragraphs, lists,
-tables, figures with alt text, links) that screen readers can navigate.
-
-**Try it out:** Edit the document — the PDF preview updates live. Click
-"Download" to save it, then verify it with a tool like
-[veraPDF](https://verapdf.org/) (`--flavour ua1`) or the Acrobat Tags panel.
-
-> The first export downloads the Typst compiler (wasm) and fonts, so it may take
-> a moment. Images render as tagged placeholder figures for now.
diff --git a/examples/05-interoperability/11-converting-blocks-to-pdf-ua/src/App.tsx b/examples/05-interoperability/11-converting-blocks-to-pdf-ua/src/App.tsx
deleted file mode 100644
index bfce34f37a..0000000000
--- a/examples/05-interoperability/11-converting-blocks-to-pdf-ua/src/App.tsx
+++ /dev/null
@@ -1,304 +0,0 @@
-import { testDocumentBlocks } from "./testDocumentBlocks";
-import {
- Block,
- BlockNoteSchema,
- combineByGroup,
- withPageBreak,
-} from "@blocknote/core";
-import { filterSuggestionItems } from "@blocknote/core/extensions";
-import "@blocknote/core/fonts/inter.css";
-import * as locales from "@blocknote/core/locales";
-import { BlockNoteView } from "@blocknote/mantine";
-import "@blocknote/mantine/style.css";
-import { createReactDiagramBlockSpec } from "@blocknote/diagram-block";
-import { diagramBlockMapping } from "@blocknote/diagram-block/typst-exporter";
-import {
- createReactInlineMathSpec,
- createReactMathBlockSpec,
-} from "@blocknote/math-block";
-import {
- inlineMathMapping,
- mathBlockMapping,
-} from "@blocknote/math-block/typst-exporter";
-import {
- SuggestionMenuController,
- getDefaultReactSlashMenuItems,
- getPageBreakReactSlashMenuItems,
- useCreateBlockNote,
-} from "@blocknote/react";
-import {
- PDFExporter,
- typstDefaultSchemaMappings,
-} from "@blocknote/xl-pdf-exporter";
-import {
- getMultiColumnSlashMenuItems,
- locales as multiColumnLocales,
- multiColumnDropCursor,
- withMultiColumn,
-} from "@blocknote/xl-multi-column";
-// Bundle the Typst compiler wasm explicitly (it would otherwise load from
-// the package's own files - also CDN-free - but an explicit URL keeps the
-// bundling visible in this example).
-// Fonts need no setup: the exporter's bundled defaults (Inter, Geist Mono,
-// math, emoji - matching the editor) load lazily from the package.
-import compilerWasmUrl from "@blocknote/xl-typst-compiler/wasm?url";
-import { useCallback, useEffect, useMemo, useRef, useState } from "react";
-
-import "./styles.css";
-
-/**
- * Exports the given document to a PDF/UA object URL, re-exporting whenever
- * `blocks` changes.
- *
- * The effect-with-cleanup idiom keeps only the newest result: when a newer
- * version (or unmount) invalidates the effect, the cleanup marks the running
- * export stale and its result is dropped. Overlapping exports are *safe* -
- * the exporter serializes its shared compile stage internally - but like any
- * async calls they may complete out of call order, and which result to
- * display is this component's concern, not the exporter's.
- */
-function usePdfUA(
- makeExporter: () => PDFExporter,
- blocks: Block[],
-) {
- const [pdfUrl, setPdfUrl] = useState();
- const [status, setStatus] = useState<
- "loading" | "ready" | "unclaimed" | "error"
- >("loading");
-
- useEffect(() => {
- let stale = false;
- setStatus("loading");
- void (async () => {
- try {
- const result = await makeExporter().toPDF(blocks, {
- title: "BlockNote document",
- lang: "en",
- });
- if (stale) {
- return;
- }
- // A document that fails to compile (e.g. text no supplied font
- // covers) is an expected outcome, reported in the result.
- if (result.error) {
- // eslint-disable-next-line no-console
- console.error(
- "PDF export failed:",
- result.compileErrors.map((d) => d.message).join("; "),
- );
- setStatus("error");
- return;
- }
- // A nonconforming document (e.g. one not starting with an H1) still
- // exports - tagged but without the PDF/UA-1 claim; surface why, and
- // show a distinct status instead of claiming conformance.
- if (!result.pdfUA.declared && result.pdfUA.reason === "nonconforming") {
- // eslint-disable-next-line no-console
- console.info(
- "Exported without PDF/UA-1 declaration:",
- result.pdfUA.violations.map((v) => v.message).join("; "),
- );
- }
- setPdfUrl(URL.createObjectURL(result.blob));
- setStatus(result.pdfUA.declared ? "ready" : "unclaimed");
- } catch (e) {
- if (stale) {
- return;
- }
- // eslint-disable-next-line no-console
- console.error(e);
- setStatus("error");
- }
- })();
- return () => {
- stale = true;
- };
- }, [makeExporter, blocks]);
-
- // Each object URL is revoked when replaced by the next one (and the last
- // one on unmount).
- useEffect(() => {
- return () => {
- if (pdfUrl) {
- URL.revokeObjectURL(pdfUrl);
- }
- };
- }, [pdfUrl]);
-
- return { pdfUrl, status };
-}
-
-export default function App() {
- // Creates a new editor instance with support for page breaks.
- const editor = useCreateBlockNote({
- // Adds support for math & diagram blocks.
- schema: withMultiColumn(withPageBreak(BlockNoteSchema.create())).extend({
- blockSpecs: {
- mathBlock: createReactMathBlockSpec(),
- diagram: createReactDiagramBlockSpec(),
- },
- inlineContentSpecs: {
- math: createReactInlineMathSpec(),
- },
- }),
- dropCursor: multiColumnDropCursor,
- dictionary: {
- ...locales.en,
- multi_column: multiColumnLocales.en,
- },
- tables: {
- splitCells: true,
- cellBackgroundColor: true,
- cellTextColor: true,
- headers: true,
- },
- initialContent: [
- ...testDocumentBlocks,
- // The math & diagram blocks aren't part of the shared test document,
- // since the exporter unit tests' schemas don't register them, so they're
- // appended here instead.
- {
- type: "mathBlock",
- content: "a^2 = \\sqrt{b^2 + c^2}",
- },
- {
- type: "diagram",
- content: `graph TD
- A[Start] --> B{Works?}
- B -->|Yes| C[Ship it]
- B -->|No| A`,
- },
- {
- type: "paragraph",
- content: [
- {
- type: "text",
- text: "Inline math: ",
- styles: {},
- },
- {
- type: "math",
- content: "e^{i\\pi} + 1 = 0",
- },
- ],
- },
- ],
- });
-
- // Additional Slash Menu items for page breaks.
- const getSlashMenuItems = useMemo(
- () => async (query: string) =>
- filterSuggestionItems(
- combineByGroup(
- getDefaultReactSlashMenuItems(editor),
- getPageBreakReactSlashMenuItems(editor),
- getMultiColumnSlashMenuItems(editor),
- ),
- query,
- ),
- [editor],
- );
-
- // A fresh exporter per export: its asset registry is append-only for the
- // exporter's lifetime, so reusing one across re-exports would accumulate
- // every image/diagram variant it has ever rendered.
- const makeExporter = useCallback(
- () =>
- new PDFExporter(
- editor.schema,
- {
- ...typstDefaultSchemaMappings,
- blockMapping: {
- ...typstDefaultSchemaMappings.blockMapping,
- // Renders math blocks as native Typst equations, and diagrams as
- // embedded images - both carrying alt text for PDF/UA.
- mathBlock: mathBlockMapping,
- diagram: diagramBlockMapping,
- },
- inlineContentMapping: {
- ...typstDefaultSchemaMappings.inlineContentMapping,
- math: inlineMathMapping,
- },
- },
- {
- // The bundled compiler wasm (see the import above) - engine setup
- // belongs to the exporter, per-document facts go to toPDF.
- wasm: compilerWasmUrl,
- },
- ),
- [editor],
- );
-
- // The document snapshot driving the export - the export effect depends on
- // the data it exports. Updated debounced: reading `editor.document`
- // converts the whole document to blocks, so it shouldn't run (and the
- // export shouldn't restart) on every keystroke.
- const [blocks, setBlocks] = useState(() => editor.document);
- const { pdfUrl, status } = usePdfUA(makeExporter, blocks);
-
- const debounceTimer = useRef>(undefined);
- useEffect(() => () => clearTimeout(debounceTimer.current), []);
- const onChange = () => {
- clearTimeout(debounceTimer.current);
- debounceTimer.current = setTimeout(() => setBlocks(editor.document), 600);
- };
-
- const onDownloadClick = () => {
- if (!pdfUrl) {
- return;
- }
- const link = document.createElement("a");
- link.href = pdfUrl;
- link.download = "blocknote (pdf-ua).pdf";
- document.body.appendChild(link);
- link.click();
- link.remove();
- };
-
- const label =
- status === "loading"
- ? "Generating…"
- : status === "error"
- ? "Export failed (see console)"
- : status === "unclaimed"
- ? "Tagged PDF, no UA-1 claim (see console)"
- : "✓ Tagged PDF/UA-1";
-
- return (
-
-
-
Editor Input
-
-
-
-
-
-
-
-
- {label}
-
- Download
-
-
-
- {pdfUrl ? (
-
- ) : null}
-
-
-
- );
-}
diff --git a/packages/diagram-block/package.json b/packages/diagram-block/package.json
index ae7bb72a2e..c2315ed200 100644
--- a/packages/diagram-block/package.json
+++ b/packages/diagram-block/package.json
@@ -55,11 +55,6 @@
"import": "./dist/odt-exporter.js",
"require": "./dist/odt-exporter.cjs"
},
- "./pdf-exporter": {
- "types": "./types/src/pdf-exporter/index.d.ts",
- "import": "./dist/pdf-exporter.js",
- "require": "./dist/pdf-exporter.cjs"
- },
"./email-exporter": {
"types": "./types/src/email-exporter/index.d.ts",
"import": "./dist/email-exporter.js",
@@ -89,16 +84,13 @@
"@blocknote/xl-docx-exporter": "workspace:^",
"@blocknote/xl-email-exporter": "workspace:^",
"@blocknote/xl-odt-exporter": "workspace:^",
- "@blocknote/xl-pdf-exporter": "workspace:^",
"@react-email/components": "^1.0.12",
- "@react-pdf/renderer": "^4.5.1",
"@types/react": "^19.2.3",
"@zip.js/zip.js": "^2.8.8",
"@types/react-dom": "^19.2.3",
"docx": "^9.6.1",
"react": "^19.2.5",
"react-dom": "^19.2.5",
- "react-element-to-jsx-string": "^17.0.1",
"rimraf": "^5.0.10",
"rollup-plugin-webpack-stats": "^0.2.6",
"typescript": "^7.0.2",
@@ -110,9 +102,7 @@
"@blocknote/xl-docx-exporter": "workspace:^",
"@blocknote/xl-email-exporter": "workspace:^",
"@blocknote/xl-odt-exporter": "workspace:^",
- "@blocknote/xl-pdf-exporter": "workspace:^",
"@react-email/components": "^1.0.12",
- "@react-pdf/renderer": "^4.5.1",
"docx": "^9.6.1",
"react": "^18.0 || ^19.0 || >= 19.0.0-rc",
"react-dom": "^18.0 || ^19.0 || >= 19.0.0-rc",
@@ -128,15 +118,9 @@
"@blocknote/xl-odt-exporter": {
"optional": true
},
- "@blocknote/xl-pdf-exporter": {
- "optional": true
- },
"@react-email/components": {
"optional": true
},
- "@react-pdf/renderer": {
- "optional": true
- },
"docx": {
"optional": true
},
diff --git a/packages/diagram-block/src/pdf-exporter/index.tsx b/packages/diagram-block/src/pdf-exporter/index.tsx
deleted file mode 100644
index 847e988f66..0000000000
--- a/packages/diagram-block/src/pdf-exporter/index.tsx
+++ /dev/null
@@ -1,118 +0,0 @@
-import type {
- BlockConfig,
- BlockFromConfigNoChildren,
- Exporter,
-} from "@blocknote/core";
-import { exportImageToDataURL, plainContentToString } from "@blocknote/core";
-import { Image, Text, View } from "@react-pdf/renderer";
-
-import {
- RenderDiagram,
- renderDiagramToImage,
-} from "../helpers/renderDiagramToImage.js";
-import { getDiagramExporterDictionary } from "../i18n/dictionary.js";
-
-export type { RenderDiagram } from "../helpers/renderDiagramToImage.js";
-
-type DiagramBlock = BlockFromConfigNoChildren<
- BlockConfig<"diagram", {}, "plain">,
- any,
- any
->;
-
-const PIXELS_PER_POINT = 0.75;
-const MAX_WIDTH_POINTS = 400;
-
-// Mirrors the editor, which shows the error state in the preview
-// placeholder, identifying the diagram by the (first line of the) source.
-// The parser's message is deliberately NOT rendered: it's authoring detail
-// (and untranslated English) - the editor is where the author sees and
-// fixes it.
-function errorText(
- exporter: Exporter,
- source: string,
-) {
- return (
-
-
- {getDiagramExporterDictionary(exporter).invalid_diagram(
- source.split("\n")[0],
- )}
-
-
- );
-}
-
-/**
- * Creates a PDF block mapping for `@blocknote/diagram-block` that embeds
- * diagrams as images. Rendering runs in the browser by default (Mermaid
- * can't render outside of it); when exporting elsewhere (e.g. server-side),
- * pass a `renderDiagram` function backed by e.g. `@mermaid-js/mermaid-cli`
- * or a Kroki server. Invalid sources render an error placeholder (mirroring
- * the editor):
- *
- * ```ts
- * import { createDiagramBlockMapping } from "@blocknote/diagram-block/pdf-exporter";
- *
- * new PDFExporter(schema, {
- * ...pdfDefaultSchemaMappings,
- * blockMapping: {
- * ...pdfDefaultSchemaMappings.blockMapping,
- * diagram: createDiagramBlockMapping({ renderDiagram }),
- * },
- * });
- * ```
- */
-export function createDiagramBlockMapping(options?: {
- renderDiagram?: RenderDiagram;
-}) {
- return async (
- block: DiagramBlock,
- exporter: Exporter,
- ) => {
- const source = plainContentToString(block.content);
- if (!source.trim()) {
- return ;
- }
-
- const renderDiagram =
- options?.renderDiagram ??
- (typeof document !== "undefined" ? renderDiagramToImage : undefined);
- if (!renderDiagram) {
- throw new Error(
- "Rendering diagrams to images requires a browser. When exporting elsewhere, pass a `renderDiagram` function to `createDiagramBlockMapping` (e.g. backed by @mermaid-js/mermaid-cli or a Kroki server).",
- );
- }
-
- const result = await renderDiagram(source);
- if (result.error !== undefined) {
- return errorText(exporter, source);
- }
-
- return (
-
- );
- };
-}
-
-/**
- * PDF block mapping for `@blocknote/diagram-block` with the default options
- * - see {@link createDiagramBlockMapping}. Browser-only; when exporting
- * elsewhere, use the factory to pass a `renderDiagram` function.
- */
-/**
- * @deprecated Mapping for the react-pdf based exporter
- * (`@blocknote/xl-pdf-exporter/react-pdf`), which is deprecated - use the
- * Typst mapping from `@blocknote/diagram-block/typst-exporter` with the
- * Typst-based `PDFExporter` instead.
- */
-export const diagramBlockMapping = createDiagramBlockMapping();
diff --git a/packages/diagram-block/src/pdf-exporter/pdfExporter.test.tsx b/packages/diagram-block/src/pdf-exporter/pdfExporter.test.tsx
deleted file mode 100644
index d5207c86f8..0000000000
--- a/packages/diagram-block/src/pdf-exporter/pdfExporter.test.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import { BlockNoteSchema, defaultBlockSpecs } from "@blocknote/core";
-import {
- PDFExporter,
- pdfDefaultSchemaMappings,
-} from "@blocknote/xl-pdf-exporter/react-pdf";
-import reactElementToJSXString from "react-element-to-jsx-string";
-import { describe, expect, it } from "vite-plus/test";
-
-import {
- diagramDocument,
- renderDiagram,
- renderInvalidDiagram,
-} from "../exporterTestUtil.js";
-import { createDiagramBlockMapping, diagramBlockMapping } from "./index.js";
-
-function createExporter(diagram: ReturnType) {
- const mappings = {
- ...pdfDefaultSchemaMappings,
- blockMapping: {
- ...pdfDefaultSchemaMappings.blockMapping,
- diagram,
- },
- };
- return new PDFExporter(
- BlockNoteSchema.create({ blockSpecs: defaultBlockSpecs }),
- mappings as any,
- );
-}
-
-describe("pdf exporter mappings", () => {
- it("should embed the rendered diagram as an image", async () => {
- const exporter = createExporter(
- createDiagramBlockMapping({ renderDiagram }),
- );
-
- const str = reactElementToJSXString(
- await exporter.toReactPDFDocument(diagramDocument),
- );
-
- expect(str).toContain("data:image/png;base64,");
- // 100px wide at 0.75 points per pixel.
- expect(str).toContain("width: 75");
- });
-
- it("should throw a descriptive error without a renderer outside the browser", async () => {
- const exporter = createExporter(diagramBlockMapping);
-
- await expect(exporter.toReactPDFDocument(diagramDocument)).rejects.toThrow(
- "pass a `renderDiagram` function",
- );
- });
-
- it("should render an error placeholder for invalid sources", async () => {
- // The renderer returns invalid sources as a typed error, and the
- // mapping renders the error placeholder - never the raw source.
- const exporter = createExporter(
- createDiagramBlockMapping({ renderDiagram: renderInvalidDiagram }),
- );
-
- const str = reactElementToJSXString(
- await exporter.toReactPDFDocument(diagramDocument),
- );
-
- expect(str).toContain("Invalid diagram");
- expect(str).toContain("graph TD");
- expect(str).not.toContain("data:image");
- });
-});
diff --git a/packages/diagram-block/vite.config.ts b/packages/diagram-block/vite.config.ts
index 8ec4142c88..97ef0c2d39 100644
--- a/packages/diagram-block/vite.config.ts
+++ b/packages/diagram-block/vite.config.ts
@@ -61,10 +61,6 @@ export default defineConfig(
__dirname,
"../xl-odt-exporter/src/",
),
- "@blocknote/xl-pdf-exporter": path.resolve(
- __dirname,
- "../xl-pdf-exporter/src/",
- ),
"@blocknote/xl-typst-exporter": path.resolve(
__dirname,
"../xl-typst-exporter/src/",
@@ -84,10 +80,6 @@ export default defineConfig(
__dirname,
"src/odt-exporter/index.ts",
),
- "pdf-exporter": path.resolve(
- __dirname,
- "src/pdf-exporter/index.tsx",
- ),
"email-exporter": path.resolve(
__dirname,
"src/email-exporter/index.tsx",
diff --git a/packages/math-block/package.json b/packages/math-block/package.json
index 5bea233906..052a8742f5 100644
--- a/packages/math-block/package.json
+++ b/packages/math-block/package.json
@@ -55,11 +55,6 @@
"import": "./dist/odt-exporter.js",
"require": "./dist/odt-exporter.cjs"
},
- "./pdf-exporter": {
- "types": "./types/src/pdf-exporter/index.d.ts",
- "import": "./dist/pdf-exporter.js",
- "require": "./dist/pdf-exporter.cjs"
- },
"./email-exporter": {
"types": "./types/src/email-exporter/index.d.ts",
"import": "./dist/email-exporter.js",
@@ -92,10 +87,7 @@
"@blocknote/xl-docx-exporter": "workspace:^",
"@blocknote/xl-email-exporter": "workspace:^",
"@blocknote/xl-odt-exporter": "workspace:^",
- "@blocknote/xl-pdf-exporter": "workspace:^",
"@react-email/components": "^1.0.12",
- "@react-pdf/math": "^2.0.1",
- "@react-pdf/renderer": "^4.5.1",
"@types/katex": "^0.16.7",
"mathml2omml": "^0.5.0",
"@types/react": "^19.2.3",
@@ -105,7 +97,6 @@
"mathjax-full": "^3.2.2",
"react": "^19.2.5",
"react-dom": "^19.2.5",
- "react-element-to-jsx-string": "^17.0.1",
"rimraf": "^5.0.10",
"rollup-plugin-webpack-stats": "^0.2.6",
"typescript": "^7.0.2",
@@ -119,10 +110,7 @@
"@blocknote/xl-docx-exporter": "workspace:^",
"@blocknote/xl-email-exporter": "workspace:^",
"@blocknote/xl-odt-exporter": "workspace:^",
- "@blocknote/xl-pdf-exporter": "workspace:^",
"@react-email/components": "^1.0.12",
- "@react-pdf/math": "^2.0.0",
- "@react-pdf/renderer": "^4.5.1",
"docx": "^9.6.1",
"mathjax-full": "^3.2.2",
"mathml2omml": "^0.5.0",
@@ -141,18 +129,9 @@
"@blocknote/xl-odt-exporter": {
"optional": true
},
- "@blocknote/xl-pdf-exporter": {
- "optional": true
- },
"@react-email/components": {
"optional": true
},
- "@react-pdf/math": {
- "optional": true
- },
- "@react-pdf/renderer": {
- "optional": true
- },
"docx": {
"optional": true
},
diff --git a/packages/math-block/src/exporterHelpers/renderMathToImage.ts b/packages/math-block/src/exporterHelpers/renderMathToImage.ts
index bd24f76e08..09e35fee0c 100644
--- a/packages/math-block/src/exporterHelpers/renderMathToImage.ts
+++ b/packages/math-block/src/exporterHelpers/renderMathToImage.ts
@@ -49,8 +49,7 @@ const TEX_PACKAGES = [
// MathJax (rather than KaTeX, which the math block itself renders with) is
// used for image-based export: its SVG output is self-contained paths, so it
// rasterizes without needing the KaTeX webfonts. `mathjax-full` is an
-// optional peer dependency - when exporting math to PDF it's already
-// installed transitively via `@react-pdf/math`.
+// optional peer dependency, needed only for these image-based exports.
let mathDocument: ReturnType | undefined;
let documentAdaptor: ReturnType | undefined;
diff --git a/packages/math-block/src/pdf-exporter/__snapshots__/exampleWithMathMappings.jsx b/packages/math-block/src/pdf-exporter/__snapshots__/exampleWithMathMappings.jsx
deleted file mode 100644
index cd3a3b0a49..0000000000
--- a/packages/math-block/src/pdf-exporter/__snapshots__/exampleWithMathMappings.jsx
+++ /dev/null
@@ -1,60 +0,0 @@
-
-
-
-
-
-
- {`a^2 = \sqrt{b^2 + c^2}`}
-
-
-
-
-
-
-
-
- Inline math:{' '}
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/packages/math-block/src/pdf-exporter/index.tsx b/packages/math-block/src/pdf-exporter/index.tsx
deleted file mode 100644
index ff1f631b1f..0000000000
--- a/packages/math-block/src/pdf-exporter/index.tsx
+++ /dev/null
@@ -1,186 +0,0 @@
-import type {
- BlockConfig,
- BlockFromConfigNoChildren,
- Exporter,
-} from "@blocknote/core";
-import { exportImageToDataURL, plainContentToString } from "@blocknote/core";
-import { Math } from "@react-pdf/math";
-import { Image, Text, View } from "@react-pdf/renderer";
-
-import {
- latexToMathSVG,
- RasterizeSVG,
- rasterizeSVGInBrowser,
-} from "../exporterHelpers/renderMathToImage.js";
-import { getMathExporterDictionary } from "../i18n/dictionary.js";
-
-type MathBlock = BlockFromConfigNoChildren<
- BlockConfig<"mathBlock", {}, "plain">,
- any,
- any
->;
-
-type InlineMath = { type: "math"; content: string };
-
-export {
- latexToMathSVG,
- rasterizeSVGInBrowser,
-} from "../exporterHelpers/renderMathToImage.js";
-export type {
- RasterizeSVG,
- SVGExportImage,
-} from "../exporterHelpers/renderMathToImage.js";
-
-// The PDF exporter's body text is 12pt (16px at 0.75 pixels per point).
-const FONT_SIZE_POINTS = 16 * 0.75;
-
-// Mirrors the editor, which shows the error state in the preview
-// placeholder, identifying the formula by its source. The parser's message
-// is deliberately NOT rendered: it's authoring detail (and untranslated
-// English) - the editor is where the author sees and fixes it.
-function errorText(
- exporter: Exporter,
- source: string,
- key: string,
-) {
- return (
-
- {getMathExporterDictionary(exporter).invalid_formula(source)}
-
- );
-}
-
-/**
- * PDF block mapping for `@blocknote/math-block` that renders math blocks as
- * actual formulas (via `@react-pdf/math`, which converts the LaTeX to SVG
- * paths with MathJax). Invalid LaTeX renders an error placeholder
- * (mirroring the editor):
- *
- * ```ts
- * import { mathBlockMapping } from "@blocknote/math-block/pdf-exporter";
- *
- * new PDFExporter(schema, {
- * ...pdfDefaultSchemaMappings,
- * blockMapping: {
- * ...pdfDefaultSchemaMappings.blockMapping,
- * mathBlock: mathBlockMapping,
- * },
- * });
- * ```
- */
-/**
- * @deprecated Mapping for the react-pdf based exporter
- * (`@blocknote/xl-pdf-exporter/react-pdf`), which is deprecated - use the
- * Typst mapping from `@blocknote/math-block/typst-exporter` with the
- * Typst-based `PDFExporter` instead.
- */
-export function mathBlockMapping(
- block: MathBlock,
- exporter: Exporter,
-) {
- const source = plainContentToString(block.content);
- if (!source.trim()) {
- return ;
- }
-
- // `Math` renders MathJax's own error output for invalid LaTeX; validate
- // up front to render the editor-style error placeholder instead.
- const validation = latexToMathSVG(source, {
- inline: false,
- fontSize: FONT_SIZE_POINTS,
- });
- if (validation.error !== undefined) {
- return (
-
- {errorText(exporter, source, "math-error")}
-
- );
- }
-
- return (
-
- {source}
-
- );
-}
-
-/**
- * Creates a PDF inline content mapping for `@blocknote/math-block` that
- * renders inline math as formulas, rasterized to images that flow inline
- * with the text (react-pdf drops SVG elements inside `Text`, so the vector
- * output used for math blocks isn't an option here). Note that the image
- * sits on the text baseline, so expressions with depth (fractions,
- * subscripts) render slightly raised - and it's sized for the exporter's
- * 12pt body text (mappings get no font context from react-pdf), so inline
- * math inside headings renders at body-text size.
- *
- * Rasterization runs in the browser by default; when exporting elsewhere
- * (e.g. server-side), pass a `rasterize` function backed by an SVG
- * rasterizer such as `@resvg/resvg-js` or `sharp`. Invalid LaTeX renders an
- * error placeholder (mirroring the editor):
- *
- * ```ts
- * import { createInlineMathMapping } from "@blocknote/math-block/pdf-exporter";
- *
- * new PDFExporter(schema, {
- * ...pdfDefaultSchemaMappings,
- * inlineContentMapping: {
- * ...pdfDefaultSchemaMappings.inlineContentMapping,
- * math: createInlineMathMapping({ rasterize }),
- * },
- * });
- * ```
- */
-export function createInlineMathMapping(options?: {
- rasterize?: RasterizeSVG;
-}) {
- return (
- inlineContent: InlineMath,
- exporter: Exporter,
- ) => {
- const source = inlineContent.content;
- if (!source.trim()) {
- return ;
- }
-
- const rasterize = options?.rasterize ?? rasterizeSVGInBrowser;
- if (!options?.rasterize && typeof document === "undefined") {
- throw new Error(
- "Rendering inline math requires rasterizing SVGs, which the built-in rasterizer can only do in the browser. When exporting elsewhere, pass a `rasterize` function to `createInlineMathMapping` (e.g. backed by @resvg/resvg-js or sharp).",
- );
- }
-
- // The metrics are needed synchronously for react-pdf's text layout; only
- // the rasterization itself is deferred, via react-pdf's support for
- // async `src` functions (resolved before layout). Rasterization failures
- // are unexpected and propagate - react-pdf skips the image and warns.
- const result = latexToMathSVG(source, {
- inline: true,
- fontSize: FONT_SIZE_POINTS,
- });
- if (result.error !== undefined) {
- return errorText(exporter, source, "inlineMath");
- }
-
- return (
- rasterize(result.image).then(exportImageToDataURL)}
- style={{ width: result.image.width, height: result.image.height }}
- />
- );
- };
-}
-
-/**
- * PDF inline content mapping for `@blocknote/math-block` with the default
- * options - see {@link createInlineMathMapping}. Browser-only; when
- * exporting elsewhere, use the factory to pass a `rasterize` function.
- */
-/**
- * @deprecated Mappings for the react-pdf based exporter
- * (`@blocknote/xl-pdf-exporter/react-pdf`), which is deprecated - use the
- * Typst mappings from `@blocknote/math-block/typst-exporter` with the
- * Typst-based `PDFExporter` instead.
- */
-export const inlineMathMapping = createInlineMathMapping();
diff --git a/packages/math-block/src/pdf-exporter/pdfExporter.test.tsx b/packages/math-block/src/pdf-exporter/pdfExporter.test.tsx
deleted file mode 100644
index f11771899c..0000000000
--- a/packages/math-block/src/pdf-exporter/pdfExporter.test.tsx
+++ /dev/null
@@ -1,142 +0,0 @@
-import {
- BlockNoteSchema,
- createPageBreakBlockSpec,
- defaultBlockSpecs,
- ExportImage,
-} from "@blocknote/core";
-import {
- PDFExporter,
- pdfDefaultSchemaMappings,
-} from "@blocknote/xl-pdf-exporter/react-pdf";
-import { testDocumentWithSourceBlocks } from "@shared/testDocument.js";
-import reactElementToJSXString from "react-element-to-jsx-string";
-import { describe, expect, it } from "vite-plus/test";
-
-import {
- createInlineMathMapping,
- inlineMathMapping,
- mathBlockMapping,
-} from "./index.js";
-
-// A stub rasterizer, standing in for e.g. @resvg/resvg-js on a server - the
-// real (browser-only) rasterization is covered by the browser test suite.
-const rasterize = async (svg: ExportImage) => ({
- mimeType: "image/png",
- data: new Uint8Array([0, 0, 0]),
- width: svg.width,
- height: svg.height,
-});
-
-function createExporter(
- inlineMath: ReturnType,
-) {
- // Assembled outside the constructor call as the schema doesn't include
- // the math specs - like the default mappings, the math entries just map
- // the block JSON.
- const mappings = {
- ...pdfDefaultSchemaMappings,
- blockMapping: {
- ...pdfDefaultSchemaMappings.blockMapping,
- mathBlock: mathBlockMapping,
- },
- inlineContentMapping: {
- ...pdfDefaultSchemaMappings.inlineContentMapping,
- math: inlineMath,
- },
- };
-
- return new PDFExporter(
- BlockNoteSchema.create({
- blockSpecs: {
- ...defaultBlockSpecs,
- pageBreak: createPageBreakBlockSpec(),
- },
- }),
- mappings,
- );
-}
-
-describe("pdf exporter mappings", () => {
- it("should export math as formulas and inline math as images", async () => {
- const exporter = createExporter(createInlineMathMapping({ rasterize }));
-
- // The math block & inline math paragraph from the shared test document.
- const transformed = await exporter.toReactPDFDocument(
- // `as any`: the filtered subset holds no multi-column blocks, but the
- // fixture's type still carries them and this schema doesn't.
- testDocumentWithSourceBlocks.filter((block) =>
- ["math-block", "paragraph-with-inline-math"].includes(block.id),
- ) as any,
- );
- const str = reactElementToJSXString(transformed);
-
- await expect(str).toMatchFileSnapshot(
- "__snapshots__/exampleWithMathMappings.jsx",
- );
- });
-
- it("should render error placeholders for invalid LaTeX", async () => {
- const exporter = createExporter(createInlineMathMapping({ rasterize }));
-
- const transformed = await exporter.toReactPDFDocument([
- {
- id: "1",
- type: "mathBlock",
- props: {},
- content: [{ type: "text", text: "\\invalidcommand{", styles: {} }],
- children: [],
- },
- {
- id: "2",
- type: "paragraph",
- props: {},
- content: [
- { type: "text", text: "Broken: ", styles: {} },
- { type: "math", props: {}, content: "\\invalidcommand{" },
- ],
- children: [],
- },
- ] as any);
- const str = reactElementToJSXString(transformed);
-
- // Mirrors the editor's error placeholder rather than dumping the LaTeX
- // source on readers - once for the block, once for the inline math.
- expect(str.match(/Invalid formula/g)).toHaveLength(2);
- });
-
- it("should render empty math as nothing", async () => {
- // Empty source isn't an error - there's just nothing to render (and no
- // rasterizer is needed).
- const exporter = createExporter(inlineMathMapping);
-
- const transformed = await exporter.toReactPDFDocument([
- { id: "1", type: "mathBlock", props: {}, content: [], children: [] },
- {
- id: "2",
- type: "paragraph",
- props: {},
- content: [{ type: "math", props: {}, content: "" }],
- children: [],
- },
- ] as any);
- const str = reactElementToJSXString(transformed);
-
- expect(str).not.toContain("Invalid formula");
- expect(str).not.toContain("Math");
- });
-
- it("should throw a descriptive error without a rasterizer outside the browser", async () => {
- // The default mapping's built-in rasterizer only works in the browser,
- // and silently degrading is worse than failing loudly - the error names
- // the `rasterize` option to pass.
- const exporter = createExporter(inlineMathMapping);
-
- await expect(
- exporter.toReactPDFDocument(
- testDocumentWithSourceBlocks.filter(
- (block) => block.id === "paragraph-with-inline-math",
- ) as any,
- ),
- ).rejects.toThrow("pass a `rasterize` function");
- });
-});
diff --git a/packages/math-block/vite.config.ts b/packages/math-block/vite.config.ts
index f2bf1f59df..ef267e8837 100644
--- a/packages/math-block/vite.config.ts
+++ b/packages/math-block/vite.config.ts
@@ -61,10 +61,6 @@ export default defineConfig(
__dirname,
"../xl-odt-exporter/src/",
),
- "@blocknote/xl-pdf-exporter": path.resolve(
- __dirname,
- "../xl-pdf-exporter/src/",
- ),
"@blocknote/xl-typst-exporter": path.resolve(
__dirname,
"../xl-typst-exporter/src/",
@@ -84,10 +80,6 @@ export default defineConfig(
__dirname,
"src/odt-exporter/index.ts",
),
- "pdf-exporter": path.resolve(
- __dirname,
- "src/pdf-exporter/index.tsx",
- ),
"email-exporter": path.resolve(
__dirname,
"src/email-exporter/index.tsx",
diff --git a/playground/package.json b/playground/package.json
index eb8b7400ed..62cd0019ce 100644
--- a/playground/package.json
+++ b/playground/package.json
@@ -29,7 +29,6 @@
"@blocknote/xl-multi-column": "workspace:^",
"@blocknote/xl-odt-exporter": "workspace:^",
"@blocknote/xl-pdf-exporter": "workspace:^",
- "@react-pdf/math": "^2.0.1",
"katex": "^0.16.11",
"@emotion/react": "11.14.0",
"@emotion/styled": "11.14.1",
diff --git a/playground/src/examples.gen.tsx b/playground/src/examples.gen.tsx
index 2da413c0f0..2d32e8c524 100644
--- a/playground/src/examples.gen.tsx
+++ b/playground/src/examples.gen.tsx
@@ -1125,26 +1125,24 @@ export const examples = {
playground: true,
docs: true,
author: "yousefed",
- tags: ["Interoperability"],
+ tags: ["Interoperability", "Accessibility"],
sharedTestDocument: true,
dependencies: {
- "@blocknote/diagram-block": "latest",
- "@blocknote/math-block": "latest",
- "@blocknote/xl-multi-column": "latest",
"@blocknote/xl-pdf-exporter": "latest",
- "@react-pdf/math": "^2.0.1",
- "@react-pdf/renderer": "^4.5.1",
- "mathjax-full": "^3.2.2",
+ "@blocknote/xl-multi-column": "latest",
+ "@blocknote/math-block": "latest",
+ "@blocknote/diagram-block": "latest",
+ "@blocknote/xl-typst-compiler": "latest",
} as any,
pro: true,
},
- title: "Exporting documents to PDF (react-pdf, deprecated)",
+ title: "Exporting documents to PDF (PDF/UA)",
group: {
pathFromRoot: "examples/05-interoperability",
slug: "interoperability",
},
readme:
- '> **Deprecated:** this example uses the react-pdf based exporter\n> (`@blocknote/xl-pdf-exporter/react-pdf`), which is deprecated and will be\n> removed after a few releases. Use the Typst-based `PDFExporter` instead -\n> see the "Exporting documents to tagged PDF (PDF/UA)" example - which\n> produces accessible, tagged PDF/UA-1 output.\n\nThis example exports the current document (all blocks) as a PDF file and downloads it to your computer.\n\n**Try it out:** Edit the document and click "Download .pdf" at the top to download the PDF file.',
+ 'This example exports the current document to an **accessible, tagged PDF/UA-1**\nfile using the Typst-powered `@blocknote/xl-pdf-exporter`. Unlike a plain PDF,\na tagged PDF carries a logical structure tree (headings, paragraphs, lists,\ntables, figures with alt text, links) that screen readers can navigate.\n\n**Try it out:** Edit the document — the PDF preview updates live. Click\n"Download" to save it, then verify it with a tool like\n[veraPDF](https://verapdf.org/) (`--flavour ua1`) or the Acrobat Tags panel.\n\n> The first export downloads the Typst compiler (wasm) and fonts, so it may take\n> a moment. Images render as tagged placeholder figures for now.',
},
{
projectSlug: "converting-blocks-to-docx",
@@ -1268,32 +1266,31 @@ export const examples = {
"This example exports the current document (all blocks) as HTML and renders it.\n\n**Relevant Docs:**\n\n- [Converting Blocks to HTML](/docs/features/export/html)",
},
{
- projectSlug: "converting-blocks-to-pdf-ua",
- fullSlug: "interoperability/converting-blocks-to-pdf-ua",
+ projectSlug: "converting-blocks-to-pdf-react-pdf-deprecated",
+ fullSlug:
+ "interoperability/converting-blocks-to-pdf-react-pdf-deprecated",
pathFromRoot:
- "examples/05-interoperability/11-converting-blocks-to-pdf-ua",
+ "examples/05-interoperability/11-converting-blocks-to-pdf-react-pdf-deprecated",
config: {
playground: true,
docs: true,
author: "yousefed",
- tags: ["Interoperability", "Accessibility"],
+ tags: ["Interoperability"],
sharedTestDocument: true,
dependencies: {
- "@blocknote/xl-pdf-exporter": "latest",
"@blocknote/xl-multi-column": "latest",
- "@blocknote/math-block": "latest",
- "@blocknote/diagram-block": "latest",
- "@blocknote/xl-typst-compiler": "latest",
+ "@blocknote/xl-pdf-exporter": "latest",
+ "@react-pdf/renderer": "^4.5.1",
} as any,
pro: true,
},
- title: "Exporting documents to tagged PDF (PDF/UA)",
+ title: "Exporting documents to PDF (react-pdf, deprecated)",
group: {
pathFromRoot: "examples/05-interoperability",
slug: "interoperability",
},
readme:
- 'This example exports the current document to an **accessible, tagged PDF/UA-1**\nfile using the Typst-powered `@blocknote/xl-pdf-exporter`. Unlike a plain PDF,\na tagged PDF carries a logical structure tree (headings, paragraphs, lists,\ntables, figures with alt text, links) that screen readers can navigate.\n\n**Try it out:** Edit the document — the PDF preview updates live. Click\n"Download" to save it, then verify it with a tool like\n[veraPDF](https://verapdf.org/) (`--flavour ua1`) or the Acrobat Tags panel.\n\n> The first export downloads the Typst compiler (wasm) and fonts, so it may take\n> a moment. Images render as tagged placeholder figures for now.',
+ '> **Deprecated:** this example uses the react-pdf based exporter\n> (`@blocknote/xl-pdf-exporter/react-pdf`), which is deprecated and will be\n> removed after a few releases. Use the Typst-based `PDFExporter` instead -\n> see the "Exporting documents to PDF (PDF/UA)" example - which\n> produces accessible, tagged PDF/UA-1 output.\n\nThis example exports the current document (all blocks) as a PDF file and downloads it to your computer.\n\n**Try it out:** Edit the document and click "Download .pdf" at the top to download the PDF file.',
},
],
},
diff --git a/playground/vite.config.ts b/playground/vite.config.ts
index bd0d7ccd97..43174b567e 100644
--- a/playground/vite.config.ts
+++ b/playground/vite.config.ts
@@ -136,15 +136,11 @@ export default defineConfig(((conf: { command: string }) => ({
"../packages/xl-typst-exporter/src",
),
// math-block's typst entry's optional peer, resolved from the
- // playground's own dependencies (like @react-pdf/math below).
+ // playground's own dependencies (like katex below).
tex2typst: resolve(__dirname, "node_modules/tex2typst"),
// The shared test-utils package the suggestion-gallery example
// imports; dev mode resolves it via devAliases above.
"@shared": resolve(__dirname, "../shared"),
- "@react-pdf/math": resolve(
- __dirname,
- "node_modules/@react-pdf/math",
- ),
katex: resolve(__dirname, "node_modules/katex"),
}
: devAliases,
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d4bb3206da..0a4aeb2f0e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -183,9 +183,6 @@ importers:
'@react-email/render':
specifier: ^2.0.4
version: 2.1.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
- '@react-pdf/math':
- specifier: ^2.0.1
- version: 2.0.1(@react-pdf/renderer@4.5.1(react@19.2.5))(react@19.2.5)
'@react-pdf/renderer':
specifier: ^4.5.1
version: 4.5.1(react@19.2.5)
@@ -2817,21 +2814,15 @@ importers:
'@blocknote/xl-pdf-exporter':
specifier: latest
version: link:../../../packages/xl-pdf-exporter
+ '@blocknote/xl-typst-compiler':
+ specifier: latest
+ version: link:../../../packages/xl-typst-compiler
'@mantine/core':
specifier: ^9.0.2
version: 9.1.1(@mantine/hooks@9.1.1(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
'@mantine/hooks':
specifier: ^9.0.2
version: 9.1.1(react@19.2.5)
- '@react-pdf/math':
- specifier: ^2.0.1
- version: 2.0.1(@react-pdf/renderer@4.5.1(react@19.2.5))(react@19.2.5)
- '@react-pdf/renderer':
- specifier: ^4.5.1
- version: 4.5.1(react@19.2.5)
- mathjax-full:
- specifier: ^3.2.2
- version: 3.2.2
react:
specifier: ^19.2.3
version: 19.2.5
@@ -3112,7 +3103,7 @@ importers:
specifier: ^8.0.0
version: 8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0)
- examples/05-interoperability/11-converting-blocks-to-pdf-ua:
+ examples/05-interoperability/11-converting-blocks-to-pdf-react-pdf-deprecated:
dependencies:
'@blocknote/ariakit':
specifier: latest
@@ -3120,15 +3111,9 @@ importers:
'@blocknote/core':
specifier: latest
version: link:../../../packages/core
- '@blocknote/diagram-block':
- specifier: latest
- version: link:../../../packages/diagram-block
'@blocknote/mantine':
specifier: latest
version: link:../../../packages/mantine
- '@blocknote/math-block':
- specifier: latest
- version: link:../../../packages/math-block
'@blocknote/react':
specifier: latest
version: link:../../../packages/react
@@ -3141,15 +3126,15 @@ importers:
'@blocknote/xl-pdf-exporter':
specifier: latest
version: link:../../../packages/xl-pdf-exporter
- '@blocknote/xl-typst-compiler':
- specifier: latest
- version: link:../../../packages/xl-typst-compiler
'@mantine/core':
specifier: ^9.0.2
version: 9.1.1(@mantine/hooks@9.1.1(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
'@mantine/hooks':
specifier: ^9.0.2
version: 9.1.1(react@19.2.5)
+ '@react-pdf/renderer':
+ specifier: ^4.5.1
+ version: 4.5.1(react@19.2.5)
react:
specifier: ^19.2.3
version: 19.2.5
@@ -5421,9 +5406,6 @@ importers:
'@blocknote/xl-odt-exporter':
specifier: workspace:^
version: link:../xl-odt-exporter
- '@blocknote/xl-pdf-exporter':
- specifier: workspace:^
- version: link:../xl-pdf-exporter
'@blocknote/xl-typst-compiler':
specifier: workspace:^
version: link:../xl-typst-compiler
@@ -5433,9 +5415,6 @@ importers:
'@react-email/components':
specifier: ^1.0.12
version: 1.0.12(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
- '@react-pdf/renderer':
- specifier: ^4.5.1
- version: 4.5.1(react@19.2.5)
'@types/react':
specifier: ^19.2.3
version: 19.2.14
@@ -5454,9 +5433,6 @@ importers:
react-dom:
specifier: ^19.2.5
version: 19.2.5(react@19.2.5)
- react-element-to-jsx-string:
- specifier: ^17.0.1
- version: 17.0.1(react-dom@19.2.5(react@19.2.5))(react-is@19.2.4)(react@19.2.5)
react-icons:
specifier: ^5.5.0
version: 5.6.0(react@19.2.5)
@@ -5555,9 +5531,6 @@ importers:
'@blocknote/xl-odt-exporter':
specifier: workspace:^
version: link:../xl-odt-exporter
- '@blocknote/xl-pdf-exporter':
- specifier: workspace:^
- version: link:../xl-pdf-exporter
'@blocknote/xl-typst-compiler':
specifier: workspace:^
version: link:../xl-typst-compiler
@@ -5567,12 +5540,6 @@ importers:
'@react-email/components':
specifier: ^1.0.12
version: 1.0.12(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
- '@react-pdf/math':
- specifier: ^2.0.1
- version: 2.0.1(@react-pdf/renderer@4.5.1(react@19.2.5))(react@19.2.5)
- '@react-pdf/renderer':
- specifier: ^4.5.1
- version: 4.5.1(react@19.2.5)
'@types/katex':
specifier: ^0.16.7
version: 0.16.8
@@ -5600,9 +5567,6 @@ importers:
react-dom:
specifier: ^19.2.5
version: 19.2.5(react@19.2.5)
- react-element-to-jsx-string:
- specifier: ^17.0.1
- version: 17.0.1(react-dom@19.2.5(react@19.2.5))(react-is@19.2.4)(react@19.2.5)
react-icons:
specifier: ^5.5.0
version: 5.6.0(react@19.2.5)
@@ -6434,9 +6398,6 @@ importers:
'@mui/material':
specifier: ^5.18.0
version: 5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react@19.2.5))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
- '@react-pdf/math':
- specifier: ^2.0.1
- version: 2.0.1(@react-pdf/renderer@4.5.1(react@19.2.5))(react@19.2.5)
'@uppy/core':
specifier: ^3.13.1
version: 3.13.1
@@ -6611,9 +6572,6 @@ importers:
'@playwright/test':
specifier: 1.60.0
version: 1.60.0
- '@react-pdf/renderer':
- specifier: ^4.5.1
- version: 4.5.1(react@19.2.5)
'@tailwindcss/vite':
specifier: ^4.1.14
version: 4.2.2(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.5)(jiti@2.6.1)(terser@5.46.2)(tsx@4.21.0)(yaml@2.9.0))
@@ -9885,12 +9843,6 @@ packages:
'@react-pdf/layout@4.6.1':
resolution: {integrity: sha512-gN6PmWoEffvlIkifLfEhMsVucRywVMyH3rnxdyOVOhGy0nWJKKGpHyPc4plbDdpP6EfZ0r8prHXujDSkIG2nSA==}
- '@react-pdf/math@2.0.1':
- resolution: {integrity: sha512-o+QQA03iT76RiKe8uhOgof+5X7GzA4SUjjPbffi6IGwcOotuxTKm3uhZAnx18ioylSpJf4BcVgUazzb5Ycv8qw==}
- peerDependencies:
- '@react-pdf/renderer': '>=4.5.1'
- react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
-
'@react-pdf/pdfkit@5.1.1':
resolution: {integrity: sha512-wNcdSsNlNYyGHGAgIdt453egBF7fiF9UxpRlklUfVvu8OWCrUppG9xiUrPLVoKiqWet5tMi0w6LmuFUJuYqjEg==}
@@ -20020,13 +19972,6 @@ snapshots:
queue: 6.0.2
yoga-layout: 3.2.1
- '@react-pdf/math@2.0.1(@react-pdf/renderer@4.5.1(react@19.2.5))(react@19.2.5)':
- dependencies:
- '@react-pdf/renderer': 4.5.1(react@19.2.5)
- '@react-pdf/svg': 1.1.0
- mathjax-full: 3.2.2
- react: 19.2.5
-
'@react-pdf/pdfkit@5.1.1':
dependencies:
'@babel/runtime': 7.29.2
diff --git a/tests/package.json b/tests/package.json
index 7a9ef2573b..68c8844a79 100644
--- a/tests/package.json
+++ b/tests/package.json
@@ -15,7 +15,6 @@
"@blocknote/diagram-block": "workspace:^",
"@blocknote/xl-email-exporter": "workspace:^",
"@blocknote/xl-pdf-exporter": "workspace:^",
- "@react-pdf/renderer": "^4.5.1",
"pdfjs-dist": "^4.10.38",
"@blocknote/math-block": "workspace:^",
"@blocknote/react": "workspace:^",
diff --git a/tests/src/end-to-end/exporters/__screenshots__/reactPdfImages.test.tsx/pdf-export-page-1-chromium-linux.png b/tests/src/end-to-end/exporters/__screenshots__/reactPdfImages.test.tsx/pdf-export-page-1-chromium-linux.png
deleted file mode 100644
index 20ddc8d64b..0000000000
Binary files a/tests/src/end-to-end/exporters/__screenshots__/reactPdfImages.test.tsx/pdf-export-page-1-chromium-linux.png and /dev/null differ
diff --git a/tests/src/end-to-end/exporters/__screenshots__/reactPdfImages.test.tsx/pdf-export-page-2-chromium-linux.png b/tests/src/end-to-end/exporters/__screenshots__/reactPdfImages.test.tsx/pdf-export-page-2-chromium-linux.png
deleted file mode 100644
index c4f983a18e..0000000000
Binary files a/tests/src/end-to-end/exporters/__screenshots__/reactPdfImages.test.tsx/pdf-export-page-2-chromium-linux.png and /dev/null differ
diff --git a/tests/src/end-to-end/exporters/__screenshots__/reactPdfImages.test.tsx/pdf-export-page-3-chromium-linux.png b/tests/src/end-to-end/exporters/__screenshots__/reactPdfImages.test.tsx/pdf-export-page-3-chromium-linux.png
deleted file mode 100644
index a657763884..0000000000
Binary files a/tests/src/end-to-end/exporters/__screenshots__/reactPdfImages.test.tsx/pdf-export-page-3-chromium-linux.png and /dev/null differ
diff --git a/tests/src/end-to-end/exporters/__screenshots__/reactPdfImages.test.tsx/pdf-export-page-4-chromium-linux.png b/tests/src/end-to-end/exporters/__screenshots__/reactPdfImages.test.tsx/pdf-export-page-4-chromium-linux.png
deleted file mode 100644
index 753164e708..0000000000
Binary files a/tests/src/end-to-end/exporters/__screenshots__/reactPdfImages.test.tsx/pdf-export-page-4-chromium-linux.png and /dev/null differ
diff --git a/tests/src/end-to-end/exporters/__screenshots__/reactPdfImages.test.tsx/pdf-export-page-5-chromium-linux.png b/tests/src/end-to-end/exporters/__screenshots__/reactPdfImages.test.tsx/pdf-export-page-5-chromium-linux.png
deleted file mode 100644
index e5d9207710..0000000000
Binary files a/tests/src/end-to-end/exporters/__screenshots__/reactPdfImages.test.tsx/pdf-export-page-5-chromium-linux.png and /dev/null differ
diff --git a/tests/src/end-to-end/exporters/exporterTestUtil.tsx b/tests/src/end-to-end/exporters/exporterTestUtil.tsx
index 8961d707a2..bd857d6ef9 100644
--- a/tests/src/end-to-end/exporters/exporterTestUtil.tsx
+++ b/tests/src/end-to-end/exporters/exporterTestUtil.tsx
@@ -2,8 +2,8 @@ import { BlockNoteSchema, defaultBlockSpecs } from "@blocknote/core";
import { expect } from "vite-plus/test";
import { screenshotFull } from "../../utils/screenshotFull.js";
-// The exporter e2e files (emailImages / typstPdfImages / reactPdfImages)
-// run complete exports of the full shared test document with the default
+// The exporter e2e files (emailImages / typstPdfImages) run complete
+// exports of the full shared test document with the default
// mappings in a real browser, where the mappings' `typeof document` checks
// select the built-in image implementations. These are the only tests of
// that composition - the packages' (node) unit suites always take the
diff --git a/tests/src/end-to-end/exporters/reactPdfImages.test.tsx b/tests/src/end-to-end/exporters/reactPdfImages.test.tsx
deleted file mode 100644
index 2b8d7e52d6..0000000000
--- a/tests/src/end-to-end/exporters/reactPdfImages.test.tsx
+++ /dev/null
@@ -1,94 +0,0 @@
-import { diagramBlockMapping as pdfDiagramBlockMapping } from "@blocknote/diagram-block/pdf-exporter";
-import {
- inlineMathMapping as pdfInlineMathMapping,
- mathBlockMapping as pdfMathBlockMapping,
-} from "@blocknote/math-block/pdf-exporter";
-import {
- PDFExporter,
- pdfDefaultSchemaMappings,
-} from "@blocknote/xl-pdf-exporter/react-pdf";
-import { pdf } from "@react-pdf/renderer";
-import { testDocumentWithSourceBlocks } from "@shared/testDocument.js";
-import { isPdf } from "@shared/util/testBytesUtil.js";
-import { testResolveFileUrl } from "@shared/util/testFileResolver.js";
-import { afterEach, describe, expect, test } from "vite-plus/test";
-import { browserName } from "../../utils/context.js";
-import {
- invalidDiagramBlock,
- invalidMathBlock,
- removeExportFrame,
- schema,
- screenshotPdfPages,
-} from "./exporterTestUtil.js";
-
-// The deprecated react-pdf exporter's complete-export browser test. See
-// exporterTestUtil.tsx for why it lives here rather than in the package.
-
-afterEach(removeExportFrame);
-
-describe("pdf export through a complete exporter in the browser", () => {
- // Chromium only: the produced PDF is the same file everywhere (react-pdf
- // lays it out from bundled fonts, not browser rendering), so per-browser
- // runs would only re-test pdf.js's rasterizer at 3x the suite cost.
- test.skipIf(browserName !== "chromium")(
- "renders math and diagrams to images in the produced PDF",
- { timeout: 60000 },
- async () => {
- const mappings = {
- ...pdfDefaultSchemaMappings,
- blockMapping: {
- ...pdfDefaultSchemaMappings.blockMapping,
- mathBlock: pdfMathBlockMapping,
- diagram: pdfDiagramBlockMapping,
- },
- inlineContentMapping: {
- ...pdfDefaultSchemaMappings.inlineContentMapping,
- math: pdfInlineMathMapping,
- },
- };
- // The full shared test document: unlike the email mappings, the PDF
- // exporter fetches media through `resolveFileUrl`, so the test
- // resolver keeps it deterministic and offline.
- const exporter = new PDFExporter(schema(), mappings as any, {
- resolveFileUrl: testResolveFileUrl,
- });
-
- const transformed = await exporter.toReactPDFDocument([
- ...testDocumentWithSourceBlocks,
- invalidDiagramBlock,
- invalidMathBlock,
- ] as any);
-
- // The element tree carries the inline math as an image with
- // react-pdf's async (rasterizing) src function, and the diagram as a
- // rendered PNG.
- const images: any[] = [];
- const collectImages = (node: any) => {
- if (!node || typeof node !== "object") {
- return;
- }
- if (Array.isArray(node)) {
- node.forEach(collectImages);
- return;
- }
- if (node.type === "IMAGE") {
- images.push(node);
- }
- collectImages(node.props?.children);
- };
- collectImages(transformed);
- expect(images.some((i) => typeof i.props.src === "function")).toBe(true);
- expect(
- images.some((i) => String(i.props.src).startsWith("data:image/png")),
- ).toBe(true);
-
- // Produce the actual file - this runs react-pdf's asset resolution,
- // which invokes the inline math's rasterizing src function.
- const blob = await pdf(transformed as any).toBlob();
- const bytes = new Uint8Array(await blob.arrayBuffer());
- expect(isPdf(bytes)).toBe(true);
-
- await screenshotPdfPages(bytes, "pdf-export-page");
- },
- );
-});