Last active
March 10, 2025 12:30
-
-
Save bettysteger/499e5fc603df772858ba26825a4c5169 to your computer and use it in GitHub Desktop.
jsPDF-with-emojis.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// PDF Configuration | |
const PDF_CONFIG = { | |
unit: 'px', | |
hotfixes: ['px_scaling'], | |
putOnlyUsedFonts: true, | |
compress: true | |
}; | |
const PDF_MARGIN = 80; | |
// Emoji handling utilities | |
const processEmoji = { | |
// Regex for matching emojis and common symbols | |
// Includes: | |
// - Basic symbols (⚠️, ℹ️, etc): \u2000-\u2BFF | |
// - Emojis: \u{1F300}-\u{1F9FF} | |
// - Variation Selectors (for emoji styling): \uFE00-\uFE0F | |
// - Zero Width Joiner (for compound emojis): \u200D | |
regex: /[\u2000-\u2BFF][\uFE00-\uFE0F]?|[\u{1F300}-\u{1F9FF}][\uFE00-\uFE0F]?|[\u{1F300}-\u{1F9FF}]\u200D[\u{1F300}-\u{1F9FF}][\uFE00-\uFE0F]?/gu, | |
// Create a canvas with emoji | |
createCanvas(emoji, parentElement) { | |
const computedStyle = window.getComputedStyle(parentElement); | |
const fontSize = parseInt(computedStyle.fontSize); | |
const emojiSize = Math.round(fontSize * 1.2); // 20% larger than text | |
const size = emojiSize * 2; // Double size for better quality | |
const canvas = document.createElement('canvas'); | |
canvas.width = size; | |
canvas.height = size; | |
const ctx = canvas.getContext('2d'); | |
ctx.font = `${size}px Arial`; | |
ctx.textAlign = 'center'; | |
ctx.textBaseline = 'middle'; | |
ctx.fillText(emoji, size/2, size/2); | |
return { canvas, emojiSize }; | |
}, | |
// Create an image element from canvas | |
createImageElement(canvas, emojiSize) { | |
const img = document.createElement('img'); | |
img.src = canvas.toDataURL('image/png'); | |
img.style.width = `${emojiSize}px`; | |
img.style.height = `${emojiSize}px`; | |
img.style.verticalAlign = 'middle'; | |
img.style.display = 'inline-block'; | |
img.style.margin = '0 1px'; | |
return img; | |
}, | |
// Process a text node and replace emojis with images | |
processTextNode(node) { | |
const text = node.textContent; | |
const matches = text.match(this.regex); | |
if (!matches) return; | |
// Create document fragment for new content | |
const fragment = document.createDocumentFragment(); | |
let lastIndex = 0; | |
matches.forEach((emoji, index) => { | |
// Add text before emoji | |
const textBefore = text.slice(lastIndex, text.indexOf(emoji, lastIndex)); | |
if (textBefore) { | |
fragment.appendChild(document.createTextNode(textBefore)); | |
} | |
// Add emoji image | |
const { canvas, emojiSize } = this.createCanvas(emoji, node.parentElement); | |
const img = this.createImageElement(canvas, emojiSize); | |
fragment.appendChild(img); | |
lastIndex = text.indexOf(emoji, lastIndex) + emoji.length; | |
}); | |
// Add remaining text | |
if (lastIndex < text.length) { | |
fragment.appendChild(document.createTextNode(text.slice(lastIndex))); | |
} | |
node.parentNode.replaceChild(fragment, node); | |
} | |
}; | |
const pdf = new jsPDF(PDF_CONFIG); | |
const element = document.body; // document.querySelector('.export-pdf'); | |
const width = pdf.internal.pageSize.getWidth(); | |
element.style.width = width + 'px'; | |
element.style.fontFamily = 'Helvetica'; | |
// Process all emojis in element | |
element.querySelectorAll('*').forEach(el => { | |
if (el.childNodes?.length) { | |
Array.from(el.childNodes) | |
.filter(node => node.nodeType === 3) // Text nodes only | |
.forEach(node => processEmoji.processTextNode(node)); | |
} | |
}); | |
// Generate PDF | |
pdf.html(element, { | |
callback: function (pdf) { | |
pdf.save('some-title.pdf'); | |
}, | |
autoPaging: 'text', | |
html2canvas: { | |
useCORS: true, | |
// fix image compression | |
// @see https://github.com/parallax/jsPDF/issues/3178#issuecomment-1384411475 | |
onclone: () => { | |
const targetRect = element.getBoundingClientRect(); | |
element.querySelectorAll('img').forEach((img) => { | |
const rect = img.getBoundingClientRect(); | |
try { | |
pdf.addImage(img, 'JPEG', targetRect.x - rect.x, targetRect.y - rect.y, rect.width, rect.height); | |
} catch (e) { | |
console.error(`PDF image error: ${e}`); | |
} | |
img.remove(); | |
}); | |
} | |
}, | |
margin: PDF_MARGIN, | |
width: width - PDF_MARGIN * 2, | |
windowWidth: width, | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment