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