import jsPDF from "jspdf"; import html2canvas from "html2canvas"; /** * Converts markdown content to a styled PDF document * @param {string} content - Markdown content * @param {string} filename - Name for the exported file (without extension) */ export async function exportToPdf(content, filename = "document") { // Create a temporary container to render the content const container = document.createElement("div"); container.style.cssText = ` position: absolute; left: -9999px; top: 0; width: 210mm; padding: 20mm; background: white; color: black; font-family: 'Inter', 'Segoe UI', 'Arial', sans-serif; font-size: 12pt; line-height: 1.6; `; // Process the markdown content to HTML with basic styling const htmlContent = markdownToHtml(content); container.innerHTML = htmlContent; document.body.appendChild(container); try { // Wait for any images/fonts to load await new Promise(resolve => setTimeout(resolve, 100)); // Capture the content as canvas const canvas = await html2canvas(container, { scale: 2, useCORS: true, logging: false, backgroundColor: "#ffffff" }); // Create PDF const imgWidth = 210; // A4 width in mm const pageHeight = 297; // A4 height in mm const imgHeight = (canvas.height * imgWidth) / canvas.width; let heightLeft = imgHeight; let position = 0; const pdf = new jsPDF("p", "mm", "a4"); const imgData = canvas.toDataURL("image/png"); // Add first page pdf.addImage(imgData, "PNG", 0, position, imgWidth, imgHeight); heightLeft -= pageHeight; // Add additional pages if content is longer than one page while (heightLeft > 0) { position = heightLeft - imgHeight; pdf.addPage(); pdf.addImage(imgData, "PNG", 0, position, imgWidth, imgHeight); heightLeft -= pageHeight; } // Save the PDF pdf.save(`${filename}.pdf`); } finally { // Clean up document.body.removeChild(container); } } /** * Converts markdown to styled HTML for PDF rendering * Handles common markdown syntax including LaTeX (as plain text for PDF) */ function markdownToHtml(markdown) { if (!markdown) return ""; let html = markdown; // Escape HTML entities html = html .replace(/&/g, "&") .replace(//g, ">"); // Convert LaTeX blocks to styled spans (rendered as formatted text) html = html.replace(/\$\$([^$]+)\$\$/g, '
$1
'); html = html.replace(/\$([^$]+)\$/g, '$1'); // Headers html = html.replace(/^### (.+)$/gm, '

$1

'); html = html.replace(/^## (.+)$/gm, '

$1

'); html = html.replace(/^# (.+)$/gm, '

$1

'); // Bold and Italic html = html.replace(/\*\*\*(.+?)\*\*\*/g, '$1'); html = html.replace(/\*\*(.+?)\*\*/g, '$1'); html = html.replace(/\*(.+?)\*/g, '$1'); html = html.replace(/__(.+?)__/g, '$1'); html = html.replace(/_(.+?)_/g, '$1'); // Inline code html = html.replace(/`([^`]+)`/g, '$1'); // Code blocks html = html.replace(/```[\w]*\n([\s\S]*?)```/g, '
$1
'); // Blockquotes html = html.replace(/^> (.+)$/gm, '
$1
'); // Horizontal rules html = html.replace(/^---$/gm, '
'); // Unordered lists html = html.replace(/^- (.+)$/gm, '
  • $1
  • '); html = html.replace(/(]*>.*<\/li>\n?)+/g, ''); // Ordered lists html = html.replace(/^\d+\. (.+)$/gm, '
  • $1
  • '); // Links html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1'); // Tables - parse markdown tables to HTML tables html = html.replace(/^(\|.+\|)\r?\n(\|[-:| ]+\|)\r?\n((?:\|.+\|\r?\n?)+)/gm, (match, headerRow, separatorRow, bodyRows) => { // Parse header cells const headers = headerRow.split('|').slice(1, -1).map(h => h.trim()); // Parse alignment from separator row const alignments = separatorRow.split('|').slice(1, -1).map(sep => { const s = sep.trim(); if (s.startsWith(':') && s.endsWith(':')) return 'center'; if (s.endsWith(':')) return 'right'; return 'left'; }); // Create header HTML const headerHtml = headers.map((h, i) => `${h}` ).join(''); // Parse and create body rows const rows = bodyRows.trim().split('\n').map((row, rowIdx) => { const cells = row.split('|').slice(1, -1).map(c => c.trim()); const cellsHtml = cells.map((c, i) => `${c}` ).join(''); return `${cellsHtml}`; }).join(''); return `${headerHtml}${rows}
    `; }); // Paragraphs (handle line breaks) html = html.replace(/\n\n/g, '

    '); html = html.replace(/\n/g, '
    '); // Wrap in paragraph tags html = `

    ${html}

    `; // Clean up empty paragraphs html = html.replace(/]*>\s*<\/p>/g, ''); return ` ${html} `; }