瀏覽代碼

refac: chat pdf rendering

Timothy Jaeryang Baek 5 月之前
父節點
當前提交
9143716463
共有 2 個文件被更改,包括 99 次插入96 次删除
  1. 47 46
      src/lib/components/layout/Navbar/Menu.svelte
  2. 52 50
      src/lib/components/layout/Sidebar/ChatMenu.svelte

+ 47 - 46
src/lib/components/layout/Navbar/Menu.svelte

@@ -68,66 +68,67 @@
 		if (containerElement) {
 			try {
 				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 2–4k is safe)
 
-				console.log('isDarkMode', isDarkMode);
-
-				// Define a fixed virtual screen size
-				const virtualWidth = 800; // Fixed width (adjust as needed)
-				// Clone the container to avoid layout shifts
+				// Clone & style once
 				const clonedElement = containerElement.cloneNode(true);
 				clonedElement.classList.add('text-black');
 				clonedElement.classList.add('dark:text-white');
-				clonedElement.style.width = `${virtualWidth}px`; // Apply fixed width
-				clonedElement.style.height = 'auto'; // Allow content to expand
-
-				document.body.appendChild(clonedElement); // Temporarily add to DOM
-
-				// Render to canvas with predefined width
-				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
+				clonedElement.style.width = `${virtualWidth}px`;
+				clonedElement.style.position = 'absolute';
+				clonedElement.style.left = '-9999px'; // Offscreen
+				clonedElement.style.height = 'auto';
+				document.body.appendChild(clonedElement);
+
+				// Get total height after attached to DOM
+				const totalHeight = clonedElement.scrollHeight;
+				let offsetY = 0;
+				let page = 0;
+
+				// Prepare PDF
 				const pdf = new jsPDF('p', 'mm', 'a4');
-				const imgWidth = 210; // A4 width in mm
-				const pageHeight = 297; // A4 height in mm
-
-				// Maintain aspect ratio
-				const imgHeight = (canvas.height * imgWidth) / canvas.width;
-				let heightLeft = imgHeight;
-				let position = 0;
-
-				// Set page background for dark mode
-				if (isDarkMode) {
-					pdf.setFillColor(0, 0, 0);
-					pdf.rect(0, 0, imgWidth, pageHeight, 'F'); // Apply black bg
-				}
-
-				pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
-				heightLeft -= pageHeight;
+				const imgWidth = 210; // A4 mm
+				const pageHeight = 297; // A4 mm
+
+				while (offsetY < totalHeight) {
+					// For each slice, adjust scrollTop to show desired part
+					clonedElement.scrollTop = offsetY;
+
+					// Optionally: mask/hide overflowing content via CSS if needed
+					clonedElement.style.maxHeight = `${pagePixelHeight}px`;
+					// Only render the visible part
+					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
 
-				// Handle additional pages
-				while (heightLeft > 0) {
-					position -= pageHeight;
-					pdf.addPage();
+					if (page > 0) pdf.addPage();
 
+					// Set page background for dark mode
 					if (isDarkMode) {
 						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);
-					heightLeft -= pageHeight;
+
+					offsetY += pagePixelHeight;
+					page++;
 				}
 
+				document.body.removeChild(clonedElement);
+
 				pdf.save(`chat-${chat.chat.title}.pdf`);
 			} catch (error) {
 				console.error('Error generating PDF', error);

+ 52 - 50
src/lib/components/layout/Sidebar/ChatMenu.svelte

@@ -85,66 +85,68 @@
 
 		if (containerElement) {
 			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 2–4k is safe)
 
-				// Define a fixed virtual screen size
-				const virtualWidth = 1024; // Fixed width (adjust as needed)
-				const virtualHeight = 1400; // Fixed height (adjust as needed)
-
-				// Clone the container to avoid layout shifts
+				// Clone & style once
 				const clonedElement = containerElement.cloneNode(true);
-				clonedElement.style.width = `${virtualWidth}px`; // Apply fixed width
-				clonedElement.style.height = 'auto'; // Allow content to expand
-
-				document.body.appendChild(clonedElement); // Temporarily add to DOM
-
-				// Render to canvas with predefined width
-				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
+				clonedElement.classList.add('text-black');
+				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);
+
+				// Get total height after attached to DOM
+				const totalHeight = clonedElement.scrollHeight;
+				let offsetY = 0;
+				let page = 0;
+
+				// Prepare PDF
 				const pdf = new jsPDF('p', 'mm', 'a4');
-				const imgWidth = 210; // A4 width in mm
-				const pageHeight = 297; // A4 height in mm
-
-				// Maintain aspect ratio
-				const imgHeight = (canvas.height * imgWidth) / canvas.width;
-				let heightLeft = imgHeight;
-				let position = 0;
-
-				// Set page background for dark mode
-				if (isDarkMode) {
-					pdf.setFillColor(0, 0, 0);
-					pdf.rect(0, 0, imgWidth, pageHeight, 'F'); // Apply black bg
-				}
-
-				pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
-				heightLeft -= pageHeight;
-
-				// Handle additional pages
-				while (heightLeft > 0) {
-					position -= pageHeight;
-					pdf.addPage();
-
+				const imgWidth = 210; // A4 mm
+				const pageHeight = 297; // A4 mm
+
+				while (offsetY < totalHeight) {
+					// For each slice, adjust scrollTop to show desired part
+					clonedElement.scrollTop = offsetY;
+
+					// Optionally: mask/hide overflowing content via CSS if needed
+					clonedElement.style.maxHeight = `${pagePixelHeight}px`;
+					// Only render the visible part
+					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
+
+					if (page > 0) pdf.addPage();
+
+					// Set page background for dark mode
 					if (isDarkMode) {
 						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);
-					heightLeft -= pageHeight;
+
+					offsetY += pagePixelHeight;
+					page++;
 				}
 
+				document.body.removeChild(clonedElement);
+
 				pdf.save(`chat-${chat.chat.title}.pdf`);
 			} catch (error) {
 				console.error('Error generating PDF', error);