@pdfme/pdf-lib 1.18.1 → 1.18.4
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 +11 -2
- package/cjs/api/PDFDocument.js +195 -225
- package/cjs/api/PDFDocument.js.map +1 -1
- package/cjs/api/PDFEmbeddedFile.js +30 -33
- package/cjs/api/PDFEmbeddedFile.js.map +1 -1
- package/cjs/api/PDFEmbeddedPage.js +5 -7
- package/cjs/api/PDFEmbeddedPage.js.map +1 -1
- package/cjs/api/PDFFont.js +6 -8
- package/cjs/api/PDFFont.js.map +1 -1
- package/cjs/api/PDFImage.js +14 -16
- package/cjs/api/PDFImage.js.map +1 -1
- package/cjs/api/PDFJavaScript.js +19 -22
- package/cjs/api/PDFJavaScript.js.map +1 -1
- package/cjs/api/PDFPage.d.ts +1 -0
- package/cjs/api/PDFPage.d.ts.map +1 -1
- package/cjs/api/PDFPage.js +30 -17
- package/cjs/api/PDFPage.js.map +1 -1
- package/cjs/api/PDFPageOptions.d.ts +15 -8
- package/cjs/api/PDFPageOptions.d.ts.map +1 -1
- package/cjs/api/PDFPageOptions.js.map +1 -1
- package/cjs/api/form/PDFField.js +1 -1
- package/cjs/api/form/PDFField.js.map +1 -1
- package/cjs/api/form/PDFForm.js +1 -1
- package/cjs/api/form/PDFForm.js.map +1 -1
- package/cjs/api/form/appearances.js +56 -16
- package/cjs/api/form/appearances.js.map +1 -1
- package/cjs/api/operations.d.ts +16 -0
- package/cjs/api/operations.d.ts.map +1 -1
- package/cjs/api/operations.js +53 -5
- package/cjs/api/operations.js.map +1 -1
- package/cjs/api/svg.d.ts +7 -1
- package/cjs/api/svg.d.ts.map +1 -1
- package/cjs/api/svg.js +332 -1016
- package/cjs/api/svg.js.map +1 -1
- package/cjs/core/PDFContext.js +11 -2
- package/cjs/core/PDFContext.js.map +1 -1
- package/cjs/core/embedders/CustomFontEmbedder.js +62 -74
- package/cjs/core/embedders/CustomFontEmbedder.js.map +1 -1
- package/cjs/core/embedders/CustomFontSubsetEmbedder.js +3 -5
- package/cjs/core/embedders/CustomFontSubsetEmbedder.js.map +1 -1
- package/cjs/core/embedders/FileEmbedder.js +30 -32
- package/cjs/core/embedders/FileEmbedder.js.map +1 -1
- package/cjs/core/embedders/JavaScriptEmbedder.js +12 -14
- package/cjs/core/embedders/JavaScriptEmbedder.js.map +1 -1
- package/cjs/core/embedders/JpegEmbedder.js +54 -59
- package/cjs/core/embedders/JpegEmbedder.js.map +1 -1
- package/cjs/core/embedders/PDFPageEmbedder.js +22 -26
- package/cjs/core/embedders/PDFPageEmbedder.js.map +1 -1
- package/cjs/core/embedders/PngEmbedder.js +20 -25
- package/cjs/core/embedders/PngEmbedder.js.map +1 -1
- package/cjs/core/parser/PDFObjectStreamParser.js +15 -17
- package/cjs/core/parser/PDFObjectStreamParser.js.map +1 -1
- package/cjs/core/parser/PDFParser.js +66 -74
- package/cjs/core/parser/PDFParser.js.map +1 -1
- package/cjs/core/writers/PDFStreamWriter.js +53 -55
- package/cjs/core/writers/PDFStreamWriter.js.map +1 -1
- package/cjs/core/writers/PDFWriter.js +62 -66
- package/cjs/core/writers/PDFWriter.js.map +1 -1
- package/cjs/types/index.d.ts +4 -4
- package/cjs/types/index.d.ts.map +1 -1
- package/dist/pdf-lib.esm.js +1153 -2590
- package/dist/pdf-lib.esm.js.map +1 -1
- package/dist/pdf-lib.esm.min.js +1 -15
- package/dist/pdf-lib.esm.min.js.map +1 -1
- package/dist/pdf-lib.js +1153 -2590
- package/dist/pdf-lib.js.map +1 -1
- package/dist/pdf-lib.min.js +1 -15
- package/dist/pdf-lib.min.js.map +1 -1
- package/es/api/PDFDocument.js +195 -226
- package/es/api/PDFDocument.js.map +1 -1
- package/es/api/PDFEmbeddedFile.js +30 -33
- package/es/api/PDFEmbeddedFile.js.map +1 -1
- package/es/api/PDFEmbeddedPage.js +5 -8
- package/es/api/PDFEmbeddedPage.js.map +1 -1
- package/es/api/PDFFont.js +6 -9
- package/es/api/PDFFont.js.map +1 -1
- package/es/api/PDFImage.js +14 -17
- package/es/api/PDFImage.js.map +1 -1
- package/es/api/PDFJavaScript.js +19 -22
- package/es/api/PDFJavaScript.js.map +1 -1
- package/es/api/PDFPage.d.ts +1 -0
- package/es/api/PDFPage.d.ts.map +1 -1
- package/es/api/PDFPage.js +30 -18
- package/es/api/PDFPage.js.map +1 -1
- package/es/api/PDFPageOptions.d.ts +15 -8
- package/es/api/PDFPageOptions.d.ts.map +1 -1
- package/es/api/PDFPageOptions.js.map +1 -1
- package/es/api/form/PDFField.js +1 -1
- package/es/api/form/PDFField.js.map +1 -1
- package/es/api/form/PDFForm.js +1 -1
- package/es/api/form/PDFForm.js.map +1 -1
- package/es/api/form/appearances.js +56 -16
- package/es/api/form/appearances.js.map +1 -1
- package/es/api/operations.d.ts +16 -0
- package/es/api/operations.d.ts.map +1 -1
- package/es/api/operations.js +54 -6
- package/es/api/operations.js.map +1 -1
- package/es/api/svg.d.ts +7 -1
- package/es/api/svg.d.ts.map +1 -1
- package/es/api/svg.js +333 -1017
- package/es/api/svg.js.map +1 -1
- package/es/core/PDFContext.js +11 -2
- package/es/core/PDFContext.js.map +1 -1
- package/es/core/embedders/CustomFontEmbedder.js +62 -75
- package/es/core/embedders/CustomFontEmbedder.js.map +1 -1
- package/es/core/embedders/CustomFontSubsetEmbedder.js +3 -6
- package/es/core/embedders/CustomFontSubsetEmbedder.js.map +1 -1
- package/es/core/embedders/FileEmbedder.js +30 -33
- package/es/core/embedders/FileEmbedder.js.map +1 -1
- package/es/core/embedders/JavaScriptEmbedder.js +12 -15
- package/es/core/embedders/JavaScriptEmbedder.js.map +1 -1
- package/es/core/embedders/JpegEmbedder.js +54 -59
- package/es/core/embedders/JpegEmbedder.js.map +1 -1
- package/es/core/embedders/PDFPageEmbedder.js +22 -27
- package/es/core/embedders/PDFPageEmbedder.js.map +1 -1
- package/es/core/embedders/PngEmbedder.js +20 -25
- package/es/core/embedders/PngEmbedder.js.map +1 -1
- package/es/core/parser/PDFObjectStreamParser.js +15 -18
- package/es/core/parser/PDFObjectStreamParser.js.map +1 -1
- package/es/core/parser/PDFParser.js +66 -75
- package/es/core/parser/PDFParser.js.map +1 -1
- package/es/core/writers/PDFStreamWriter.js +53 -56
- package/es/core/writers/PDFStreamWriter.js.map +1 -1
- package/es/core/writers/PDFWriter.js +62 -67
- package/es/core/writers/PDFWriter.js.map +1 -1
- package/es/types/index.d.ts +4 -4
- package/es/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/api/PDFPage.ts +15 -0
- package/src/api/PDFPageOptions.ts +15 -8
- package/src/api/operations.ts +82 -8
- package/src/api/svg.ts +305 -1086
- package/src/types/index.ts +6 -1
- package/ts3.4/cjs/api/PDFPage.d.ts +1 -0
- package/ts3.4/cjs/api/PDFPageOptions.d.ts +15 -8
- package/ts3.4/cjs/api/operations.d.ts +16 -0
- package/ts3.4/cjs/api/svg.d.ts +7 -1
- package/ts3.4/cjs/types/index.d.ts +4 -4
- package/ts3.4/es/api/PDFPage.d.ts +1 -0
- package/ts3.4/es/api/PDFPageOptions.d.ts +15 -8
- package/ts3.4/es/api/operations.d.ts +16 -0
- package/ts3.4/es/api/svg.d.ts +7 -1
- package/ts3.4/es/types/index.d.ts +4 -4
package/cjs/api/svg.js
CHANGED
|
@@ -1,35 +1,73 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.drawSvg = void 0;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
4
|
const node_html_better_parser_1 = require("node-html-better-parser");
|
|
6
5
|
const colors_1 = require("./colors");
|
|
7
6
|
const rotations_1 = require("./rotations");
|
|
8
7
|
const operators_1 = require("./operators");
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
8
|
+
const matrix_1 = require("../types/matrix");
|
|
9
|
+
const combineMatrix = ([a, b, c, d, e, f], [a2, b2, c2, d2, e2, f2]) => [
|
|
10
|
+
a * a2 + c * b2,
|
|
11
|
+
b * a2 + d * b2,
|
|
12
|
+
a * c2 + c * d2,
|
|
13
|
+
b * c2 + d * d2,
|
|
14
|
+
a * e2 + c * f2 + e,
|
|
15
|
+
b * e2 + d * f2 + f,
|
|
16
|
+
];
|
|
17
|
+
const applyTransformation = ([a, b, c, d, e, f], { x, y }) => ({
|
|
18
|
+
x: a * x + c * y + e,
|
|
19
|
+
y: b * x + d * y + f
|
|
20
|
+
});
|
|
21
|
+
const transformationToMatrix = (name, args) => {
|
|
22
|
+
switch (name) {
|
|
23
|
+
case 'scale':
|
|
24
|
+
case 'scaleX':
|
|
25
|
+
case 'scaleY': {
|
|
26
|
+
// [sx 0 0 sy 0 0]
|
|
27
|
+
const [sx, sy = sx] = args;
|
|
28
|
+
return [name === 'scaleY' ? 1 : sx, 0, 0, name === 'scaleX' ? 1 : sy, 0, 0];
|
|
25
29
|
}
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
case 'translate':
|
|
31
|
+
case 'translateX':
|
|
32
|
+
case 'translateY': {
|
|
33
|
+
// [1 0 0 1 tx ty]
|
|
34
|
+
const [tx, ty = tx] = args;
|
|
35
|
+
// -ty is necessary because the pdf's y axis is inverted
|
|
36
|
+
return [1, 0, 0, 1, name === 'translateY' ? 0 : tx, name === 'translateX' ? 0 : -ty];
|
|
28
37
|
}
|
|
29
|
-
|
|
30
|
-
|
|
38
|
+
case 'rotate': {
|
|
39
|
+
// [cos(a) sin(a) -sin(a) cos(a) 0 0]
|
|
40
|
+
const [a, x = 0, y = 0] = args;
|
|
41
|
+
const t1 = transformationToMatrix('translate', [x, y]);
|
|
42
|
+
const t2 = transformationToMatrix('translate', [-x, -y]);
|
|
43
|
+
// -args[0] -> the '-' operator is necessary because the pdf rotation system is inverted
|
|
44
|
+
const aRadians = (0, rotations_1.degreesToRadians)(-a);
|
|
45
|
+
const r = [Math.cos(aRadians), Math.sin(aRadians), -Math.sin(aRadians), Math.cos(aRadians), 0, 0];
|
|
46
|
+
// rotation around a point is the combination of: translate * rotate * (-translate)
|
|
47
|
+
return combineMatrix(combineMatrix(t1, r), t2);
|
|
48
|
+
}
|
|
49
|
+
case 'skewY':
|
|
50
|
+
case 'skewX': {
|
|
51
|
+
// [1 tan(a) 0 1 0 0]
|
|
52
|
+
// [1 0 tan(a) 1 0 0]
|
|
53
|
+
// -args[0] -> the '-' operator is necessary because the pdf rotation system is inverted
|
|
54
|
+
const a = (0, rotations_1.degreesToRadians)(-args[0]);
|
|
55
|
+
const skew = Math.tan(a);
|
|
56
|
+
const skewX = name === 'skewX' ? skew : 0;
|
|
57
|
+
const skewY = name === 'skewY' ? skew : 0;
|
|
58
|
+
return [1, skewY, skewX, 1, 0, 0];
|
|
59
|
+
}
|
|
60
|
+
case 'matrix': {
|
|
61
|
+
const [a, b, c, d, e, f] = args;
|
|
62
|
+
const r = transformationToMatrix('scale', [1, -1]);
|
|
63
|
+
const m = [a, b, c, d, e, f];
|
|
64
|
+
return combineMatrix(combineMatrix(r, m), r);
|
|
65
|
+
}
|
|
66
|
+
default:
|
|
67
|
+
return matrix_1.identityMatrix;
|
|
68
|
+
}
|
|
31
69
|
};
|
|
32
|
-
const
|
|
70
|
+
const combineTransformation = (matrix, name, args) => combineMatrix(matrix, transformationToMatrix(name, args));
|
|
33
71
|
const StrokeLineCapMap = {
|
|
34
72
|
butt: operators_1.LineCapStyle.Butt,
|
|
35
73
|
round: operators_1.LineCapStyle.Round,
|
|
@@ -44,662 +82,144 @@ const StrokeLineJoinMap = {
|
|
|
44
82
|
miter: operators_1.LineJoinStyle.Miter,
|
|
45
83
|
round: operators_1.LineJoinStyle.Round,
|
|
46
84
|
};
|
|
47
|
-
const getInnerSegment = (start, end, rect) => {
|
|
48
|
-
const isStartInside = isCoordinateInsideTheRect(start, rect);
|
|
49
|
-
const isEndInside = isCoordinateInsideTheRect(end, rect);
|
|
50
|
-
let resultLineStart = start;
|
|
51
|
-
let resultLineEnd = end;
|
|
52
|
-
// it means that the segment is already inside the rect
|
|
53
|
-
if (isEndInside && isStartInside)
|
|
54
|
-
return new elements_1.Segment(start, end);
|
|
55
|
-
const line = new elements_1.Segment(start, end);
|
|
56
|
-
const intersection = (0, intersections_1.getIntersections)([rect, line]);
|
|
57
|
-
// if there's no intersection it means that the line doesn't intersects the svgRect and isn't visible
|
|
58
|
-
if (intersection.length === 0)
|
|
59
|
-
return;
|
|
60
|
-
if (!isStartInside) {
|
|
61
|
-
// replace the line start point by the nearest intersection
|
|
62
|
-
const nearestPoint = intersection.sort((p1, p2) => (0, maths_1.distanceCoords)(start, p1) - (0, maths_1.distanceCoords)(start, p2))[0];
|
|
63
|
-
resultLineStart = new elements_1.Point(nearestPoint);
|
|
64
|
-
}
|
|
65
|
-
if (!isEndInside) {
|
|
66
|
-
// replace the line start point by the nearest intersection
|
|
67
|
-
const nearestPoint = intersection.sort((p1, p2) => (0, maths_1.distanceCoords)(end, p1) - (0, maths_1.distanceCoords)(end, p2))[0];
|
|
68
|
-
resultLineEnd = new elements_1.Point(nearestPoint);
|
|
69
|
-
}
|
|
70
|
-
return new elements_1.Segment(resultLineStart, resultLineEnd);
|
|
71
|
-
};
|
|
72
|
-
const cropSvgElement = (svgRect, element) => {
|
|
73
|
-
var _a, _b;
|
|
74
|
-
switch (element.tagName) {
|
|
75
|
-
case 'text': {
|
|
76
|
-
const fontSize = element.svgAttributes.fontSize || 12;
|
|
77
|
-
// TODO: compute the right font boundaries to know which characters should be drawn
|
|
78
|
-
// this is an workaround to draw text that are just a little outside the viewbox boundaries
|
|
79
|
-
const start = new elements_1.Point({
|
|
80
|
-
x: element.svgAttributes.x || 0,
|
|
81
|
-
y: element.svgAttributes.y || 0,
|
|
82
|
-
});
|
|
83
|
-
const paddingRect = new elements_1.Rectangle(new elements_1.Point({
|
|
84
|
-
x: svgRect.start.x - fontSize,
|
|
85
|
-
y: svgRect.start.y + fontSize,
|
|
86
|
-
}), new elements_1.Point({ x: svgRect.end.x + fontSize, y: svgRect.end.y - fontSize }));
|
|
87
|
-
if (!isCoordinateInsideTheRect(start, paddingRect)) {
|
|
88
|
-
element.set_content('');
|
|
89
|
-
}
|
|
90
|
-
break;
|
|
91
|
-
}
|
|
92
|
-
case 'line': {
|
|
93
|
-
const start = new elements_1.Point({
|
|
94
|
-
x: element.svgAttributes.x1,
|
|
95
|
-
y: element.svgAttributes.y1,
|
|
96
|
-
});
|
|
97
|
-
const end = new elements_1.Point({
|
|
98
|
-
x: element.svgAttributes.x2,
|
|
99
|
-
y: element.svgAttributes.y2,
|
|
100
|
-
});
|
|
101
|
-
const line = getInnerSegment(start, end, svgRect);
|
|
102
|
-
element.svgAttributes.x1 = line ? line.A.x : 0;
|
|
103
|
-
element.svgAttributes.x2 = line ? line.B.x : 0;
|
|
104
|
-
element.svgAttributes.y1 = line ? line.A.y : 0;
|
|
105
|
-
element.svgAttributes.y2 = line ? line.B.y : 0;
|
|
106
|
-
break;
|
|
107
|
-
}
|
|
108
|
-
case 'path': {
|
|
109
|
-
// the path origin coordinate
|
|
110
|
-
const basePoint = new elements_1.Point({
|
|
111
|
-
x: element.svgAttributes.x || 0,
|
|
112
|
-
y: element.svgAttributes.y || 0,
|
|
113
|
-
});
|
|
114
|
-
const normalizePoint = (p) => new elements_1.Point({ x: p.x - basePoint.x, y: p.y - basePoint.y });
|
|
115
|
-
/**
|
|
116
|
-
*
|
|
117
|
-
* @param origin is the origin of the current drawing in the page coordinate system
|
|
118
|
-
* @param command the path instruction
|
|
119
|
-
* @param params the instruction params
|
|
120
|
-
* @returns the point where the next instruction starts and the new instruction text
|
|
121
|
-
*/
|
|
122
|
-
const handlePath = (origin, command, params) => {
|
|
123
|
-
switch (command) {
|
|
124
|
-
case 'm':
|
|
125
|
-
case 'M': {
|
|
126
|
-
const isLocalInstruction = command === command.toLocaleLowerCase();
|
|
127
|
-
const nextPoint = new elements_1.Point({
|
|
128
|
-
x: (isLocalInstruction ? origin.x : basePoint.x) + params[0],
|
|
129
|
-
y: (isLocalInstruction ? origin.y : basePoint.y) + params[1],
|
|
130
|
-
});
|
|
131
|
-
return {
|
|
132
|
-
point: nextPoint,
|
|
133
|
-
command: `${command}${params[0]},${params[1]}`,
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
case 'v':
|
|
137
|
-
case 'V':
|
|
138
|
-
case 'h':
|
|
139
|
-
case 'H':
|
|
140
|
-
case 'l':
|
|
141
|
-
case 'L': {
|
|
142
|
-
const isLocalInstruction = ['l', 'v', 'h'].includes(command);
|
|
143
|
-
const getNextPoint = () => {
|
|
144
|
-
switch (command.toLocaleLowerCase()) {
|
|
145
|
-
case 'l':
|
|
146
|
-
return new elements_1.Point({
|
|
147
|
-
x: (isLocalInstruction ? origin.x : basePoint.x) + params[0],
|
|
148
|
-
y: (isLocalInstruction ? origin.y : basePoint.y) + params[1],
|
|
149
|
-
});
|
|
150
|
-
case 'v':
|
|
151
|
-
return new elements_1.Point({
|
|
152
|
-
x: origin.x,
|
|
153
|
-
y: (isLocalInstruction ? origin.y : basePoint.y) + params[0],
|
|
154
|
-
});
|
|
155
|
-
case 'h':
|
|
156
|
-
return new elements_1.Point({
|
|
157
|
-
x: (isLocalInstruction ? origin.x : basePoint.x) + params[0],
|
|
158
|
-
y: origin.y,
|
|
159
|
-
});
|
|
160
|
-
default:
|
|
161
|
-
return new elements_1.Point({
|
|
162
|
-
x: 0,
|
|
163
|
-
y: 0,
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
|
-
const nextPoint = getNextPoint();
|
|
168
|
-
const normalizedNext = normalizePoint(nextPoint);
|
|
169
|
-
let endPoint = new elements_1.Point({ x: nextPoint.x, y: nextPoint.y });
|
|
170
|
-
let startPoint = new elements_1.Point({ x: origin.x, y: origin.y });
|
|
171
|
-
const result = getInnerSegment(startPoint, endPoint, svgRect);
|
|
172
|
-
if (!result) {
|
|
173
|
-
return {
|
|
174
|
-
point: nextPoint,
|
|
175
|
-
command: `M${normalizedNext.x},${normalizedNext.y}`,
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
// if the point wasn't moved it means that it's inside the rect
|
|
179
|
-
const isStartInside = result.A.isEqual(startPoint);
|
|
180
|
-
const isEndInside = result.B.isEqual(endPoint);
|
|
181
|
-
// the intersection points are referencing the pdf coordinates, it's necessary to convert these points to the path's origin point
|
|
182
|
-
endPoint = normalizePoint(new elements_1.Point(result.B.toCoords()));
|
|
183
|
-
startPoint = normalizePoint(new elements_1.Point(result.A.toCoords()));
|
|
184
|
-
const startInstruction = isStartInside
|
|
185
|
-
? ''
|
|
186
|
-
: `M${startPoint.x},${startPoint.y}`;
|
|
187
|
-
const endInstruction = isEndInside
|
|
188
|
-
? ''
|
|
189
|
-
: `M${normalizedNext.x},${normalizedNext.y}`;
|
|
190
|
-
return {
|
|
191
|
-
point: nextPoint,
|
|
192
|
-
command: `${startInstruction} L${endPoint.x},${endPoint.y} ${endInstruction} `,
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
case 'a':
|
|
196
|
-
case 'A': {
|
|
197
|
-
const isLocalInstruction = command === 'a';
|
|
198
|
-
const [, , , , , x, y] = params;
|
|
199
|
-
const nextPoint = new elements_1.Point({
|
|
200
|
-
x: (isLocalInstruction ? origin.x : basePoint.x) + x,
|
|
201
|
-
y: (isLocalInstruction ? origin.y : basePoint.y) + y,
|
|
202
|
-
});
|
|
203
|
-
// TODO: implement the code to fit the Elliptical Arc Curve instructions into the viewbox
|
|
204
|
-
return {
|
|
205
|
-
point: nextPoint,
|
|
206
|
-
command: `${command} ${params.map((p) => `${p}`).join()}`,
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
case 'c':
|
|
210
|
-
case 'C': {
|
|
211
|
-
const isLocalInstruction = command === 'c';
|
|
212
|
-
let x = 0;
|
|
213
|
-
let y = 0;
|
|
214
|
-
for (let pendingParams = params; pendingParams.length > 0; pendingParams = pendingParams.slice(6)) {
|
|
215
|
-
const [, , , , pendingX, pendingY] = pendingParams;
|
|
216
|
-
if (isLocalInstruction) {
|
|
217
|
-
x += pendingX;
|
|
218
|
-
y += pendingY;
|
|
219
|
-
}
|
|
220
|
-
else {
|
|
221
|
-
x = pendingX;
|
|
222
|
-
y = pendingY;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
const nextPoint = new elements_1.Point({
|
|
226
|
-
x: (isLocalInstruction ? origin.x : basePoint.x) + x,
|
|
227
|
-
y: (isLocalInstruction ? origin.y : basePoint.y) + y,
|
|
228
|
-
});
|
|
229
|
-
// TODO: implement the code to fit the Cubic Bézier Curve instructions into the viewbox
|
|
230
|
-
return {
|
|
231
|
-
point: nextPoint,
|
|
232
|
-
command: `${command} ${params.map((p) => `${p}`).join()}`,
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
case 's':
|
|
236
|
-
case 'S':
|
|
237
|
-
const isLocalInstruction = command === 's';
|
|
238
|
-
let x = 0;
|
|
239
|
-
let y = 0;
|
|
240
|
-
for (let pendingParams = params; pendingParams.length > 0; pendingParams = pendingParams.slice(4)) {
|
|
241
|
-
const [, , pendingX, pendingY] = pendingParams;
|
|
242
|
-
x += pendingX;
|
|
243
|
-
y += pendingY;
|
|
244
|
-
}
|
|
245
|
-
const nextPoint = new elements_1.Point({
|
|
246
|
-
x: (isLocalInstruction ? origin.x : basePoint.x) + x,
|
|
247
|
-
y: (isLocalInstruction ? origin.y : basePoint.y) + y,
|
|
248
|
-
});
|
|
249
|
-
return {
|
|
250
|
-
point: nextPoint,
|
|
251
|
-
command: `${command} ${params.map((p) => `${p}`).join()}`,
|
|
252
|
-
};
|
|
253
|
-
case 'q':
|
|
254
|
-
case 'Q': {
|
|
255
|
-
const isLocalInstruction = command === 'q';
|
|
256
|
-
const [, , x, y] = params;
|
|
257
|
-
const nextPoint = new elements_1.Point({
|
|
258
|
-
x: (isLocalInstruction ? origin.x : basePoint.x) + x,
|
|
259
|
-
y: (isLocalInstruction ? origin.y : basePoint.y) + y,
|
|
260
|
-
});
|
|
261
|
-
// TODO: implement the code to fit the Quadratic Bézier Curve instructions into the viewbox
|
|
262
|
-
return {
|
|
263
|
-
point: nextPoint,
|
|
264
|
-
command: `${command} ${params.map((p) => `${p}`).join()}`,
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
// TODO: Handle the remaining svg instructions: t,q
|
|
268
|
-
default:
|
|
269
|
-
return {
|
|
270
|
-
point: origin,
|
|
271
|
-
command: `${command} ${params.map((p) => `${p}`).join()}`,
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
};
|
|
275
|
-
const commands = (_a = element.svgAttributes.d) === null || _a === void 0 ? void 0 : _a.match(/(v|h|a|l|t|m|q|c|s|z)([0-9,e\s.-]*)/gi);
|
|
276
|
-
let currentPoint = new elements_1.Point({ x: basePoint.x, y: basePoint.y });
|
|
277
|
-
const newPath = commands === null || commands === void 0 ? void 0 : commands.map((command) => {
|
|
278
|
-
var _a, _b;
|
|
279
|
-
const letter = (_a = command.match(/[a-z]/i)) === null || _a === void 0 ? void 0 : _a[0];
|
|
280
|
-
const params = (_b = command
|
|
281
|
-
.match(/(-?[0-9]+\.[0-9]+(e[+-]?[0-9]+)?)|(-?\.[0-9]+(e[+-]?[0-9]+)?)|(-?[0-9]+)/gi)) === null || _b === void 0 ? void 0 : _b.filter((m) => m !== '').map((v) => parseFloat(v));
|
|
282
|
-
if (letter && params) {
|
|
283
|
-
const result = handlePath(currentPoint, letter, params);
|
|
284
|
-
if (result) {
|
|
285
|
-
currentPoint = result.point;
|
|
286
|
-
return result.command;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
return command;
|
|
290
|
-
}).join(' ');
|
|
291
|
-
element.svgAttributes.d = newPath;
|
|
292
|
-
break;
|
|
293
|
-
}
|
|
294
|
-
case 'ellipse':
|
|
295
|
-
case 'circle': {
|
|
296
|
-
if (element.svgAttributes.cx === undefined ||
|
|
297
|
-
element.svgAttributes.cy === undefined ||
|
|
298
|
-
element.svgAttributes.rx === undefined ||
|
|
299
|
-
element.svgAttributes.ry === undefined) {
|
|
300
|
-
break;
|
|
301
|
-
}
|
|
302
|
-
const { cx = 0, cy = 0, rx = 0, ry = 0 } = element.svgAttributes;
|
|
303
|
-
const center = new elements_1.Point({
|
|
304
|
-
x: cx,
|
|
305
|
-
y: cy,
|
|
306
|
-
});
|
|
307
|
-
const rotation = ((_b = element.svgAttributes.rotation) === null || _b === void 0 ? void 0 : _b.angle) || 0;
|
|
308
|
-
// these points are relative to the ellipse's center
|
|
309
|
-
const a = new elements_1.Point((0, maths_1.rotate)({ x: -rx, y: 0 }, (0, rotations_1.degreesToRadians)(rotation)));
|
|
310
|
-
const b = new elements_1.Point((0, maths_1.rotate)({ x: rx, y: 0 }, (0, rotations_1.degreesToRadians)(rotation)));
|
|
311
|
-
const c = new elements_1.Point((0, maths_1.rotate)({ x: 0, y: ry }, (0, rotations_1.degreesToRadians)(rotation)));
|
|
312
|
-
// these points are relative to the real coordinate system
|
|
313
|
-
const A = center.plus(a);
|
|
314
|
-
const B = center.plus(b);
|
|
315
|
-
const C = center.plus(c);
|
|
316
|
-
const ellipse = new elements_1.Ellipse(A, B, C);
|
|
317
|
-
const intersections = (0, intersections_1.getIntersections)([svgRect, ellipse]);
|
|
318
|
-
const isCenterInsideRect = isCoordinateInsideTheRect(center, svgRect);
|
|
319
|
-
/**
|
|
320
|
-
* if there are less than 2 intersection, there are two possibilities:
|
|
321
|
-
* - the ellipse is outside the viewbox and therefore isn't visible
|
|
322
|
-
* - the ellipse is inside the viewbox and don't need to be cropped
|
|
323
|
-
*/
|
|
324
|
-
if (intersections.length < 2) {
|
|
325
|
-
!isCenterInsideRect && element.setAttribute('rx', '0');
|
|
326
|
-
!isCenterInsideRect && (element.svgAttributes.rx = 0);
|
|
327
|
-
!isCenterInsideRect && element.setAttribute('ry', '0');
|
|
328
|
-
!isCenterInsideRect && (element.svgAttributes.ry = 0);
|
|
329
|
-
break;
|
|
330
|
-
}
|
|
331
|
-
// viewbox rectangle coordinates
|
|
332
|
-
const P1 = new elements_1.Point(svgRect.getCoords());
|
|
333
|
-
const P3 = new elements_1.Point(svgRect.getEnd());
|
|
334
|
-
const P2 = new elements_1.Point({ x: P3.x, y: P1.y });
|
|
335
|
-
const P4 = new elements_1.Point({ x: P1.x, y: P3.y });
|
|
336
|
-
const top = new elements_1.Segment(P1, P2);
|
|
337
|
-
const right = new elements_1.Segment(P2, P3);
|
|
338
|
-
const bottom = new elements_1.Segment(P3, P4);
|
|
339
|
-
const left = new elements_1.Segment(P4, P1);
|
|
340
|
-
// Warning: keep the order of the segments, it's important when building the path that will represent the ellipse
|
|
341
|
-
const rectSegments = [top, right, bottom, left];
|
|
342
|
-
const isPointInsideEllipse = (P) => (P.x - cx) ** 2 / rx ** 2 + (P.y - cy) ** 2 / ry ** 2 <= 1;
|
|
343
|
-
// check if the rect boundaries are inside the circle
|
|
344
|
-
const isRectInsideEllipse = isPointInsideEllipse(P1) &&
|
|
345
|
-
isPointInsideEllipse(P2) &&
|
|
346
|
-
isPointInsideEllipse(P3) &&
|
|
347
|
-
isPointInsideEllipse(P4);
|
|
348
|
-
// the segments that are intersecting the circle. And, therefore, are lines that are cropping the drawing
|
|
349
|
-
const circleSegments = isRectInsideEllipse
|
|
350
|
-
? rectSegments
|
|
351
|
-
: rectSegments.map((segment, i) => {
|
|
352
|
-
const [p1, p2] = (0, intersections_1.getIntersections)([segment, ellipse])
|
|
353
|
-
// it's important to sort the segment's point because it impacts the angle of the arc, the points are sorted on clockwise direction
|
|
354
|
-
.sort((p1, p2) => {
|
|
355
|
-
// top
|
|
356
|
-
if (i === 0) {
|
|
357
|
-
return p1.x - p2.x;
|
|
358
|
-
// right
|
|
359
|
-
}
|
|
360
|
-
else if (i === 1) {
|
|
361
|
-
return p2.y - p1.y;
|
|
362
|
-
// bottom
|
|
363
|
-
}
|
|
364
|
-
else if (i === 2) {
|
|
365
|
-
return p2.x - p1.x;
|
|
366
|
-
// left
|
|
367
|
-
}
|
|
368
|
-
else {
|
|
369
|
-
return p1.y - p2.y;
|
|
370
|
-
}
|
|
371
|
-
});
|
|
372
|
-
if (p1 && p2) {
|
|
373
|
-
return new elements_1.Segment(new elements_1.Point(p1), new elements_1.Point(p2));
|
|
374
|
-
// if the other point isn't inside the circle it means that the circle isn't cropped by the segment
|
|
375
|
-
}
|
|
376
|
-
else if (p1 &&
|
|
377
|
-
(isPointInsideEllipse(segment.A) ||
|
|
378
|
-
isPointInsideEllipse(segment.B))) {
|
|
379
|
-
const intersectionPoint = new elements_1.Point(p1);
|
|
380
|
-
const innerPoint = isPointInsideEllipse(segment.A)
|
|
381
|
-
? segment.A
|
|
382
|
-
: segment.B;
|
|
383
|
-
// ensures that the segment is always following the clockwise direction
|
|
384
|
-
const start = innerPoint.isEqual(segment.A)
|
|
385
|
-
? innerPoint
|
|
386
|
-
: intersectionPoint;
|
|
387
|
-
const end = innerPoint.isEqual(segment.A)
|
|
388
|
-
? intersectionPoint
|
|
389
|
-
: innerPoint;
|
|
390
|
-
return new elements_1.Segment(start, end);
|
|
391
|
-
// if there's no intersection and the segment's points are inside the Ellipse it means that the segment should be drawn as part of the ellipse
|
|
392
|
-
}
|
|
393
|
-
else if (!(p1 && p2) &&
|
|
394
|
-
isPointInsideEllipse(segment.A) &&
|
|
395
|
-
isPointInsideEllipse(segment.B)) {
|
|
396
|
-
return segment;
|
|
397
|
-
}
|
|
398
|
-
return;
|
|
399
|
-
});
|
|
400
|
-
const inverseAngle = (angle) => (360 - angle) % 360;
|
|
401
|
-
const pointsAngle = (p1, p2, direction = 'clockwise') => {
|
|
402
|
-
const startAngle = (0, rotations_1.radiansToDegrees)(Math.atan2(p1.y - center.y, p1.x - center.x));
|
|
403
|
-
const endAngle = (0, rotations_1.radiansToDegrees)(Math.atan2(p2.y - center.y, p2.x - center.x));
|
|
404
|
-
const arcAngle = (endAngle + (360 - startAngle)) % 360;
|
|
405
|
-
return direction === 'clockwise' ? arcAngle : inverseAngle(arcAngle);
|
|
406
|
-
};
|
|
407
|
-
/**
|
|
408
|
-
* - draw a line for each segment
|
|
409
|
-
* - if two segments aren't connected draw an arc connecting them
|
|
410
|
-
*/
|
|
411
|
-
let startPoint;
|
|
412
|
-
// the point where the pen is located
|
|
413
|
-
let currentPoint;
|
|
414
|
-
let lastSegment;
|
|
415
|
-
let path = circleSegments.reduce((path, segment) => {
|
|
416
|
-
if (!segment)
|
|
417
|
-
return path;
|
|
418
|
-
if (!startPoint) {
|
|
419
|
-
startPoint = segment.A;
|
|
420
|
-
path = `M ${segment.A.x},${segment.A.y}`;
|
|
421
|
-
}
|
|
422
|
-
// if the current segment isn't connected to the last one, connect both with an arc
|
|
423
|
-
if (lastSegment && !lastSegment.B.isEqual(segment.A)) {
|
|
424
|
-
const arcAngle = pointsAngle(segment.A, lastSegment.B);
|
|
425
|
-
// angles greater than 180 degrees are marked as large-arc-flag = 1
|
|
426
|
-
path += `A ${rx},${ry} ${rotation} ${arcAngle > 180 ? 1 : 0},0 ${segment.A.x}, ${segment.A.y}`;
|
|
427
|
-
}
|
|
428
|
-
path += ` L ${segment.B.x},${segment.B.y}`;
|
|
429
|
-
currentPoint = segment.B;
|
|
430
|
-
lastSegment = segment;
|
|
431
|
-
return path;
|
|
432
|
-
}, '');
|
|
433
|
-
// if the path isn't closed, close it by drawing an arc
|
|
434
|
-
if (startPoint && currentPoint && !startPoint.isEqual(currentPoint)) {
|
|
435
|
-
const arcAngle = pointsAngle(currentPoint, startPoint, 'counter-clockwise');
|
|
436
|
-
// angles greater than 180 degrees are marked as large-arc-flag = 1
|
|
437
|
-
path += `A ${rx},${ry} ${rotation} ${arcAngle > 180 ? 1 : 0},0 ${startPoint.x}, ${startPoint.y}`;
|
|
438
|
-
}
|
|
439
|
-
// create a new element that will represent the cropped ellipse
|
|
440
|
-
const newElement = (0, node_html_better_parser_1.parse)(`<path d="${path}" fill="red"/>`).firstChild;
|
|
441
|
-
const svgAttributes = Object.assign(Object.assign({}, element.svgAttributes), {
|
|
442
|
-
// the x and y values are 0 because all the path coordinates are global
|
|
443
|
-
x: 0, y: 0,
|
|
444
|
-
// the path coordinates are already rotated
|
|
445
|
-
rotate: undefined, d: path });
|
|
446
|
-
Object.assign(newElement, { svgAttributes });
|
|
447
|
-
return newElement;
|
|
448
|
-
}
|
|
449
|
-
case 'rect': {
|
|
450
|
-
const { x = 0, y = 0, width = 0, height = 0, rotate: rawRotation, } = element.svgAttributes;
|
|
451
|
-
const rotation = (rawRotation === null || rawRotation === void 0 ? void 0 : rawRotation.angle) || 0;
|
|
452
|
-
if (!(width && height))
|
|
453
|
-
return element;
|
|
454
|
-
// bottomLeft point
|
|
455
|
-
const origin = new elements_1.Point({ x, y });
|
|
456
|
-
const rotateAroundOrigin = (p) => new elements_1.Point((0, maths_1.rotate)(normalize(p), (0, rotations_1.degreesToRadians)(rotation))).plus(origin);
|
|
457
|
-
const normalize = (p) => p.plus({ x: -origin.x, y: -origin.y });
|
|
458
|
-
const topLeft = rotateAroundOrigin(origin.plus({ x: 0, y: -height }));
|
|
459
|
-
const topRight = rotateAroundOrigin(origin.plus({ x: width, y: -height }));
|
|
460
|
-
const bottomRight = rotateAroundOrigin(origin.plus({ x: width, y: 0 }));
|
|
461
|
-
const pointToString = (p) => [p.x, p.y].join();
|
|
462
|
-
const d = `M${pointToString(topLeft)} L${pointToString(topRight)} L${pointToString(bottomRight)} L${pointToString(origin)} L${pointToString(topLeft)}`;
|
|
463
|
-
const el = (0, node_html_better_parser_1.parse)(`<path d="${d}"/>`).firstChild;
|
|
464
|
-
const newAttributes = Object.assign(Object.assign({}, element.svgAttributes), { d, x: 0, y: 0 });
|
|
465
|
-
// @ts-ignore
|
|
466
|
-
delete newAttributes.width;
|
|
467
|
-
// @ts-ignore
|
|
468
|
-
delete newAttributes.height;
|
|
469
|
-
delete newAttributes.rotate;
|
|
470
|
-
delete newAttributes.rotation;
|
|
471
|
-
Object.assign(el, {
|
|
472
|
-
svgAttributes: newAttributes,
|
|
473
|
-
});
|
|
474
|
-
return cropSvgElement(svgRect, el);
|
|
475
|
-
}
|
|
476
|
-
// TODO: implement the crop for the following elements
|
|
477
|
-
case 'image':
|
|
478
|
-
default:
|
|
479
|
-
return element;
|
|
480
|
-
}
|
|
481
|
-
return element;
|
|
482
|
-
};
|
|
483
85
|
// TODO: Improve type system to require the correct props for each tagName.
|
|
484
86
|
/** methods to draw SVGElements onto a PDFPage */
|
|
485
87
|
const runnersToPage = (page, options) => ({
|
|
486
|
-
text(element) {
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
font,
|
|
527
|
-
size: fontSize,
|
|
528
|
-
color: element.svgAttributes.fill,
|
|
529
|
-
opacity: element.svgAttributes.fillOpacity,
|
|
530
|
-
rotate: element.svgAttributes.rotate,
|
|
531
|
-
});
|
|
88
|
+
async text(element) {
|
|
89
|
+
const anchor = element.svgAttributes.textAnchor;
|
|
90
|
+
const dominantBaseline = element.svgAttributes.dominantBaseline;
|
|
91
|
+
const text = element.text.trim().replace(/\s/g, ' ');
|
|
92
|
+
const fontSize = element.svgAttributes.fontSize || 12;
|
|
93
|
+
/** This will find the best font for the provided style in the list */
|
|
94
|
+
function getBestFont(style, fonts) {
|
|
95
|
+
const family = style.fontFamily;
|
|
96
|
+
if (!family)
|
|
97
|
+
return undefined;
|
|
98
|
+
const isBold = style.fontWeight === 'bold' || Number(style.fontWeight) >= 700;
|
|
99
|
+
const isItalic = style.fontStyle === 'italic';
|
|
100
|
+
const getFont = (bold, italic, family) => fonts[family + (bold ? '_bold' : '') + (italic ? '_italic' : '')];
|
|
101
|
+
return (getFont(isBold, isItalic, family) ||
|
|
102
|
+
getFont(isBold, false, family) ||
|
|
103
|
+
getFont(false, isItalic, family) ||
|
|
104
|
+
getFont(false, false, family) ||
|
|
105
|
+
Object.keys(fonts).find((fontFamily) => fontFamily.startsWith(family)));
|
|
106
|
+
}
|
|
107
|
+
const font = options.fonts && getBestFont(element.svgAttributes, options.fonts);
|
|
108
|
+
const textWidth = (font || page.getFont()[0]).widthOfTextAtSize(text, fontSize);
|
|
109
|
+
const textHeight = (font || page.getFont()[0]).heightAtSize(fontSize);
|
|
110
|
+
const offsetX = anchor === 'middle' ? textWidth / 2 : anchor === 'end' ? textWidth : 0;
|
|
111
|
+
const offsetY = dominantBaseline === 'text-before-edge'
|
|
112
|
+
? textHeight
|
|
113
|
+
: dominantBaseline === 'text-after-edge'
|
|
114
|
+
? -textHeight
|
|
115
|
+
: dominantBaseline === 'middle'
|
|
116
|
+
? textHeight / 2
|
|
117
|
+
: 0;
|
|
118
|
+
page.drawText(text, {
|
|
119
|
+
x: -offsetX,
|
|
120
|
+
y: -offsetY,
|
|
121
|
+
font,
|
|
122
|
+
// TODO: the font size should be correctly scaled too
|
|
123
|
+
size: fontSize,
|
|
124
|
+
color: element.svgAttributes.fill,
|
|
125
|
+
opacity: element.svgAttributes.fillOpacity,
|
|
126
|
+
matrix: element.svgAttributes.matrix,
|
|
127
|
+
clipSpaces: element.svgAttributes.clipSpaces,
|
|
532
128
|
});
|
|
533
129
|
},
|
|
534
|
-
line(element) {
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
130
|
+
async line(element) {
|
|
131
|
+
page.drawLine({
|
|
132
|
+
start: {
|
|
133
|
+
x: element.svgAttributes.x1 || 0,
|
|
134
|
+
y: -element.svgAttributes.y1 || 0,
|
|
135
|
+
},
|
|
136
|
+
end: {
|
|
137
|
+
x: element.svgAttributes.x2 || 0,
|
|
138
|
+
y: -element.svgAttributes.y2 || 0,
|
|
139
|
+
},
|
|
140
|
+
thickness: element.svgAttributes.strokeWidth,
|
|
141
|
+
color: element.svgAttributes.stroke,
|
|
142
|
+
opacity: element.svgAttributes.strokeOpacity,
|
|
143
|
+
lineCap: element.svgAttributes.strokeLineCap,
|
|
144
|
+
matrix: element.svgAttributes.matrix,
|
|
145
|
+
clipSpaces: element.svgAttributes.clipSpaces,
|
|
550
146
|
});
|
|
551
147
|
},
|
|
552
|
-
path(element) {
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
fillRule: element.svgAttributes.fillRule,
|
|
569
|
-
});
|
|
148
|
+
async path(element) {
|
|
149
|
+
if (!element.svgAttributes.d)
|
|
150
|
+
return;
|
|
151
|
+
// See https://jsbin.com/kawifomupa/edit?html,output and
|
|
152
|
+
page.drawSvgPath(element.svgAttributes.d, {
|
|
153
|
+
x: 0,
|
|
154
|
+
y: 0,
|
|
155
|
+
borderColor: element.svgAttributes.stroke,
|
|
156
|
+
borderWidth: element.svgAttributes.strokeWidth,
|
|
157
|
+
borderOpacity: element.svgAttributes.strokeOpacity,
|
|
158
|
+
borderLineCap: element.svgAttributes.strokeLineCap,
|
|
159
|
+
color: element.svgAttributes.fill,
|
|
160
|
+
opacity: element.svgAttributes.fillOpacity,
|
|
161
|
+
fillRule: element.svgAttributes.fillRule,
|
|
162
|
+
matrix: element.svgAttributes.matrix,
|
|
163
|
+
clipSpaces: element.svgAttributes.clipSpaces,
|
|
570
164
|
});
|
|
571
165
|
},
|
|
572
|
-
image(element) {
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
ySkew: element.svgAttributes.skewY,
|
|
590
|
-
rotate: element.svgAttributes.rotate,
|
|
591
|
-
});
|
|
166
|
+
async image(element) {
|
|
167
|
+
const { src } = element.svgAttributes;
|
|
168
|
+
if (!src)
|
|
169
|
+
return;
|
|
170
|
+
const isPng = src.match(/\.png(\?|$)|^data:image\/png;base64/gim);
|
|
171
|
+
const img = isPng
|
|
172
|
+
? await page.doc.embedPng(src)
|
|
173
|
+
: await page.doc.embedJpg(src);
|
|
174
|
+
const { x, y, width, height } = getFittingRectangle(img.width, img.height, element.svgAttributes.width || img.width, element.svgAttributes.height || img.height, element.svgAttributes.preserveAspectRatio);
|
|
175
|
+
page.drawImage(img, {
|
|
176
|
+
x,
|
|
177
|
+
y: -y - height,
|
|
178
|
+
width,
|
|
179
|
+
height,
|
|
180
|
+
opacity: element.svgAttributes.fillOpacity,
|
|
181
|
+
matrix: element.svgAttributes.matrix,
|
|
182
|
+
clipSpaces: element.svgAttributes.clipSpaces,
|
|
592
183
|
});
|
|
593
184
|
},
|
|
594
|
-
rect(element) {
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
ySkew: element.svgAttributes.skewY,
|
|
611
|
-
rotate: element.svgAttributes.rotate,
|
|
612
|
-
});
|
|
185
|
+
async rect(element) {
|
|
186
|
+
if (!element.svgAttributes.fill && !element.svgAttributes.stroke)
|
|
187
|
+
return;
|
|
188
|
+
page.drawRectangle({
|
|
189
|
+
x: 0,
|
|
190
|
+
y: 0,
|
|
191
|
+
width: element.svgAttributes.width,
|
|
192
|
+
height: element.svgAttributes.height * -1,
|
|
193
|
+
borderColor: element.svgAttributes.stroke,
|
|
194
|
+
borderWidth: element.svgAttributes.strokeWidth,
|
|
195
|
+
borderOpacity: element.svgAttributes.strokeOpacity,
|
|
196
|
+
borderLineCap: element.svgAttributes.strokeLineCap,
|
|
197
|
+
color: element.svgAttributes.fill,
|
|
198
|
+
opacity: element.svgAttributes.fillOpacity,
|
|
199
|
+
matrix: element.svgAttributes.matrix,
|
|
200
|
+
clipSpaces: element.svgAttributes.clipSpaces,
|
|
613
201
|
});
|
|
614
202
|
},
|
|
615
|
-
ellipse(element) {
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
});
|
|
203
|
+
async ellipse(element) {
|
|
204
|
+
page.drawEllipse({
|
|
205
|
+
x: element.svgAttributes.cx || 0,
|
|
206
|
+
y: -(element.svgAttributes.cy || 0),
|
|
207
|
+
xScale: element.svgAttributes.rx,
|
|
208
|
+
yScale: element.svgAttributes.ry,
|
|
209
|
+
borderColor: element.svgAttributes.stroke,
|
|
210
|
+
borderWidth: element.svgAttributes.strokeWidth,
|
|
211
|
+
borderOpacity: element.svgAttributes.strokeOpacity,
|
|
212
|
+
borderLineCap: element.svgAttributes.strokeLineCap,
|
|
213
|
+
color: element.svgAttributes.fill,
|
|
214
|
+
opacity: element.svgAttributes.fillOpacity,
|
|
215
|
+
matrix: element.svgAttributes.matrix,
|
|
216
|
+
clipSpaces: element.svgAttributes.clipSpaces,
|
|
630
217
|
});
|
|
631
218
|
},
|
|
632
|
-
circle(element) {
|
|
633
|
-
return (
|
|
634
|
-
return runnersToPage(page, options).ellipse(element);
|
|
635
|
-
});
|
|
219
|
+
async circle(element) {
|
|
220
|
+
return runnersToPage(page, options).ellipse(element);
|
|
636
221
|
},
|
|
637
222
|
});
|
|
638
|
-
const transform = (converter, name, args) => {
|
|
639
|
-
switch (name) {
|
|
640
|
-
case 'scaleX':
|
|
641
|
-
return transform(converter, 'scale', [args[0], 0]);
|
|
642
|
-
case 'scaleY':
|
|
643
|
-
return transform(converter, 'scale', [0, args[0]]);
|
|
644
|
-
case 'scale':
|
|
645
|
-
const [xScale, yScale = xScale] = args;
|
|
646
|
-
return {
|
|
647
|
-
point: (x, y) => converter.point(x * xScale, y * yScale),
|
|
648
|
-
size: (w, h) => converter.size(w * xScale, h * yScale),
|
|
649
|
-
};
|
|
650
|
-
case 'translateX':
|
|
651
|
-
return transform(converter, 'translate', [args[0], 0]);
|
|
652
|
-
case 'translateY':
|
|
653
|
-
return transform(converter, 'translate', [0, args[0]]);
|
|
654
|
-
case 'translate':
|
|
655
|
-
const [dx, dy = dx] = args;
|
|
656
|
-
return {
|
|
657
|
-
point: (x, y) => converter.point(x + dx, y + dy),
|
|
658
|
-
size: converter.size,
|
|
659
|
-
};
|
|
660
|
-
case 'rotate': {
|
|
661
|
-
if (args.length > 1) {
|
|
662
|
-
const [a, x, y = x] = args;
|
|
663
|
-
let tempResult = transform(converter, 'translate', [x, y]);
|
|
664
|
-
tempResult = transform(tempResult, 'rotate', [a]);
|
|
665
|
-
return transform(tempResult, 'translate', [-x, -y]);
|
|
666
|
-
}
|
|
667
|
-
else {
|
|
668
|
-
const [a] = args;
|
|
669
|
-
const angle = (0, rotations_1.degreesToRadians)(a);
|
|
670
|
-
return {
|
|
671
|
-
point: (x, y) => converter.point(x * Math.cos(angle) - y * Math.sin(angle), y * Math.cos(angle) + x * Math.sin(angle)),
|
|
672
|
-
size: converter.size,
|
|
673
|
-
};
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
case 'matrix': {
|
|
677
|
-
const [scaleX, skewY, skewX, scaleY, translateX, translateY] = args;
|
|
678
|
-
return {
|
|
679
|
-
point: (x, y) => converter.point(x * scaleX + y * skewX + translateX, x * skewY + y * scaleY + translateY),
|
|
680
|
-
size: (w, h) => converter.size(w * scaleX, h * scaleY),
|
|
681
|
-
};
|
|
682
|
-
}
|
|
683
|
-
case 'skewX': {
|
|
684
|
-
const angle = (0, rotations_1.degreesToRadians)(args[0]);
|
|
685
|
-
return {
|
|
686
|
-
point: (x, y) => converter.point((1 + x) * Math.tan(angle), y),
|
|
687
|
-
size: converter.size,
|
|
688
|
-
};
|
|
689
|
-
}
|
|
690
|
-
case 'skewY': {
|
|
691
|
-
const angle = (0, rotations_1.degreesToRadians)(args[0]);
|
|
692
|
-
return {
|
|
693
|
-
point: (x, y) => converter.point(x, (1 + y) * Math.tan(angle)),
|
|
694
|
-
size: converter.size,
|
|
695
|
-
};
|
|
696
|
-
}
|
|
697
|
-
default: {
|
|
698
|
-
console.log('transformation unsupported:', name);
|
|
699
|
-
return converter;
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
};
|
|
703
223
|
const styleOrAttribute = (attributes, style, attribute, def) => {
|
|
704
224
|
const value = style[attribute] || attributes[attribute];
|
|
705
225
|
if (!value && typeof def !== 'undefined')
|
|
@@ -729,8 +249,8 @@ const parseColor = (color, inherited) => {
|
|
|
729
249
|
alpha: parsedColor.alpha ? parsedColor.alpha + '' : undefined,
|
|
730
250
|
};
|
|
731
251
|
};
|
|
732
|
-
const parseAttributes = (element, inherited,
|
|
733
|
-
var _a, _b, _c, _d
|
|
252
|
+
const parseAttributes = (element, inherited, matrix) => {
|
|
253
|
+
var _a, _b, _c, _d;
|
|
734
254
|
const attributes = element.attributes;
|
|
735
255
|
const style = parseStyles(attributes.style);
|
|
736
256
|
const widthRaw = styleOrAttribute(attributes, style, 'width', '');
|
|
@@ -786,7 +306,6 @@ const parseAttributes = (element, inherited, converter) => {
|
|
|
786
306
|
dominantBaseline: attributes['dominant-baseline'],
|
|
787
307
|
preserveAspectRatio: attributes.preserveAspectRatio,
|
|
788
308
|
};
|
|
789
|
-
let newConverter = converter;
|
|
790
309
|
let transformList = attributes.transform || '';
|
|
791
310
|
// Handle transformations set as direct attributes
|
|
792
311
|
[
|
|
@@ -805,28 +324,11 @@ const parseAttributes = (element, inherited, converter) => {
|
|
|
805
324
|
transformList = attributes[name] + ' ' + transformList;
|
|
806
325
|
}
|
|
807
326
|
});
|
|
808
|
-
// skewX, skewY, rotate and scale are handled by the pdf-lib
|
|
809
|
-
['skewX', 'skewY', 'rotate'].forEach((name) => {
|
|
810
|
-
var _a;
|
|
811
|
-
if (attributes[name]) {
|
|
812
|
-
const d = (_a = attributes[name].match(/-?(\d+\.?|\.)\d*/)) === null || _a === void 0 ? void 0 : _a[0];
|
|
813
|
-
if (d !== undefined) {
|
|
814
|
-
svgAttributes[name] = {
|
|
815
|
-
angle: parseInt(d, 10),
|
|
816
|
-
type: rotations_1.RotationTypes.Degrees,
|
|
817
|
-
};
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
});
|
|
821
|
-
if (attributes.scale) {
|
|
822
|
-
const d = (_e = attributes.scale.match(/-?(\d+\.?|\.)\d*/)) === null || _e === void 0 ? void 0 : _e[0];
|
|
823
|
-
if (d !== undefined)
|
|
824
|
-
svgAttributes.scale = parseInt(d, 10);
|
|
825
|
-
}
|
|
826
327
|
// Convert x/y as if it was a translation
|
|
827
328
|
if (x || y) {
|
|
828
329
|
transformList = transformList + `translate(${x || 0} ${y || 0}) `;
|
|
829
330
|
}
|
|
331
|
+
let newMatrix = matrix;
|
|
830
332
|
// Apply the transformations
|
|
831
333
|
if (transformList) {
|
|
832
334
|
const regexTransform = /(\w+)\((.+?)\)/g;
|
|
@@ -837,295 +339,38 @@ const parseAttributes = (element, inherited, converter) => {
|
|
|
837
339
|
.split(/\s*,\s*|\s+/)
|
|
838
340
|
.filter((value) => value.length > 0)
|
|
839
341
|
.map((value) => parseFloat(value));
|
|
840
|
-
|
|
841
|
-
newConverter = transform(newConverter, name, args);
|
|
842
|
-
const xAxisVector = (0, maths_1.minus)(currentConverter.point(0, 0), currentConverter.point(1, 0));
|
|
843
|
-
const xAxisVectorPostTransform = (0, maths_1.minus)(newConverter.point(0, 0), newConverter.point(1, 0));
|
|
844
|
-
// matrix transform may also represent rotations: https://www.w3.org/TR/SVGTiny12/coords.html
|
|
845
|
-
if (name === 'rotate' || name === 'matrix') {
|
|
846
|
-
// transformations over x and y axis might change the page coord direction
|
|
847
|
-
const { width: xDirection, height: yDirection } = currentConverter.size(1, 1);
|
|
848
|
-
const rotationAdded = name === 'rotate' ? args[0] : (0, rotations_1.radiansToDegrees)((0, maths_1.angle)(xAxisVectorPostTransform, xAxisVector));
|
|
849
|
-
// the page Y coord is inverted so the angle rotation is inverted too
|
|
850
|
-
const pageYDirection = -1;
|
|
851
|
-
newInherited.rotation = (0, rotations_1.degrees)(pageYDirection * rotationAdded * Math.sign(xDirection * yDirection) +
|
|
852
|
-
(((_f = inherited.rotation) === null || _f === void 0 ? void 0 : _f.angle) || 0));
|
|
853
|
-
svgAttributes.rotate = newInherited.rotation;
|
|
854
|
-
}
|
|
342
|
+
newMatrix = combineTransformation(newMatrix, name, args);
|
|
855
343
|
parsed = regexTransform.exec(transformList);
|
|
856
344
|
}
|
|
857
345
|
}
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
svgAttributes.x = newX;
|
|
861
|
-
svgAttributes.y = newY;
|
|
346
|
+
svgAttributes.x = x;
|
|
347
|
+
svgAttributes.y = y;
|
|
862
348
|
if (attributes.cx || attributes.cy) {
|
|
863
|
-
|
|
864
|
-
svgAttributes.
|
|
865
|
-
svgAttributes.cy = newCY;
|
|
349
|
+
svgAttributes.cx = cx;
|
|
350
|
+
svgAttributes.cy = cy;
|
|
866
351
|
}
|
|
867
352
|
if (attributes.rx || attributes.ry || attributes.r) {
|
|
868
|
-
|
|
869
|
-
svgAttributes.
|
|
870
|
-
svgAttributes.ry = newRY;
|
|
353
|
+
svgAttributes.rx = rx;
|
|
354
|
+
svgAttributes.ry = ry;
|
|
871
355
|
}
|
|
872
356
|
if (attributes.x1 || attributes.y1) {
|
|
873
|
-
|
|
874
|
-
svgAttributes.
|
|
875
|
-
svgAttributes.y1 = newY1;
|
|
357
|
+
svgAttributes.x1 = x1;
|
|
358
|
+
svgAttributes.y1 = y1;
|
|
876
359
|
}
|
|
877
360
|
if (attributes.x2 || attributes.y2) {
|
|
878
|
-
|
|
879
|
-
svgAttributes.
|
|
880
|
-
svgAttributes.y2 = newY2;
|
|
361
|
+
svgAttributes.x2 = x2;
|
|
362
|
+
svgAttributes.y2 = y2;
|
|
881
363
|
}
|
|
882
364
|
if (attributes.width || attributes.height) {
|
|
883
|
-
|
|
884
|
-
svgAttributes.
|
|
885
|
-
svgAttributes.height = size.height;
|
|
365
|
+
svgAttributes.width = width !== null && width !== void 0 ? width : inherited.width;
|
|
366
|
+
svgAttributes.height = height !== null && height !== void 0 ? height : inherited.height;
|
|
886
367
|
}
|
|
887
|
-
// We convert all the points from the path
|
|
888
368
|
if (attributes.d) {
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
let currentX = 0;
|
|
892
|
-
let currentY = 0;
|
|
893
|
-
svgAttributes.d = (_g = attributes.d) === null || _g === void 0 ? void 0 : _g.replace(/(l|m|s|t|q|c|z|a|v|h)([0-9,e\s.-]*)/gi, (command) => {
|
|
894
|
-
var _a, _b;
|
|
895
|
-
const letter = (_a = command.match(/[a-z]/i)) === null || _a === void 0 ? void 0 : _a[0];
|
|
896
|
-
if ((letter === null || letter === void 0 ? void 0 : letter.toLocaleLowerCase()) === 'z')
|
|
897
|
-
return letter;
|
|
898
|
-
// const params = command.match(/([0-9e.-]+)/ig)?.filter(m => m !== '')//.map(v => parseFloat(v))
|
|
899
|
-
const params = (_b = command
|
|
900
|
-
.match(/(-?[0-9]+\.[0-9]+(e[+-]?[0-9]+)?)|(-?\.[0-9]+(e[+-]?[0-9]+)?)|(-?[0-9]+)/gi)) === null || _b === void 0 ? void 0 : _b.filter((m) => m !== ''); // .map(v => parseFloat(v))
|
|
901
|
-
if (!params)
|
|
902
|
-
return letter || '';
|
|
903
|
-
switch (letter === null || letter === void 0 ? void 0 : letter.toLocaleLowerCase()) {
|
|
904
|
-
case 'm':
|
|
905
|
-
case 'l': {
|
|
906
|
-
const groupedParams = groupBy(params, 2);
|
|
907
|
-
return groupedParams
|
|
908
|
-
.map((pair, pairIndex) => {
|
|
909
|
-
const [x, y] = pair;
|
|
910
|
-
const xReal = parseFloatValue(x, inherited.width) || 0;
|
|
911
|
-
const yReal = parseFloatValue(y, innerHeight) || 0;
|
|
912
|
-
if (letter === letter.toLowerCase()) {
|
|
913
|
-
currentX += xReal;
|
|
914
|
-
currentY += yReal;
|
|
915
|
-
}
|
|
916
|
-
else {
|
|
917
|
-
currentX = xReal;
|
|
918
|
-
currentY = yReal;
|
|
919
|
-
}
|
|
920
|
-
const point = newConverter.point(currentX, currentY);
|
|
921
|
-
return ((pairIndex > 0 || letter.toUpperCase() === 'L' ? 'L' : 'M') +
|
|
922
|
-
[point.x - xOrigin, point.y - yOrigin].join(','));
|
|
923
|
-
})
|
|
924
|
-
.join(' ');
|
|
925
|
-
}
|
|
926
|
-
case 'v': {
|
|
927
|
-
return params
|
|
928
|
-
.map((value) => {
|
|
929
|
-
const coord = parseFloatValue(value) || 0;
|
|
930
|
-
if (letter === letter.toLowerCase()) {
|
|
931
|
-
currentY += coord;
|
|
932
|
-
}
|
|
933
|
-
else {
|
|
934
|
-
currentY = coord;
|
|
935
|
-
}
|
|
936
|
-
const point = newConverter.point(currentX, currentY);
|
|
937
|
-
// we can't use 'v' as the final command because rotations might require a different command after the path parsing
|
|
938
|
-
// for instance, a 90 degree rotation would turn a 'v' into an 'h' command
|
|
939
|
-
return `L${point.x - xOrigin} ${point.y - yOrigin}`;
|
|
940
|
-
})
|
|
941
|
-
.join(' ');
|
|
942
|
-
}
|
|
943
|
-
case 'h': {
|
|
944
|
-
return params
|
|
945
|
-
.map((value) => {
|
|
946
|
-
const coord = parseFloatValue(value) || 0;
|
|
947
|
-
if (letter === letter.toLowerCase()) {
|
|
948
|
-
currentX += coord;
|
|
949
|
-
}
|
|
950
|
-
else {
|
|
951
|
-
currentX = coord;
|
|
952
|
-
}
|
|
953
|
-
const point = newConverter.point(currentX, currentY);
|
|
954
|
-
// we can't use 'h' as the final command because rotations might require a different command after the path parsing
|
|
955
|
-
// for instance, a 90 degree rotation would turn a 'h' into an 'v' command
|
|
956
|
-
return `L${point.x - xOrigin} ${point.y - yOrigin}`;
|
|
957
|
-
})
|
|
958
|
-
.join(' ');
|
|
959
|
-
}
|
|
960
|
-
case 'a': {
|
|
961
|
-
const groupedParams = groupBy(params, 7);
|
|
962
|
-
return groupedParams
|
|
963
|
-
.map((p) => {
|
|
964
|
-
const [rxPixel, ryPixel, xAxisRotation = '0', largeArc = '0', sweepFlag = '0', xPixel, yPixel,] = p;
|
|
965
|
-
const realRx = parseFloatValue(rxPixel, inherited.width) || 0;
|
|
966
|
-
const realRy = parseFloatValue(ryPixel, inherited.height) || 0;
|
|
967
|
-
const realX = parseFloatValue(xPixel, inherited.width) || 0;
|
|
968
|
-
const realY = parseFloatValue(yPixel, inherited.height) || 0;
|
|
969
|
-
const { width: newRx, height: newRy } = newConverter.size(realRx, realRy);
|
|
970
|
-
let point;
|
|
971
|
-
if (letter === letter.toLowerCase()) {
|
|
972
|
-
currentX += realX;
|
|
973
|
-
currentY += realY;
|
|
974
|
-
}
|
|
975
|
-
else {
|
|
976
|
-
currentX = realX;
|
|
977
|
-
currentY = realY;
|
|
978
|
-
}
|
|
979
|
-
point = newConverter.point(currentX, currentY);
|
|
980
|
-
// transformations over x and y axis might change the page coord direction
|
|
981
|
-
const { width: xDirection, height: yDirection } = newConverter.size(1, 1);
|
|
982
|
-
// -1 is the default direction
|
|
983
|
-
const pageYDirection = -1 * Math.sign(xDirection * yDirection);
|
|
984
|
-
const oppositeSweepFlag = sweepFlag === '0' ? '1' : '0';
|
|
985
|
-
return [
|
|
986
|
-
letter.toUpperCase(),
|
|
987
|
-
newRx,
|
|
988
|
-
newRy,
|
|
989
|
-
xAxisRotation,
|
|
990
|
-
largeArc,
|
|
991
|
-
pageYDirection === -1 ? oppositeSweepFlag : sweepFlag,
|
|
992
|
-
point.x - xOrigin,
|
|
993
|
-
point.y - yOrigin,
|
|
994
|
-
].join(' ');
|
|
995
|
-
})
|
|
996
|
-
.join(' ');
|
|
997
|
-
}
|
|
998
|
-
case 'c': {
|
|
999
|
-
const groupedParams = groupBy(params, 6);
|
|
1000
|
-
const result = groupedParams
|
|
1001
|
-
.map(([c1X, c1Y, c2X, c2Y, xString, yString]) => [
|
|
1002
|
-
parseFloatValue(c1X, inherited.width) || 0,
|
|
1003
|
-
parseFloatValue(c1Y, inherited.height) || 0,
|
|
1004
|
-
parseFloatValue(c2X, inherited.width) || 0,
|
|
1005
|
-
parseFloatValue(c2Y, inherited.height) || 0,
|
|
1006
|
-
parseFloatValue(xString, inherited.width) || 0,
|
|
1007
|
-
parseFloatValue(yString, inherited.height) || 0,
|
|
1008
|
-
])
|
|
1009
|
-
.map(([c1X, c1Y, c2X, c2Y, xReal, yReal]) => {
|
|
1010
|
-
let controlPoint1X;
|
|
1011
|
-
let controlPoint1Y;
|
|
1012
|
-
let controlPoint2X;
|
|
1013
|
-
let controlPoint2Y;
|
|
1014
|
-
if (letter === letter.toLowerCase()) {
|
|
1015
|
-
controlPoint1X = currentX + c1X;
|
|
1016
|
-
controlPoint1Y = currentY + c1Y;
|
|
1017
|
-
controlPoint2X = currentX + c2X;
|
|
1018
|
-
controlPoint2Y = currentY + c2Y;
|
|
1019
|
-
currentX += xReal;
|
|
1020
|
-
currentY += yReal;
|
|
1021
|
-
}
|
|
1022
|
-
else {
|
|
1023
|
-
controlPoint1X = c1X;
|
|
1024
|
-
controlPoint1Y = c1Y;
|
|
1025
|
-
controlPoint2X = c2X;
|
|
1026
|
-
controlPoint2Y = c2Y;
|
|
1027
|
-
currentX = xReal;
|
|
1028
|
-
currentY = yReal;
|
|
1029
|
-
}
|
|
1030
|
-
const controlPoint1 = newConverter.point(controlPoint1X, controlPoint1Y);
|
|
1031
|
-
const controlPoint2 = newConverter.point(controlPoint2X, controlPoint2Y);
|
|
1032
|
-
const point = newConverter.point(currentX, currentY);
|
|
1033
|
-
return [
|
|
1034
|
-
controlPoint1.x - xOrigin,
|
|
1035
|
-
controlPoint1.y - yOrigin,
|
|
1036
|
-
controlPoint2.x - xOrigin,
|
|
1037
|
-
controlPoint2.y - yOrigin,
|
|
1038
|
-
point.x - xOrigin,
|
|
1039
|
-
point.y - yOrigin,
|
|
1040
|
-
].join(',');
|
|
1041
|
-
})
|
|
1042
|
-
.join(' ');
|
|
1043
|
-
return (letter === null || letter === void 0 ? void 0 : letter.toUpperCase()) + '' + result;
|
|
1044
|
-
}
|
|
1045
|
-
case 's': {
|
|
1046
|
-
const groupedParams = groupBy(params, 4);
|
|
1047
|
-
const result = groupedParams
|
|
1048
|
-
// the control point 1 is omitted because it's the reflection of c2
|
|
1049
|
-
.map(([c2X, c2Y, xString, yString]) => [
|
|
1050
|
-
parseFloatValue(c2X, inherited.width) || 0,
|
|
1051
|
-
parseFloatValue(c2Y, inherited.height) || 0,
|
|
1052
|
-
parseFloatValue(xString, inherited.width) || 0,
|
|
1053
|
-
parseFloatValue(yString, inherited.height) || 0,
|
|
1054
|
-
])
|
|
1055
|
-
.map(([c2X, c2Y, xReal, yReal]) => {
|
|
1056
|
-
let controlPoint2X;
|
|
1057
|
-
let controlPoint2Y;
|
|
1058
|
-
if (letter === letter.toLowerCase()) {
|
|
1059
|
-
controlPoint2X = currentX + c2X;
|
|
1060
|
-
controlPoint2Y = currentY + c2Y;
|
|
1061
|
-
currentX += xReal;
|
|
1062
|
-
currentY += yReal;
|
|
1063
|
-
}
|
|
1064
|
-
else {
|
|
1065
|
-
controlPoint2X = c2X;
|
|
1066
|
-
controlPoint2Y = c2Y;
|
|
1067
|
-
currentX = xReal;
|
|
1068
|
-
currentY = yReal;
|
|
1069
|
-
}
|
|
1070
|
-
const controlPoint2 = newConverter.point(controlPoint2X, controlPoint2Y);
|
|
1071
|
-
const point = newConverter.point(currentX, currentY);
|
|
1072
|
-
return [
|
|
1073
|
-
controlPoint2.x - xOrigin,
|
|
1074
|
-
controlPoint2.y - yOrigin,
|
|
1075
|
-
point.x - xOrigin,
|
|
1076
|
-
point.y - yOrigin,
|
|
1077
|
-
].join(',');
|
|
1078
|
-
})
|
|
1079
|
-
.join(' ');
|
|
1080
|
-
return (letter === null || letter === void 0 ? void 0 : letter.toUpperCase()) + '' + result;
|
|
1081
|
-
}
|
|
1082
|
-
default: {
|
|
1083
|
-
const groupedParams = groupBy(params, 2);
|
|
1084
|
-
const result = groupedParams
|
|
1085
|
-
.map(([xString, yString]) => [
|
|
1086
|
-
parseFloatValue(xString, inherited.width) || 0,
|
|
1087
|
-
parseFloatValue(yString, inherited.height) || 0,
|
|
1088
|
-
])
|
|
1089
|
-
.map(([xReal, yReal]) => {
|
|
1090
|
-
if (letter === letter.toLowerCase()) {
|
|
1091
|
-
currentX += xReal;
|
|
1092
|
-
currentY += yReal;
|
|
1093
|
-
}
|
|
1094
|
-
else {
|
|
1095
|
-
currentX = xReal;
|
|
1096
|
-
currentY = yReal;
|
|
1097
|
-
}
|
|
1098
|
-
const point = newConverter.point(currentX, currentY);
|
|
1099
|
-
return [point.x - xOrigin, point.y - yOrigin].join(',');
|
|
1100
|
-
})
|
|
1101
|
-
.join(' ');
|
|
1102
|
-
return (letter === null || letter === void 0 ? void 0 : letter.toUpperCase()) + '' + result;
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
});
|
|
1106
|
-
}
|
|
1107
|
-
if (attributes.viewBox) {
|
|
1108
|
-
const viewBox = parseViewBox(attributes.viewBox);
|
|
1109
|
-
const size = {
|
|
1110
|
-
width: width || inherited.viewBox.width,
|
|
1111
|
-
height: height || inherited.viewBox.height,
|
|
1112
|
-
};
|
|
1113
|
-
const localConverter = getConverterWithAspectRatio(size, viewBox, attributes.preserveAspectRatio);
|
|
1114
|
-
const oldConverter = newConverter;
|
|
1115
|
-
newConverter = {
|
|
1116
|
-
point: (px, py) => {
|
|
1117
|
-
const { x: localX, y: localY } = localConverter.point(px, py);
|
|
1118
|
-
return oldConverter.point(localX, localY);
|
|
1119
|
-
},
|
|
1120
|
-
size: (w, h) => {
|
|
1121
|
-
const { width: localWidth, height: localHeight } = localConverter.size(w, h);
|
|
1122
|
-
return oldConverter.size(localWidth, localHeight);
|
|
1123
|
-
},
|
|
1124
|
-
};
|
|
369
|
+
newMatrix = combineTransformation(newMatrix, 'scale', [1, -1]);
|
|
370
|
+
svgAttributes.d = attributes.d;
|
|
1125
371
|
}
|
|
1126
|
-
// apply the converter only when there's a local fontSize instruction
|
|
1127
372
|
if (fontSizeRaw && newInherited.fontSize) {
|
|
1128
|
-
newInherited.fontSize =
|
|
373
|
+
newInherited.fontSize = newInherited.fontSize;
|
|
1129
374
|
}
|
|
1130
375
|
if (newInherited.fontFamily) {
|
|
1131
376
|
// Handle complex fontFamily like `"Linux Libertine O", serif`
|
|
@@ -1134,43 +379,13 @@ const parseAttributes = (element, inherited, converter) => {
|
|
|
1134
379
|
newInherited.fontFamily = inner[1] || inner[2];
|
|
1135
380
|
}
|
|
1136
381
|
if (newInherited.strokeWidth) {
|
|
1137
|
-
|
|
1138
|
-
svgAttributes.strokeWidth = Math.max(Math.min(Math.abs(result.width), Math.abs(result.height)), 1);
|
|
382
|
+
svgAttributes.strokeWidth = newInherited.strokeWidth;
|
|
1139
383
|
}
|
|
1140
384
|
return {
|
|
1141
385
|
inherited: newInherited,
|
|
1142
386
|
svgAttributes,
|
|
1143
|
-
converter: newConverter,
|
|
1144
387
|
tagName: element.tagName,
|
|
1145
|
-
|
|
1146
|
-
};
|
|
1147
|
-
const getConverter = (box, viewBox) => {
|
|
1148
|
-
const { width, height } = box;
|
|
1149
|
-
const { x: xMin, y: yMin, width: viewWidth, height: viewHeight } = viewBox;
|
|
1150
|
-
const converter = {
|
|
1151
|
-
point: (xReal, yReal) => ({
|
|
1152
|
-
x: ((xReal - xMin) / viewWidth) * (width || 0),
|
|
1153
|
-
y: ((yReal - yMin) / viewHeight) * (height || 0),
|
|
1154
|
-
}),
|
|
1155
|
-
size: (wReal, hReal) => ({
|
|
1156
|
-
width: (wReal / viewWidth) * (width || 0),
|
|
1157
|
-
height: (hReal / viewHeight) * (height || 0),
|
|
1158
|
-
}),
|
|
1159
|
-
};
|
|
1160
|
-
return converter;
|
|
1161
|
-
};
|
|
1162
|
-
const getConverterWithAspectRatio = (size, viewBox, preserveAspectRatio) => {
|
|
1163
|
-
// explanation about how the svg attributes applies transformations to the child elements
|
|
1164
|
-
// https://www.w3.org/TR/SVG/coords.html#ComputingAViewportsTransform
|
|
1165
|
-
const { x, y, width, height } = getFittingRectangle(viewBox.width, viewBox.height, size.width, size.height, preserveAspectRatio);
|
|
1166
|
-
const ratioConverter = getConverter({ width, height }, viewBox);
|
|
1167
|
-
// We translate the drawing in the page when the aspect ratio is different, according to the preserveAspectRatio instructions.
|
|
1168
|
-
return {
|
|
1169
|
-
point: (xReal, yReal) => {
|
|
1170
|
-
const P = ratioConverter.point(xReal, yReal);
|
|
1171
|
-
return { x: P.x + x, y: P.y + y };
|
|
1172
|
-
},
|
|
1173
|
-
size: ratioConverter.size,
|
|
388
|
+
matrix: newMatrix,
|
|
1174
389
|
};
|
|
1175
390
|
};
|
|
1176
391
|
const getFittingRectangle = (originalWidth, originalHeight, targetWidth, targetHeight, preserveAspectRatio) => {
|
|
@@ -1208,16 +423,62 @@ const getFittingRectangle = (originalWidth, originalHeight, targetWidth, targetH
|
|
|
1208
423
|
})();
|
|
1209
424
|
return { x, y, width, height };
|
|
1210
425
|
};
|
|
1211
|
-
const
|
|
426
|
+
const getAspectRatioTransformation = (matrix, originalWidth, originalHeight, targetWidth, targetHeight, preserveAspectRatio) => {
|
|
427
|
+
const scaleX = targetWidth / originalWidth;
|
|
428
|
+
const scaleY = targetHeight / originalHeight;
|
|
429
|
+
const boxScale = combineTransformation(matrix, 'scale', [
|
|
430
|
+
scaleX,
|
|
431
|
+
scaleY
|
|
432
|
+
]);
|
|
433
|
+
if (preserveAspectRatio === 'none') {
|
|
434
|
+
return {
|
|
435
|
+
clipBox: boxScale,
|
|
436
|
+
content: boxScale
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
// TODO: the following code works for the 'meet' param but not for the 'slice'
|
|
440
|
+
const scale = targetWidth > targetHeight ? scaleY : scaleX;
|
|
441
|
+
const dx = targetWidth - (originalWidth * scale);
|
|
442
|
+
const dy = targetHeight - (originalHeight * scale);
|
|
443
|
+
const [x, y] = (() => {
|
|
444
|
+
switch (preserveAspectRatio) {
|
|
445
|
+
case 'xMinYMin':
|
|
446
|
+
return [0, 0];
|
|
447
|
+
case 'xMidYMin':
|
|
448
|
+
return [dx / 2, 0];
|
|
449
|
+
case 'xMaxYMin':
|
|
450
|
+
return [dx, dy / 2];
|
|
451
|
+
case 'xMinYMid':
|
|
452
|
+
return [0, dy];
|
|
453
|
+
case 'xMaxYMid':
|
|
454
|
+
return [dx, dy / 2];
|
|
455
|
+
case 'xMinYMax':
|
|
456
|
+
return [0, dy];
|
|
457
|
+
case 'xMidYMax':
|
|
458
|
+
return [dx / 2, dy];
|
|
459
|
+
case 'xMaxYMax':
|
|
460
|
+
return [dx, dy];
|
|
461
|
+
case 'xMidYMid':
|
|
462
|
+
default:
|
|
463
|
+
return [dx / 2, dy / 2];
|
|
464
|
+
}
|
|
465
|
+
})();
|
|
466
|
+
const contentTransform = combineTransformation(combineTransformation(matrix, 'translate', [x, y]), 'scale', [scale]);
|
|
467
|
+
return {
|
|
468
|
+
clipBox: boxScale,
|
|
469
|
+
content: contentTransform
|
|
470
|
+
};
|
|
471
|
+
};
|
|
472
|
+
const parseHTMLNode = (node, inherited, matrix, clipSpaces) => {
|
|
1212
473
|
if (node.nodeType === node_html_better_parser_1.NodeType.COMMENT_NODE)
|
|
1213
474
|
return [];
|
|
1214
475
|
else if (node.nodeType === node_html_better_parser_1.NodeType.TEXT_NODE)
|
|
1215
476
|
return [];
|
|
1216
477
|
else if (node.tagName === 'g') {
|
|
1217
|
-
return parseGroupNode(node, inherited,
|
|
478
|
+
return parseGroupNode(node, inherited, matrix, clipSpaces);
|
|
1218
479
|
}
|
|
1219
480
|
else if (node.tagName === 'svg') {
|
|
1220
|
-
return parseSvgNode(node, inherited,
|
|
481
|
+
return parseSvgNode(node, inherited, matrix, clipSpaces);
|
|
1221
482
|
}
|
|
1222
483
|
else {
|
|
1223
484
|
if (node.tagName === 'polygon') {
|
|
@@ -1225,36 +486,69 @@ const parseHTMLNode = (node, inherited, converter) => {
|
|
|
1225
486
|
node.attributes.d = `M${node.attributes.points}Z`;
|
|
1226
487
|
delete node.attributes.points;
|
|
1227
488
|
}
|
|
1228
|
-
const attributes = parseAttributes(node, inherited,
|
|
1229
|
-
const svgAttributes =
|
|
489
|
+
const attributes = parseAttributes(node, inherited, matrix);
|
|
490
|
+
const svgAttributes = {
|
|
491
|
+
...attributes.inherited,
|
|
492
|
+
...attributes.svgAttributes,
|
|
493
|
+
matrix: attributes.matrix,
|
|
494
|
+
clipSpaces
|
|
495
|
+
};
|
|
1230
496
|
Object.assign(node, { svgAttributes });
|
|
1231
497
|
return [node];
|
|
1232
498
|
}
|
|
1233
499
|
};
|
|
1234
|
-
const parseSvgNode = (node, inherited,
|
|
500
|
+
const parseSvgNode = (node, inherited, matrix, clipSpaces) => {
|
|
1235
501
|
var _a, _b;
|
|
1236
502
|
// if the width/height aren't set, the svg will have the same dimension as the current drawing space
|
|
1237
503
|
(_a = node.attributes.width) !== null && _a !== void 0 ? _a : node.setAttribute('width', inherited.viewBox.width + '');
|
|
1238
504
|
(_b = node.attributes.height) !== null && _b !== void 0 ? _b : node.setAttribute('height', inherited.viewBox.height + '');
|
|
1239
|
-
const attributes = parseAttributes(node, inherited,
|
|
505
|
+
const attributes = parseAttributes(node, inherited, matrix);
|
|
1240
506
|
const result = [];
|
|
1241
507
|
const viewBox = node.attributes.viewBox
|
|
1242
508
|
? parseViewBox(node.attributes.viewBox)
|
|
1243
509
|
: node.attributes.width && node.attributes.height
|
|
1244
510
|
? parseViewBox(`0 0 ${node.attributes.width} ${node.attributes.height}`)
|
|
1245
511
|
: inherited.viewBox;
|
|
1246
|
-
const
|
|
512
|
+
const x = parseFloat(node.attributes.x) || 0;
|
|
513
|
+
const y = parseFloat(node.attributes.y) || 0;
|
|
514
|
+
let newMatrix = combineTransformation(matrix, 'translate', [x, y]);
|
|
515
|
+
const { clipBox: clipBoxTransform, content: contentTransform } = getAspectRatioTransformation(newMatrix, viewBox.width, viewBox.height, parseFloat(node.attributes.width), parseFloat(node.attributes.height), node.attributes.preserveAspectRatio || 'xMidYMid');
|
|
516
|
+
const topLeft = applyTransformation(clipBoxTransform, {
|
|
517
|
+
x: 0,
|
|
518
|
+
y: 0,
|
|
519
|
+
});
|
|
520
|
+
const topRight = applyTransformation(clipBoxTransform, {
|
|
521
|
+
x: viewBox.width,
|
|
522
|
+
y: 0,
|
|
523
|
+
});
|
|
524
|
+
const bottomRight = applyTransformation(clipBoxTransform, {
|
|
525
|
+
x: viewBox.width,
|
|
526
|
+
y: -viewBox.height,
|
|
527
|
+
});
|
|
528
|
+
const bottomLeft = applyTransformation(clipBoxTransform, {
|
|
529
|
+
x: 0,
|
|
530
|
+
y: -viewBox.height,
|
|
531
|
+
});
|
|
532
|
+
const baseClipSpace = {
|
|
533
|
+
topLeft,
|
|
534
|
+
topRight,
|
|
535
|
+
bottomRight,
|
|
536
|
+
bottomLeft
|
|
537
|
+
};
|
|
538
|
+
// TODO: maybe this is the correct transformation
|
|
539
|
+
// newMatrix = combineTransformation(newMatrix, 'translate', [-baseClipSpace.xMin, -baseClipSpace.yMin])
|
|
540
|
+
newMatrix = combineTransformation(contentTransform, 'translate', [-viewBox.x, -viewBox.y]);
|
|
1247
541
|
node.childNodes.forEach((child) => {
|
|
1248
|
-
const parsedNodes = parseHTMLNode(child,
|
|
542
|
+
const parsedNodes = parseHTMLNode(child, { ...attributes.inherited, viewBox }, newMatrix, [...clipSpaces, baseClipSpace]);
|
|
1249
543
|
result.push(...parsedNodes);
|
|
1250
544
|
});
|
|
1251
545
|
return result;
|
|
1252
546
|
};
|
|
1253
|
-
const parseGroupNode = (node, inherited,
|
|
1254
|
-
const attributes = parseAttributes(node, inherited,
|
|
547
|
+
const parseGroupNode = (node, inherited, matrix, clipSpaces) => {
|
|
548
|
+
const attributes = parseAttributes(node, inherited, matrix);
|
|
1255
549
|
const result = [];
|
|
1256
550
|
node.childNodes.forEach((child) => {
|
|
1257
|
-
result.push(...parseHTMLNode(child, attributes.inherited, attributes.
|
|
551
|
+
result.push(...parseHTMLNode(child, attributes.inherited, attributes.matrix, clipSpaces));
|
|
1258
552
|
});
|
|
1259
553
|
return result;
|
|
1260
554
|
};
|
|
@@ -1281,22 +575,21 @@ const parseViewBox = (viewBox) => {
|
|
|
1281
575
|
height: heightViewBox,
|
|
1282
576
|
};
|
|
1283
577
|
};
|
|
1284
|
-
const parse = (svg, { width, height,
|
|
578
|
+
const parse = (svg, { width, height, fontSize }, size, matrix) => {
|
|
1285
579
|
const htmlElement = (0, node_html_better_parser_1.parse)(svg).firstChild;
|
|
1286
580
|
if (width)
|
|
1287
581
|
htmlElement.setAttribute('width', width + '');
|
|
1288
582
|
if (height)
|
|
1289
583
|
htmlElement.setAttribute('height', height + '');
|
|
1290
|
-
if (x !== undefined)
|
|
1291
|
-
htmlElement.setAttribute('x', x + '');
|
|
1292
|
-
if (y !== undefined)
|
|
1293
|
-
htmlElement.setAttribute('y', size.height - y + '');
|
|
1294
584
|
if (fontSize)
|
|
1295
585
|
htmlElement.setAttribute('font-size', fontSize + '');
|
|
1296
586
|
// TODO: what should be the default viewBox?
|
|
1297
|
-
return parseHTMLNode(htmlElement,
|
|
587
|
+
return parseHTMLNode(htmlElement, {
|
|
588
|
+
...size,
|
|
589
|
+
viewBox: parseViewBox(htmlElement.attributes.viewBox || '0 0 1 1'),
|
|
590
|
+
}, matrix, []);
|
|
1298
591
|
};
|
|
1299
|
-
const drawSvg = (page, svg, options) =>
|
|
592
|
+
const drawSvg = async (page, svg, options) => {
|
|
1300
593
|
if (!svg)
|
|
1301
594
|
return;
|
|
1302
595
|
const size = page.getSize();
|
|
@@ -1321,18 +614,41 @@ const drawSvg = (page, svg, options) => (0, tslib_1.__awaiter)(void 0, void 0, v
|
|
|
1321
614
|
.map(([key, val]) => `${key}:${val};`)
|
|
1322
615
|
.join(''));
|
|
1323
616
|
}
|
|
1324
|
-
|
|
1325
|
-
const defaultConverter = {
|
|
1326
|
-
point: (xP, yP) => ({ x: xP, y: size.height - yP }),
|
|
1327
|
-
size: (w, h) => ({ width: w, height: h }),
|
|
1328
|
-
};
|
|
617
|
+
const baseTransformation = [1, 0, 0, 1, options.x || 0, options.y || 0];
|
|
1329
618
|
const runners = runnersToPage(page, options);
|
|
1330
|
-
const elements = parse(firstChild.outerHTML, options, size,
|
|
1331
|
-
|
|
619
|
+
const elements = parse(firstChild.outerHTML, options, size, baseTransformation);
|
|
620
|
+
await elements.reduce(async (prev, elt) => {
|
|
1332
621
|
var _a;
|
|
1333
|
-
|
|
622
|
+
await prev;
|
|
623
|
+
// uncomment these lines to draw the clipSpaces
|
|
624
|
+
// elt.svgAttributes.clipSpaces.forEach(space => {
|
|
625
|
+
// page.drawLine({
|
|
626
|
+
// start: space.topLeft,
|
|
627
|
+
// end: space.topRight,
|
|
628
|
+
// color: parseColor('#000000')?.rgb,
|
|
629
|
+
// thickness: 1
|
|
630
|
+
// })
|
|
631
|
+
// page.drawLine({
|
|
632
|
+
// start: space.topRight,
|
|
633
|
+
// end: space.bottomRight,
|
|
634
|
+
// color: parseColor('#000000')?.rgb,
|
|
635
|
+
// thickness: 1
|
|
636
|
+
// })
|
|
637
|
+
// page.drawLine({
|
|
638
|
+
// start: space.bottomRight,
|
|
639
|
+
// end: space.bottomLeft,
|
|
640
|
+
// color: parseColor('#000000')?.rgb,
|
|
641
|
+
// thickness: 1
|
|
642
|
+
// })
|
|
643
|
+
// page.drawLine({
|
|
644
|
+
// start: space.bottomLeft,
|
|
645
|
+
// end: space.topLeft,
|
|
646
|
+
// color: parseColor('#000000')?.rgb,
|
|
647
|
+
// thickness: 1
|
|
648
|
+
// })
|
|
649
|
+
// })
|
|
1334
650
|
return (_a = runners[elt.tagName]) === null || _a === void 0 ? void 0 : _a.call(runners, elt);
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
651
|
+
}, Promise.resolve());
|
|
652
|
+
};
|
|
1337
653
|
exports.drawSvg = drawSvg;
|
|
1338
654
|
//# sourceMappingURL=svg.js.map
|