Skip to content

Commit d735b39

Browse files
committed
Improvement - PDF - Add orientation and overflowTolerance config options #243
1 parent 2f5a2ba commit d735b39

File tree

4 files changed

+77
-50
lines changed

4 files changed

+77
-50
lines changed

src/pdf.js

Lines changed: 70 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,27 @@
11
import { domToPng } from "./dom-to-png";
22

3-
export default async function pdf({ domElement, fileName, scale = 2, options = {} }) {
3+
export default async function pdf({
4+
domElement,
5+
fileName,
6+
scale = 2,
7+
orientation = "auto", // 'auto' | 'portrait' | 'landscape'
8+
overflowTolerance = 0.2, // up to +n% height overflow gets squeezed onto 1 page
9+
}) {
410
if (!domElement) return Promise.reject("No domElement provided");
511

6-
const isSafari = typeof navigator !== 'undefined' &&
12+
const isSafari =
13+
typeof navigator !== "undefined" &&
714
/^((?!chrome|android).)*safari/i.test(navigator.userAgent);
815

916
let JsPDF;
10-
1117
try {
12-
JsPDF = (await import('jspdf')).default;
13-
} catch (e) {
14-
return Promise.reject('jspdf is not installed. Run npm install jspdf')
18+
JsPDF = (await import("jspdf")).default;
19+
} catch (_) {
20+
return Promise.reject("jspdf is not installed. Run npm install jspdf");
1521
}
1622

17-
const a4 = {
18-
width: 595.28,
19-
height: 841.89,
20-
};
23+
const A4_PORTRAIT = { width: 595.28, height: 841.89 };
24+
const A4_LANDSCAPE = { width: 841.89, height: 595.28 };
2125

2226
if (isSafari) {
2327
// Warming up in Safari, because it never works on the first try
@@ -32,54 +36,82 @@ export default async function pdf({ domElement, fileName, scale = 2, options = {
3236
}
3337

3438
const imgData = await domToPng({ container: domElement, scale });
39+
3540
return await new Promise((resolve, reject) => {
3641
const img = new window.Image();
3742
img.onload = function () {
43+
const EPS = 0.5; // small epsilon to avoid off-by-one paging due to rounding
44+
3845
const contentWidth = img.naturalWidth;
3946
const contentHeight = img.naturalHeight;
4047

41-
let imgWidth = a4.width;
42-
let imgHeight = (a4.width / contentWidth) * contentHeight;
43-
44-
const pdf = new JsPDF("", "pt", "a4");
45-
let position = 0;
46-
let leftHeight = contentHeight;
47-
const pageHeight = (contentWidth / a4.width) * a4.height;
48-
49-
if (leftHeight < pageHeight) {
50-
pdf.addImage(
51-
imgData,
52-
"PNG",
53-
0,
54-
0,
55-
imgWidth,
56-
imgHeight,
57-
"",
58-
"FAST"
59-
);
48+
const chosenOrientation =
49+
orientation === "auto"
50+
? contentHeight >= contentWidth
51+
? "p"
52+
: "l"
53+
: orientation;
54+
55+
const a4 =
56+
chosenOrientation === "l" ? A4_LANDSCAPE : A4_PORTRAIT;
57+
58+
const ratioToWidth = a4.width / contentWidth;
59+
const ratioToHeight = a4.height / contentHeight;
60+
const scaledHeightAtWidth = contentHeight * ratioToWidth;
61+
62+
let mode = "single"; // 'single' | 'multi'
63+
let ratio;
64+
65+
if (scaledHeightAtWidth <= a4.height + EPS) {
66+
ratio = ratioToWidth;
67+
} else if (scaledHeightAtWidth <= a4.height * (1 + overflowTolerance)) {
68+
ratio = Math.min(ratioToWidth, ratioToHeight);
69+
} else {
70+
mode = "multi";
71+
ratio = ratioToWidth;
72+
}
73+
74+
const imgWidth = contentWidth * ratio;
75+
const imgHeight = contentHeight * ratio;
76+
77+
const x = (a4.width - imgWidth) / 2;
78+
79+
const pdf = new JsPDF({
80+
orientation: chosenOrientation,
81+
unit: "pt",
82+
format: "a4"
83+
});
84+
85+
if (mode === "single") {
86+
const y = (a4.height - imgHeight) / 2;
87+
pdf.addImage(imgData, "PNG", x, y, imgWidth, imgHeight, "", "FAST");
6088
} else {
61-
while (leftHeight > 0) {
89+
const pageHeightInImagePx = a4.height / ratio;
90+
91+
let leftHeight = contentHeight;
92+
let positionY = 0;
93+
94+
while (leftHeight > EPS) {
6295
pdf.addImage(
6396
imgData,
6497
"PNG",
65-
0,
66-
position,
98+
x,
99+
positionY,
67100
imgWidth,
68101
imgHeight,
69102
"",
70103
"FAST"
71104
);
72-
leftHeight -= pageHeight;
73-
position -= a4.height;
74-
if (leftHeight > 0) {
75-
pdf.addPage();
76-
}
105+
leftHeight -= pageHeightInImagePx;
106+
positionY -= a4.height;
107+
if (leftHeight > EPS) pdf.addPage();
77108
}
78109
}
110+
79111
pdf.save(`${fileName}.pdf`);
80112
resolve();
81113
};
82-
img.onerror = err => reject("Failed to load image for PDF: " + err);
114+
img.onerror = (err) => reject("Failed to load image for PDF: " + err);
83115
img.src = imgData;
84116
});
85117
}

src/useConfig.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -207,12 +207,9 @@ export function useConfig() {
207207
},
208208
buttonTitles,
209209
print: {
210-
allowTaint: false,
211-
backgroundColor: COLOR_WHITE,
212-
useCORS: false,
213-
onclone: null,
214210
scale: 2,
215-
logging: false
211+
orientation: 'auto', // 'auto' | 'l' | 'p'
212+
overflowTolerance: 0.2,
216213
}
217214
}
218215
}

src/usePrinter.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ export function usePrinter({
2121
await pdf({
2222
domElement: document.getElementById(elementId),
2323
fileName,
24-
options
24+
orientation: options.orientation,
25+
overflowTolerance: options.overflowTolerance,
26+
scale: options.scale
2527
});
2628
} catch (error) {
2729
console.error("Error generating PDF:", error);

types/vue-data-ui.d.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -267,14 +267,10 @@ declare module "vue-data-ui" {
267267
table?: null | (() => void);
268268
tooltip?: null | (() => void);
269269
};
270-
// old html2canvas options
271270
print?: {
272-
allowTaint?: boolean;
273-
backgroundColor?: string;
274-
useCORS?: boolean;
275-
onclone?: null | ((doc: Document) => void);
276271
scale?: number;
277-
logging?: boolean;
272+
orientation?: 'auto' | 'l' | 'p';
273+
overflowTolerance?: number;
278274
};
279275
};
280276

0 commit comments

Comments
 (0)