1
1
import { domToPng } from "./dom-to-png" ;
2
2
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
+ } ) {
4
10
if ( ! domElement ) return Promise . reject ( "No domElement provided" ) ;
5
11
6
- const isSafari = typeof navigator !== 'undefined' &&
12
+ const isSafari =
13
+ typeof navigator !== "undefined" &&
7
14
/ ^ ( (? ! c h r o m e | a n d r o i d ) .) * s a f a r i / i. test ( navigator . userAgent ) ;
8
15
9
16
let JsPDF ;
10
-
11
17
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" ) ;
15
21
}
16
22
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 } ;
21
25
22
26
if ( isSafari ) {
23
27
// 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 = {
32
36
}
33
37
34
38
const imgData = await domToPng ( { container : domElement , scale } ) ;
39
+
35
40
return await new Promise ( ( resolve , reject ) => {
36
41
const img = new window . Image ( ) ;
37
42
img . onload = function ( ) {
43
+ const EPS = 0.5 ; // small epsilon to avoid off-by-one paging due to rounding
44
+
38
45
const contentWidth = img . naturalWidth ;
39
46
const contentHeight = img . naturalHeight ;
40
47
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" ) ;
60
88
} 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 ) {
62
95
pdf . addImage (
63
96
imgData ,
64
97
"PNG" ,
65
- 0 ,
66
- position ,
98
+ x ,
99
+ positionY ,
67
100
imgWidth ,
68
101
imgHeight ,
69
102
"" ,
70
103
"FAST"
71
104
) ;
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 ( ) ;
77
108
}
78
109
}
110
+
79
111
pdf . save ( `${ fileName } .pdf` ) ;
80
112
resolve ( ) ;
81
113
} ;
82
- img . onerror = err => reject ( "Failed to load image for PDF: " + err ) ;
114
+ img . onerror = ( err ) => reject ( "Failed to load image for PDF: " + err ) ;
83
115
img . src = imgData ;
84
116
} ) ;
85
117
}
0 commit comments