@muhammara/native-core 7.0.0-beta.4 → 7.0.0-rc.2
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 +57 -7
- package/THIRD_PARTY_NOTICES.md +39 -0
- package/index.js +120 -1
- package/lib/PDFRStreamForBuffer.js +52 -7
- package/lib/PDFRStreamForFile.js +56 -7
- package/lib/PDFStreamForResponse.js +22 -5
- package/lib/PDFWStreamForBuffer.js +49 -10
- package/lib/PDFWStreamForFile.js +26 -5
- package/lib/Recipe.js +91 -38
- package/lib/content-stream.js +503 -0
- package/lib/css-colors.js +149 -0
- package/lib/font-text.js +1461 -0
- package/lib/glyph-list.js +1127 -0
- package/lib/recipe/annotation.js +464 -101
- package/lib/recipe/appendPage.js +2 -0
- package/lib/recipe/colors.js +132 -35
- package/lib/recipe/coordinate.js +82 -16
- package/lib/recipe/encrypt.js +32 -13
- package/lib/recipe/font.js +70 -25
- package/lib/recipe/graphics-state.js +3 -2
- package/lib/recipe/htmlToTextObjects.js +121 -24
- package/lib/recipe/image.js +385 -39
- package/lib/recipe/info.js +93 -22
- package/lib/recipe/insertPage.js +12 -3
- package/lib/recipe/overlay.js +4 -2
- package/lib/recipe/page-references.js +436 -0
- package/lib/recipe/page.js +363 -262
- package/lib/recipe/parameters.js +8 -1
- package/lib/recipe/replaceText.js +464 -30
- package/lib/recipe/shapes.js +418 -45
- package/lib/recipe/split.js +6 -2
- package/lib/recipe/table.js +41 -8
- package/lib/recipe/text.helper.js +172 -8
- package/lib/recipe/text.js +294 -56
- package/lib/recipe/utils.js +17 -6
- package/lib/recipe/vector-line.js +101 -39
- package/lib/recipe/vector-polygon.js +18 -6
- package/lib/recipe/vector.helper.js +156 -14
- package/lib/recipe/vector.js +156 -52
- package/lib/recipe/xObjectForm.js +73 -10
- package/lib/recipe-constants.js +373 -0
- package/lib/recipe-options.js +47 -0
- package/muhammara.d.ts +3501 -160
- package/package.json +2 -3
package/lib/recipe/appendPage.js
CHANGED
|
@@ -14,6 +14,8 @@ const utils = require("./utils");
|
|
|
14
14
|
* @returns {Recipe} The recipe instance.
|
|
15
15
|
* @throws {RangeError} If a selection is not a positive integer or a two-value
|
|
16
16
|
* range in ascending order.
|
|
17
|
+
* @throws {Error} If pages were deleted with deletePage() on this Recipe.
|
|
18
|
+
* @throws {Error} If the source PDF cannot be read.
|
|
17
19
|
*/
|
|
18
20
|
exports.appendPage = function appendPage(pdfSrc, pages = []) {
|
|
19
21
|
if (this.deletedPages?.size) {
|
package/lib/recipe/colors.js
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
const muhammara = require("../muhammara");
|
|
2
2
|
const fs = require("fs");
|
|
3
|
+
const { Colorspace, ChromaCommand } = require("../recipe-constants");
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Register a color under a name as an own property, so any name, including
|
|
7
|
+
* `__proto__`, is stored and later found by an own-property lookup.
|
|
8
|
+
* @private
|
|
9
|
+
* @param {Object} colors - The colors of one colorspace.
|
|
10
|
+
* @param {string} name - The color name.
|
|
11
|
+
* @param {string} value - The color value.
|
|
12
|
+
* @returns {void}
|
|
13
|
+
*/
|
|
14
|
+
function setKnownColor(colors, name, value) {
|
|
15
|
+
Object.defineProperty(colors, name, {
|
|
16
|
+
value,
|
|
17
|
+
writable: true,
|
|
18
|
+
enumerable: true,
|
|
19
|
+
configurable: true,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
3
22
|
|
|
4
23
|
this.knownColors = {
|
|
5
24
|
// knownColors.colorspace.colorName = value
|
|
@@ -36,7 +55,7 @@ this.knownColors = {
|
|
|
36
55
|
* PDF color space called 'separation' may also be used. The color value is then
|
|
37
56
|
* treated as the alternative color when the named 'separation' color is unavailable.
|
|
38
57
|
*
|
|
39
|
-
* If the 'name' parameter is '!load', the second parameter is the name of a JSON
|
|
58
|
+
* If the 'name' parameter is `Recipe.ChromaCommand.LOAD` ('!load'), the second parameter is the name of a JSON
|
|
40
59
|
* formatted file containing a formatted list of defined colors associated with the
|
|
41
60
|
* color spaces rgb, cmyk, gray, or separation (think PANTONE color definitions).
|
|
42
61
|
* This file will be merged with existing set of known colors. The color values
|
|
@@ -52,25 +71,28 @@ this.knownColors = {
|
|
|
52
71
|
* @name chroma
|
|
53
72
|
* @function
|
|
54
73
|
* @memberof Recipe#
|
|
55
|
-
* @param {string} name - the name to be associated to given color value, or
|
|
56
|
-
* @param {string|number[]} value - the color value (HexColor, DecimalColor, or PercentColor), or
|
|
57
|
-
* @param {
|
|
74
|
+
* @param {string} name - the name to be associated to given color value, or `Recipe.ChromaCommand.LOAD`
|
|
75
|
+
* @param {string|number[]} value - the color value (HexColor, DecimalColor, or PercentColor), or the path of the JSON file to load
|
|
76
|
+
* @param {Recipe.Colorspace|""} [colorspace=''] - One of the `Recipe.Colorspace`
|
|
77
|
+
* values; empty picks gray, rgb or cmyk from the value length.
|
|
58
78
|
* @returns {Recipe} The recipe instance.
|
|
79
|
+
* @throws {Error} If the file to load cannot be read or is not valid JSON.
|
|
59
80
|
* @throws {Error} If a loaded color definition has an unrecognized colorspace.
|
|
60
81
|
* @throws {Error} If a color value has an invalid size.
|
|
61
|
-
* @throws {
|
|
82
|
+
* @throws {TypeError} If the colorspace is unknown.
|
|
62
83
|
*/
|
|
63
84
|
exports.chroma = function chroma(name, value, colorspace = "") {
|
|
64
85
|
if (name) {
|
|
65
|
-
if (name ===
|
|
86
|
+
if (name === ChromaCommand.LOAD) {
|
|
66
87
|
let newColors = JSON.parse(fs.readFileSync(value));
|
|
67
88
|
// Add new colors to existing colorspaces
|
|
68
|
-
for (
|
|
69
|
-
if (
|
|
70
|
-
Object.assign(this.knownColors[cs], newColors[cs]);
|
|
71
|
-
} else {
|
|
89
|
+
for (const cs of Object.keys(newColors)) {
|
|
90
|
+
if (!Object.values(Colorspace).includes(cs)) {
|
|
72
91
|
throw new Error(`Unrecognized colorspace: ${cs}`);
|
|
73
92
|
}
|
|
93
|
+
for (const [colorName, color] of Object.entries(newColors[cs])) {
|
|
94
|
+
setKnownColor(this.knownColors[cs], colorName, color);
|
|
95
|
+
}
|
|
74
96
|
}
|
|
75
97
|
} else {
|
|
76
98
|
if (Array.isArray(value)) {
|
|
@@ -92,14 +114,18 @@ exports.chroma = function chroma(name, value, colorspace = "") {
|
|
|
92
114
|
// Determine colorspace by length of given input
|
|
93
115
|
// value when colorspace not provided in call.
|
|
94
116
|
if (colorspace === "") {
|
|
95
|
-
const colorSpaces = {
|
|
117
|
+
const colorSpaces = {
|
|
118
|
+
2: Colorspace.GRAY,
|
|
119
|
+
6: Colorspace.RGB,
|
|
120
|
+
8: Colorspace.CMYK,
|
|
121
|
+
};
|
|
96
122
|
colorspace = colorSpaces[`${value.length}`];
|
|
97
|
-
} else if (!
|
|
98
|
-
throw new
|
|
123
|
+
} else if (!Object.values(Colorspace).includes(colorspace)) {
|
|
124
|
+
throw new TypeError(`Unknown colorspace: ${colorspace}`);
|
|
99
125
|
}
|
|
100
126
|
|
|
101
127
|
if (colorspace) {
|
|
102
|
-
this.knownColors[colorspace]
|
|
128
|
+
setKnownColor(this.knownColors[colorspace], name, value);
|
|
103
129
|
}
|
|
104
130
|
}
|
|
105
131
|
}
|
|
@@ -107,12 +133,23 @@ exports.chroma = function chroma(name, value, colorspace = "") {
|
|
|
107
133
|
return this;
|
|
108
134
|
};
|
|
109
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Write a Separation color space for a named color once per Recipe and
|
|
138
|
+
* return its object ID.
|
|
139
|
+
* @private
|
|
140
|
+
* @param {Recipe} self - The recipe instance that owns the cache.
|
|
141
|
+
* @param {string} colorName - The separation color name.
|
|
142
|
+
* @param {number[]} color - The alternate device color components, 0 to 1.
|
|
143
|
+
* @returns {number} The object ID of the Separation color space.
|
|
144
|
+
*/
|
|
110
145
|
function createColorSpaces(self, colorName, color) {
|
|
111
146
|
const deviceCS = { 1: "DeviceGray", 3: "DeviceRGB", 4: "DeviceCMYK" };
|
|
112
147
|
const altCS = deviceCS[`${color.length}`];
|
|
113
|
-
this
|
|
114
|
-
|
|
115
|
-
|
|
148
|
+
// Cache per Recipe: the IDs belong to this Recipe's PDF writer. A Map keyed
|
|
149
|
+
// like Wasm's keeps ink names such as "__proto__" from hitting inherited keys.
|
|
150
|
+
self.colorSpaces = self.colorSpaces || new Map();
|
|
151
|
+
const key = `${altCS}:${colorName}`;
|
|
152
|
+
let colorSpaceID = self.colorSpaces.get(key);
|
|
116
153
|
|
|
117
154
|
if (!colorSpaceID) {
|
|
118
155
|
const transformFunction = tintTransform(self, color);
|
|
@@ -128,12 +165,20 @@ function createColorSpaces(self, colorName, color) {
|
|
|
128
165
|
.endArray(muhammara.eTokenSeparatorEndLine)
|
|
129
166
|
.endIndirectObject();
|
|
130
167
|
self.resumeContext();
|
|
131
|
-
|
|
168
|
+
self.colorSpaces.set(key, colorSpaceID);
|
|
132
169
|
}
|
|
133
170
|
|
|
134
171
|
return colorSpaceID;
|
|
135
172
|
}
|
|
136
173
|
|
|
174
|
+
/**
|
|
175
|
+
* Write the type 2 tint transform function that maps a separation tint to
|
|
176
|
+
* its alternate device color.
|
|
177
|
+
* @private
|
|
178
|
+
* @param {Recipe} self - The recipe instance.
|
|
179
|
+
* @param {number[]} color - The alternate device color components, 0 to 1.
|
|
180
|
+
* @returns {number} The object ID of the function.
|
|
181
|
+
*/
|
|
137
182
|
function tintTransform(self, color) {
|
|
138
183
|
const rangeCount = color.length;
|
|
139
184
|
self.pauseContext();
|
|
@@ -170,6 +215,14 @@ function tintTransform(self, color) {
|
|
|
170
215
|
return tintFuncID;
|
|
171
216
|
}
|
|
172
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Write the stroke and fill opacity graphics states for a value once per
|
|
220
|
+
* Recipe.
|
|
221
|
+
* @private
|
|
222
|
+
* @param {number} value - The opacity, from 0 to 1.
|
|
223
|
+
* @returns {{stroke: number, fill: number}} The object IDs of the stroking
|
|
224
|
+
* (CA) and non-stroking (ca) ExtGState dictionaries.
|
|
225
|
+
*/
|
|
173
226
|
exports._createExtGStates = function _createExtGStates(value) {
|
|
174
227
|
this.extGStates = this.extGStates || {};
|
|
175
228
|
if (this.extGStates[value]) {
|
|
@@ -181,7 +234,7 @@ exports._createExtGStates = function _createExtGStates(value) {
|
|
|
181
234
|
const objCxt = this.writer.getObjectsContext();
|
|
182
235
|
const gsId = objCxt.startNewIndirectObject();
|
|
183
236
|
const dict = objCxt.startDictionary();
|
|
184
|
-
dict.writeKey("
|
|
237
|
+
dict.writeKey("Type");
|
|
185
238
|
dict.writeNameValue("ExtGState");
|
|
186
239
|
dict.writeKey(key);
|
|
187
240
|
objCxt.writeNumber(value);
|
|
@@ -198,16 +251,22 @@ exports._createExtGStates = function _createExtGStates(value) {
|
|
|
198
251
|
return this.extGStates[value];
|
|
199
252
|
};
|
|
200
253
|
|
|
201
|
-
|
|
254
|
+
/**
|
|
255
|
+
* The hex color used when a color value is missing or invalid.
|
|
256
|
+
* @private
|
|
257
|
+
* @param {Recipe.Colorspace} [colorspace=Recipe.Colorspace.RGB] - The colorspace.
|
|
258
|
+
* @returns {string} The default color as a hex string without '#'.
|
|
259
|
+
*/
|
|
260
|
+
function _defaultColor(colorspace = Colorspace.RGB) {
|
|
202
261
|
let defaultColor;
|
|
203
262
|
switch (colorspace) {
|
|
204
|
-
case
|
|
263
|
+
case Colorspace.CMYK:
|
|
205
264
|
defaultColor = "FF000000";
|
|
206
265
|
break;
|
|
207
|
-
case
|
|
266
|
+
case Colorspace.GRAY:
|
|
208
267
|
defaultColor = "00";
|
|
209
268
|
break;
|
|
210
|
-
case
|
|
269
|
+
case Colorspace.RGB:
|
|
211
270
|
default:
|
|
212
271
|
defaultColor = "1777d1";
|
|
213
272
|
break;
|
|
@@ -232,7 +291,7 @@ function _defaultColor(colorspace = "rgb") {
|
|
|
232
291
|
* @private
|
|
233
292
|
* @param {Recipe} self The recipe instance.
|
|
234
293
|
* @param {string} code the color encoding as HexColor
|
|
235
|
-
* @param {
|
|
294
|
+
* @param {Recipe.Colorspace} colorspace the name of the colorspace of given color code
|
|
236
295
|
* @param {string} colorName the name to be associated with given color code
|
|
237
296
|
* @returns {Object} The color model.
|
|
238
297
|
*/
|
|
@@ -246,17 +305,17 @@ function toColorModel(self, code, colorspace, colorName) {
|
|
|
246
305
|
|
|
247
306
|
switch (color.length) {
|
|
248
307
|
default:
|
|
249
|
-
color = hexToArray(_defaultColor(
|
|
308
|
+
color = hexToArray(_defaultColor(Colorspace.RGB));
|
|
250
309
|
// purposely want to fall through to 'rgb' case below.
|
|
251
310
|
case 3:
|
|
252
|
-
cmodel.colorspace =
|
|
311
|
+
cmodel.colorspace = Colorspace.RGB;
|
|
253
312
|
cmodel.r = color[0];
|
|
254
313
|
cmodel.g = color[1];
|
|
255
314
|
cmodel.b = color[2];
|
|
256
315
|
break;
|
|
257
316
|
|
|
258
317
|
case 4:
|
|
259
|
-
cmodel.colorspace =
|
|
318
|
+
cmodel.colorspace = Colorspace.CMYK;
|
|
260
319
|
cmodel.c = color[0];
|
|
261
320
|
cmodel.m = color[1];
|
|
262
321
|
cmodel.y = color[2];
|
|
@@ -264,7 +323,7 @@ function toColorModel(self, code, colorspace, colorName) {
|
|
|
264
323
|
break;
|
|
265
324
|
|
|
266
325
|
case 1:
|
|
267
|
-
cmodel.colorspace =
|
|
326
|
+
cmodel.colorspace = Colorspace.GRAY;
|
|
268
327
|
cmodel.gray = color[0];
|
|
269
328
|
break;
|
|
270
329
|
}
|
|
@@ -273,7 +332,7 @@ function toColorModel(self, code, colorspace, colorName) {
|
|
|
273
332
|
// use the colorspace from above as the
|
|
274
333
|
// alternative color transformation when
|
|
275
334
|
// the named color is unavailable.
|
|
276
|
-
if (colorspace ===
|
|
335
|
+
if (colorspace === Colorspace.SEPARATION && colorName !== "") {
|
|
277
336
|
cmodel.colorspace = colorspace;
|
|
278
337
|
cmodel.colorName = colorName;
|
|
279
338
|
cmodel.colorspaceId = createColorSpaces(self, colorName, color);
|
|
@@ -300,11 +359,23 @@ function percentToHex(code) {
|
|
|
300
359
|
* @param {string|number[]} [code=''] Color specification in the form of HexColor (string beginning with '#'),
|
|
301
360
|
* DecimalColor (1, 3, or 4 element array with values between 0-255),
|
|
302
361
|
* PercentColor (string, begins with '%' followed by values separated
|
|
303
|
-
* by commas with values between 0-100)
|
|
362
|
+
* by commas with values between 0-100), or a color name known to
|
|
363
|
+
* `chroma()`
|
|
364
|
+
* @param {Object} [opt] - The options.
|
|
365
|
+
* @param {Recipe.Colorspace} [opt.colorspace] - The colorspace; when omitted it
|
|
366
|
+
* is picked from the value length.
|
|
367
|
+
* @param {boolean} [opt.wantColorModel=false] - Return a color model instead of a number.
|
|
368
|
+
* @param {string} [opt.colorName] - The name to record the color under.
|
|
369
|
+
* @returns {number|Object} The color as a number, or the color model when
|
|
370
|
+
* `opt.wantColorModel` is set. Invalid values fall back to the default color.
|
|
371
|
+
* @throws {TypeError} If `opt.colorspace` is not a `Recipe.Colorspace` value.
|
|
304
372
|
*/
|
|
305
373
|
exports._transformColor = function _transformColor(code = "", opt = {}) {
|
|
306
374
|
this.knownColors = this.knownColors || {};
|
|
307
|
-
|
|
375
|
+
if (opt.colorspace && !Object.values(Colorspace).includes(opt.colorspace)) {
|
|
376
|
+
throw new TypeError(`Unknown colorspace: ${opt.colorspace}`);
|
|
377
|
+
}
|
|
378
|
+
let colorspace = opt.colorspace || Colorspace.RGB;
|
|
308
379
|
let wantColorModel = opt.wantColorModel || false;
|
|
309
380
|
let colorName = opt.colorName || "";
|
|
310
381
|
let defaultColor = _defaultColor(colorspace);
|
|
@@ -317,7 +388,10 @@ exports._transformColor = function _transformColor(code = "", opt = {}) {
|
|
|
317
388
|
} else if (code.startsWith("%")) {
|
|
318
389
|
code = percentToHex(code.replace("%", ""));
|
|
319
390
|
} else if (code !== "") {
|
|
320
|
-
|
|
391
|
+
// A color name counts only when registered, not an inherited key such as
|
|
392
|
+
// "constructor".
|
|
393
|
+
const colors = this.knownColors[colorspace];
|
|
394
|
+
let color = Object.hasOwn(colors, code) ? colors[code] : undefined;
|
|
321
395
|
if (!color) {
|
|
322
396
|
color = "";
|
|
323
397
|
code = defaultColor;
|
|
@@ -339,7 +413,10 @@ exports._transformColor = function _transformColor(code = "", opt = {}) {
|
|
|
339
413
|
// When colorspace is not explicitly given,
|
|
340
414
|
// use size of value to determine colorspace.
|
|
341
415
|
if (!opt.colorspace) {
|
|
342
|
-
colorspace =
|
|
416
|
+
colorspace =
|
|
417
|
+
{ 2: Colorspace.GRAY, 6: Colorspace.RGB, 8: Colorspace.CMYK }[
|
|
418
|
+
`${code.length}`
|
|
419
|
+
] || Colorspace.RGB;
|
|
343
420
|
defaultColor = _defaultColor(colorspace);
|
|
344
421
|
}
|
|
345
422
|
|
|
@@ -347,7 +424,7 @@ exports._transformColor = function _transformColor(code = "", opt = {}) {
|
|
|
347
424
|
// when colorspace is given and given color code does not have appropriate length, or
|
|
348
425
|
// when colorspace is missing, verify allowable hex value sizes for rgb, cmyk, or gray.
|
|
349
426
|
if (
|
|
350
|
-
([
|
|
427
|
+
([Colorspace.RGB, Colorspace.CMYK, Colorspace.GRAY].includes(colorspace) &&
|
|
351
428
|
code.length != defaultColor.length) ||
|
|
352
429
|
![2, 6, 8].includes(code.toString().length)
|
|
353
430
|
) {
|
|
@@ -356,7 +433,7 @@ exports._transformColor = function _transformColor(code = "", opt = {}) {
|
|
|
356
433
|
|
|
357
434
|
if (wantColorModel) {
|
|
358
435
|
transformation = toColorModel(this, code, colorspace, colorName);
|
|
359
|
-
if (colorName && !this.knownColors[colorspace]
|
|
436
|
+
if (colorName && !Object.hasOwn(this.knownColors[colorspace], colorName)) {
|
|
360
437
|
this.chroma(colorName, code, colorspace);
|
|
361
438
|
}
|
|
362
439
|
} else {
|
|
@@ -366,6 +443,12 @@ exports._transformColor = function _transformColor(code = "", opt = {}) {
|
|
|
366
443
|
return transformation;
|
|
367
444
|
};
|
|
368
445
|
|
|
446
|
+
/**
|
|
447
|
+
* Convert DecimalColor components (0 to 255) to a hex string.
|
|
448
|
+
* @private
|
|
449
|
+
* @param {number[]} [color] - The color components.
|
|
450
|
+
* @returns {string} The hex string without '#'.
|
|
451
|
+
*/
|
|
369
452
|
function arrayToHex(color = []) {
|
|
370
453
|
let code = "";
|
|
371
454
|
color.forEach((item) => {
|
|
@@ -376,6 +459,13 @@ function arrayToHex(color = []) {
|
|
|
376
459
|
return code;
|
|
377
460
|
}
|
|
378
461
|
|
|
462
|
+
/**
|
|
463
|
+
* Convert a 2, 6 or 8 digit hex color to components between 0 and 1.
|
|
464
|
+
* @private
|
|
465
|
+
* @param {string} hex - The hex color, with or without '#'.
|
|
466
|
+
* @returns {number[]} One gray, three RGB or four CMYK components.
|
|
467
|
+
* @throws {TypeError} If the value is not a 2, 4, 6 or 8 digit hex string.
|
|
468
|
+
*/
|
|
379
469
|
function hexToArray(hex) {
|
|
380
470
|
const result =
|
|
381
471
|
/^#?([a-f\d]{2})([a-f\d]{2})?([a-f\d]{2})?([a-f\d]{2})?$/i.exec(hex);
|
|
@@ -387,6 +477,13 @@ function hexToArray(hex) {
|
|
|
387
477
|
}, []);
|
|
388
478
|
}
|
|
389
479
|
|
|
480
|
+
/**
|
|
481
|
+
* Split an RGB color number into its 0 to 255 components.
|
|
482
|
+
* @private
|
|
483
|
+
* @param {number} bigint - The color as 0xRRGGBB.
|
|
484
|
+
* @returns {{r: number, g: number, b: number}} The components; black for 0 or
|
|
485
|
+
* a missing value.
|
|
486
|
+
*/
|
|
390
487
|
exports._colorNumberToRGB = (bigint) => {
|
|
391
488
|
if (!bigint) {
|
|
392
489
|
return {
|
package/lib/recipe/coordinate.js
CHANGED
|
@@ -1,20 +1,52 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
const { Coordinate } = require("../recipe-constants");
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Look up the metadata of a page, or of the active page.
|
|
5
|
+
* @private
|
|
6
|
+
* @param {Recipe} recipe - The recipe instance.
|
|
7
|
+
* @param {number} [pageNumber] - The one-based page number; defaults to the active page.
|
|
8
|
+
* @returns {Object} The page metadata.
|
|
9
|
+
* @throws {TypeError} If there is no such page, for example before createPage().
|
|
10
|
+
*/
|
|
11
|
+
function pageMetadata(recipe, pageNumber) {
|
|
12
|
+
const metadata = recipe.metadata[pageNumber || recipe.pageNumber];
|
|
13
|
+
if (!metadata) {
|
|
14
|
+
throw new TypeError(
|
|
15
|
+
"No page is active; call createPage() or editPage() first",
|
|
16
|
+
);
|
|
13
17
|
}
|
|
18
|
+
return metadata;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Replace `Recipe.Coordinate.CENTER` coordinates with the page center.
|
|
23
|
+
* @private
|
|
24
|
+
* @param {number|"center"} x - The x coordinate.
|
|
25
|
+
* @param {number|"center"} y - The y coordinate.
|
|
26
|
+
* @param {number} [pageNumber] - The one-based page number; defaults to the active page.
|
|
27
|
+
* @returns {number[]} The numeric [x, y].
|
|
28
|
+
* @throws {TypeError} If no page is active.
|
|
29
|
+
*/
|
|
30
|
+
exports._centrify = function _centrify(x, y, pageNumber) {
|
|
31
|
+
const { width, height } = pageMetadata(this, pageNumber);
|
|
32
|
+
if (x === Coordinate.CENTER) x = width / 2;
|
|
33
|
+
if (y === Coordinate.CENTER) y = height / 2;
|
|
14
34
|
|
|
15
35
|
return [x, y];
|
|
16
36
|
};
|
|
17
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Convert Recipe coordinates (top-left origin, y down) to PDF coordinates
|
|
40
|
+
* (bottom-left origin of the media box, y up).
|
|
41
|
+
* @private
|
|
42
|
+
* @param {number|"center"} x - The Recipe x coordinate.
|
|
43
|
+
* @param {number|"center"} y - The Recipe y coordinate.
|
|
44
|
+
* @param {number} [offsetX=0] - Added to the PDF x coordinate.
|
|
45
|
+
* @param {number} [offsetY=0] - Added to the PDF y coordinate.
|
|
46
|
+
* @param {number} [pageNumber] - The one-based page number; defaults to the active page.
|
|
47
|
+
* @returns {{nx: number, ny: number}} The PDF coordinates.
|
|
48
|
+
* @throws {TypeError} If no page is active.
|
|
49
|
+
*/
|
|
18
50
|
exports._calibrateCoordinate = function _calibrateCoordinate(
|
|
19
51
|
x,
|
|
20
52
|
y,
|
|
@@ -23,7 +55,7 @@ exports._calibrateCoordinate = function _calibrateCoordinate(
|
|
|
23
55
|
pageNumber,
|
|
24
56
|
) {
|
|
25
57
|
pageNumber = pageNumber || this.pageNumber;
|
|
26
|
-
const { height, mediaBox } = this
|
|
58
|
+
const { height, mediaBox } = pageMetadata(this, pageNumber);
|
|
27
59
|
const startX = mediaBox[0];
|
|
28
60
|
const startY = mediaBox[1];
|
|
29
61
|
[x, y] = this._centrify(x, y, pageNumber);
|
|
@@ -36,6 +68,18 @@ exports._calibrateCoordinate = function _calibrateCoordinate(
|
|
|
36
68
|
};
|
|
37
69
|
};
|
|
38
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Convert Recipe coordinates to PDF coordinates for an annotation, undoing
|
|
73
|
+
* the page rotation so the annotation lands where it appears on screen.
|
|
74
|
+
* @private
|
|
75
|
+
* @param {number|"center"} x - The Recipe x coordinate.
|
|
76
|
+
* @param {number|"center"} y - The Recipe y coordinate.
|
|
77
|
+
* @param {number} [offsetX=0] - Added to the PDF x coordinate.
|
|
78
|
+
* @param {number} [offsetY=0] - Added to the PDF y coordinate.
|
|
79
|
+
* @param {number} pageNumber - The one-based page number.
|
|
80
|
+
* @returns {{nx: number, ny: number}} The PDF coordinates.
|
|
81
|
+
* @throws {TypeError} If the page is unknown.
|
|
82
|
+
*/
|
|
39
83
|
exports._calibrateCoordinateForAnnots = function _calibrateCoordinateForAnnots(
|
|
40
84
|
x,
|
|
41
85
|
y,
|
|
@@ -50,7 +94,7 @@ exports._calibrateCoordinateForAnnots = function _calibrateCoordinateForAnnots(
|
|
|
50
94
|
offsetY,
|
|
51
95
|
pageNumber,
|
|
52
96
|
);
|
|
53
|
-
const { width, height, rotate, mediaBox } = this
|
|
97
|
+
const { width, height, rotate, mediaBox } = pageMetadata(this, pageNumber);
|
|
54
98
|
const startX = mediaBox[0];
|
|
55
99
|
const startY = mediaBox[1];
|
|
56
100
|
let rotateOffsetX = 0,
|
|
@@ -86,6 +130,17 @@ exports._calibrateCoordinateForAnnots = function _calibrateCoordinateForAnnots(
|
|
|
86
130
|
};
|
|
87
131
|
};
|
|
88
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Convert PDF coordinates back to Recipe coordinates.
|
|
135
|
+
* @private
|
|
136
|
+
* @param {number} x - The PDF x coordinate.
|
|
137
|
+
* @param {number} y - The PDF y coordinate.
|
|
138
|
+
* @param {number} [offsetX=0] - Subtracted from x.
|
|
139
|
+
* @param {number} [offsetY=0] - Subtracted from the Recipe y coordinate.
|
|
140
|
+
* @param {number} [pageNumber] - The one-based page number; defaults to the active page.
|
|
141
|
+
* @returns {{ox: number, oy: number}} The Recipe coordinates.
|
|
142
|
+
* @throws {TypeError} If no page is active.
|
|
143
|
+
*/
|
|
89
144
|
exports._reverseCoordinate = function _reverseCoordinate(
|
|
90
145
|
x,
|
|
91
146
|
y,
|
|
@@ -93,8 +148,7 @@ exports._reverseCoordinate = function _reverseCoordinate(
|
|
|
93
148
|
offsetY = 0,
|
|
94
149
|
pageNumber,
|
|
95
150
|
) {
|
|
96
|
-
|
|
97
|
-
const { height } = this.metadata[pageNumber];
|
|
151
|
+
const { height } = pageMetadata(this, pageNumber);
|
|
98
152
|
const ox = x - offsetX;
|
|
99
153
|
const oy = height - y - offsetY;
|
|
100
154
|
return {
|
|
@@ -103,6 +157,18 @@ exports._reverseCoordinate = function _reverseCoordinate(
|
|
|
103
157
|
};
|
|
104
158
|
};
|
|
105
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Rotate a point around a center and translate it.
|
|
162
|
+
* @private
|
|
163
|
+
* @param {number} cx - The center x.
|
|
164
|
+
* @param {number} cy - The center y.
|
|
165
|
+
* @param {number} x - The point x.
|
|
166
|
+
* @param {number} y - The point y.
|
|
167
|
+
* @param {number} angle - The rotation in degrees, clockwise in PDF space.
|
|
168
|
+
* @param {number} [offsetX=0] - Added to the rotated x.
|
|
169
|
+
* @param {number} [offsetY=0] - Added to the rotated y.
|
|
170
|
+
* @returns {{nx: number, ny: number}} The rotated point.
|
|
171
|
+
*/
|
|
106
172
|
function rotateCoord(cx, cy, x, y, angle, offsetX = 0, offsetY = 0) {
|
|
107
173
|
const radians = (Math.PI / 180) * angle,
|
|
108
174
|
cos = Math.cos(radians),
|
package/lib/recipe/encrypt.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const muhammara = require("../muhammara");
|
|
2
2
|
const fs = require("fs");
|
|
3
|
+
const { Permission } = require("../recipe-constants");
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Encryption user access permissions
|
|
@@ -10,23 +11,25 @@ const fs = require("fs");
|
|
|
10
11
|
* @name permission
|
|
11
12
|
* @function
|
|
12
13
|
* @memberof Recipe#
|
|
13
|
-
* @param {string} [flags='print']
|
|
14
|
-
*
|
|
14
|
+
* @param {string} [flags='print'] One or more `Recipe.Permission` values
|
|
15
|
+
* (print, modify, copy, edit, fillform, extract, assemble, printbest),
|
|
16
|
+
* separated by commas, for example `[Permission.PRINT, Permission.COPY].join()`.
|
|
15
17
|
* @returns {number} The numeric user protection flag.
|
|
18
|
+
* @throws {Error} If a name is not a `Recipe.Permission` value.
|
|
16
19
|
*/
|
|
17
|
-
exports.permission = function permission(flags =
|
|
20
|
+
exports.permission = function permission(flags = Permission.PRINT) {
|
|
18
21
|
// https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/PDF32000_2008.pdf
|
|
19
22
|
|
|
20
23
|
const userAccessPermissions = {
|
|
21
24
|
// see table on page 61 of above document
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
[Permission.PRINT]: 1 << 2, // allow printing
|
|
26
|
+
[Permission.MODIFY]: 1 << 3, // allow template creation, signing, filling form fields
|
|
27
|
+
[Permission.COPY]: 1 << 4, // allow content copying and copying for accessibility
|
|
28
|
+
[Permission.EDIT]: 1 << 5, // allow commenting
|
|
29
|
+
[Permission.FILL_FORM]: 1 << 8, // allow filling of form fields
|
|
30
|
+
[Permission.EXTRACT]: 1 << 9, // allow content copying for accessibility
|
|
31
|
+
[Permission.ASSEMBLE]: 1 << 10, // unused
|
|
32
|
+
[Permission.PRINT_BEST]: 1 << 11, // allow high resolution printing when 'print' is allowed
|
|
30
33
|
};
|
|
31
34
|
|
|
32
35
|
const perms = flags.split(",").map((x) => {
|
|
@@ -43,6 +46,15 @@ exports.permission = function permission(flags = "print") {
|
|
|
43
46
|
return access;
|
|
44
47
|
};
|
|
45
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Build writer encryption options from Recipe options. `password` and
|
|
51
|
+
* `ownerPassword` are aliases; when only an owner password is given the user
|
|
52
|
+
* password is empty and the default permission applies.
|
|
53
|
+
* @private
|
|
54
|
+
* @param {Object} options - The Recipe or encrypt() options.
|
|
55
|
+
* @param {boolean} [addPermissions=true] - Copy `userProtectionFlag` into the result.
|
|
56
|
+
* @returns {Object} The writer encryption options; empty when no password is set.
|
|
57
|
+
*/
|
|
46
58
|
exports._getEncryptOptions = function _getEncryptOptions(
|
|
47
59
|
options,
|
|
48
60
|
addPermissions = true,
|
|
@@ -89,8 +101,9 @@ exports._getEncryptOptions = function _getEncryptOptions(
|
|
|
89
101
|
* @param {string} [options.password] - The permission password.
|
|
90
102
|
* @param {string} [options.ownerPassword] - The password for editing.
|
|
91
103
|
* @param {string} [options.userPassword] - The password for viewing & encryption.
|
|
92
|
-
* @param {number} [options.userProtectionFlag] - The flag for the security level
|
|
93
|
-
* @returns {Recipe} The recipe instance.
|
|
104
|
+
* @param {number} [options.userProtectionFlag] - The flag for the security level, see `permission()`.
|
|
105
|
+
* @returns {Recipe} The recipe instance. The file is encrypted by `endPDF()`;
|
|
106
|
+
* Buffer sources are not encrypted.
|
|
94
107
|
*/
|
|
95
108
|
exports.encrypt = function encrypt(options = {}) {
|
|
96
109
|
this.needToEncrypt = true;
|
|
@@ -100,6 +113,12 @@ exports.encrypt = function encrypt(options = {}) {
|
|
|
100
113
|
};
|
|
101
114
|
|
|
102
115
|
// http://pdfhummus.com/post/147451287581/hummus-1058-and-pdf-writer-updates-encryption
|
|
116
|
+
/**
|
|
117
|
+
* Re-encrypt the finished output file with the encrypt() options.
|
|
118
|
+
* @private
|
|
119
|
+
* @returns {void}
|
|
120
|
+
* @throws {Error} If the output cannot be renamed, re-encrypted or removed.
|
|
121
|
+
*/
|
|
103
122
|
exports._encrypt = function _encrypt() {
|
|
104
123
|
if (!this.encryption_) {
|
|
105
124
|
return;
|