pdfbooklet 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/pdfbooklet.js +151 -129
package/package.json
CHANGED
package/pdfbooklet.js
CHANGED
|
@@ -5,33 +5,36 @@ const {PDFDocument, degrees, grayscale} = require('pdf-lib');
|
|
|
5
5
|
|
|
6
6
|
const INCH = 72;
|
|
7
7
|
const marginInsets = {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
left: 0,
|
|
9
|
+
right: 0,
|
|
10
|
+
top: 0,
|
|
11
|
+
bottom: 0,
|
|
12
12
|
}
|
|
13
13
|
const pageSize = {
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
width: 11 * INCH,
|
|
15
|
+
height: 8.5 * INCH,
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
let opt_pages = [];
|
|
19
|
+
|
|
18
20
|
const err = (msg, exitCode) => {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
console.error(msg);
|
|
22
|
+
process.exit(exitCode ?? 1);
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
const usage = (msg) => {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
if (msg) console.error(msg);
|
|
27
|
+
console.log(`
|
|
26
28
|
pdfbooklet <input.pdf>
|
|
27
29
|
|
|
28
30
|
OPTIONS:
|
|
31
|
+
--range X-Y,+N,A-B,... from page X to Y, insert N blank pages, from page A to B
|
|
29
32
|
--paper-size (a4|letter), default is letter
|
|
30
33
|
--margin-inset X reduce margin of the original pdf on all sides by X (in inches)
|
|
31
34
|
--margin-inset X,Y reduce margin X on top and bottom, Y on left and right
|
|
32
35
|
--margin-inset X,Y,Z,A reduce margin-top by X, right by Y, bottom by Z, left by A
|
|
33
36
|
`)
|
|
34
|
-
|
|
37
|
+
process.exit(msg ? 1 : 0)
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
/*
|
|
@@ -54,143 +57,162 @@ OPTIONS:
|
|
|
54
57
|
* and when you fold it, it is in the natural order of A B C D.
|
|
55
58
|
*/
|
|
56
59
|
async function createBooklet(inputPdfPath, outputPdfPath) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (i < totalPages) {
|
|
76
|
-
const [a] = await bookletPdf.embedPdf(pdfDoc, [i]);
|
|
77
|
-
return a;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const leftBox = {
|
|
83
|
-
x: 0,
|
|
84
|
-
y: 0, // Draw the first page on the left half
|
|
85
|
-
width: pageSize.width / 2,
|
|
86
|
-
height: pageSize.height,
|
|
87
|
-
}
|
|
88
|
-
const rightBox = {
|
|
89
|
-
x: pageSize.width / 2,
|
|
90
|
-
y: 0, // Draw the first page on the left half
|
|
91
|
-
width: pageSize.width / 2,
|
|
92
|
-
height: pageSize.height,
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const drawPageInBox = async (newPage, box, originPageIndex, flip) => {
|
|
96
|
-
const a = await getEmbedPage(originPageIndex)
|
|
97
|
-
if (a) {
|
|
98
|
-
const {width, height} = a.size()
|
|
99
|
-
const w = width - marginInsets.left - marginInsets.right;
|
|
100
|
-
const h = height - marginInsets.top - marginInsets.bottom;
|
|
101
|
-
const xScale = box.width / w;
|
|
102
|
-
const yScale = box.height / h;
|
|
103
|
-
const scale = xScale < yScale ? xScale : yScale;
|
|
104
|
-
const x = box.width / 2 + box.x - w * scale / 2 - marginInsets.left * scale;
|
|
105
|
-
const y = box.height / 2 + box.y - h * scale / 2 - marginInsets.bottom * scale;
|
|
106
|
-
newPage.drawPage(a, {
|
|
107
|
-
x, y,
|
|
108
|
-
xScale: scale,
|
|
109
|
-
yScale: scale,
|
|
110
|
-
})
|
|
111
|
-
}
|
|
60
|
+
// Read the input PDF
|
|
61
|
+
const existingPdfBytes = fs.readFileSync(inputPdfPath);
|
|
62
|
+
const pdfDoc = await PDFDocument.load(existingPdfBytes);
|
|
63
|
+
const totalPages = pdfDoc.getPageCount();
|
|
64
|
+
const pageNumbers = opt_pages || Array.from({length: totalPages}, (_, i) => i);
|
|
65
|
+
while (pageNumbers.length % 4 !== 0) {
|
|
66
|
+
pageNumbers.push(-1);
|
|
67
|
+
}
|
|
68
|
+
console.log(`Total pages in the input PDF: ${totalPages}`);
|
|
69
|
+
console.log(`Output booklet will have ${pageNumbers.length} pages`);
|
|
70
|
+
|
|
71
|
+
// Create a new PDF for the booklet
|
|
72
|
+
const bookletPdf = await PDFDocument.create();
|
|
73
|
+
|
|
74
|
+
const getEmbedPage = async (i) => {
|
|
75
|
+
if (i < totalPages) {
|
|
76
|
+
const [a] = await bookletPdf.embedPdf(pdfDoc, [i]);
|
|
77
|
+
return a;
|
|
112
78
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
const leftBox = {
|
|
83
|
+
x: 0,
|
|
84
|
+
y: 0, // Draw the first page on the left half
|
|
85
|
+
width: pageSize.width / 2,
|
|
86
|
+
height: pageSize.height,
|
|
87
|
+
}
|
|
88
|
+
const rightBox = {
|
|
89
|
+
x: pageSize.width / 2,
|
|
90
|
+
y: 0, // Draw the first page on the left half
|
|
91
|
+
width: pageSize.width / 2,
|
|
92
|
+
height: pageSize.height,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const drawPageInBox = async (newPage, box, originPageIndex, flip) => {
|
|
96
|
+
if (originPageIndex < 0) return;
|
|
97
|
+
const a = await getEmbedPage(originPageIndex)
|
|
98
|
+
if (a) {
|
|
99
|
+
const {width, height} = a.size()
|
|
100
|
+
const w = width - marginInsets.left - marginInsets.right;
|
|
101
|
+
const h = height - marginInsets.top - marginInsets.bottom;
|
|
102
|
+
const xScale = box.width / w;
|
|
103
|
+
const yScale = box.height / h;
|
|
104
|
+
const scale = xScale < yScale ? xScale : yScale;
|
|
105
|
+
const x = box.width / 2 + box.x - w * scale / 2 - marginInsets.left * scale;
|
|
106
|
+
const y = box.height / 2 + box.y - h * scale / 2 - marginInsets.bottom * scale;
|
|
107
|
+
newPage.drawPage(a, {
|
|
108
|
+
x, y,
|
|
109
|
+
xScale: scale,
|
|
110
|
+
yScale: scale,
|
|
111
|
+
})
|
|
123
112
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Booklet page order calculation
|
|
116
|
+
for (let i = 0; i < pageNumbers.length; i += 4) {
|
|
117
|
+
const newPage1 = bookletPdf.addPage([pageSize.width, pageSize.height])
|
|
118
|
+
await drawPageInBox(newPage1, leftBox, pageNumbers[i + 3])
|
|
119
|
+
await drawPageInBox(newPage1, rightBox, pageNumbers[i])
|
|
120
|
+
const newPage2 = bookletPdf.addPage([pageSize.width, pageSize.height])
|
|
121
|
+
await drawPageInBox(newPage2, rightBox, pageNumbers[i + 2])
|
|
122
|
+
await drawPageInBox(newPage2, leftBox, pageNumbers[i + 1])
|
|
123
|
+
newPage2.setRotation(degrees(180))
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Write the final booklet PDF to a file
|
|
127
|
+
const pdfBytes = await bookletPdf.save();
|
|
128
|
+
fs.writeFileSync(outputPdfPath, pdfBytes);
|
|
129
|
+
console.log(`Booklet PDF created: ${outputPdfPath} pages=${totalPages}`);
|
|
129
130
|
}
|
|
130
131
|
|
|
131
132
|
let inputPdfPath;
|
|
132
133
|
|
|
133
134
|
for (let i = 2; i < process.argv.length; i++) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
} else if (insets.length === 4) {
|
|
165
|
-
marginInsets.top = insets[0];
|
|
166
|
-
marginInsets.right = insets[1];
|
|
167
|
-
marginInsets.bottom = insets[2];
|
|
168
|
-
marginInsets.left = insets[3];
|
|
169
|
-
} else {
|
|
170
|
-
err("Invalid insets");
|
|
171
|
-
}
|
|
172
|
-
break;
|
|
173
|
-
case '--help':
|
|
174
|
-
usage();
|
|
175
|
-
break;
|
|
135
|
+
switch (process.argv[i]) {
|
|
136
|
+
case '--range':
|
|
137
|
+
opt_pages = process.argv[++i].split(',').map(x => {
|
|
138
|
+
const m = x.match(/^\+(\d+)$/);
|
|
139
|
+
if (m) {
|
|
140
|
+
return Array(parseInt(m[1])).fill(0)
|
|
141
|
+
}
|
|
142
|
+
if (x.match(/^\d+$/)) {
|
|
143
|
+
return parseInt(x);
|
|
144
|
+
}
|
|
145
|
+
const m2 = x.match(/^(\d+)-(\d+)$/);
|
|
146
|
+
if (!m2) {
|
|
147
|
+
throw new Error(`Bad range: ${x}`)
|
|
148
|
+
}
|
|
149
|
+
const begin = parseInt(m2[1]);
|
|
150
|
+
const end = parseInt(m2[2]);
|
|
151
|
+
return Array.from({length: end - begin + 1}, (v, i) => begin + i)
|
|
152
|
+
}).flat().map(x => x - 1);
|
|
153
|
+
break;
|
|
154
|
+
case '--paper-size':
|
|
155
|
+
switch (process.argv[++i]) {
|
|
156
|
+
case 'a4':
|
|
157
|
+
pageSize.width = 11.7 * INCH;
|
|
158
|
+
pageSize.height = 8.3 * INCH;
|
|
159
|
+
break;
|
|
160
|
+
case 'letter':
|
|
161
|
+
pageSize.width = 11 * INCH;
|
|
162
|
+
pageSize.height = 8.5 * INCH;
|
|
163
|
+
break;
|
|
164
|
+
|
|
176
165
|
default:
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
166
|
+
err("Unknown paper size")
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
break;
|
|
170
|
+
case '--margin-inset':
|
|
171
|
+
// follow css convention
|
|
172
|
+
const insets = process.argv[++i].split(',')
|
|
173
|
+
.map(x => parseFloat(x) * INCH);
|
|
174
|
+
if (insets.length === 1) {
|
|
175
|
+
marginInsets.left = insets[0];
|
|
176
|
+
marginInsets.right = insets[0];
|
|
177
|
+
marginInsets.bottom = insets[0];
|
|
178
|
+
marginInsets.top = insets[0];
|
|
179
|
+
} else if (insets.length === 2) {
|
|
180
|
+
marginInsets.top = insets[0];
|
|
181
|
+
marginInsets.bottom = insets[0];
|
|
182
|
+
marginInsets.left = insets[1];
|
|
183
|
+
marginInsets.right = insets[1];
|
|
184
|
+
} else if (insets.length === 4) {
|
|
185
|
+
marginInsets.top = insets[0];
|
|
186
|
+
marginInsets.right = insets[1];
|
|
187
|
+
marginInsets.bottom = insets[2];
|
|
188
|
+
marginInsets.left = insets[3];
|
|
189
|
+
} else {
|
|
190
|
+
err("Invalid insets");
|
|
191
|
+
}
|
|
192
|
+
break;
|
|
193
|
+
case '--help':
|
|
194
|
+
usage();
|
|
195
|
+
break;
|
|
196
|
+
default:
|
|
197
|
+
inputPdfPath = process.argv[i];
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
180
200
|
}
|
|
181
201
|
|
|
202
|
+
console.log("pages=", JSON.stringify(opt_pages));
|
|
203
|
+
|
|
182
204
|
if (!inputPdfPath) {
|
|
183
|
-
|
|
205
|
+
usage("Missing input file");
|
|
184
206
|
}
|
|
185
207
|
|
|
186
208
|
if (!fs.existsSync(inputPdfPath)) {
|
|
187
|
-
|
|
209
|
+
err(`File not found: ${inputPdfPath}`)
|
|
188
210
|
}
|
|
189
211
|
|
|
190
212
|
// Automatically generate the output filename by appending '-booklet' before '.pdf'
|
|
191
213
|
const outputPdfPath = inputPdfPath.replace(/\.pdf$/i, '-booklet.pdf');
|
|
192
214
|
// Usage example
|
|
193
215
|
createBooklet(inputPdfPath, outputPdfPath).catch(err => {
|
|
194
|
-
|
|
216
|
+
console.error('Error creating booklet:', err);
|
|
195
217
|
});
|
|
196
218
|
|