11import { 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 / ^ ( (? ! c h r o m e | a n d r o i d ) .) * s a f a r i / 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}
0 commit comments