app/composables/usePdfExport.ts

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,
};
};