pdfbooklet 1.0.2 → 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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/pdfbooklet.js +151 -129
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pdfbooklet",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Create booklet from a pdf file suitable for compact printing",
5
5
  "main": "pdfbooklet.js",
6
6
  "bin": {
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
- left: 0,
9
- right: 0,
10
- top: 0,
11
- bottom: 0,
8
+ left: 0,
9
+ right: 0,
10
+ top: 0,
11
+ bottom: 0,
12
12
  }
13
13
  const pageSize = {
14
- width: 11 * INCH,
15
- height: 8.5 * INCH,
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
- console.error(msg);
20
- process.exit(exitCode ?? 1);
21
+ console.error(msg);
22
+ process.exit(exitCode ?? 1);
21
23
  }
22
24
 
23
25
  const usage = (msg) => {
24
- if (msg) console.error(msg);
25
- console.log(`
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
- process.exit(msg ? 1 : 0)
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
- // Read the input PDF
58
- const existingPdfBytes = fs.readFileSync(inputPdfPath);
59
- const pdfDoc = await PDFDocument.load(existingPdfBytes);
60
- const totalPages = pdfDoc.getPageCount();
61
-
62
- // Ensure the number of pages is a multiple of 4 (for booklet printing)
63
- let numPagesToAdd = (4 - (totalPages % 4)) % 4; // Calculate how many blank pages are needed
64
- const adjustedTotalPages = totalPages + numPagesToAdd;
65
-
66
- console.log(`Total pages in the input PDF: ${totalPages}`);
67
- if (numPagesToAdd > 0) {
68
- console.log(`Adding ${numPagesToAdd} blank pages to make it a multiple of 4.`);
69
- }
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;
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;
105
- const y = box.height / 2 + box.y - h * scale / 2;
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
- // Booklet page order calculation
115
- for (let i = 0; i < adjustedTotalPages; i += 4) {
116
- const newPage1 = bookletPdf.addPage([pageSize.width, pageSize.height])
117
- await drawPageInBox(newPage1, leftBox, i + 3)
118
- await drawPageInBox(newPage1, rightBox, i)
119
- const newPage2 = bookletPdf.addPage([pageSize.width, pageSize.height])
120
- await drawPageInBox(newPage2, rightBox, i + 2,)
121
- await drawPageInBox(newPage2, leftBox, i + 1,)
122
- newPage2.setRotation(degrees(180))
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
- // Write the final booklet PDF to a file
126
- const pdfBytes = await bookletPdf.save();
127
- fs.writeFileSync(outputPdfPath, pdfBytes);
128
- console.log(`Booklet PDF created: ${outputPdfPath} pages=${totalPages}`);
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
- switch (process.argv[i]) {
135
- case '--paper-size':
136
- switch (process.argv[++i]) {
137
- case 'a4':
138
- pageSize.width = 11.7 * INCH;
139
- pageSize.height = 8.3 * INCH;
140
- break;
141
- case 'letter':
142
- pageSize.width = 11 * INCH;
143
- pageSize.height = 8.5 * INCH;
144
- break;
145
-
146
- default:
147
- err("Unknown paper size")
148
- break;
149
- }
150
- break;
151
- case '--margin-inset':
152
- // follow css convention
153
- const insets = process.argv[++i].split(',').map(x => parseFloat(x) * INCH);
154
- if (insets.length === 1) {
155
- marginInsets.left = insets[0];
156
- marginInsets.right = insets[0];
157
- marginInsets.bottom = insets[0];
158
- marginInsets.top = insets[0];
159
- } else if (insets.length === 2) {
160
- marginInsets.top = insets[0];
161
- marginInsets.bottom = insets[0];
162
- marginInsets.left = insets[1];
163
- marginInsets.right = insets[1];
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
- inputPdfPath = process.argv[i];
178
- break;
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
- usage("Missing input file");
205
+ usage("Missing input file");
184
206
  }
185
207
 
186
208
  if (!fs.existsSync(inputPdfPath)) {
187
- err(`File not found: ${inputPdfPath}`)
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
- console.error('Error creating booklet:', err);
216
+ console.error('Error creating booklet:', err);
195
217
  });
196
218