refac: chat pdf rendering

This commit is contained in:
Timothy Jaeryang Baek 2025-05-09 13:56:53 +04:00
parent 9657474a8d
commit 9143716463
2 changed files with 89 additions and 86 deletions

View File

@ -68,66 +68,67 @@
if (containerElement) { if (containerElement) {
try { try {
const isDarkMode = document.documentElement.classList.contains('dark'); const isDarkMode = document.documentElement.classList.contains('dark');
const virtualWidth = 800; // Fixed width in px
const pagePixelHeight = 1200; // Each slice height (adjust to avoid canvas bugs; generally 24k is safe)
console.log('isDarkMode', isDarkMode); // Clone & style once
// Define a fixed virtual screen size
const virtualWidth = 800; // Fixed width (adjust as needed)
// Clone the container to avoid layout shifts
const clonedElement = containerElement.cloneNode(true); const clonedElement = containerElement.cloneNode(true);
clonedElement.classList.add('text-black'); clonedElement.classList.add('text-black');
clonedElement.classList.add('dark:text-white'); clonedElement.classList.add('dark:text-white');
clonedElement.style.width = `${virtualWidth}px`; // Apply fixed width clonedElement.style.width = `${virtualWidth}px`;
clonedElement.style.height = 'auto'; // Allow content to expand clonedElement.style.position = 'absolute';
clonedElement.style.left = '-9999px'; // Offscreen
clonedElement.style.height = 'auto';
document.body.appendChild(clonedElement);
document.body.appendChild(clonedElement); // Temporarily add to DOM // Get total height after attached to DOM
const totalHeight = clonedElement.scrollHeight;
let offsetY = 0;
let page = 0;
// Render to canvas with predefined width // Prepare PDF
const canvas = await html2canvas(clonedElement, {
backgroundColor: isDarkMode ? '#000' : '#fff',
useCORS: true,
scale: 2, // Keep at 1x to avoid unexpected enlargements
width: virtualWidth, // Set fixed virtual screen width
windowWidth: virtualWidth // Ensure consistent rendering
});
document.body.removeChild(clonedElement); // Clean up temp element
const imgData = canvas.toDataURL('image/png');
// A4 page settings
const pdf = new jsPDF('p', 'mm', 'a4'); const pdf = new jsPDF('p', 'mm', 'a4');
const imgWidth = 210; // A4 width in mm const imgWidth = 210; // A4 mm
const pageHeight = 297; // A4 height in mm const pageHeight = 297; // A4 mm
// Maintain aspect ratio while (offsetY < totalHeight) {
const imgHeight = (canvas.height * imgWidth) / canvas.width; // For each slice, adjust scrollTop to show desired part
let heightLeft = imgHeight; clonedElement.scrollTop = offsetY;
let position = 0;
// Set page background for dark mode // Optionally: mask/hide overflowing content via CSS if needed
if (isDarkMode) { clonedElement.style.maxHeight = `${pagePixelHeight}px`;
pdf.setFillColor(0, 0, 0); // Only render the visible part
pdf.rect(0, 0, imgWidth, pageHeight, 'F'); // Apply black bg const canvas = await html2canvas(clonedElement, {
} backgroundColor: isDarkMode ? '#000' : '#fff',
useCORS: true,
scale: 2,
width: virtualWidth,
height: Math.min(pagePixelHeight, totalHeight - offsetY),
// Optionally: y offset for correct region?
windowWidth: virtualWidth
//windowHeight: pagePixelHeight,
});
const imgData = canvas.toDataURL('image/png');
// Maintain aspect ratio
const imgHeight = (canvas.height * imgWidth) / canvas.width;
const position = 0; // Always first line, since we've clipped vertically
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight); if (page > 0) pdf.addPage();
heightLeft -= pageHeight;
// Handle additional pages
while (heightLeft > 0) {
position -= pageHeight;
pdf.addPage();
// Set page background for dark mode
if (isDarkMode) { if (isDarkMode) {
pdf.setFillColor(0, 0, 0); pdf.setFillColor(0, 0, 0);
pdf.rect(0, 0, imgWidth, pageHeight, 'F'); pdf.rect(0, 0, imgWidth, pageHeight, 'F'); // black bg
} }
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight); pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
offsetY += pagePixelHeight;
page++;
} }
document.body.removeChild(clonedElement);
pdf.save(`chat-${chat.chat.title}.pdf`); pdf.save(`chat-${chat.chat.title}.pdf`);
} catch (error) { } catch (error) {
console.error('Error generating PDF', error); console.error('Error generating PDF', error);

View File

@ -85,66 +85,68 @@
if (containerElement) { if (containerElement) {
try { try {
const isDarkMode = $theme.includes('dark'); // Check theme mode const isDarkMode = document.documentElement.classList.contains('dark');
const virtualWidth = 800; // Fixed width in px
const pagePixelHeight = 1200; // Each slice height (adjust to avoid canvas bugs; generally 24k is safe)
// Define a fixed virtual screen size // Clone & style once
const virtualWidth = 1024; // Fixed width (adjust as needed)
const virtualHeight = 1400; // Fixed height (adjust as needed)
// Clone the container to avoid layout shifts
const clonedElement = containerElement.cloneNode(true); const clonedElement = containerElement.cloneNode(true);
clonedElement.style.width = `${virtualWidth}px`; // Apply fixed width clonedElement.classList.add('text-black');
clonedElement.style.height = 'auto'; // Allow content to expand clonedElement.classList.add('dark:text-white');
clonedElement.style.width = `${virtualWidth}px`;
clonedElement.style.position = 'absolute';
clonedElement.style.left = '-9999px'; // Offscreen
clonedElement.style.height = 'auto';
document.body.appendChild(clonedElement);
document.body.appendChild(clonedElement); // Temporarily add to DOM // Get total height after attached to DOM
const totalHeight = clonedElement.scrollHeight;
let offsetY = 0;
let page = 0;
// Render to canvas with predefined width // Prepare PDF
const canvas = await html2canvas(clonedElement, {
backgroundColor: isDarkMode ? '#000' : '#fff',
useCORS: true,
scale: 2, // Keep at 1x to avoid unexpected enlargements
width: virtualWidth, // Set fixed virtual screen width
windowWidth: virtualWidth, // Ensure consistent rendering
windowHeight: virtualHeight
});
document.body.removeChild(clonedElement); // Clean up temp element
const imgData = canvas.toDataURL('image/png');
// A4 page settings
const pdf = new jsPDF('p', 'mm', 'a4'); const pdf = new jsPDF('p', 'mm', 'a4');
const imgWidth = 210; // A4 width in mm const imgWidth = 210; // A4 mm
const pageHeight = 297; // A4 height in mm const pageHeight = 297; // A4 mm
// Maintain aspect ratio while (offsetY < totalHeight) {
const imgHeight = (canvas.height * imgWidth) / canvas.width; // For each slice, adjust scrollTop to show desired part
let heightLeft = imgHeight; clonedElement.scrollTop = offsetY;
let position = 0;
// Set page background for dark mode // Optionally: mask/hide overflowing content via CSS if needed
if (isDarkMode) { clonedElement.style.maxHeight = `${pagePixelHeight}px`;
pdf.setFillColor(0, 0, 0); // Only render the visible part
pdf.rect(0, 0, imgWidth, pageHeight, 'F'); // Apply black bg const canvas = await html2canvas(clonedElement, {
} backgroundColor: isDarkMode ? '#000' : '#fff',
useCORS: true,
scale: 2,
width: virtualWidth,
height: Math.min(pagePixelHeight, totalHeight - offsetY),
// Optionally: y offset for correct region?
windowWidth: virtualWidth
//windowHeight: pagePixelHeight,
});
const imgData = canvas.toDataURL('image/png');
// Maintain aspect ratio
const imgHeight = (canvas.height * imgWidth) / canvas.width;
const position = 0; // Always first line, since we've clipped vertically
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight); if (page > 0) pdf.addPage();
heightLeft -= pageHeight;
// Handle additional pages
while (heightLeft > 0) {
position -= pageHeight;
pdf.addPage();
// Set page background for dark mode
if (isDarkMode) { if (isDarkMode) {
pdf.setFillColor(0, 0, 0); pdf.setFillColor(0, 0, 0);
pdf.rect(0, 0, imgWidth, pageHeight, 'F'); pdf.rect(0, 0, imgWidth, pageHeight, 'F'); // black bg
} }
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight); pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
offsetY += pagePixelHeight;
page++;
} }
document.body.removeChild(clonedElement);
pdf.save(`chat-${chat.chat.title}.pdf`); pdf.save(`chat-${chat.chat.title}.pdf`);
} catch (error) { } catch (error) {
console.error('Error generating PDF', error); console.error('Error generating PDF', error);