const domo = require('sdk'); const { PDFDocument, StandardFonts, rgb } = require('pdf-lib'); /** * Removes Unicode characters not supported by PDF Standard Fonts * - Decomposes accented characters (é → e) * - Removes remaining non-ASCII characters */ function sanitizeForPdf(text) { if (!text) return ''; return text .normalize('NFKD') .replace(/[\u0300-\u036f]/g, '') .replace(/[^\x20-\x7E]/g, ''); } /** * Convert Outlook-safe HTML to PDF and upload to Domo Files * using the two-step reliable upload method */ async function Convert_and_Export_HTML_To_PDF(html, fileName) { /************************************************* * 1. NORMALIZE + SANITIZE HTML *************************************************/ let input = sanitizeForPdf( html .replace(/\r?\n/g, '') .replace(/ /gi, ' ') .replace(/<\/p>/gi, '\n\n') .replace(//gi, '\n') .replace(/<(style|script)[^>]*>.*?<\/\1>/gi, '') .replace(/<\/?(div|span)>/gi, '') ); /************************************************* * 2. CREATE PDF *************************************************/ const pdfDoc = await PDFDocument.create(); let page = pdfDoc.addPage([612, 792]); const font = await pdfDoc.embedFont(StandardFonts.Helvetica); const boldFont = await pdfDoc.embedFont(StandardFonts.HelveticaBold); const margin = 50; let y = page.getHeight() - margin; const newPageIfNeeded = () => { if (y < margin) { page = pdfDoc.addPage([612, 792]); y = page.getHeight() - margin; } }; const drawText = (text, size = 11, bold = false) => { const safeText = sanitizeForPdf(text); if (!safeText) return; newPageIfNeeded(); page.drawText(safeText, { x: margin, y, size, font: bold ? boldFont : font, maxWidth: 512, lineHeight: 14, color: rgb(0, 0, 0), }); y -= 14; }; /************************************************* * 3. TABLE PARSING (OUTLOOK CORE FEATURE) *************************************************/ const tableRegex = /]*>(.*?)<\/table>/gi; let match; while ((match = tableRegex.exec(input)) !== null) { const tableHtml = match[1]; const rows = [...tableHtml.matchAll(/]*>(.*?)<\/tr>/gi)] .map(r => [...r[1].matchAll(/]*>(.*?)<\/t[dh]>/gi)] .map(c => sanitizeForPdf(c[1].replace(/<[^>]+>/g, '').trim()) ) ); rows.forEach(row => { let x = margin; const colWidth = 512 / row.length; row.forEach(cell => { if (!cell) return; newPageIfNeeded(); page.drawText(cell, { x, y, size: 10, font, maxWidth: colWidth - 4, lineHeight: 12, }); x += colWidth; }); y -= 16; }); y -= 10; } // Remove tables from remaining text input = input.replace(tableRegex, ''); /************************************************* * 4. PARAGRAPHS / INLINE TEXT *************************************************/ const lines = input .split('\n') .map(l => l.trim()) .filter(Boolean); for (const line of lines) { const isBold = /<(strong|b)>/.test(line); const text = sanitizeForPdf( line.replace(/<[^>]+>/g, '') ); drawText(text, 11, isBold); } /************************************************* * 5. SAVE PDF *************************************************/ const pdfBytes = await pdfDoc.save(); //const pdfBuffer = Buffer.from(pdfBytes); const pdfBase64 = Buffer.from(pdfBytes).toString('base64'); // 1. Confirm PDF header console.log(Buffer.from(pdfBytes).slice(0, 8).toString()); // should print "%PDF-1." // 2. Roundtrip base64 -> buffer -> header const base64 = Buffer.from(pdfBytes).toString('base64'); const roundtrip = Buffer.from(base64, 'base64'); console.log(roundtrip.slice(0, 8).toString()); // should print "%PDF-1." // 3. Save to disk and open locally (for debugging) require('fs').writeFileSync('/tmp/debug.pdf', Buffer.from(pdfBytes)); /************************************************* * 6. CREATE DATA FILE (EMAIL‑ATTACHABLE) *************************************************/ const upload = await domo.post( '/api/data/v1/data-files', { name: `${fileName}.pdf`, description: 'HTML to PDF', public: false, fileBase64: pdfBase64 }, { Accept: 'application/json', 'Content-Type': 'application/json', } ); return upload.dataFileId; }