How drawio-desktop PDF Export Works Using @cantoo/pdf-lib
Draw.io Desktop generates PDFs through a two-stage pipeline where Electron first rasterizes each diagram page into individual buffers using webContents.printToPDF(), then leverages @cantoo/pdf-lib to merge these buffers, embed metadata, and optionally attach the original diagram XML as a file or encoded Subject field.
The drawio-desktop application (also known as diagrams.net desktop) implements its PDF export functionality using @cantoo/pdf-lib to manipulate PDF documents without native dependencies. This implementation combines Electron's printing APIs with pure-JavaScript PDF assembly to support both single-page exports and complex multi-page diagrams while preserving editable XML content.
The Two-Stage PDF Export Architecture
The export process operates in distinct rasterization and assembly phases coordinated between the Electron main process and an off-screen renderer.
Stage 1: Page-by-Page Rasterization
When a user initiates a PDF export, the main process in src/main/electron.js spawns a hidden BrowserWindow loading export3.html. The renderer draws each diagram page onto a hidden canvas and signals completion via IPC. The main process then captures the output:
- For physical printing (
args.print === true), the window callswebContents.print()and returns immediately. - For digital PDF export, the window calls
webContents.printToPDF(pdfOptions), which returns a Buffer containing a single-page PDF for the current diagram page.
These buffers accumulate in an array (pdfs) until all pages are rendered.
Stage 2: PDF Assembly with @cantoo/pdf-lib
Once the last page is collected, the main process invokes mergePdfs(pdfs, xml), the sole location where @cantoo/pdf-lib is utilized. This function handles metadata injection, XML embedding, and page consolidation according to the source code in src/main/electron.js.
Inside the mergePdfs Function
The mergePdfs function, located in src/main/electron.js, differentiates between single-page and multi-page exports to optimize metadata storage.
Single-Page Export Flow
When pdfFiles.length === 1, the function loads the buffer using PDFDocument.load() and sets the Creator metadata to diagrams.net. If XML embedding is requested, the diagram data is encoded and stored in the Subject field rather than as a file attachment, ensuring compatibility with PDF viewers that poorly support attachments:
const doc = await PDFDocument.load(pdfBuffers[0]);
doc.setCreator('diagrams.net');
if (xml) {
doc.setSubject(
encodeURIComponent(xml).replace(/\(/g, '\\(').replace(/\)/g, '\\)')
);
}
return Buffer.from(await doc.save());
Multi-Page Export Flow
For documents with multiple pages, the function creates a fresh PDF using PDFDocument.create() and copies pages from each rendered buffer:
const doc = await PDFDocument.create();
doc.setCreator('diagrams.net');
if (xml) {
await doc.attach(
Buffer.from(xml).toString('base64'),
'diagram.xml',
{
mimeType: 'application/vnd.jgraph.mxfile',
description: 'Diagram Content',
}
);
}
for (const buf of pdfBuffers) {
const part = await PDFDocument.load(buf);
const pages = await doc.copyPages(part, part.getPageIndices());
pages.forEach(p => doc.addPage(p));
}
return Buffer.from(await doc.save());
The multi-page approach uses pdfDoc.attach() to embed the diagram XML as a base64-encoded file attachment, which is more reliable for multi-page documents than the Subject field approach.
Key Files in the Export Pipeline
src/main/electron.js– Contains the main process logic, IPC handlers, and themergePdfsfunction that importsPDFDocumentfrom@cantoo/pdf-libat line 13.src/main/electron-preload.js– Provides the secure preload bridge exposingexportDiagramto the renderer process.export3.html– The hidden renderer page that receivesrendermessages, draws diagrams on a hidden canvas, and emitsrender-finishedsignals to trigger the PDF capture.
Why @cantoo/pdf-lib?
The library selection provides three critical capabilities for drawio-desktop:
- Pure JavaScript implementation – Operates without native system dependencies, ensuring cross-platform compatibility across Windows, macOS, and Linux.
- Page copying – The
PDFDocument.copyPages()method enables efficient merging of multiple Electron-generated PDF buffers into a single document. - Arbitrary data attachment – The
pdfDoc.attach()API allows embedding the original diagram XML directly into the PDF container, preserving editability when files are reopened in draw.io.
Summary
- Drawio-desktop uses Electron's
webContents.printToPDF()to render diagram pages as individual PDF buffers in an off-screen BrowserWindow. - The
mergePdfsfunction insrc/main/electron.jsuses@cantoo/pdf-libto consolidate these buffers, with distinct logic for single-page (Subject field embedding) and multi-page (file attachment) exports. - Metadata injection includes setting the Creator field to
diagrams.netand optionally embedding the original diagram XML for round-trip editing. - The process relies on standard PDFDocument methods including
load(),create(),copyPages(),addPage(), andattach().
Frequently Asked Questions
Why does drawio-desktop embed XML into exported PDFs?
The application embeds diagram XML to enable round-trip editing. When a user reopens an exported PDF in draw.io, the application extracts the embedded XML from either the Subject field (single-page) or the file attachment (multi-page) to restore the editable diagram state.
How does @cantoo/pdf-lib differ from pdf-lib?
@cantoo/pdf-lib is a fork or specific distribution of the pdf-lib library that provides a pure-JavaScript API for PDF manipulation. It allows drawio-desktop to create, load, and modify PDF documents without requiring native compilation steps, making it ideal for Electron applications targeting multiple platforms.
What triggers the multi-page versus single-page export path?
The export path depends on the page count of the diagram. If the user exports a diagram containing only one page (or selects a single page in the export dialog), the system uses the single-page logic where XML is stored in the Subject field. Multi-page diagrams automatically trigger the merge logic that uses copyPages and file attachments.
Where is the PDF export logic implemented in the source code?
All PDF export orchestration resides in src/main/electron.js, specifically within the IPC handlers that manage the export-diagram event and the mergePdfs helper function (lines 78-130 in the current source). The actual diagram rendering occurs in export3.html, which communicates back to the main process via ipcRenderer.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →