191 lines
5.9 KiB
TypeScript
191 lines
5.9 KiB
TypeScript
export const usePdfExport = () => {
|
|
const generatePDF = async (
|
|
element: HTMLElement,
|
|
filename: string = "document.pdf",
|
|
options: any = {}
|
|
): Promise<void> => {
|
|
// Default options optimized for document templates
|
|
const defaultOptions = {
|
|
margin: [0.5, 0.5, 0.5, 0.5], // top, left, bottom, right in inches
|
|
filename,
|
|
image: { type: "jpeg", quality: 0.98 },
|
|
html2canvas: {
|
|
scale: 2, // Higher scale for better quality
|
|
useCORS: true,
|
|
allowTaint: false,
|
|
letterRendering: true,
|
|
logging: false,
|
|
dpi: 300,
|
|
height: undefined,
|
|
width: undefined,
|
|
},
|
|
jsPDF: {
|
|
unit: "in",
|
|
format: "letter",
|
|
orientation: "portrait",
|
|
compress: true,
|
|
},
|
|
pagebreak: {
|
|
mode: ["avoid-all", "css", "legacy"],
|
|
before: ".page-break-before",
|
|
after: ".page-break-after",
|
|
avoid: ".no-page-break",
|
|
},
|
|
};
|
|
|
|
// Merge provided options with defaults
|
|
const finalOptions = {
|
|
...defaultOptions,
|
|
...options,
|
|
html2canvas: {
|
|
...defaultOptions.html2canvas,
|
|
...(options.html2canvas || {}),
|
|
},
|
|
jsPDF: {
|
|
...defaultOptions.jsPDF,
|
|
...(options.jsPDF || {}),
|
|
},
|
|
pagebreak: {
|
|
...defaultOptions.pagebreak,
|
|
...(options.pagebreak || {}),
|
|
},
|
|
};
|
|
|
|
try {
|
|
// Dynamic import for client-side only
|
|
if (process.server) {
|
|
throw new Error("PDF generation is only available on the client side");
|
|
}
|
|
|
|
// Import html2pdf dynamically with better error handling
|
|
let html2pdf;
|
|
try {
|
|
const module = await import("html2pdf.js");
|
|
html2pdf = module.default || module;
|
|
} catch (importError) {
|
|
console.error("Failed to import html2pdf.js:", importError);
|
|
throw new Error(
|
|
"Failed to load PDF library. Please refresh the page and try again."
|
|
);
|
|
}
|
|
|
|
// Clone the element to avoid modifying the original
|
|
const clonedElement = element.cloneNode(true) as HTMLElement;
|
|
|
|
// Apply PDF-specific styling to the clone while preserving font choices
|
|
const currentFontClass = element.className.match(/font-[\w-]+/)?.[0];
|
|
let fontFamily = '"Times New Roman", "Times", serif'; // default
|
|
|
|
// Preserve selected font for PDF
|
|
if (currentFontClass) {
|
|
if (currentFontClass.includes("source-serif")) {
|
|
fontFamily = '"Source Serif 4", "Times New Roman", serif';
|
|
} else if (currentFontClass.includes("ubuntu")) {
|
|
fontFamily =
|
|
'"Ubuntu", -apple-system, BlinkMacSystemFont, sans-serif';
|
|
} else if (currentFontClass.includes("inter")) {
|
|
fontFamily = '"Inter", -apple-system, BlinkMacSystemFont, sans-serif';
|
|
}
|
|
}
|
|
|
|
clonedElement.style.fontFamily = fontFamily;
|
|
clonedElement.style.fontSize = "11pt";
|
|
clonedElement.style.lineHeight = "1.5";
|
|
clonedElement.style.color = "#000000";
|
|
clonedElement.style.backgroundColor = "#ffffff";
|
|
clonedElement.style.width = "8.5in";
|
|
clonedElement.style.maxWidth = "8.5in";
|
|
clonedElement.style.padding = "0.5in";
|
|
clonedElement.style.boxSizing = "border-box";
|
|
|
|
// Hide export controls in the clone
|
|
const exportControls = clonedElement.querySelector(".export-controls");
|
|
if (exportControls) {
|
|
(exportControls as HTMLElement).style.display = "none";
|
|
}
|
|
|
|
// Ensure proper font loading by adding a slight delay
|
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
|
|
// Generate the PDF with better error handling
|
|
console.log("Starting PDF generation with options:", finalOptions);
|
|
|
|
if (typeof html2pdf !== "function") {
|
|
throw new Error(
|
|
"html2pdf is not a function. Library may not have loaded correctly."
|
|
);
|
|
}
|
|
|
|
const pdfInstance = html2pdf();
|
|
|
|
if (!pdfInstance || typeof pdfInstance.set !== "function") {
|
|
throw new Error("html2pdf instance is invalid");
|
|
}
|
|
|
|
await pdfInstance.set(finalOptions).from(clonedElement).save();
|
|
} catch (error: any) {
|
|
console.error("PDF generation failed:", error);
|
|
const errorMessage =
|
|
error?.message || error?.toString() || "Unknown error";
|
|
throw new Error(`PDF generation failed: ${errorMessage}`);
|
|
}
|
|
};
|
|
|
|
const generatePDFFromContent = async (
|
|
htmlContent: string,
|
|
filename: string = "document.pdf",
|
|
options: any = {}
|
|
): Promise<void> => {
|
|
if (process.server) {
|
|
throw new Error("PDF generation is only available on the client side");
|
|
}
|
|
|
|
// Create a temporary element with the HTML content
|
|
const tempDiv = document.createElement("div");
|
|
tempDiv.innerHTML = htmlContent;
|
|
tempDiv.style.position = "absolute";
|
|
tempDiv.style.left = "-9999px";
|
|
tempDiv.style.top = "-9999px";
|
|
tempDiv.style.width = "8.5in";
|
|
tempDiv.style.fontFamily = '"Times New Roman", "Times", serif';
|
|
tempDiv.style.fontSize = "11pt";
|
|
tempDiv.style.lineHeight = "1.5";
|
|
tempDiv.style.color = "#000000";
|
|
tempDiv.style.backgroundColor = "#ffffff";
|
|
|
|
document.body.appendChild(tempDiv);
|
|
|
|
try {
|
|
await generatePDF(tempDiv, filename, options);
|
|
} finally {
|
|
document.body.removeChild(tempDiv);
|
|
}
|
|
};
|
|
|
|
const exportDocumentAsPDF = async (
|
|
selector: string = ".document-page",
|
|
filename?: string
|
|
): Promise<void> => {
|
|
if (process.server) {
|
|
throw new Error("PDF generation is only available on the client side");
|
|
}
|
|
|
|
const element = document.querySelector(selector) as HTMLElement;
|
|
if (!element) {
|
|
throw new Error(`Element with selector "${selector}" not found`);
|
|
}
|
|
|
|
// Generate filename based on current date if not provided
|
|
const defaultFilename = `document-${
|
|
new Date().toISOString().split("T")[0]
|
|
}.pdf`;
|
|
|
|
await generatePDF(element, filename || defaultFilename);
|
|
};
|
|
|
|
return {
|
|
generatePDF,
|
|
generatePDFFromContent,
|
|
exportDocumentAsPDF,
|
|
};
|
|
};
|