@muhammara/native-core 7.0.0-beta.1 → 7.0.0-beta.3
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/README.md +4 -0
- package/lib/Recipe.js +119 -57
- package/lib/recipe/annotation.js +53 -4
- package/lib/recipe/appendPage.js +22 -9
- package/lib/recipe/graphics-state.js +20 -0
- package/lib/recipe/image.js +4 -0
- package/lib/recipe/info.js +14 -19
- package/lib/recipe/insertPage.js +15 -10
- package/lib/recipe/page.js +808 -11
- package/lib/recipe/parameters.js +2 -0
- package/lib/recipe/replaceText.js +5 -3
- package/lib/recipe/shapes.js +22 -1
- package/lib/recipe/text.helper.js +1 -1
- package/lib/recipe/text.js +106 -8
- package/lib/recipe/vector-polygon.js +11 -0
- package/lib/recipe/vector.helper.js +15 -3
- package/lib/recipe/vector.js +108 -10
- package/lib/recipe-info.js +10 -0
- package/muhammara.d.ts +472 -84
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,3 +3,7 @@
|
|
|
3
3
|
Internal shared JavaScript runtime for the MuhammaraJS native packages. Install
|
|
4
4
|
`@muhammara/native` for prebuilt binaries or `@muhammara/native-with-source` for
|
|
5
5
|
local source builds.
|
|
6
|
+
|
|
7
|
+
See the [Recipe API invariants](https://muhammarajs.readthedocs.io/latest/recipe/invariants/)
|
|
8
|
+
for behavior that is not obvious from the method signatures alone, notably the
|
|
9
|
+
direct-write behavior of file-backed `endPDF()`.
|
package/lib/Recipe.js
CHANGED
|
@@ -2,6 +2,7 @@ const muhammara = require("./muhammara");
|
|
|
2
2
|
const path = require("path");
|
|
3
3
|
const fs = require("fs");
|
|
4
4
|
const streams = require("memory-streams");
|
|
5
|
+
var { standardInfoKeys } = require("./recipe-info");
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* @name Recipe
|
|
@@ -15,7 +16,7 @@ const streams = require("memory-streams");
|
|
|
15
16
|
* @param {string} [options.author] - The author
|
|
16
17
|
* @param {string} [options.title] - The title
|
|
17
18
|
* @param {string} [options.subject] - The subject
|
|
18
|
-
* @param {string} [options.colorspace] - The default colorspace: rgb, cmyk, gray
|
|
19
|
+
* @param {string} [options.colorspace] - The default colorspace: rgb, cmyk, gray, separation
|
|
19
20
|
* @param {string[]} [options.keywords] - The array of keywords
|
|
20
21
|
* @param {string} [options.password] - permission password
|
|
21
22
|
* @param {string} [options.userPassword] - this 'view' password also enables encryption
|
|
@@ -117,7 +118,11 @@ class Recipe {
|
|
|
117
118
|
}
|
|
118
119
|
}
|
|
119
120
|
|
|
120
|
-
|
|
121
|
+
var info = {};
|
|
122
|
+
standardInfoKeys.forEach((key) => {
|
|
123
|
+
if (this.options[key] !== undefined) info[key] = this.options[key];
|
|
124
|
+
});
|
|
125
|
+
this.info(info);
|
|
121
126
|
}
|
|
122
127
|
|
|
123
128
|
_getVersion(version) {
|
|
@@ -211,6 +216,7 @@ class Recipe {
|
|
|
211
216
|
this._releaseReader();
|
|
212
217
|
this.pdfReader = pdfReader;
|
|
213
218
|
this.metadata = metadata;
|
|
219
|
+
this.sourcePageCount = pages;
|
|
214
220
|
isAdopted = true;
|
|
215
221
|
}
|
|
216
222
|
return metadata;
|
|
@@ -255,73 +261,128 @@ class Recipe {
|
|
|
255
261
|
}
|
|
256
262
|
|
|
257
263
|
/**
|
|
258
|
-
* End the pdfDoc
|
|
264
|
+
* End the pdfDoc. Finalization happens once; later calls do not rewrite the
|
|
265
|
+
* PDF and invoke the callback with the completed output when applicable.
|
|
266
|
+
* A failed finalization retires the Recipe and later calls rethrow the
|
|
267
|
+
* original error.
|
|
259
268
|
* @function
|
|
260
269
|
* @memberof Recipe
|
|
261
270
|
* @param {function} [callback] - The callback function.
|
|
262
271
|
* @returns {*} The callback result, if a callback is provided.
|
|
263
272
|
*/
|
|
264
273
|
endPDF(callback) {
|
|
265
|
-
this.
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
(this.
|
|
271
|
-
(this.
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
274
|
+
if (this.endError) {
|
|
275
|
+
throw this.endError;
|
|
276
|
+
}
|
|
277
|
+
if (this.ended) {
|
|
278
|
+
if (!callback) return;
|
|
279
|
+
if (!this.isBufferSrc) return callback();
|
|
280
|
+
return callback(this.output || this.outStream.toBuffer());
|
|
281
|
+
}
|
|
282
|
+
var deletingPages = Boolean(this.deletedPages?.size);
|
|
283
|
+
var deletionState = deletingPages
|
|
284
|
+
? {
|
|
285
|
+
metadata: this.metadata,
|
|
286
|
+
metadataValues: { ...this.metadata },
|
|
287
|
+
annotations: this.annotations,
|
|
288
|
+
annotationsToWrite: this.annotationsToWrite,
|
|
289
|
+
deletedPages: this.deletedPages,
|
|
290
|
+
}
|
|
291
|
+
: null;
|
|
292
|
+
if (deletingPages && this.page) {
|
|
293
|
+
throw new Error("Finish the current page before endPDF");
|
|
294
|
+
}
|
|
295
|
+
try {
|
|
296
|
+
this._deletePages();
|
|
297
|
+
this._writeInfo();
|
|
298
|
+
this.writer.end();
|
|
299
|
+
// This is a temporary work around for copying context will overwrite the current one
|
|
300
|
+
// write annotations at the end.
|
|
301
|
+
if (
|
|
302
|
+
(this.annotations && this.annotations.length > 0) ||
|
|
303
|
+
(this.annotationsToWrite && this.annotationsToWrite.length > 0)
|
|
304
|
+
) {
|
|
305
|
+
if (this.isBufferSrc) {
|
|
306
|
+
const oldStream = this.outStream;
|
|
307
|
+
this.outStream = new streams.WritableStream();
|
|
276
308
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
309
|
+
this.writer = muhammara.createWriterToModify(
|
|
310
|
+
new muhammara.PDFRStreamForBuffer(oldStream.toBuffer()),
|
|
311
|
+
new muhammara.PDFStreamForResponse(this.outStream),
|
|
312
|
+
Object.assign({}, this.encryptOptions, {
|
|
313
|
+
log: this.logFile,
|
|
314
|
+
}),
|
|
315
|
+
);
|
|
316
|
+
} else {
|
|
317
|
+
this.writer = muhammara.createWriterToModify(
|
|
318
|
+
this.output,
|
|
319
|
+
Object.assign({}, this.encryptOptions, {
|
|
320
|
+
modifiedFilePath: this.output,
|
|
321
|
+
log: this.logFile,
|
|
322
|
+
}),
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
this._writeAnnotations();
|
|
327
|
+
this._writeInfo();
|
|
328
|
+
this.writer.end();
|
|
292
329
|
}
|
|
293
330
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
331
|
+
// Every step above may still read the source; _insertPages() and _encrypt()
|
|
332
|
+
// below rename the output, which is the source itself when no separate
|
|
333
|
+
// output was given, and Windows refuses that while the reader holds it.
|
|
334
|
+
this._releaseReader();
|
|
298
335
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
336
|
+
if (this.needToInsertPages) {
|
|
337
|
+
if (this.isBufferSrc) {
|
|
338
|
+
// eslint-disable-next-line no-console
|
|
339
|
+
console.log(
|
|
340
|
+
"Feature: Inserting Pages is not supported in Buffer Mode yet.",
|
|
341
|
+
);
|
|
342
|
+
} else {
|
|
343
|
+
this._insertPages();
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
if (this.needToEncrypt) {
|
|
347
|
+
if (this.isBufferSrc) {
|
|
348
|
+
// eslint-disable-next-line no-console
|
|
349
|
+
console.log(
|
|
350
|
+
"Feature: Encryption is not supported in Buffer Mode yet.",
|
|
351
|
+
);
|
|
352
|
+
} else {
|
|
353
|
+
this._encrypt();
|
|
354
|
+
}
|
|
355
|
+
}
|
|
303
356
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
357
|
+
if (this.isBufferSrc && this.output) {
|
|
358
|
+
fs.writeFileSync(this.output, this.outStream.toBuffer());
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
this.ended = true;
|
|
362
|
+
} catch (error) {
|
|
363
|
+
if (deletingPages) {
|
|
364
|
+
Object.keys(deletionState.metadata).forEach(
|
|
365
|
+
(key) => delete deletionState.metadata[key],
|
|
309
366
|
);
|
|
310
|
-
|
|
311
|
-
this.
|
|
367
|
+
Object.assign(deletionState.metadata, deletionState.metadataValues);
|
|
368
|
+
this.metadata = deletionState.metadata;
|
|
369
|
+
this.annotations = deletionState.annotations;
|
|
370
|
+
this.annotationsToWrite = deletionState.annotationsToWrite;
|
|
371
|
+
this.deletedPages = deletionState.deletedPages;
|
|
312
372
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
this._encrypt();
|
|
373
|
+
this.endError = error;
|
|
374
|
+
this.ended = true;
|
|
375
|
+
try {
|
|
376
|
+
this.writer._abort();
|
|
377
|
+
} catch (_) {
|
|
378
|
+
// Preserve the finalization error if native cleanup also fails.
|
|
320
379
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
380
|
+
try {
|
|
381
|
+
this._releaseReader();
|
|
382
|
+
} catch (_) {
|
|
383
|
+
// Preserve the finalization error if releasing its separate reader fails.
|
|
384
|
+
}
|
|
385
|
+
throw error;
|
|
325
386
|
}
|
|
326
387
|
|
|
327
388
|
if (callback) {
|
|
@@ -347,7 +408,7 @@ class Recipe {
|
|
|
347
408
|
* @throws {string} If the callback function is unnamed when no key is provided.
|
|
348
409
|
* @throws {string} If the key conflicts with an existing Recipe prototype member.
|
|
349
410
|
* @throws {string} If the callback is not a function.
|
|
350
|
-
* @returns {
|
|
411
|
+
* @returns {Recipe} The recipe instance.
|
|
351
412
|
*/
|
|
352
413
|
register(key, callback) {
|
|
353
414
|
// Assume simply registering a function which will have an embedded name
|
|
@@ -368,11 +429,12 @@ class Recipe {
|
|
|
368
429
|
}
|
|
369
430
|
|
|
370
431
|
this.__proto__[key] = callback;
|
|
432
|
+
return this;
|
|
371
433
|
}
|
|
372
434
|
}
|
|
373
435
|
|
|
374
436
|
function loadPrototypes() {
|
|
375
|
-
const ignores = ["xObjectForm.js"];
|
|
437
|
+
const ignores = ["utils.js", "xObjectForm.js"];
|
|
376
438
|
fs.readdirSync(path.join(__dirname, "recipe"))
|
|
377
439
|
.filter((file) => {
|
|
378
440
|
return file[0] != "." && !ignores.includes(file);
|
package/lib/recipe/annotation.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* @param {string} [options.date] - The date.
|
|
12
12
|
* @param {boolean} [options.open=false] - Open the annotation by default?
|
|
13
13
|
* @param {boolean} [options.richText] - Display with rich text format, text will be transformed automatically, or you may pass in your own rich text starts with "<?xml..."
|
|
14
|
+
* @param {Array} [options.replies] - Array of annotation replies, each with text and optional title, date, subject, richText, and flag.
|
|
14
15
|
* @param {'invisible'|'hidden'|'print'|'nozoom'|'norotate'|'noview'|'readonly'|'locked'|'togglenoview'} [options.flag] - The flag property
|
|
15
16
|
* @returns {Recipe} The recipe instance.
|
|
16
17
|
*/
|
|
@@ -19,17 +20,52 @@ exports.comment = function comment(text = "", x, y, options = {}) {
|
|
|
19
20
|
subtype: "Text",
|
|
20
21
|
pageNumber: this.pageNumber,
|
|
21
22
|
args: { text, x, y, options: Object.assign({ icon: "Comment" }, options) },
|
|
23
|
+
replies: options.replies,
|
|
22
24
|
});
|
|
23
25
|
return this;
|
|
24
26
|
};
|
|
25
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Add a clickable URL link to the current page.
|
|
30
|
+
* @name link
|
|
31
|
+
* @function
|
|
32
|
+
* @memberof Recipe#
|
|
33
|
+
* @param {string} url - The URL to open.
|
|
34
|
+
* @param {number} x - The top-left x coordinate.
|
|
35
|
+
* @param {number} y - The top-left y coordinate.
|
|
36
|
+
* @param {number} width - The link width.
|
|
37
|
+
* @param {number} height - The link height.
|
|
38
|
+
* @returns {Recipe} The recipe instance.
|
|
39
|
+
*/
|
|
40
|
+
exports.link = function link(url, x, y, width, height) {
|
|
41
|
+
const { nx, ny } = this._calibrateCoordinate(x, y, 0, -height);
|
|
42
|
+
return linkPdf(this, url, nx, ny, width, height);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
function linkPdf(recipe, url, left, bottom, width, height) {
|
|
46
|
+
recipe.pauseContext();
|
|
47
|
+
try {
|
|
48
|
+
recipe.writer.attachURLLinktoCurrentPage(
|
|
49
|
+
url,
|
|
50
|
+
left,
|
|
51
|
+
bottom,
|
|
52
|
+
left + width,
|
|
53
|
+
bottom + height,
|
|
54
|
+
);
|
|
55
|
+
} finally {
|
|
56
|
+
recipe.resumeContext();
|
|
57
|
+
}
|
|
58
|
+
return recipe;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
Object.defineProperty(exports, "linkPdf", { value: linkPdf });
|
|
62
|
+
|
|
26
63
|
/**
|
|
27
64
|
* Create an annotation
|
|
28
65
|
* @name annot
|
|
29
66
|
* @function
|
|
30
67
|
* @memberof Recipe#
|
|
31
68
|
* @todo support for rich text RC
|
|
32
|
-
* @todo support for opacity CA
|
|
33
69
|
* @param {number} x - The coordinate x
|
|
34
70
|
* @param {number} y - The coordinate y
|
|
35
71
|
* @param {string} subtype - The markup annotation type 'Text'|'Link'|'FreeText'|'Line'|'Square'|'Circle'|'Polygon'|'PolyLine'|'Highlight'|'Underline'|'Squiggly'|'StrikeOut'|'Caret'|'Stamp'|'Ink'|'Popup'|'FileAttachment'|'Sound'|'Movie'|'Screen'|'Widget'|'PrinterMark'|'TrapNet'|'Watermark'|'3D'|'Redact'|'Projection'|'RichMedia'
|
|
@@ -47,6 +83,7 @@ exports.comment = function comment(text = "", x, y, options = {}) {
|
|
|
47
83
|
* @param {Array} [options.replies] - Array of annotation replies
|
|
48
84
|
* @param {number} [options.border] - The border width.
|
|
49
85
|
* @param {string|number[]} [options.color] - The annotation color.
|
|
86
|
+
* @param {number} [options.opacity=1] - Annotation opacity from 0 (transparent) to 1 (opaque).
|
|
50
87
|
* @param {boolean} [options.followOriginalPageRotation=false] - Preserve the original page rotation when positioning the annotation.
|
|
51
88
|
* @returns {Recipe} The recipe instance.
|
|
52
89
|
*/
|
|
@@ -146,6 +183,11 @@ exports._annot = function _annot(subtype, args = {}, pageNumber, ref) {
|
|
|
146
183
|
.writeKey("F")
|
|
147
184
|
.writeNumberValue(getFlagBitNumberByName(params.flag));
|
|
148
185
|
|
|
186
|
+
var opacity = (reply || options).opacity ?? 1;
|
|
187
|
+
if (opacity !== 1) {
|
|
188
|
+
this.dictionaryContext.writeKey("CA").writeNumberValue(opacity);
|
|
189
|
+
}
|
|
190
|
+
|
|
149
191
|
/**
|
|
150
192
|
* Rich Text Strings
|
|
151
193
|
* 12.7.3.4
|
|
@@ -252,8 +294,12 @@ exports._writeAnnotations = function _writeAnnotations() {
|
|
|
252
294
|
|
|
253
295
|
if (annot.replies) {
|
|
254
296
|
annot.replies.forEach((reply) => {
|
|
255
|
-
|
|
256
|
-
|
|
297
|
+
this._annot(
|
|
298
|
+
annot.subtype,
|
|
299
|
+
{ ...annot.args, reply },
|
|
300
|
+
annot.pageNumber,
|
|
301
|
+
ref,
|
|
302
|
+
);
|
|
257
303
|
});
|
|
258
304
|
}
|
|
259
305
|
});
|
|
@@ -289,7 +335,10 @@ exports._writeAnnotation = function _writeAnnotation(pageIndex) {
|
|
|
289
335
|
objectsContext.startArray();
|
|
290
336
|
if (pageObject["Annots"] && pageObject["Annots"].toJSArray) {
|
|
291
337
|
pageObject["Annots"].toJSArray().forEach((annot) => {
|
|
292
|
-
objectsContext.writeIndirectObjectReference(
|
|
338
|
+
objectsContext.writeIndirectObjectReference(
|
|
339
|
+
annot.getObjectID(),
|
|
340
|
+
annot.getVersion(),
|
|
341
|
+
);
|
|
293
342
|
});
|
|
294
343
|
}
|
|
295
344
|
this.annotations[pageIndex].forEach((item) => {
|
package/lib/recipe/appendPage.js
CHANGED
|
@@ -7,13 +7,18 @@ const utils = require("./utils");
|
|
|
7
7
|
* @function
|
|
8
8
|
* @memberof Recipe#
|
|
9
9
|
* @param {string} pdfSrc - The path for the other pdf.
|
|
10
|
-
* @param {number|number
|
|
10
|
+
* @param {number|Array.<number|Array.<number>>} [pages=[]] - A one-based page
|
|
11
|
+
* number or array of page numbers and inclusive ranges. Omitting it appends all
|
|
12
|
+
* pages; endpoints beyond the source are clamped to its final page.
|
|
11
13
|
* @returns {Recipe} The recipe instance.
|
|
14
|
+
* @throws {RangeError} If a selection is not a positive integer or a two-value
|
|
15
|
+
* range in ascending order.
|
|
12
16
|
*/
|
|
13
17
|
exports.appendPage = function appendPage(pdfSrc, pages = []) {
|
|
14
|
-
if (
|
|
15
|
-
|
|
18
|
+
if (this.deletedPages?.size) {
|
|
19
|
+
throw new Error("appendPage cannot be combined with deletePage");
|
|
16
20
|
}
|
|
21
|
+
if (!Array.isArray(pages)) pages = [pages];
|
|
17
22
|
// Using stream so it can be closed to release reader resource (Issue #61)
|
|
18
23
|
const instream = new muhammara.PDFRStreamForFile(pdfSrc);
|
|
19
24
|
let pageCount;
|
|
@@ -30,18 +35,25 @@ exports.appendPage = function appendPage(pdfSrc, pages = []) {
|
|
|
30
35
|
instream.close();
|
|
31
36
|
}
|
|
32
37
|
|
|
33
|
-
//
|
|
38
|
+
// Preserve the established upper-bound clamping after validating that the
|
|
39
|
+
// caller supplied meaningful one-based page numbers.
|
|
34
40
|
const transformPageNumber = (pageNum) => {
|
|
35
41
|
pageNum = pageNum > pageCount ? pageCount : pageNum;
|
|
36
|
-
pageNum = pageNum < 1 ? 1 : pageNum;
|
|
37
42
|
return pageNum - 1;
|
|
38
43
|
};
|
|
39
44
|
pages = pages.map((element) => {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
let range = Array.isArray(element) ? element : [element, element];
|
|
46
|
+
range = range.map(Number);
|
|
47
|
+
if (
|
|
48
|
+
range.length !== 2 ||
|
|
49
|
+
!range.every(
|
|
50
|
+
(pageNumber) => Number.isInteger(pageNumber) && pageNumber > 0,
|
|
51
|
+
) ||
|
|
52
|
+
range[1] < range[0]
|
|
53
|
+
) {
|
|
54
|
+
throw new RangeError("Page ranges use one-based inclusive page numbers");
|
|
44
55
|
}
|
|
56
|
+
return range.map(transformPageNumber);
|
|
45
57
|
});
|
|
46
58
|
if (pages.length > 0) {
|
|
47
59
|
utils.appendPDFPagesFromPDFWithAnnotations(this.writer, pdfSrc, {
|
|
@@ -50,5 +62,6 @@ exports.appendPage = function appendPage(pdfSrc, pages = []) {
|
|
|
50
62
|
} else {
|
|
51
63
|
utils.appendPDFPagesFromPDFWithAnnotations(this.writer, pdfSrc);
|
|
52
64
|
}
|
|
65
|
+
this.pagesAppended = true;
|
|
53
66
|
return this;
|
|
54
67
|
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rotate subsequent content around a point in Recipe coordinates.
|
|
3
|
+
* @name rotateContent
|
|
4
|
+
* @function
|
|
5
|
+
* @memberof Recipe#
|
|
6
|
+
* @param {number} degrees - Clockwise rotation in degrees.
|
|
7
|
+
* @param {number} [x=0] - Rotation origin x coordinate.
|
|
8
|
+
* @param {number} [y=0] - Rotation origin y coordinate.
|
|
9
|
+
* @returns {Recipe} The recipe instance.
|
|
10
|
+
*/
|
|
11
|
+
exports.rotateContent = function rotateContent(degrees, x = 0, y = 0) {
|
|
12
|
+
const radians = (degrees * Math.PI) / 180;
|
|
13
|
+
const cosine = Math.cos(radians);
|
|
14
|
+
const sine = Math.sin(radians);
|
|
15
|
+
const point = this._calibrateCoordinate(x, y);
|
|
16
|
+
this.pageContext.cm(1, 0, 0, 1, point.nx, point.ny);
|
|
17
|
+
this.pageContext.cm(cosine, sine, -sine, cosine, 0, 0);
|
|
18
|
+
this.pageContext.cm(1, 0, 0, 1, -point.nx, -point.ny);
|
|
19
|
+
return this;
|
|
20
|
+
};
|
package/lib/recipe/image.js
CHANGED
|
@@ -58,6 +58,10 @@ exports.image = function image(imgSrc, x, y, options = {}) {
|
|
|
58
58
|
ctx.gs(xObject.getGsName(gsId)).drawImage(0, 0, imgSrc, imgOptions);
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
+
if (options.link) {
|
|
62
|
+
this.link(options.link, x + offsetX, y - offsetY - height, width, height);
|
|
63
|
+
}
|
|
64
|
+
|
|
61
65
|
return this;
|
|
62
66
|
};
|
|
63
67
|
|
package/lib/recipe/info.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
const fs = require("fs");
|
|
2
2
|
const muhammara = require("../muhammara");
|
|
3
|
+
var { recipeInfoKeys, standardInfoKeys } = require("../recipe-info");
|
|
3
4
|
/**
|
|
4
5
|
* @name info
|
|
5
|
-
* @desc Add
|
|
6
|
+
* @desc Add standard and custom PDF information, or retrieve existing PDF information. Custom keys retain their spelling when written; array values are joined with a comma and space.
|
|
6
7
|
* @memberof Recipe#
|
|
7
8
|
* @function
|
|
8
9
|
* @param {Object} [options] - The options (when missing obtains existing PDF information)
|
|
@@ -21,6 +22,14 @@ exports.info = function info(options) {
|
|
|
21
22
|
} else {
|
|
22
23
|
this.toWriteInfo_ = this.toWriteInfo_ || {};
|
|
23
24
|
Object.assign(this.toWriteInfo_, options);
|
|
25
|
+
Object.entries(options).forEach(([key, value]) => {
|
|
26
|
+
if (!standardInfoKeys.includes(key)) {
|
|
27
|
+
this.custom(
|
|
28
|
+
key,
|
|
29
|
+
Array.isArray(value) ? value.join(", ") : String(value),
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
24
33
|
result = this;
|
|
25
34
|
}
|
|
26
35
|
|
|
@@ -105,24 +114,10 @@ exports._writeInfo = function _writeInfo() {
|
|
|
105
114
|
*/
|
|
106
115
|
|
|
107
116
|
const infoDictionary = this.writer.getDocumentContext().getInfoDictionary();
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
},
|
|
113
|
-
{
|
|
114
|
-
key: "title",
|
|
115
|
-
type: "string",
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
key: "subject",
|
|
119
|
-
type: "string",
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
key: "keywords",
|
|
123
|
-
type: "array",
|
|
124
|
-
},
|
|
125
|
-
];
|
|
117
|
+
var fields = standardInfoKeys.map((key) => ({
|
|
118
|
+
key,
|
|
119
|
+
type: key === recipeInfoKeys.keywords ? "array" : "string",
|
|
120
|
+
}));
|
|
126
121
|
// const ignores = [
|
|
127
122
|
// 'CreationDate', 'Creator', 'ModDate', 'Producer'
|
|
128
123
|
// ];
|
package/lib/recipe/insertPage.js
CHANGED
|
@@ -11,26 +11,31 @@ const utils = require("./utils");
|
|
|
11
11
|
* @param {number} srcPageNumber - The page number to be insterted from the other pdf.
|
|
12
12
|
* @returns {Recipe} The recipe instance.
|
|
13
13
|
* @throws {Error} If afterPageNumber is not a number.
|
|
14
|
+
* @throws {TypeError} If pdfSrc or srcPageNumber is missing.
|
|
14
15
|
*/
|
|
15
16
|
exports.insertPage = function insertPage(
|
|
16
17
|
afterPageNumber,
|
|
17
18
|
pdfSrc,
|
|
18
19
|
srcPageNumber,
|
|
19
20
|
) {
|
|
21
|
+
if (this.deletedPages?.size) {
|
|
22
|
+
throw new Error("insertPage cannot be combined with deletePage");
|
|
23
|
+
}
|
|
20
24
|
if (isNaN(afterPageNumber)) {
|
|
21
25
|
throw new Error("The afterPageNumber is inValid.");
|
|
22
26
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
this.insertInformation = this.insertInformation || {};
|
|
26
|
-
this.insertInformation[afterPageNumber] =
|
|
27
|
-
this.insertInformation[afterPageNumber] || [];
|
|
28
|
-
this.insertInformation[afterPageNumber].push({
|
|
29
|
-
afterPageNumber,
|
|
30
|
-
pdfSrc,
|
|
31
|
-
srcPageNumber,
|
|
32
|
-
});
|
|
27
|
+
if (!pdfSrc || !srcPageNumber) {
|
|
28
|
+
throw new TypeError("insertPage requires pdfSrc and srcPageNumber");
|
|
33
29
|
}
|
|
30
|
+
this.insertInformation = this.insertInformation || {};
|
|
31
|
+
this.insertInformation[afterPageNumber] =
|
|
32
|
+
this.insertInformation[afterPageNumber] || [];
|
|
33
|
+
this.insertInformation[afterPageNumber].push({
|
|
34
|
+
afterPageNumber,
|
|
35
|
+
pdfSrc,
|
|
36
|
+
srcPageNumber,
|
|
37
|
+
});
|
|
38
|
+
this.needToInsertPages = true;
|
|
34
39
|
return this;
|
|
35
40
|
};
|
|
36
41
|
|