/** * Markdown to PDF renderer for document export. * * Converts markdown content to styled HTML via `marked`, then renders * the HTML to a PDF buffer using Playwright headless Chromium. * The HTML template uses print-friendly styling that matches the * document editor typography. */ import { marked } from "marked"; import { ensureChromiumHeadlessShell, importPlaywright, } from "../../tools/browser/runtime-check.js"; // --------------------------------------------------------------------------- // Print template // --------------------------------------------------------------------------- const FONT_STACK = `"DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif`; function wrapInPrintTemplate(innerHtml: string): string { return ` ${innerHtml} `; } // --------------------------------------------------------------------------- // Public API // --------------------------------------------------------------------------- /** * Convert a markdown string to a PDF buffer. * * Parses markdown to HTML via `marked`, wraps it in a print-friendly * template, then renders to PDF using Playwright headless Chromium. * The browser is always closed in a `finally` block. */ export async function renderMarkdownToPDF( title: string, markdown: string, ): Promise { const innerHtml = marked.parse(markdown, { gfm: true, breaks: true, }) as string; const fullHtml = wrapInPrintTemplate(innerHtml); const pw = await importPlaywright(); await ensureChromiumHeadlessShell(pw); const browser = await pw.chromium.launch({ headless: true }); try { const context = await browser.newContext({ javaScriptEnabled: false, }); const page = await context.newPage(); await page.route("**/*", (route) => route.abort()); await page.setContent(fullHtml, { waitUntil: "domcontentloaded" }); const pdfBuffer = await page.pdf({ format: "A4", margin: { top: "0.75in", bottom: "0.75in", left: "0.75in", right: "0.75in", }, printBackground: true, }); return Buffer.from(pdfBuffer); } finally { await browser.close(); } }