@muhammara/native-core 7.0.0-beta.3 → 7.0.0-rc.1
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 +86 -30
- 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 +482 -105
- package/lib/recipe/appendPage.js +8 -1
- 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 +154 -21
- package/lib/recipe/image.js +34 -9
- 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 +379 -275
- package/lib/recipe/parameters.js +11 -2
- 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 +260 -173
- package/lib/recipe/text.helper.js +176 -10
- package/lib/recipe/text.js +420 -115
- package/lib/recipe/utils.js +66 -3
- package/lib/recipe/vector-line.js +101 -39
- package/lib/recipe/vector-polygon.js +18 -6
- package/lib/recipe/vector.helper.js +150 -13
- package/lib/recipe/vector.js +150 -53
- package/lib/recipe/xObjectForm.js +73 -10
- package/lib/recipe-constants.js +373 -0
- package/muhammara.d.ts +3487 -152
- package/package.json +1 -1
package/lib/recipe/utils.js
CHANGED
|
@@ -1,12 +1,39 @@
|
|
|
1
1
|
const ANNOTATION_PREFIX = "Annots";
|
|
2
2
|
|
|
3
|
+
/** Recipe page content-stream lifecycle states. */
|
|
4
|
+
const PAGE_CONTEXT_STATE = Object.freeze({
|
|
5
|
+
IDLE: "idle",
|
|
6
|
+
ACTIVE_NEW: "active-new",
|
|
7
|
+
ACTIVE_EDIT: "active-edit",
|
|
8
|
+
PAUSED_NEW: "paused-new",
|
|
9
|
+
PAUSED_EDIT: "paused-edit",
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Copies nested option objects and arrays without discarding callback values.
|
|
14
|
+
* @private
|
|
15
|
+
* @param {*} value - The options or value to copy.
|
|
16
|
+
* @returns {*} The copy; functions and primitives are returned as they are.
|
|
17
|
+
*/
|
|
18
|
+
function cloneOptions(value) {
|
|
19
|
+
if (Array.isArray(value)) return value.map(cloneOptions);
|
|
20
|
+
if (value && typeof value === "object") {
|
|
21
|
+
return Object.fromEntries(
|
|
22
|
+
Object.entries(value).map(([key, item]) => [key, cloneOptions(item)]),
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
|
|
3
28
|
/**
|
|
4
29
|
* Append PDF Page with annotations.
|
|
5
30
|
*
|
|
6
31
|
* @param {any} pdfWriter - Hummus writer.
|
|
7
|
-
* @param {string|any} sourcePDFPath - The
|
|
8
|
-
* @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.
|
|
9
34
|
* @private
|
|
35
|
+
* @returns {void}
|
|
36
|
+
* @throws {Error} If the source cannot be read or the page does not exist.
|
|
10
37
|
*/
|
|
11
38
|
function appendPDFPageFromPDFWithAnnotations(
|
|
12
39
|
pdfWriter,
|
|
@@ -44,8 +71,13 @@ function appendPDFPageFromPDFWithAnnotations(
|
|
|
44
71
|
* Append PDF Pages with annotations.
|
|
45
72
|
*
|
|
46
73
|
* @param {any} pdfWriter - Hummus writer.
|
|
47
|
-
* @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.
|
|
48
78
|
* @private
|
|
79
|
+
* @returns {void}
|
|
80
|
+
* @throws {Error} If the source cannot be read or a page does not exist.
|
|
49
81
|
*/
|
|
50
82
|
function appendPDFPagesFromPDFWithAnnotations(
|
|
51
83
|
pdfWriter,
|
|
@@ -72,7 +104,38 @@ function appendPDFPagesFromPDFWithAnnotations(
|
|
|
72
104
|
}
|
|
73
105
|
}
|
|
74
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Resolves a text font size from `size`, its `fontSize` alias, or a fallback,
|
|
109
|
+
* rejecting a size that is not a finite number greater than zero. Zero,
|
|
110
|
+
* negative, and infinite sizes have
|
|
111
|
+
* no usable meaning: they draw nothing readable and measure to nonsensical font
|
|
112
|
+
* metrics, so they are reported as invalid input naming the option and the
|
|
113
|
+
* value. Omitting both options, or passing `null` or `undefined`, selects the
|
|
114
|
+
* fallback.
|
|
115
|
+
* @param {Object} [options] - Options holding `size` or `fontSize`.
|
|
116
|
+
* @param {number} [fallback] - Size used when neither option is given.
|
|
117
|
+
* @returns {number} The resolved font size in PDF points.
|
|
118
|
+
* @throws {RangeError} If the given size is not a finite number greater than
|
|
119
|
+
* zero.
|
|
120
|
+
*/
|
|
121
|
+
function resolveFontSize(options = {}, fallback) {
|
|
122
|
+
const name = options.size == null ? "fontSize" : "size";
|
|
123
|
+
const size = options[name];
|
|
124
|
+
if (size == null) {
|
|
125
|
+
return fallback;
|
|
126
|
+
}
|
|
127
|
+
if (!(size > 0) || size === Infinity) {
|
|
128
|
+
throw new RangeError(
|
|
129
|
+
`Text ${name} must be a number greater than zero, received ${size}`,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
return size;
|
|
133
|
+
}
|
|
134
|
+
|
|
75
135
|
exports.ANNOTATION_PREFIX = ANNOTATION_PREFIX;
|
|
136
|
+
exports.PAGE_CONTEXT_STATE = PAGE_CONTEXT_STATE;
|
|
137
|
+
exports.cloneOptions = cloneOptions;
|
|
138
|
+
exports.resolveFontSize = resolveFontSize;
|
|
76
139
|
exports.appendPDFPageFromPDFWithAnnotations =
|
|
77
140
|
appendPDFPageFromPDFWithAnnotations;
|
|
78
141
|
exports.appendPDFPagesFromPDFWithAnnotations =
|
|
@@ -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,5 +1,19 @@
|
|
|
1
1
|
const { xObjectForm } = require("./xObjectForm");
|
|
2
|
-
|
|
2
|
+
const { resolveFontSize } = require("./utils");
|
|
3
|
+
const { Colorspace, LineCap, LineJoin } = require("../recipe-constants");
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Resolve drawing and text options into path options: font, size, colors
|
|
7
|
+
* and color models, line style, opacity graphics states, rotation, skew and
|
|
8
|
+
* dash. Clamps `options.opacity` to 0..1 in place.
|
|
9
|
+
* @private
|
|
10
|
+
* @param {Object} [options] - The drawing or text options.
|
|
11
|
+
* @param {number} originX - The PDF x of the default rotation origin.
|
|
12
|
+
* @param {number} originY - The PDF y of the default rotation origin.
|
|
13
|
+
* @returns {Object} The resolved path options.
|
|
14
|
+
* @throws {RangeError} If a given font size is not greater than zero.
|
|
15
|
+
* @throws {Error} If the font cannot be loaded.
|
|
16
|
+
*/
|
|
3
17
|
exports._getPathOptions = function _getPathOptions(
|
|
4
18
|
options = {},
|
|
5
19
|
originX,
|
|
@@ -13,7 +27,7 @@ exports._getPathOptions = function _getPathOptions(
|
|
|
13
27
|
originX,
|
|
14
28
|
originY,
|
|
15
29
|
font: this._getFont(options),
|
|
16
|
-
size: options
|
|
30
|
+
size: resolveFontSize(options, this.current.defaultFontSize),
|
|
17
31
|
charSpace: options.charSpace || 0,
|
|
18
32
|
underline: false,
|
|
19
33
|
strikeOut: false,
|
|
@@ -46,20 +60,15 @@ exports._getPathOptions = function _getPathOptions(
|
|
|
46
60
|
if (options.opacity == void 0 || isNaN(options.opacity)) {
|
|
47
61
|
options.opacity = this.current.opacity ?? 1;
|
|
48
62
|
} else {
|
|
49
|
-
|
|
50
|
-
|
|
63
|
+
// A numeric string such as "0.5" passes the isNaN check; write it as a
|
|
64
|
+
// number, as Wasm does.
|
|
65
|
+
options.opacity = Math.max(0, Math.min(1, Number(options.opacity)));
|
|
51
66
|
}
|
|
52
67
|
pathOptions.opacity = options.opacity;
|
|
53
68
|
const extGStates = this._createExtGStates(options.opacity);
|
|
54
69
|
pathOptions.strokeGsId = extGStates.stroke;
|
|
55
70
|
pathOptions.fillGsId = extGStates.fill;
|
|
56
71
|
|
|
57
|
-
if (options.size || options.fontSize) {
|
|
58
|
-
const size = options.size || options.fontSize;
|
|
59
|
-
if (!isNaN(size)) {
|
|
60
|
-
pathOptions.size = size <= 0 ? 1 : size;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
72
|
if (options.width || options.lineWidth) {
|
|
64
73
|
const width = options.width || options.lineWidth;
|
|
65
74
|
if (!isNaN(width)) {
|
|
@@ -83,9 +92,13 @@ exports._getPathOptions = function _getPathOptions(
|
|
|
83
92
|
pathOptions.fill = pathOptions.fillModel.color;
|
|
84
93
|
}
|
|
85
94
|
|
|
95
|
+
// A fill or stroke replaces the default color. Resolving that default under
|
|
96
|
+
// colorName would write an unused Separation color space for the ink.
|
|
97
|
+
const paintsColor =
|
|
98
|
+
options.color || options.colour || (!options.fill && !options.stroke);
|
|
86
99
|
pathOptions.colorModel = this._transformColor(
|
|
87
100
|
options.color || options.colour,
|
|
88
|
-
colorOpts,
|
|
101
|
+
paintsColor ? colorOpts : { ...colorOpts, colorName: undefined },
|
|
89
102
|
);
|
|
90
103
|
pathOptions.color = pathOptions.colorModel.color;
|
|
91
104
|
pathOptions.colorspace = pathOptions.colorModel.colorspace;
|
|
@@ -131,6 +144,13 @@ exports._getPathOptions = function _getPathOptions(
|
|
|
131
144
|
return pathOptions;
|
|
132
145
|
};
|
|
133
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
|
+
*/
|
|
134
154
|
exports._getDistance = function _getDistance(coordA, coordB) {
|
|
135
155
|
const disX = Math.abs(coordB[0] - coordA[0]);
|
|
136
156
|
const disY = Math.abs(coordB[1] - coordA[1]);
|
|
@@ -141,6 +161,16 @@ exports._getDistance = function _getDistance(coordA, coordB) {
|
|
|
141
161
|
|
|
142
162
|
exports._getTransformParams = getTransformParams;
|
|
143
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
|
+
*/
|
|
144
174
|
function getTransformParams(inAngle, x, y, offsetX, offsetY) {
|
|
145
175
|
const theta = toRadians(inAngle);
|
|
146
176
|
const cosTheta = Math.cos(theta);
|
|
@@ -150,6 +180,17 @@ function getTransformParams(inAngle, x, y, offsetX, offsetY) {
|
|
|
150
180
|
return [cosTheta, -sinTheta, sinTheta, cosTheta, x - nx, y - ny];
|
|
151
181
|
}
|
|
152
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
|
+
*/
|
|
153
194
|
exports._setRotationContext = function _setRotationTransform(
|
|
154
195
|
context,
|
|
155
196
|
x,
|
|
@@ -190,6 +231,12 @@ exports._setRotationContext = function _setRotationTransform(
|
|
|
190
231
|
}
|
|
191
232
|
};
|
|
192
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
|
+
*/
|
|
193
240
|
function hasRotation(options) {
|
|
194
241
|
return (
|
|
195
242
|
options.rotationOrigin &&
|
|
@@ -198,10 +245,23 @@ function hasRotation(options) {
|
|
|
198
245
|
);
|
|
199
246
|
}
|
|
200
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
|
+
*/
|
|
201
254
|
function toRadians(angle) {
|
|
202
255
|
return 2 * Math.PI * ((angle % 360) / 360);
|
|
203
256
|
}
|
|
204
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
|
+
*/
|
|
205
265
|
function getSkewTransform(skewXAngle = 0, skewYAngle = 0) {
|
|
206
266
|
const alpha = toRadians(skewXAngle);
|
|
207
267
|
const beta = toRadians(skewYAngle);
|
|
@@ -211,6 +271,13 @@ function getSkewTransform(skewXAngle = 0, skewYAngle = 0) {
|
|
|
211
271
|
return [1, tanAlpha, tanBeta, 1, 0, 0];
|
|
212
272
|
}
|
|
213
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
|
+
*/
|
|
214
281
|
exports._setSkewContext = function _setSkewTransform(context, options) {
|
|
215
282
|
if (options.skewX || options.skewY) {
|
|
216
283
|
const sm = getSkewTransform(options.skewX, options.skewY);
|
|
@@ -219,12 +286,68 @@ exports._setSkewContext = function _setSkewTransform(context, options) {
|
|
|
219
286
|
}
|
|
220
287
|
};
|
|
221
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
|
+
*/
|
|
222
296
|
exports._setScalingTransform = function _setScalingTransform(context, options) {
|
|
223
297
|
if (options.ratio) {
|
|
224
298
|
context.cm(options.ratio[0], 0, 0, options.ratio[1], 0, 0);
|
|
225
299
|
}
|
|
226
300
|
};
|
|
227
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
|
+
*/
|
|
228
351
|
exports._drawObject = function _drawObject(
|
|
229
352
|
self,
|
|
230
353
|
x,
|
|
@@ -258,12 +381,19 @@ exports._drawObject = function _drawObject(
|
|
|
258
381
|
context.doXObject(xObject).Q();
|
|
259
382
|
};
|
|
260
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
|
+
*/
|
|
261
390
|
exports._lineCap = function _lineCap(type) {
|
|
262
391
|
const round = 1;
|
|
263
392
|
let cap = round;
|
|
264
393
|
|
|
265
394
|
if (type) {
|
|
266
|
-
|
|
395
|
+
// In PDF line cap order.
|
|
396
|
+
const capStyle = [LineCap.BUTT, LineCap.ROUND, LineCap.SQUARE];
|
|
267
397
|
const capType = capStyle.indexOf(type);
|
|
268
398
|
cap = capType !== -1 ? capType : round;
|
|
269
399
|
}
|
|
@@ -271,12 +401,19 @@ exports._lineCap = function _lineCap(type) {
|
|
|
271
401
|
return cap;
|
|
272
402
|
};
|
|
273
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
|
+
*/
|
|
274
410
|
exports._lineJoin = function _lineJoin(type) {
|
|
275
411
|
const round = 1;
|
|
276
412
|
let join = round;
|
|
277
413
|
|
|
278
414
|
if (type) {
|
|
279
|
-
|
|
415
|
+
// In PDF line join order.
|
|
416
|
+
const joinStyle = [LineJoin.MITER, LineJoin.ROUND, LineJoin.BEVEL];
|
|
280
417
|
const joinType = joinStyle.indexOf(type);
|
|
281
418
|
join = joinType !== -1 ? joinType : round;
|
|
282
419
|
}
|