import html2canvas from 'html2canvas';
import type { Options as Html2canvasOptions } from 'html2canvas';
import JsPDF from 'jspdf';
import type { jsPDFOptions as JsPDFOptions } from 'jspdf';
type GeneratePDFOptions = {
/** 文件名称 */
fileName?: string;
/** 单页高度 */
heightPerPage?: number;
/** tml2canvas配置 */
html2canvasOptions?: Partial;
/** jspdf配置 */
jsPDFOptions?: Partial;
/** 生成成功的回调方法 */
onSuccess?: () => void;
/** 生成错误的回调方法 */
onError?: (error: unknown) => void;
/** 生成之后的回调方法 */
onSettled?: () => void;
/** 生成之前的回调方法 */
onBeforeCapture?: () => void;
};
const htmlToPdf = async (element: HTMLElement | null, options: GeneratePDFOptions) => {
const {
heightPerPage = 840,
fileName = 'document.pdf',
html2canvasOptions = {},
jsPDFOptions = {},
onSuccess,
onError,
onSettled,
onBeforeCapture,
} = options;
if (!element) return;
const CANVAS_CHROME_FF_LIMIT = 32_767;
const CANVAS_SAFARI_LIMIT = 4096;
// eslint-disable-next-line no-param-reassign
element.style.gap = '0px';
const MAX_CANVAS_HEIGHT =
navigator.userAgent.includes('Chrome') || navigator.userAgent.includes('Firefox')
? (CANVAS_CHROME_FF_LIMIT % heightPerPage) * heightPerPage
: (CANVAS_SAFARI_LIMIT % heightPerPage) * heightPerPage;
onBeforeCapture?.();
try {
const pdf = new JsPDF({
orientation: 'l',
unit: 'px',
format: [element.offsetWidth, heightPerPage],
compress: true,
...jsPDFOptions,
});
const totalHeight = element.scrollHeight;
const totalWidth = element.scrollWidth;
let capturedHeight = 0;
const canvasPromises: Promise[] = [];
while (capturedHeight < totalHeight) {
const remainingHeight = totalHeight - capturedHeight;
const captureHeight = Math.min(remainingHeight, MAX_CANVAS_HEIGHT);
const canvasPromise = html2canvas(element, {
logging: false,
windowWidth: totalWidth,
windowHeight: totalHeight,
x: 0,
y: capturedHeight,
width: totalWidth,
height: captureHeight,
...html2canvasOptions,
});
canvasPromises.push(canvasPromise);
capturedHeight += captureHeight;
}
const canvases = await Promise.all(canvasPromises);
canvases.forEach((canvas) => {
const contentWidth = canvas.width;
let contentHeight = canvas.height;
const pageHeight = (contentWidth / element.offsetWidth) * heightPerPage;
let position = 0;
const imgWidth = element.offsetWidth;
const imgHeight = (element.offsetWidth / contentWidth) * contentHeight;
const imgData = canvas.toDataURL('image/jpeg');
if (contentHeight < pageHeight) {
pdf.addImage(imgData, 'JPEG', 0, 0, imgWidth, imgHeight, undefined, 'FAST');
} else {
while (contentHeight > 0) {
pdf.addImage(
imgData,
'JPEG',
0,
position,
imgWidth,
imgHeight,
undefined,
'FAST',
);
contentHeight -= pageHeight;
position -= heightPerPage;
if (contentHeight > 0) {
pdf.addPage();
}
}
}
});
pdf.save(fileName);
onSuccess?.();
} catch (error) {
onError?.(error);
} finally {
onSettled?.();
}
};
export default htmlToPdf;