| import jsPDF from "jspdf"; |
| import html2canvas from "html2canvas"; |
|
|
| |
| |
| |
| |
| |
| export async function exportToPdf(content, filename = "document") { |
| |
| 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; |
| `; |
|
|
| |
| const htmlContent = markdownToHtml(content); |
| container.innerHTML = htmlContent; |
| document.body.appendChild(container); |
|
|
| try { |
| |
| await new Promise(resolve => setTimeout(resolve, 100)); |
|
|
| |
| const canvas = await html2canvas(container, { |
| scale: 2, |
| useCORS: true, |
| logging: false, |
| backgroundColor: "#ffffff" |
| }); |
|
|
| |
| const imgWidth = 210; |
| const pageHeight = 297; |
| 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"); |
|
|
| |
| pdf.addImage(imgData, "PNG", 0, position, imgWidth, imgHeight); |
| heightLeft -= pageHeight; |
|
|
| |
| while (heightLeft > 0) { |
| position = heightLeft - imgHeight; |
| pdf.addPage(); |
| pdf.addImage(imgData, "PNG", 0, position, imgWidth, imgHeight); |
| heightLeft -= pageHeight; |
| } |
|
|
| |
| pdf.save(`${filename}.pdf`); |
| } finally { |
| |
| document.body.removeChild(container); |
| } |
| } |
|
|
| |
| |
| |
| |
| function markdownToHtml(markdown) { |
| if (!markdown) return ""; |
|
|
| let html = markdown; |
|
|
| |
| html = html |
| .replace(/&/g, "&") |
| .replace(/</g, "<") |
| .replace(/>/g, ">"); |
|
|
| |
| html = html.replace(/\$\$([^$]+)\$\$/g, '<div style="text-align: center; font-style: italic; padding: 10px; background: #f5f5f5; border-radius: 4px; margin: 10px 0;">$1</div>'); |
| html = html.replace(/\$([^$]+)\$/g, '<span style="font-style: italic; background: #f0f0f0; padding: 2px 4px; border-radius: 2px;">$1</span>'); |
|
|
| |
| html = html.replace(/^### (.+)$/gm, '<h3 style="font-size: 14pt; font-weight: 600; margin: 16px 0 8px 0; color: #333;">$1</h3>'); |
| html = html.replace(/^## (.+)$/gm, '<h2 style="font-size: 16pt; font-weight: 600; margin: 20px 0 10px 0; color: #222;">$1</h2>'); |
| html = html.replace(/^# (.+)$/gm, '<h1 style="font-size: 20pt; font-weight: 700; margin: 24px 0 12px 0; color: #111; border-bottom: 1px solid #ddd; padding-bottom: 8px;">$1</h1>'); |
|
|
| |
| html = html.replace(/\*\*\*(.+?)\*\*\*/g, '<strong><em>$1</em></strong>'); |
| html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>'); |
| html = html.replace(/\*(.+?)\*/g, '<em>$1</em>'); |
| html = html.replace(/__(.+?)__/g, '<strong>$1</strong>'); |
| html = html.replace(/_(.+?)_/g, '<em>$1</em>'); |
|
|
| |
| html = html.replace(/`([^`]+)`/g, '<code style="background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-family: Consolas, monospace; font-size: 11pt;">$1</code>'); |
|
|
| |
| html = html.replace(/```[\w]*\n([\s\S]*?)```/g, '<pre style="background: #f5f5f5; padding: 12px; border-radius: 6px; font-family: Consolas, monospace; font-size: 10pt; overflow-x: auto; margin: 12px 0; border: 1px solid #e0e0e0;">$1</pre>'); |
|
|
| |
| html = html.replace(/^> (.+)$/gm, '<blockquote style="border-left: 3px solid #4a90d9; padding-left: 12px; margin: 12px 0; color: #555; font-style: italic;">$1</blockquote>'); |
|
|
| |
| html = html.replace(/^---$/gm, '<hr style="border: none; border-top: 1px solid #ddd; margin: 20px 0;">'); |
|
|
| |
| html = html.replace(/^- (.+)$/gm, '<li style="margin: 4px 0;">$1</li>'); |
| html = html.replace(/(<li[^>]*>.*<\/li>\n?)+/g, '<ul style="margin: 10px 0; padding-left: 24px;">$&</ul>'); |
|
|
| |
| html = html.replace(/^\d+\. (.+)$/gm, '<li style="margin: 4px 0;">$1</li>'); |
|
|
| |
| html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" style="color: #4a90d9; text-decoration: underline;">$1</a>'); |
|
|
| |
| html = html.replace(/^(\|.+\|)\r?\n(\|[-:| ]+\|)\r?\n((?:\|.+\|\r?\n?)+)/gm, (match, headerRow, separatorRow, bodyRows) => { |
| |
| const headers = headerRow.split('|').slice(1, -1).map(h => h.trim()); |
|
|
| |
| 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'; |
| }); |
|
|
| |
| const headerHtml = headers.map((h, i) => |
| `<th style="border: 1px solid #ddd; padding: 10px 12px; text-align: ${alignments[i] || 'left'}; background: #f5f5f5; font-weight: 600;">${h}</th>` |
| ).join(''); |
|
|
| |
| 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) => |
| `<td style="border: 1px solid #ddd; padding: 8px 12px; text-align: ${alignments[i] || 'left'};">${c}</td>` |
| ).join(''); |
| return `<tr style="background: ${rowIdx % 2 === 0 ? '#fff' : '#fafafa'};">${cellsHtml}</tr>`; |
| }).join(''); |
|
|
| return `<table style="border-collapse: collapse; width: 100%; margin: 16px 0; font-size: 11pt;"> |
| <thead><tr>${headerHtml}</tr></thead> |
| <tbody>${rows}</tbody> |
| </table>`; |
| }); |
|
|
| |
| html = html.replace(/\n\n/g, '</p><p style="margin: 10px 0;">'); |
| html = html.replace(/\n/g, '<br>'); |
|
|
| |
| html = `<p style="margin: 10px 0;">${html}</p>`; |
|
|
| |
| html = html.replace(/<p[^>]*>\s*<\/p>/g, ''); |
|
|
| return ` |
| <style> |
| * { box-sizing: border-box; } |
| body { margin: 0; padding: 0; } |
| </style> |
| ${html} |
| `; |
| } |
|
|