app/composables/usePdfExportSimple.ts

170 lines
6.1 KiB
TypeScript

export const usePdfExportSimple = () => {
const exportToPDF = async (elementSelector: string, filename: string) => {
// Only run on client side
if (process.server || typeof window === "undefined") {
throw new Error("PDF generation is only available on the client side");
}
try {
// Dynamic import for client-side only
const html2pdf = (await import("html2pdf.js")).default;
// Get the element to export
const element = document.querySelector(elementSelector);
if (!element) {
throw new Error(`Element with selector "${elementSelector}" not found`);
}
// Clone the element to avoid modifying the original
const clonedElement = element.cloneNode(true) as HTMLElement;
// Fix CSS compatibility issues by applying only computed styles
const fixCSSCompatibility = (el: HTMLElement) => {
// Get all elements including the root
const allElements = [el, ...el.querySelectorAll("*")] as HTMLElement[];
allElements.forEach((elem) => {
// Get computed styles
const computedStyle = window.getComputedStyle(elem);
// Clear all existing styles to start fresh
elem.removeAttribute("style");
elem.removeAttribute("class");
// Apply only essential computed styles that are safe
elem.style.display = computedStyle.display;
elem.style.position = computedStyle.position;
elem.style.width = computedStyle.width;
elem.style.height = computedStyle.height;
elem.style.margin = computedStyle.margin;
elem.style.padding = computedStyle.padding;
elem.style.fontSize = computedStyle.fontSize;
elem.style.fontWeight = computedStyle.fontWeight;
elem.style.fontFamily = computedStyle.fontFamily;
elem.style.lineHeight = computedStyle.lineHeight;
elem.style.textAlign = computedStyle.textAlign;
// Apply safe color values - convert any complex colors to simple ones
const safeColor =
computedStyle.color.includes("oklch") ||
computedStyle.color.includes("oklab")
? "#000000"
: computedStyle.color;
const safeBgColor =
computedStyle.backgroundColor.includes("oklch") ||
computedStyle.backgroundColor.includes("oklab")
? "transparent"
: computedStyle.backgroundColor;
elem.style.color = safeColor;
elem.style.backgroundColor = safeBgColor;
elem.style.borderWidth = computedStyle.borderWidth;
elem.style.borderStyle = computedStyle.borderStyle;
elem.style.borderColor = "#cccccc"; // Safe fallback color
});
};
// Apply CSS fixes
fixCSSCompatibility(clonedElement);
// Temporarily add to DOM for processing
clonedElement.style.position = "absolute";
clonedElement.style.left = "-9999px";
clonedElement.style.top = "-9999px";
document.body.appendChild(clonedElement);
// Simple options for better compatibility
const options = {
margin: 0.5,
filename: filename,
image: { type: "jpeg", quality: 0.98 },
html2canvas: {
scale: 2,
useCORS: true,
allowTaint: false,
logging: false,
ignoreElements: (element: Element) => {
// Skip elements that might cause issues
return (
element.classList.contains("no-pdf") ||
element.classList.contains("export-controls")
);
},
onclone: (clonedDoc: Document) => {
// Remove ALL existing stylesheets to avoid oklch() issues
const stylesheets = clonedDoc.querySelectorAll(
'style, link[rel="stylesheet"]'
);
stylesheets.forEach((sheet) => sheet.remove());
// Add only safe, basic CSS
const safeStyle = clonedDoc.createElement("style");
safeStyle.textContent = `
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 12px;
line-height: 1.5;
color: #000000;
background: #ffffff;
}
.export-controls, .no-pdf {
display: none !important;
}
/* Basic typography */
h1 { font-size: 24px; font-weight: bold; margin: 20px 0 10px 0; }
h2 { font-size: 20px; font-weight: bold; margin: 16px 0 8px 0; }
h3 { font-size: 16px; font-weight: bold; margin: 12px 0 6px 0; }
p { margin: 8px 0; }
ul, ol { margin: 8px 0; padding-left: 20px; }
li { margin: 4px 0; }
/* Form elements */
input, textarea, select {
border: 1px solid #ccc;
padding: 4px;
font-size: 12px;
background: #fff;
color: #000;
}
`;
clonedDoc.head.appendChild(safeStyle);
},
},
jsPDF: {
unit: "in",
format: "letter",
orientation: "portrait",
},
};
console.log("Generating PDF with html2pdf...");
// Wait a moment for DOM changes to settle
await new Promise((resolve) => setTimeout(resolve, 100));
try {
// Generate and save the PDF using the cloned element
await html2pdf().set(options).from(clonedElement).save();
console.log("PDF generated successfully!");
} finally {
// Clean up the cloned element
if (clonedElement.parentNode) {
document.body.removeChild(clonedElement);
}
}
} catch (error: any) {
console.error("PDF generation error:", error);
throw new Error(`PDF generation failed: ${error.message || error}`);
}
};
return { exportToPDF };
};