@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/image.js
CHANGED
|
@@ -1,28 +1,73 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const muhammara = require("../muhammara");
|
|
3
|
+
const { HorizontalAlign, VerticalAlign } = require("../recipe-constants");
|
|
4
|
+
|
|
1
5
|
/**
|
|
2
6
|
* Place images to pdf
|
|
3
7
|
* @name image
|
|
4
8
|
* @function
|
|
5
9
|
* @memberof Recipe#
|
|
6
|
-
* @param {string} imgSrc - The path for the image. [JPEG, PNG, TIFF]
|
|
7
|
-
* @param {number} x - The coordinate x
|
|
8
|
-
* @param {number} y - The coordinate y
|
|
10
|
+
* @param {string} imgSrc - The path for the image. [JPEG, PNG, TIFF, PDF]
|
|
11
|
+
* @param {number|"center"} x - The coordinate x of the top-left corner
|
|
12
|
+
* @param {number|"center"} y - The coordinate y of the top-left corner
|
|
9
13
|
* @param {Object} [options] - The options
|
|
10
14
|
* @returns {Recipe} The recipe instance.
|
|
11
|
-
* @param {number} [options.
|
|
15
|
+
* @param {number} [options.page=1] - The one-based page of a PDF source, or the
|
|
16
|
+
* image of a multi-image TIFF, as in `overlay()`.
|
|
17
|
+
* @param {number} [options.width] - The new width. The frame's line width is
|
|
18
|
+
* `options.lineWidth`.
|
|
12
19
|
* @param {number} [options.height] - The new height
|
|
13
20
|
* @param {number} [options.scale] - Scale the image from the original width and height.
|
|
14
21
|
* @param {boolean} [options.keepAspectRatio=true] - Keep the aspect ratio.
|
|
15
|
-
* @param {number} [options.opacity] - The opacity.
|
|
16
|
-
* @param {string} [options.align] -
|
|
22
|
+
* @param {number} [options.opacity] - The opacity of the image and its frame.
|
|
23
|
+
* @param {string} [options.align] - A `Recipe.HorizontalAlign` value, optionally
|
|
24
|
+
* followed by a space and a `Recipe.VerticalAlign` value, for example
|
|
25
|
+
* "center center". Horizontal center moves the image left by half its width
|
|
26
|
+
* and right moves it right by half; vertical center moves it up by half its
|
|
27
|
+
* height and bottom moves it down by half from its top-left placement.
|
|
28
|
+
* @param {number} [options.rotation] - Rotate the image, in degrees.
|
|
29
|
+
* @param {number[]} [options.rotationOrigin] - [x, y] of the rotation origin;
|
|
30
|
+
* the bottom-left corner of the drawn image when omitted.
|
|
31
|
+
* @param {number} [options.skewX] - Skew angle off the x axis, in degrees.
|
|
32
|
+
* @param {number} [options.skewY] - Skew angle off the y axis, in degrees.
|
|
33
|
+
* @param {string|number[]} [options.fill] - Paint the image box beneath the
|
|
34
|
+
* image in this color.
|
|
35
|
+
* @param {string|number[]} [options.stroke] - Outline the image box above the
|
|
36
|
+
* image in this color.
|
|
37
|
+
* @param {string|number[]} [options.color] - Outline color when `stroke` is
|
|
38
|
+
* not given.
|
|
39
|
+
* @param {Recipe.Colorspace} [options.colorspace] - The frame colorspace.
|
|
40
|
+
* @param {string} [options.colorName] - The Separation ink name of a
|
|
41
|
+
* `"separation"` frame color.
|
|
42
|
+
* @param {number} [options.lineWidth] - The outline width. The outline lies
|
|
43
|
+
* inside the image box, as a `rectangle()` stroke does.
|
|
44
|
+
* @param {number[]} [options.dash] - The outline dash pattern.
|
|
45
|
+
* @param {number} [options.dashPhase] - The outline dash phase.
|
|
46
|
+
* @param {Recipe.LineCap} [options.lineCap] - The outline dash cap.
|
|
47
|
+
* @param {Recipe.LineJoin} [options.lineJoin] - The outline corner join.
|
|
48
|
+
* @param {number} [options.miterLimit] - The outline miter limit.
|
|
49
|
+
* @param {boolean|number} [options.debug] - Outline the image box in green and
|
|
50
|
+
* mark the placement point in red.
|
|
51
|
+
* @param {string} [options.link] - Make the image open this URL.
|
|
52
|
+
* @throws {TypeError} If no page is active.
|
|
53
|
+
* @throws {RangeError} If `page` is not an integer from 1 to 4294967296,
|
|
54
|
+
* a size is not a finite number, or `miterLimit` is not a number of at
|
|
55
|
+
* least 1.
|
|
56
|
+
* @throws {TypeError} If `rotation` is not a finite number.
|
|
57
|
+
* @throws {Error} If the image, or the PDF page `page` selects, cannot be read.
|
|
17
58
|
*/
|
|
18
59
|
exports.image = function image(imgSrc, x, y, options = {}) {
|
|
19
|
-
|
|
60
|
+
// null options act like omitted options.
|
|
61
|
+
if (options === null) options = {};
|
|
62
|
+
const index = imageIndex(options);
|
|
63
|
+
const { width, height, offsetX, offsetY, page } = this._getImgOffset(
|
|
20
64
|
imgSrc,
|
|
21
65
|
options,
|
|
22
66
|
);
|
|
23
67
|
const imgOptions = {
|
|
68
|
+
index,
|
|
24
69
|
transformation: {
|
|
25
|
-
fit:
|
|
70
|
+
fit: muhammara.ImageFit.ALWAYS,
|
|
26
71
|
// proportional: true,
|
|
27
72
|
width,
|
|
28
73
|
height,
|
|
@@ -32,7 +77,13 @@ exports.image = function image(imgSrc, x, y, options = {}) {
|
|
|
32
77
|
|
|
33
78
|
const _options = this._getPathOptions(options, nx, ny);
|
|
34
79
|
const gsId = _options.fillGsId;
|
|
35
|
-
const xObjectKeyName = `${imgSrc}__${gsId}`;
|
|
80
|
+
const xObjectKeyName = `${imgSrc}__${index}__${gsId}`;
|
|
81
|
+
|
|
82
|
+
// `width` is the image width; only `lineWidth` sizes the frame outline.
|
|
83
|
+
const frame = { ...options, width: undefined };
|
|
84
|
+
if (options.fill) {
|
|
85
|
+
drawFrame(this, nx, ny, width, height, { ...frame, stroke: undefined });
|
|
86
|
+
}
|
|
36
87
|
|
|
37
88
|
// See if this image has been seen already, so as to not duplicate it.
|
|
38
89
|
let xObject = this.xObjects.find((element) => {
|
|
@@ -55,46 +106,339 @@ exports.image = function image(imgSrc, x, y, options = {}) {
|
|
|
55
106
|
xObject.set("height", height);
|
|
56
107
|
|
|
57
108
|
this.xObjects.push(xObject);
|
|
58
|
-
ctx.gs(xObject.getGsName(gsId))
|
|
109
|
+
ctx.gs(xObject.getGsName(gsId));
|
|
110
|
+
if (page) {
|
|
111
|
+
const placed = placedPage(page, width, height);
|
|
112
|
+
ctx.cm(...placed.matrix).drawImage(placed.x, placed.y, imgSrc, {
|
|
113
|
+
index,
|
|
114
|
+
transformation: { ...imgOptions.transformation, ...placed.size },
|
|
115
|
+
});
|
|
116
|
+
} else {
|
|
117
|
+
ctx.drawImage(0, 0, imgSrc, imgOptions);
|
|
118
|
+
}
|
|
59
119
|
});
|
|
60
120
|
|
|
121
|
+
if (options.stroke || options.color) {
|
|
122
|
+
drawFrame(this, nx, ny, width, height, { ...frame, fill: undefined });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (options.debug) {
|
|
126
|
+
drawFrame(this, nx, ny, width, height, {
|
|
127
|
+
stroke: DEBUG_OUTLINE,
|
|
128
|
+
lineWidth: 1,
|
|
129
|
+
rotation: options.rotation,
|
|
130
|
+
rotationOrigin: options.rotationOrigin,
|
|
131
|
+
skewX: options.skewX,
|
|
132
|
+
skewY: options.skewY,
|
|
133
|
+
});
|
|
134
|
+
this.circle(x, y, 2, { fill: DEBUG_ANCHOR });
|
|
135
|
+
}
|
|
136
|
+
|
|
61
137
|
if (options.link) {
|
|
62
|
-
this.
|
|
138
|
+
const [linkX, linkY] = this._centrify(x, y);
|
|
139
|
+
this.link(
|
|
140
|
+
options.link,
|
|
141
|
+
linkX + offsetX,
|
|
142
|
+
linkY - offsetY - height,
|
|
143
|
+
width,
|
|
144
|
+
height,
|
|
145
|
+
);
|
|
63
146
|
}
|
|
64
147
|
|
|
65
148
|
return this;
|
|
66
149
|
};
|
|
67
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Read the one-based `page` option as the zero-based index of the PDF page or
|
|
153
|
+
* TIFF image that PDFWriter takes.
|
|
154
|
+
* @private
|
|
155
|
+
* @param {Object} options - The image() options.
|
|
156
|
+
* @returns {number} The zero-based index; 0 when `page` is omitted.
|
|
157
|
+
* @throws {RangeError} If `page` is not an integer from 1 to 4294967296.
|
|
158
|
+
*/
|
|
159
|
+
function imageIndex(options) {
|
|
160
|
+
const page = options.page ?? 1;
|
|
161
|
+
if (!Number.isInteger(page) || page < 1 || page > 0x100000000) {
|
|
162
|
+
throw new RangeError("image page must be an integer from 1 to 4294967296");
|
|
163
|
+
}
|
|
164
|
+
return page - 1;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Read a size option of `image()` as a number. A numeric string counts as its
|
|
169
|
+
* number, as in Wasm; a missing or other falsy value means not given.
|
|
170
|
+
* A negative size mirrors the image.
|
|
171
|
+
* @private
|
|
172
|
+
* @param {Object} options - The image() options.
|
|
173
|
+
* @param {string} key - `width`, `height`, or `scale`.
|
|
174
|
+
* @returns {number|undefined} The size, or undefined when not given.
|
|
175
|
+
* @throws {RangeError} If the value is not a finite number.
|
|
176
|
+
*/
|
|
177
|
+
function imageSize(options, key) {
|
|
178
|
+
if (!options[key]) return undefined;
|
|
179
|
+
const size = Number(options[key]);
|
|
180
|
+
if (!Number.isFinite(size)) {
|
|
181
|
+
throw new RangeError(`image ${key} must be a finite number`);
|
|
182
|
+
}
|
|
183
|
+
return size;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Read the media box and rotation of a page of a PDF image source, caching
|
|
188
|
+
* them per file version on the Recipe. Returns null for a source that is not
|
|
189
|
+
* a readable PDF file, which is measured and drawn as an image.
|
|
190
|
+
* @private
|
|
191
|
+
* @param {Recipe} recipe - The recipe instance.
|
|
192
|
+
* @param {string} imgSrc - The image path.
|
|
193
|
+
* @param {number} index - The zero-based page.
|
|
194
|
+
* @returns {?{mediaBox: number[], rotate: number}} The page geometry.
|
|
195
|
+
* @throws {Error} If the PDF has no page at `index`: PDFWriter would read a
|
|
196
|
+
* page that does not exist.
|
|
197
|
+
*/
|
|
198
|
+
function pdfPage(recipe, imgSrc, index) {
|
|
199
|
+
let key;
|
|
200
|
+
try {
|
|
201
|
+
const stat = fs.statSync(imgSrc);
|
|
202
|
+
key = `${imgSrc}\0${stat.mtimeMs}\0${stat.size}`;
|
|
203
|
+
} catch {
|
|
204
|
+
// A missing file measures 0 by 0 and is rejected as an unknown image.
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
recipe._pdfSources = recipe._pdfSources || new Map();
|
|
208
|
+
let source = recipe._pdfSources.get(key);
|
|
209
|
+
if (!source) {
|
|
210
|
+
source = {
|
|
211
|
+
pdf: recipe.writer.getImageType(imgSrc) === muhammara.PDFImageType.PDF,
|
|
212
|
+
pages: new Map(),
|
|
213
|
+
};
|
|
214
|
+
recipe._pdfSources.set(key, source);
|
|
215
|
+
}
|
|
216
|
+
if (!source.pdf) return null;
|
|
217
|
+
if (!source.pages.has(index)) {
|
|
218
|
+
source.pages.set(index, readPdfPage(imgSrc, index));
|
|
219
|
+
}
|
|
220
|
+
const page = source.pages.get(index);
|
|
221
|
+
if (!page) throw new Error(`Unknown image: ${imgSrc}`);
|
|
222
|
+
return page;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Read the media box and rotation of one page of a PDF file.
|
|
227
|
+
* @private
|
|
228
|
+
* @param {string} imgSrc - The PDF path.
|
|
229
|
+
* @param {number} index - The zero-based page.
|
|
230
|
+
* @returns {?{mediaBox: number[], rotate: number}} The page geometry, or null
|
|
231
|
+
* when the PDF cannot be read or has no such page.
|
|
232
|
+
*/
|
|
233
|
+
function readPdfPage(imgSrc, index) {
|
|
234
|
+
let reader;
|
|
235
|
+
try {
|
|
236
|
+
reader = muhammara.createReader(imgSrc);
|
|
237
|
+
} catch {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
try {
|
|
241
|
+
if (index >= reader.getPagesCount()) return null;
|
|
242
|
+
const page = reader.parsePage(index);
|
|
243
|
+
return { mediaBox: page.getMediaBox(), rotate: page.getRotate() };
|
|
244
|
+
} finally {
|
|
245
|
+
reader.end();
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* The size of a PDF page as it is displayed: its media box, turned by its
|
|
251
|
+
* `/Rotate`.
|
|
252
|
+
* @private
|
|
253
|
+
* @param {{mediaBox: number[], rotate: number}} page - The page geometry.
|
|
254
|
+
* @returns {{width: number, height: number}} The displayed size.
|
|
255
|
+
*/
|
|
256
|
+
function displayedPageSize(page) {
|
|
257
|
+
const [left, bottom, right, top] = page.mediaBox;
|
|
258
|
+
return quarterTurns(page.rotate) % 2
|
|
259
|
+
? { width: top - bottom, height: right - left }
|
|
260
|
+
: { width: right - left, height: top - bottom };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* How to draw a PDF page into a displayed box: PDFWriter fits the unturned
|
|
265
|
+
* media box and ignores its origin, so the page is turned by its `/Rotate`
|
|
266
|
+
* and drawn at its origin scaled away.
|
|
267
|
+
* @private
|
|
268
|
+
* @param {{mediaBox: number[], rotate: number}} page - The page geometry.
|
|
269
|
+
* @param {number} width - The displayed width.
|
|
270
|
+
* @param {number} height - The displayed height.
|
|
271
|
+
* @returns {{matrix: number[], x: number, y: number, size: {width: number, height: number}}}
|
|
272
|
+
* The turning matrix, the drawing position, and the fitted unturned size.
|
|
273
|
+
*/
|
|
274
|
+
function placedPage(page, width, height) {
|
|
275
|
+
const [left, bottom, right, top] = page.mediaBox;
|
|
276
|
+
const turns = quarterTurns(page.rotate);
|
|
277
|
+
const w = turns % 2 ? height : width;
|
|
278
|
+
const h = turns % 2 ? width : height;
|
|
279
|
+
const matrix = [
|
|
280
|
+
[1, 0, 0, 1, 0, 0],
|
|
281
|
+
[0, -1, 1, 0, 0, w],
|
|
282
|
+
[-1, 0, 0, -1, w, h],
|
|
283
|
+
[0, 1, -1, 0, h, 0],
|
|
284
|
+
][turns];
|
|
285
|
+
return {
|
|
286
|
+
matrix,
|
|
287
|
+
x: (-left * w) / (right - left),
|
|
288
|
+
y: (-bottom * h) / (top - bottom),
|
|
289
|
+
size: { width: w, height: h },
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* The clockwise quarter turns of a page `/Rotate` value.
|
|
295
|
+
* @private
|
|
296
|
+
* @param {number} rotate - The `/Rotate` value, a multiple of 90.
|
|
297
|
+
* @returns {number} 0 to 3; 0 for a value that is not a multiple of 90.
|
|
298
|
+
*/
|
|
299
|
+
function quarterTurns(rotate) {
|
|
300
|
+
const turns = (((Number(rotate) || 0) % 360) + 360) % 360;
|
|
301
|
+
return turns % 90 ? 0 : turns / 90;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// The diagnostic colors the shape `debug` options draw with.
|
|
305
|
+
const DEBUG_OUTLINE = "#00ff00";
|
|
306
|
+
const DEBUG_ANCHOR = "#ff0000";
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Paint the box of a placed image, with the image's rotation and skew: a fill
|
|
310
|
+
* when `options.fill` is given, otherwise an outline inset by half its line
|
|
311
|
+
* width, as `rectangle()` strokes are.
|
|
312
|
+
* @private
|
|
313
|
+
* @param {Recipe} recipe - The recipe instance.
|
|
314
|
+
* @param {number} nx - The PDF x of the image's bottom-left corner.
|
|
315
|
+
* @param {number} ny - The PDF y of the image's bottom-left corner.
|
|
316
|
+
* @param {number} width - The drawn image width.
|
|
317
|
+
* @param {number} height - The drawn image height.
|
|
318
|
+
* @param {Object} options - The image() frame options, `width` excluded.
|
|
319
|
+
* @returns {void}
|
|
320
|
+
*/
|
|
321
|
+
function drawFrame(recipe, nx, ny, width, height, options) {
|
|
322
|
+
const pathOptions = recipe._getPathOptions(options, nx, ny);
|
|
323
|
+
// A negative size mirrors the image into the other side of its corner.
|
|
324
|
+
const left = Math.min(0, width);
|
|
325
|
+
const bottom = Math.min(0, height);
|
|
326
|
+
const boxWidth = Math.abs(width);
|
|
327
|
+
const boxHeight = Math.abs(height);
|
|
328
|
+
if (options.fill) {
|
|
329
|
+
const colorModel = pathOptions.fillModel;
|
|
330
|
+
pathOptions.type = muhammara.DrawingPathType.FILL;
|
|
331
|
+
pathOptions.color = colorModel.color;
|
|
332
|
+
pathOptions.colorspace = colorModel.colorspace;
|
|
333
|
+
recipe._drawObject(
|
|
334
|
+
recipe,
|
|
335
|
+
nx,
|
|
336
|
+
ny,
|
|
337
|
+
width,
|
|
338
|
+
height,
|
|
339
|
+
pathOptions,
|
|
340
|
+
(ctx, xObject) => {
|
|
341
|
+
ctx.gs(xObject.getGsName(pathOptions.fillGsId));
|
|
342
|
+
xObject.fill(colorModel);
|
|
343
|
+
ctx.drawRectangle(
|
|
344
|
+
left,
|
|
345
|
+
bottom,
|
|
346
|
+
boxWidth,
|
|
347
|
+
boxHeight,
|
|
348
|
+
recipe._devicePathOptions(pathOptions),
|
|
349
|
+
);
|
|
350
|
+
},
|
|
351
|
+
);
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
const colorModel =
|
|
355
|
+
pathOptions.stroke !== undefined
|
|
356
|
+
? pathOptions.strokeModel
|
|
357
|
+
: pathOptions.colorModel;
|
|
358
|
+
pathOptions.type = muhammara.DrawingPathType.STROKE;
|
|
359
|
+
pathOptions.color = colorModel.color;
|
|
360
|
+
pathOptions.colorspace = colorModel.colorspace;
|
|
361
|
+
recipe._drawObject(
|
|
362
|
+
recipe,
|
|
363
|
+
nx,
|
|
364
|
+
ny,
|
|
365
|
+
width,
|
|
366
|
+
height,
|
|
367
|
+
pathOptions,
|
|
368
|
+
(ctx, xObject) => {
|
|
369
|
+
// The form clips at the image box, so the outline lies inside it.
|
|
370
|
+
const margin = pathOptions.width;
|
|
371
|
+
ctx.gs(xObject.getGsName(pathOptions.strokeGsId));
|
|
372
|
+
xObject.stroke(colorModel);
|
|
373
|
+
ctx
|
|
374
|
+
.J(pathOptions.lineCap)
|
|
375
|
+
.j(pathOptions.lineJoin)
|
|
376
|
+
.M(pathOptions.miterLimit)
|
|
377
|
+
.d(pathOptions.dash, pathOptions.dashPhase)
|
|
378
|
+
.drawRectangle(
|
|
379
|
+
left + Math.min(margin / 2, boxWidth / 2),
|
|
380
|
+
bottom + Math.min(margin / 2, boxHeight / 2),
|
|
381
|
+
Math.max(0, boxWidth - margin),
|
|
382
|
+
Math.max(0, boxHeight - margin),
|
|
383
|
+
recipe._devicePathOptions(pathOptions),
|
|
384
|
+
);
|
|
385
|
+
},
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Compute the drawn size of an image and the offset its alignment applies.
|
|
391
|
+
* A missing `options.keepAspectRatio` keeps the aspect ratio.
|
|
392
|
+
* @private
|
|
393
|
+
* @param {string} [imgSrc=''] - The image path.
|
|
394
|
+
* @param {Object} [options] - The image() options; `page` selects the page or
|
|
395
|
+
* image that is measured.
|
|
396
|
+
* @returns {{width: number, height: number, offsetX: number, offsetY: number, page: ?Object}}
|
|
397
|
+
* The drawn size, the PDF offset from the placement point, and the page
|
|
398
|
+
* geometry of a PDF source.
|
|
399
|
+
* @throws {Error} If the image, or the PDF page `page` selects, cannot be read.
|
|
400
|
+
*/
|
|
68
401
|
exports._getImgOffset = function _getImgOffset(imgSrc = "", options = {}) {
|
|
69
|
-
//
|
|
70
|
-
options.keepAspectRatio
|
|
71
|
-
|
|
72
|
-
const
|
|
402
|
+
// A missing keepAspectRatio keeps it; the caller's options stay untouched.
|
|
403
|
+
const keepAspectRatio = options.keepAspectRatio ?? true;
|
|
404
|
+
const index = imageIndex(options);
|
|
405
|
+
const page = pdfPage(this, imgSrc, index);
|
|
406
|
+
const dimensions = page
|
|
407
|
+
? displayedPageSize(page)
|
|
408
|
+
: this.writer.getImageDimensions(imgSrc, index);
|
|
409
|
+
// An unreadable image measures 0 by 0. Drawing it would leave an image
|
|
410
|
+
// object that is never written, so endPDF() could not finish the PDF.
|
|
411
|
+
if (!(dimensions.width > 0 && dimensions.height > 0)) {
|
|
412
|
+
throw new Error(`Unknown image: ${imgSrc}`);
|
|
413
|
+
}
|
|
73
414
|
const ratio = dimensions.width / dimensions.height;
|
|
415
|
+
const scale = imageSize(options, "scale");
|
|
416
|
+
const wantedWidth = imageSize(options, "width");
|
|
417
|
+
const wantedHeight = imageSize(options, "height");
|
|
74
418
|
|
|
75
419
|
let width = dimensions.width;
|
|
76
420
|
let height = dimensions.height;
|
|
77
|
-
if (
|
|
78
|
-
width = width *
|
|
79
|
-
height = height *
|
|
80
|
-
} else if (
|
|
81
|
-
width =
|
|
82
|
-
height =
|
|
83
|
-
} else if (!
|
|
84
|
-
width =
|
|
85
|
-
height =
|
|
86
|
-
} else if (
|
|
87
|
-
if (!
|
|
88
|
-
width =
|
|
89
|
-
height =
|
|
421
|
+
if (scale) {
|
|
422
|
+
width = width * scale;
|
|
423
|
+
height = height * scale;
|
|
424
|
+
} else if (wantedWidth && !wantedHeight) {
|
|
425
|
+
width = wantedWidth;
|
|
426
|
+
height = wantedWidth / ratio;
|
|
427
|
+
} else if (!wantedWidth && wantedHeight) {
|
|
428
|
+
width = wantedHeight * ratio;
|
|
429
|
+
height = wantedHeight;
|
|
430
|
+
} else if (wantedWidth && wantedHeight) {
|
|
431
|
+
if (!keepAspectRatio) {
|
|
432
|
+
width = wantedWidth;
|
|
433
|
+
height = wantedHeight;
|
|
90
434
|
} else {
|
|
91
435
|
// fit to the smaller
|
|
92
|
-
if (
|
|
93
|
-
width =
|
|
94
|
-
height =
|
|
436
|
+
if (wantedWidth / ratio <= wantedHeight) {
|
|
437
|
+
width = wantedWidth;
|
|
438
|
+
height = wantedWidth / ratio;
|
|
95
439
|
} else {
|
|
96
|
-
width =
|
|
97
|
-
height =
|
|
440
|
+
width = wantedHeight * ratio;
|
|
441
|
+
height = wantedHeight;
|
|
98
442
|
}
|
|
99
443
|
}
|
|
100
444
|
}
|
|
@@ -105,10 +449,10 @@ exports._getImgOffset = function _getImgOffset(imgSrc = "", options = {}) {
|
|
|
105
449
|
const alignments = options.align.split(" ");
|
|
106
450
|
if (alignments[0]) {
|
|
107
451
|
switch (alignments[0]) {
|
|
108
|
-
case
|
|
452
|
+
case HorizontalAlign.CENTER:
|
|
109
453
|
offsetX = (-1 * width) / 2;
|
|
110
454
|
break;
|
|
111
|
-
case
|
|
455
|
+
case HorizontalAlign.RIGHT:
|
|
112
456
|
offsetX = width / 2;
|
|
113
457
|
break;
|
|
114
458
|
default:
|
|
@@ -116,15 +460,17 @@ exports._getImgOffset = function _getImgOffset(imgSrc = "", options = {}) {
|
|
|
116
460
|
}
|
|
117
461
|
if (alignments[1]) {
|
|
118
462
|
switch (alignments[1]) {
|
|
119
|
-
case
|
|
463
|
+
case VerticalAlign.CENTER:
|
|
120
464
|
offsetY = (-1 * height) / 2;
|
|
121
465
|
break;
|
|
122
|
-
case
|
|
123
|
-
|
|
466
|
+
case VerticalAlign.BOTTOM:
|
|
467
|
+
// Down by half the height from the top-left placement, as
|
|
468
|
+
// documented; 6.x moved the image up by 1.5 times its height.
|
|
469
|
+
offsetY = -1.5 * height;
|
|
124
470
|
break;
|
|
125
471
|
default:
|
|
126
472
|
}
|
|
127
473
|
}
|
|
128
474
|
}
|
|
129
|
-
return { width, height, offsetX, offsetY };
|
|
475
|
+
return { width, height, offsetX, offsetY, page };
|
|
130
476
|
};
|