170 lines
6.1 KiB
TypeScript
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 };
|
|
};
|