@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/utils.js
CHANGED
|
@@ -12,6 +12,8 @@ const PAGE_CONTEXT_STATE = Object.freeze({
|
|
|
12
12
|
/**
|
|
13
13
|
* Copies nested option objects and arrays without discarding callback values.
|
|
14
14
|
* @private
|
|
15
|
+
* @param {*} value - The options or value to copy.
|
|
16
|
+
* @returns {*} The copy; functions and primitives are returned as they are.
|
|
15
17
|
*/
|
|
16
18
|
function cloneOptions(value) {
|
|
17
19
|
if (Array.isArray(value)) return value.map(cloneOptions);
|
|
@@ -27,9 +29,11 @@ function cloneOptions(value) {
|
|
|
27
29
|
* Append PDF Page with annotations.
|
|
28
30
|
*
|
|
29
31
|
* @param {any} pdfWriter - Hummus writer.
|
|
30
|
-
* @param {string|any} sourcePDFPath - The
|
|
31
|
-
* @param {number} pageNumber - page
|
|
32
|
+
* @param {string|any} sourcePDFPath - The source PDF path or read stream.
|
|
33
|
+
* @param {number} pageNumber - The zero-based page index.
|
|
32
34
|
* @private
|
|
35
|
+
* @returns {void}
|
|
36
|
+
* @throws {Error} If the source cannot be read or the page does not exist.
|
|
33
37
|
*/
|
|
34
38
|
function appendPDFPageFromPDFWithAnnotations(
|
|
35
39
|
pdfWriter,
|
|
@@ -67,8 +71,13 @@ function appendPDFPageFromPDFWithAnnotations(
|
|
|
67
71
|
* Append PDF Pages with annotations.
|
|
68
72
|
*
|
|
69
73
|
* @param {any} pdfWriter - Hummus writer.
|
|
70
|
-
* @param {string|any} sourcePDFPath - The
|
|
74
|
+
* @param {string|any} sourcePDFPath - The source PDF path or read stream.
|
|
75
|
+
* @param {Object} [options] - The options.
|
|
76
|
+
* @param {number[][]} [options.specificRanges] - Zero-based inclusive
|
|
77
|
+
* [start, end] page ranges; all pages when omitted.
|
|
71
78
|
* @private
|
|
79
|
+
* @returns {void}
|
|
80
|
+
* @throws {Error} If the source cannot be read or a page does not exist.
|
|
72
81
|
*/
|
|
73
82
|
function appendPDFPagesFromPDFWithAnnotations(
|
|
74
83
|
pdfWriter,
|
|
@@ -97,7 +106,8 @@ function appendPDFPagesFromPDFWithAnnotations(
|
|
|
97
106
|
|
|
98
107
|
/**
|
|
99
108
|
* Resolves a text font size from `size`, its `fontSize` alias, or a fallback,
|
|
100
|
-
* rejecting a size that is not greater than zero. Zero
|
|
109
|
+
* rejecting a size that is not a finite number greater than zero. Zero,
|
|
110
|
+
* negative, and infinite sizes have
|
|
101
111
|
* no usable meaning: they draw nothing readable and measure to nonsensical font
|
|
102
112
|
* metrics, so they are reported as invalid input naming the option and the
|
|
103
113
|
* value. Omitting both options, or passing `null` or `undefined`, selects the
|
|
@@ -105,7 +115,8 @@ function appendPDFPagesFromPDFWithAnnotations(
|
|
|
105
115
|
* @param {Object} [options] - Options holding `size` or `fontSize`.
|
|
106
116
|
* @param {number} [fallback] - Size used when neither option is given.
|
|
107
117
|
* @returns {number} The resolved font size in PDF points.
|
|
108
|
-
* @throws {RangeError} If the given size is not greater than
|
|
118
|
+
* @throws {RangeError} If the given size is not a finite number greater than
|
|
119
|
+
* zero.
|
|
109
120
|
*/
|
|
110
121
|
function resolveFontSize(options = {}, fallback) {
|
|
111
122
|
const name = options.size == null ? "fontSize" : "size";
|
|
@@ -113,7 +124,7 @@ function resolveFontSize(options = {}, fallback) {
|
|
|
113
124
|
if (size == null) {
|
|
114
125
|
return fallback;
|
|
115
126
|
}
|
|
116
|
-
if (!(size > 0)) {
|
|
127
|
+
if (!(size > 0) || size === Infinity) {
|
|
117
128
|
throw new RangeError(
|
|
118
129
|
`Text ${name} must be a number greater than zero, received ${size}`,
|
|
119
130
|
);
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
+
const muhammara = require("../muhammara");
|
|
2
|
+
const { Colorspace } = require("../recipe-constants");
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* move the current position to target position
|
|
3
6
|
* @name moveTo
|
|
4
7
|
* @function
|
|
5
8
|
* @memberof Recipe#
|
|
6
|
-
* @param {number} x - The coordinate x
|
|
7
|
-
* @param {number} y - The coordinate y
|
|
9
|
+
* @param {number|"center"} x - The coordinate x
|
|
10
|
+
* @param {number|"center"} y - The coordinate y
|
|
8
11
|
* @returns {Recipe} The recipe instance.
|
|
12
|
+
* @throws {TypeError} If no page is active.
|
|
9
13
|
*/
|
|
10
14
|
exports.moveTo = function moveTo(x, y) {
|
|
11
15
|
const { nx, ny } = this._calibrateCoordinate(x, y);
|
|
@@ -21,53 +25,95 @@ exports.moveTo = function moveTo(x, y) {
|
|
|
21
25
|
* @name lineTo
|
|
22
26
|
* @function
|
|
23
27
|
* @memberof Recipe#
|
|
24
|
-
* @param {number} x - The coordinate x
|
|
25
|
-
* @param {number} y - The coordinate y
|
|
28
|
+
* @param {number|"center"} x - The coordinate x
|
|
29
|
+
* @param {number|"center"} y - The coordinate y
|
|
26
30
|
* @param {Object} [options] - The options
|
|
27
31
|
* @returns {Recipe} The recipe instance.
|
|
32
|
+
* @throws {TypeError} If no page is active.
|
|
28
33
|
* @param {string|number[]} [options.color] - HexColor, PercentColor or DecimalColor
|
|
29
34
|
* @param {string|number[]} [options.stroke] - HexColor, PercentColor or DecimalColor
|
|
30
35
|
* @param {number} [options.lineWidth] - The line width
|
|
31
36
|
* @param {number} [options.opacity] - how transparent should line be, from 0: invisible to 1: opaque
|
|
32
37
|
* @param {number[]} [options.dash] - The dash pattern [dashSize, gapSize] or [dashAndGapSize]
|
|
33
38
|
* @param {number} [options.dashPhase] - distance into dash pattern at which to start dash (default: 0, immediately)
|
|
34
|
-
* @param {
|
|
35
|
-
* @param {
|
|
39
|
+
* @param {Recipe.LineCap} [options.lineCap] - open line end style, a `Recipe.LineCap` value (default: 'round')
|
|
40
|
+
* @param {Recipe.LineJoin} [options.lineJoin] - joined line end style, a `Recipe.LineJoin` value (default: 'round')
|
|
36
41
|
* @param {number} [options.miterLimit] - limit at which 'miter' joins are forced to 'bevel' (default: 1.414)
|
|
37
|
-
*
|
|
38
42
|
*/
|
|
39
43
|
exports.lineTo = function lineTo(x, y, options = {}) {
|
|
40
|
-
const
|
|
41
|
-
const fromY = this._position.y;
|
|
44
|
+
const from = [this._position.x, this._position.y];
|
|
42
45
|
const { nx, ny } = this._calibrateCoordinate(x, y);
|
|
43
|
-
|
|
46
|
+
this._strokePolyline([from, [nx, ny]], options);
|
|
47
|
+
this.moveTo(x, y);
|
|
48
|
+
return this;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Stroke one path through PDF points, so segments meet at the line join
|
|
53
|
+
* instead of overlapping, and a Separation line writes one form.
|
|
54
|
+
* @private
|
|
55
|
+
* @param {number[][]} points - Two or more [x, y] points in PDF coordinates.
|
|
56
|
+
* @param {Object} options - The line options of `lineTo()` and `line()`.
|
|
57
|
+
* @returns {void}
|
|
58
|
+
* @throws {TypeError} If no page is active.
|
|
59
|
+
*/
|
|
60
|
+
exports._strokePolyline = function _strokePolyline(points, options) {
|
|
61
|
+
// _getPathOptions() may pause the page to write graphics states, and an
|
|
62
|
+
// edited page resumes into a new context, so read the context afterwards.
|
|
44
63
|
const pathOptions = this._getPathOptions(options);
|
|
45
|
-
|
|
64
|
+
const context = this.pageContext;
|
|
65
|
+
pathOptions.type = muhammara.DrawingPathType.STROKE;
|
|
46
66
|
|
|
47
67
|
if (pathOptions.stroke !== undefined) {
|
|
48
68
|
pathOptions.color = pathOptions.stroke;
|
|
49
69
|
pathOptions.colorspace = pathOptions.strokeModel.colorspace;
|
|
50
70
|
}
|
|
51
71
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
72
|
+
const colorModel = pathOptions.strokeModel || pathOptions.colorModel;
|
|
73
|
+
const drawLine = (ctx, originX, originY) =>
|
|
74
|
+
ctx
|
|
75
|
+
.J(pathOptions.lineCap)
|
|
76
|
+
.j(pathOptions.lineJoin)
|
|
77
|
+
.d(pathOptions.dash, pathOptions.dashPhase)
|
|
78
|
+
.M(pathOptions.miterLimit)
|
|
79
|
+
.drawPath(
|
|
80
|
+
...points.flatMap(([px, py]) => [px - originX, py - originY]),
|
|
81
|
+
this._devicePathOptions(pathOptions),
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
if (colorModel.colorspace === Colorspace.SEPARATION) {
|
|
85
|
+
// A Separation color space is a resource, so draw through a form XObject
|
|
86
|
+
// as the other shapes do; the padding keeps caps and miter joins inside.
|
|
87
|
+
const padding = pathOptions.width * Math.max(1, pathOptions.miterLimit);
|
|
88
|
+
const xs = points.map((point) => point[0]);
|
|
89
|
+
const ys = points.map((point) => point[1]);
|
|
90
|
+
const left = Math.min(...xs) - padding;
|
|
91
|
+
const bottom = Math.min(...ys) - padding;
|
|
92
|
+
this._drawObject(
|
|
93
|
+
this,
|
|
94
|
+
left,
|
|
95
|
+
bottom,
|
|
96
|
+
Math.max(...xs) - Math.min(...xs) + padding * 2,
|
|
97
|
+
Math.max(...ys) - Math.min(...ys) + padding * 2,
|
|
98
|
+
{},
|
|
99
|
+
(ctx, xObject) => {
|
|
100
|
+
this._setSeparationColor(xObject, colorModel, true);
|
|
101
|
+
drawLine(ctx, left, bottom);
|
|
102
|
+
},
|
|
103
|
+
);
|
|
104
|
+
} else {
|
|
105
|
+
drawLine(context.q(), 0, 0).Q();
|
|
106
|
+
}
|
|
62
107
|
};
|
|
63
108
|
|
|
64
109
|
/**
|
|
65
|
-
* Draw a line
|
|
110
|
+
* Draw a line through coordinate pairs, or from (startX, startY) to
|
|
111
|
+
* (endX, endY) when called as `line(startX, startY, endX, endY, options?)`.
|
|
66
112
|
* @name line
|
|
67
113
|
* @function
|
|
68
114
|
* @memberof Recipe#
|
|
69
|
-
* @param {number[]} coordinates - The array of coordinate [[x,y], [m,n]]
|
|
70
|
-
* @param {Object} [options] - The options
|
|
115
|
+
* @param {number[][]|number} coordinates - The array of coordinate [[x,y], [m,n]], or the start x
|
|
116
|
+
* @param {Object} [options] - The options, or the start y in the four-number form
|
|
71
117
|
* @returns {Recipe} The recipe instance.
|
|
72
118
|
* @param {string|number[]} [options.color] - HexColor, PercentColor or DecimalColor
|
|
73
119
|
* @param {string|number[]} [options.stroke] - HexColor, PercentColor or DecimalColor
|
|
@@ -75,20 +121,36 @@ exports.lineTo = function lineTo(x, y, options = {}) {
|
|
|
75
121
|
* @param {number} [options.opacity] - how transparent should line be, from 0: invisible to 1: opaque
|
|
76
122
|
* @param {number[]} [options.dash] - The dash pattern [dashSize, gapSize] or [dashAndGapSize]
|
|
77
123
|
* @param {number} [options.dashPhase] - distance into dash pattern at which to start dash (default: 0, immediately)
|
|
78
|
-
* @param {
|
|
79
|
-
* @param {
|
|
80
|
-
* @param {number} [options.miterLimit] - limit at which 'miter' joins are forced to 'bevel' (default: 1.414)
|
|
124
|
+
* @param {Recipe.LineCap} [options.lineCap] - open line end style, a `Recipe.LineCap` value (default: 'round')
|
|
125
|
+
* @param {Recipe.LineJoin} [options.lineJoin] - joined line end style, a `Recipe.LineJoin` value (default: 'round')
|
|
126
|
+
* @param {number} [options.miterLimit] - limit at which 'miter' joins are forced to 'bevel' (default: 1.414)
|
|
127
|
+
* @throws {TypeError} If no page is active.
|
|
128
|
+
*/
|
|
81
129
|
exports.line = function line(coordinates = [], options = {}) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
130
|
+
// null arguments act like omitted ones.
|
|
131
|
+
if (coordinates === null) coordinates = [];
|
|
132
|
+
if (options === null) options = {};
|
|
133
|
+
if (typeof coordinates === "number") {
|
|
134
|
+
// line(startX, startY, endX, endY, options?), as in Wasm.
|
|
135
|
+
const [startX, startY, endX, endY, lineOptions = {}] = arguments;
|
|
136
|
+
coordinates = [
|
|
137
|
+
[startX, startY],
|
|
138
|
+
[endX, endY],
|
|
139
|
+
];
|
|
140
|
+
options = lineOptions;
|
|
141
|
+
}
|
|
142
|
+
if (coordinates.length < 2) {
|
|
143
|
+
if (coordinates.length) this.moveTo(coordinates[0][0], coordinates[0][1]);
|
|
144
|
+
return this;
|
|
145
|
+
}
|
|
146
|
+
// Stroke every segment as one path, as Wasm does.
|
|
147
|
+
this._strokePolyline(
|
|
148
|
+
coordinates.map(([x, y]) => {
|
|
149
|
+
const { nx, ny } = this._calibrateCoordinate(x, y);
|
|
150
|
+
return [nx, ny];
|
|
151
|
+
}),
|
|
152
|
+
options,
|
|
153
|
+
);
|
|
154
|
+
const [lastX, lastY] = coordinates[coordinates.length - 1];
|
|
155
|
+
return this.moveTo(lastX, lastY);
|
|
94
156
|
};
|
|
@@ -29,10 +29,20 @@
|
|
|
29
29
|
* @param {number} [options.dashPhase] - distance into dash pattern at which to start dash (default: 0, immediately)
|
|
30
30
|
* @param {number} [options.rotation] - Accept: +/- 0 through 360. Default: 0
|
|
31
31
|
* @param {number[]} [options.rotationOrigin] - [originX, originY] Default: x, y
|
|
32
|
-
* @param {
|
|
33
|
-
* @param {
|
|
34
|
-
* @param {number} [options.miterLimit] - limit at which 'miter' joins are forced to 'bevel' (default: 1.414)
|
|
32
|
+
* @param {Recipe.LineCap} [options.lineCap] - open line end style, a `Recipe.LineCap` value (default: 'round')
|
|
33
|
+
* @param {Recipe.LineJoin} [options.lineJoin] - joined line end style, a `Recipe.LineJoin` value (default: 'round')
|
|
34
|
+
* @param {number} [options.miterLimit] - limit at which 'miter' joins are forced to 'bevel' (default: 1.414)
|
|
35
|
+
* @param {string} [options.link] - Make the polygon's bounding box open this URL.
|
|
36
|
+
* @throws {TypeError} If no page is active or there are no coordinates.
|
|
37
|
+
*/
|
|
35
38
|
exports.polygon = function polygon(coordinates = [], options = {}) {
|
|
39
|
+
// null options act like omitted options.
|
|
40
|
+
if (options === null) options = {};
|
|
41
|
+
if (!Array.isArray(coordinates) || coordinates.length === 0) {
|
|
42
|
+
throw new TypeError("A polygon needs at least one coordinate pair");
|
|
43
|
+
}
|
|
44
|
+
// Work on a copy so closing the path leaves the caller's array unchanged.
|
|
45
|
+
coordinates = coordinates.slice();
|
|
36
46
|
// close polygon
|
|
37
47
|
if (
|
|
38
48
|
this._getDistance(coordinates[0], coordinates[coordinates.length - 1]) != 0
|
|
@@ -110,9 +120,11 @@ exports.polygon = function polygon(coordinates = [], options = {}) {
|
|
|
110
120
|
}
|
|
111
121
|
|
|
112
122
|
if (options.stroke || options.color || !options.fill) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
123
|
+
// The fill above may have replaced colorModel; stroke with `color`.
|
|
124
|
+
colorModel =
|
|
125
|
+
pathOptions.stroke !== undefined
|
|
126
|
+
? pathOptions.strokeModel
|
|
127
|
+
: pathOptions.colorModel;
|
|
116
128
|
|
|
117
129
|
this._drawObject(
|
|
118
130
|
this,
|
|
@@ -1,12 +1,31 @@
|
|
|
1
1
|
const { xObjectForm } = require("./xObjectForm");
|
|
2
2
|
const { resolveFontSize } = require("./utils");
|
|
3
|
-
|
|
3
|
+
const { Colorspace, LineCap, LineJoin } = require("../recipe-constants");
|
|
4
|
+
const { miterLimitOption, rotationOption } = require("../recipe-options");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Resolve drawing and text options into path options: font, size, colors
|
|
8
|
+
* and color models, line style, opacity graphics states, rotation, skew and
|
|
9
|
+
* dash. Clamps `options.opacity` to 0..1 in place.
|
|
10
|
+
* @private
|
|
11
|
+
* @param {Object} [options] - The drawing or text options.
|
|
12
|
+
* @param {number} originX - The PDF x of the default rotation origin.
|
|
13
|
+
* @param {number} originY - The PDF y of the default rotation origin.
|
|
14
|
+
* @returns {Object} The resolved path options.
|
|
15
|
+
* @throws {RangeError} If a given font size is not greater than zero, or
|
|
16
|
+
* `miterLimit` is not a number of at least 1.
|
|
17
|
+
* @throws {TypeError} If `rotation` is not a finite number.
|
|
18
|
+
* @throws {Error} If the font cannot be loaded.
|
|
19
|
+
*/
|
|
4
20
|
exports._getPathOptions = function _getPathOptions(
|
|
5
21
|
options = {},
|
|
6
22
|
originX,
|
|
7
23
|
originY,
|
|
8
24
|
) {
|
|
9
25
|
this.current = this.current || {};
|
|
26
|
+
// Validate before anything is resolved or drawn.
|
|
27
|
+
const rotation = rotationOption(options.rotation);
|
|
28
|
+
const miterLimit = miterLimitOption(options.miterLimit);
|
|
10
29
|
const colorspace = options.colorspace || this.options.colorspace;
|
|
11
30
|
const colorName = options.colorName;
|
|
12
31
|
|
|
@@ -47,8 +66,9 @@ exports._getPathOptions = function _getPathOptions(
|
|
|
47
66
|
if (options.opacity == void 0 || isNaN(options.opacity)) {
|
|
48
67
|
options.opacity = this.current.opacity ?? 1;
|
|
49
68
|
} else {
|
|
50
|
-
|
|
51
|
-
|
|
69
|
+
// A numeric string such as "0.5" passes the isNaN check; write it as a
|
|
70
|
+
// number, as Wasm does.
|
|
71
|
+
options.opacity = Math.max(0, Math.min(1, Number(options.opacity)));
|
|
52
72
|
}
|
|
53
73
|
pathOptions.opacity = options.opacity;
|
|
54
74
|
const extGStates = this._createExtGStates(options.opacity);
|
|
@@ -78,16 +98,18 @@ exports._getPathOptions = function _getPathOptions(
|
|
|
78
98
|
pathOptions.fill = pathOptions.fillModel.color;
|
|
79
99
|
}
|
|
80
100
|
|
|
101
|
+
// A fill or stroke replaces the default color. Resolving that default under
|
|
102
|
+
// colorName would write an unused Separation color space for the ink.
|
|
103
|
+
const paintsColor = options.color || (!options.fill && !options.stroke);
|
|
81
104
|
pathOptions.colorModel = this._transformColor(
|
|
82
|
-
options.color
|
|
83
|
-
colorOpts,
|
|
105
|
+
options.color,
|
|
106
|
+
paintsColor ? colorOpts : { ...colorOpts, colorName: undefined },
|
|
84
107
|
);
|
|
85
108
|
pathOptions.color = pathOptions.colorModel.color;
|
|
86
109
|
pathOptions.colorspace = pathOptions.colorModel.colorspace;
|
|
87
110
|
|
|
88
111
|
// rotation
|
|
89
|
-
if (
|
|
90
|
-
const rotation = parseFloat(options.rotation);
|
|
112
|
+
if (rotation !== undefined) {
|
|
91
113
|
pathOptions.rotation = rotation;
|
|
92
114
|
pathOptions.rotationOrigin = options.rotationOrigin || null;
|
|
93
115
|
}
|
|
@@ -117,15 +139,18 @@ exports._getPathOptions = function _getPathOptions(
|
|
|
117
139
|
pathOptions.lineCap = this._lineCap(options.lineCap);
|
|
118
140
|
}
|
|
119
141
|
|
|
120
|
-
if (
|
|
121
|
-
if (!isNaN(options.miterLimit)) {
|
|
122
|
-
pathOptions.miterLimit = options.miterLimit;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
142
|
+
if (miterLimit !== undefined) pathOptions.miterLimit = miterLimit;
|
|
125
143
|
|
|
126
144
|
return pathOptions;
|
|
127
145
|
};
|
|
128
146
|
|
|
147
|
+
/**
|
|
148
|
+
* The distance between two points.
|
|
149
|
+
* @private
|
|
150
|
+
* @param {number[]} coordA - The first [x, y] point.
|
|
151
|
+
* @param {number[]} coordB - The second [x, y] point.
|
|
152
|
+
* @returns {number} The distance.
|
|
153
|
+
*/
|
|
129
154
|
exports._getDistance = function _getDistance(coordA, coordB) {
|
|
130
155
|
const disX = Math.abs(coordB[0] - coordA[0]);
|
|
131
156
|
const disY = Math.abs(coordB[1] - coordA[1]);
|
|
@@ -136,6 +161,16 @@ exports._getDistance = function _getDistance(coordA, coordB) {
|
|
|
136
161
|
|
|
137
162
|
exports._getTransformParams = getTransformParams;
|
|
138
163
|
|
|
164
|
+
/**
|
|
165
|
+
* The transformation matrix that rotates content around an origin.
|
|
166
|
+
* @private
|
|
167
|
+
* @param {number} inAngle - The rotation in degrees.
|
|
168
|
+
* @param {number} x - The origin x.
|
|
169
|
+
* @param {number} y - The origin y.
|
|
170
|
+
* @param {number} offsetX - The content x relative to the origin.
|
|
171
|
+
* @param {number} offsetY - The content y relative to the origin.
|
|
172
|
+
* @returns {number[]} The [a, b, c, d, e, f] matrix.
|
|
173
|
+
*/
|
|
139
174
|
function getTransformParams(inAngle, x, y, offsetX, offsetY) {
|
|
140
175
|
const theta = toRadians(inAngle);
|
|
141
176
|
const cosTheta = Math.cos(theta);
|
|
@@ -145,6 +180,17 @@ function getTransformParams(inAngle, x, y, offsetX, offsetY) {
|
|
|
145
180
|
return [cosTheta, -sinTheta, sinTheta, cosTheta, x - nx, y - ny];
|
|
146
181
|
}
|
|
147
182
|
|
|
183
|
+
/**
|
|
184
|
+
* Translate a content context to a point and apply the rotation options.
|
|
185
|
+
* @private
|
|
186
|
+
* @param {Object} context - The content context.
|
|
187
|
+
* @param {number} x - The PDF x.
|
|
188
|
+
* @param {number} y - The PDF y.
|
|
189
|
+
* @param {Object} options - The path options: rotation, rotationOrigin,
|
|
190
|
+
* useGivenCoords, originX, originY and deltaY.
|
|
191
|
+
* @returns {void}
|
|
192
|
+
* @throws {TypeError} If a Recipe rotation origin is converted without an active page.
|
|
193
|
+
*/
|
|
148
194
|
exports._setRotationContext = function _setRotationTransform(
|
|
149
195
|
context,
|
|
150
196
|
x,
|
|
@@ -185,6 +231,12 @@ exports._setRotationContext = function _setRotationTransform(
|
|
|
185
231
|
}
|
|
186
232
|
};
|
|
187
233
|
|
|
234
|
+
/**
|
|
235
|
+
* Whether the options carry an explicit [x, y] rotation origin.
|
|
236
|
+
* @private
|
|
237
|
+
* @param {Object} options - The path options.
|
|
238
|
+
* @returns {boolean} True for a two-element rotationOrigin array.
|
|
239
|
+
*/
|
|
188
240
|
function hasRotation(options) {
|
|
189
241
|
return (
|
|
190
242
|
options.rotationOrigin &&
|
|
@@ -193,10 +245,23 @@ function hasRotation(options) {
|
|
|
193
245
|
);
|
|
194
246
|
}
|
|
195
247
|
|
|
248
|
+
/**
|
|
249
|
+
* Convert degrees to radians, wrapping at 360.
|
|
250
|
+
* @private
|
|
251
|
+
* @param {number} angle - The angle in degrees.
|
|
252
|
+
* @returns {number} The angle in radians.
|
|
253
|
+
*/
|
|
196
254
|
function toRadians(angle) {
|
|
197
255
|
return 2 * Math.PI * ((angle % 360) / 360);
|
|
198
256
|
}
|
|
199
257
|
|
|
258
|
+
/**
|
|
259
|
+
* The transformation matrix for skew angles.
|
|
260
|
+
* @private
|
|
261
|
+
* @param {number} [skewXAngle=0] - The skew off the x axis, in degrees.
|
|
262
|
+
* @param {number} [skewYAngle=0] - The skew off the y axis, in degrees.
|
|
263
|
+
* @returns {number[]} The [a, b, c, d, e, f] matrix.
|
|
264
|
+
*/
|
|
200
265
|
function getSkewTransform(skewXAngle = 0, skewYAngle = 0) {
|
|
201
266
|
const alpha = toRadians(skewXAngle);
|
|
202
267
|
const beta = toRadians(skewYAngle);
|
|
@@ -206,6 +271,13 @@ function getSkewTransform(skewXAngle = 0, skewYAngle = 0) {
|
|
|
206
271
|
return [1, tanAlpha, tanBeta, 1, 0, 0];
|
|
207
272
|
}
|
|
208
273
|
|
|
274
|
+
/**
|
|
275
|
+
* Apply the skew options to a content context.
|
|
276
|
+
* @private
|
|
277
|
+
* @param {Object} context - The content context.
|
|
278
|
+
* @param {Object} options - The path options: skewX and skewY.
|
|
279
|
+
* @returns {void}
|
|
280
|
+
*/
|
|
209
281
|
exports._setSkewContext = function _setSkewTransform(context, options) {
|
|
210
282
|
if (options.skewX || options.skewY) {
|
|
211
283
|
const sm = getSkewTransform(options.skewX, options.skewY);
|
|
@@ -214,12 +286,68 @@ exports._setSkewContext = function _setSkewTransform(context, options) {
|
|
|
214
286
|
}
|
|
215
287
|
};
|
|
216
288
|
|
|
289
|
+
/**
|
|
290
|
+
* Apply a [scaleX, scaleY] ratio to a content context.
|
|
291
|
+
* @private
|
|
292
|
+
* @param {Object} context - The content context.
|
|
293
|
+
* @param {Object} options - The path options: ratio.
|
|
294
|
+
* @returns {void}
|
|
295
|
+
*/
|
|
217
296
|
exports._setScalingTransform = function _setScalingTransform(context, options) {
|
|
218
297
|
if (options.ratio) {
|
|
219
298
|
context.cm(options.ratio[0], 0, 0, options.ratio[1], 0, 0);
|
|
220
299
|
}
|
|
221
300
|
};
|
|
222
301
|
|
|
302
|
+
/**
|
|
303
|
+
* Options for a low-level drawing call. A separation color is not a device
|
|
304
|
+
* color: the caller sets it on the form XObject, so the low-level call gets
|
|
305
|
+
* neither its color nor its colorspace.
|
|
306
|
+
* @private
|
|
307
|
+
* @param {Object} pathOptions - The path options.
|
|
308
|
+
* @returns {Object} The path options, or a copy without color and colorspace
|
|
309
|
+
* for a separation color.
|
|
310
|
+
*/
|
|
311
|
+
exports._devicePathOptions = function _devicePathOptions(pathOptions) {
|
|
312
|
+
if (pathOptions.colorspace !== Colorspace.SEPARATION) return pathOptions;
|
|
313
|
+
const { color, colorspace, ...deviceOptions } = pathOptions;
|
|
314
|
+
return deviceOptions;
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Select a Separation color in a form XObject. Device colors are left to the
|
|
319
|
+
* low-level drawing call, so their output is unchanged.
|
|
320
|
+
* @private
|
|
321
|
+
* @param {Object} xObject - The form the color is selected in.
|
|
322
|
+
* @param {Object} [colorModel] - The color model from _transformColor().
|
|
323
|
+
* @param {boolean} stroke - Select the stroking instead of the fill color.
|
|
324
|
+
* @returns {void}
|
|
325
|
+
*/
|
|
326
|
+
exports._setSeparationColor = function _setSeparationColor(
|
|
327
|
+
xObject,
|
|
328
|
+
colorModel,
|
|
329
|
+
stroke,
|
|
330
|
+
) {
|
|
331
|
+
if (!colorModel || colorModel.colorspace !== Colorspace.SEPARATION) return;
|
|
332
|
+
if (stroke) xObject.stroke(colorModel);
|
|
333
|
+
else xObject.fill(colorModel);
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Draw content through a form XObject, creating it with the callback unless
|
|
338
|
+
* `options.xObject` supplies one, then place it with rotation, skew and
|
|
339
|
+
* scaling.
|
|
340
|
+
* @private
|
|
341
|
+
* @param {Recipe} self - The recipe instance.
|
|
342
|
+
* @param {number} x - The PDF x.
|
|
343
|
+
* @param {number} y - The PDF y.
|
|
344
|
+
* @param {number} width - The form width.
|
|
345
|
+
* @param {number} height - The form height.
|
|
346
|
+
* @param {Object} options - The path options.
|
|
347
|
+
* @param {function(Object, xObjectForm): void} callback - Draws into a new form.
|
|
348
|
+
* @returns {void}
|
|
349
|
+
* @throws {Error} If no page content context is active.
|
|
350
|
+
*/
|
|
223
351
|
exports._drawObject = function _drawObject(
|
|
224
352
|
self,
|
|
225
353
|
x,
|
|
@@ -253,12 +381,19 @@ exports._drawObject = function _drawObject(
|
|
|
253
381
|
context.doXObject(xObject).Q();
|
|
254
382
|
};
|
|
255
383
|
|
|
384
|
+
/**
|
|
385
|
+
* The PDF line cap number of a `Recipe.LineCap` value.
|
|
386
|
+
* @private
|
|
387
|
+
* @param {Recipe.LineCap} [type] - The cap style; round when omitted or unknown.
|
|
388
|
+
* @returns {number} 0 for butt, 1 for round, 2 for square.
|
|
389
|
+
*/
|
|
256
390
|
exports._lineCap = function _lineCap(type) {
|
|
257
391
|
const round = 1;
|
|
258
392
|
let cap = round;
|
|
259
393
|
|
|
260
394
|
if (type) {
|
|
261
|
-
|
|
395
|
+
// In PDF line cap order.
|
|
396
|
+
const capStyle = [LineCap.BUTT, LineCap.ROUND, LineCap.SQUARE];
|
|
262
397
|
const capType = capStyle.indexOf(type);
|
|
263
398
|
cap = capType !== -1 ? capType : round;
|
|
264
399
|
}
|
|
@@ -266,12 +401,19 @@ exports._lineCap = function _lineCap(type) {
|
|
|
266
401
|
return cap;
|
|
267
402
|
};
|
|
268
403
|
|
|
404
|
+
/**
|
|
405
|
+
* The PDF line join number of a `Recipe.LineJoin` value.
|
|
406
|
+
* @private
|
|
407
|
+
* @param {Recipe.LineJoin} [type] - The join style; round when omitted or unknown.
|
|
408
|
+
* @returns {number} 0 for miter, 1 for round, 2 for bevel.
|
|
409
|
+
*/
|
|
269
410
|
exports._lineJoin = function _lineJoin(type) {
|
|
270
411
|
const round = 1;
|
|
271
412
|
let join = round;
|
|
272
413
|
|
|
273
414
|
if (type) {
|
|
274
|
-
|
|
415
|
+
// In PDF line join order.
|
|
416
|
+
const joinStyle = [LineJoin.MITER, LineJoin.ROUND, LineJoin.BEVEL];
|
|
275
417
|
const joinType = joinStyle.indexOf(type);
|
|
276
418
|
join = joinType !== -1 ? joinType : round;
|
|
277
419
|
}
|