@pdfme/pdf-lib 1.18.1 → 1.18.3
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/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.map +1 -1
- package/cjs/api/PDFPage.js +26 -16
- package/cjs/api/PDFPage.js.map +1 -1
- package/cjs/api/PDFPageOptions.d.ts +14 -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 +15 -0
- package/cjs/api/operations.d.ts.map +1 -1
- package/cjs/api/operations.js +22 -0
- 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 +1123 -2589
- 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 +1123 -2589
- 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.map +1 -1
- package/es/api/PDFPage.js +26 -17
- package/es/api/PDFPage.js.map +1 -1
- package/es/api/PDFPageOptions.d.ts +14 -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 +15 -0
- package/es/api/operations.d.ts.map +1 -1
- package/es/api/operations.js +23 -1
- 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 +12 -0
- package/src/api/PDFPageOptions.ts +14 -8
- package/src/api/operations.ts +45 -3
- package/src/api/svg.ts +305 -1086
- package/src/types/index.ts +6 -1
- package/ts3.4/cjs/api/PDFPageOptions.d.ts +14 -8
- package/ts3.4/cjs/api/operations.d.ts +15 -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/PDFPageOptions.d.ts +14 -8
- package/ts3.4/es/api/operations.d.ts +15 -0
- package/ts3.4/es/api/svg.d.ts +7 -1
- package/ts3.4/es/types/index.d.ts +4 -4
package/src/api/svg.ts
CHANGED
|
@@ -9,17 +9,13 @@ import { Color, colorString } from './colors';
|
|
|
9
9
|
import {
|
|
10
10
|
Degrees,
|
|
11
11
|
degreesToRadians,
|
|
12
|
-
RotationTypes,
|
|
13
|
-
degrees,
|
|
14
|
-
radiansToDegrees,
|
|
15
12
|
} from './rotations';
|
|
16
13
|
import PDFFont from './PDFFont';
|
|
17
14
|
import PDFPage from './PDFPage';
|
|
18
15
|
import { PDFPageDrawSVGElementOptions } from './PDFPageOptions';
|
|
19
16
|
import { LineCapStyle, LineJoinStyle, FillRule } from './operators';
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import { distanceCoords, isEqual, distance, rotate, angle, minus } from '../utils/maths';
|
|
17
|
+
import { TransformationMatrix, identityMatrix } from '../types/matrix'
|
|
18
|
+
import { Coordinates, Space } from '../types';
|
|
23
19
|
|
|
24
20
|
interface Position {
|
|
25
21
|
x: number;
|
|
@@ -33,11 +29,6 @@ interface Size {
|
|
|
33
29
|
|
|
34
30
|
type Box = Position & Size;
|
|
35
31
|
|
|
36
|
-
interface SVGSizeConverter {
|
|
37
|
-
point: (x: number, y: number) => Position;
|
|
38
|
-
size: (w: number, h: number) => Size;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
32
|
type SVGStyle = Record<string, string>;
|
|
42
33
|
|
|
43
34
|
type InheritedAttributes = {
|
|
@@ -85,34 +76,89 @@ type SVGAttributes = {
|
|
|
85
76
|
points?: string;
|
|
86
77
|
};
|
|
87
78
|
|
|
79
|
+
type TransformAttributes = {
|
|
80
|
+
matrix: TransformationMatrix
|
|
81
|
+
clipSpaces: Space[]
|
|
82
|
+
}
|
|
83
|
+
|
|
88
84
|
export type SVGElement = HTMLElement & {
|
|
89
|
-
svgAttributes: InheritedAttributes & SVGAttributes;
|
|
85
|
+
svgAttributes: InheritedAttributes & SVGAttributes & TransformAttributes;
|
|
90
86
|
};
|
|
91
87
|
|
|
92
88
|
interface SVGElementToDrawMap {
|
|
93
89
|
[cmd: string]: (a: SVGElement) => Promise<void>;
|
|
94
90
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
91
|
+
|
|
92
|
+
const combineMatrix = (
|
|
93
|
+
[a, b, c, d, e, f]: TransformationMatrix,
|
|
94
|
+
[a2, b2, c2, d2, e2, f2]: TransformationMatrix
|
|
95
|
+
): TransformationMatrix => [
|
|
96
|
+
a * a2 + c * b2,
|
|
97
|
+
b * a2 + d * b2,
|
|
98
|
+
a * c2 + c * d2,
|
|
99
|
+
b * c2 + d * d2,
|
|
100
|
+
a * e2 + c * f2 + e,
|
|
101
|
+
b * e2 + d * f2 + f,
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
const applyTransformation = ([a, b, c, d, e, f]: TransformationMatrix, { x, y }: Coordinates): Coordinates => ({
|
|
105
|
+
x: a * x + c * y + e,
|
|
106
|
+
y: b * x + d * y + f
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
type TransformationName = 'scale' | 'scaleX' | 'scaleY' | 'translate' | 'translateX' | 'translateY' | 'rotate' | 'skewX' | 'skewY' | 'matrix'
|
|
111
|
+
const transformationToMatrix = (name: TransformationName, args: number[]) : TransformationMatrix => {
|
|
112
|
+
switch (name) {
|
|
113
|
+
case 'scale':
|
|
114
|
+
case 'scaleX':
|
|
115
|
+
case 'scaleY': {
|
|
116
|
+
// [sx 0 0 sy 0 0]
|
|
117
|
+
const [sx, sy = sx] = args
|
|
118
|
+
return [name === 'scaleY' ? 1 : sx, 0, 0, name === 'scaleX' ? 1 : sy, 0, 0]
|
|
109
119
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
120
|
+
case 'translate':
|
|
121
|
+
case 'translateX':
|
|
122
|
+
case 'translateY': {
|
|
123
|
+
// [1 0 0 1 tx ty]
|
|
124
|
+
const [tx, ty = tx] = args
|
|
125
|
+
// -ty is necessary because the pdf's y axis is inverted
|
|
126
|
+
return [1, 0, 0, 1, name === 'translateY' ? 0 : tx, name === 'translateX' ? 0 : -ty]
|
|
127
|
+
}
|
|
128
|
+
case 'rotate': {
|
|
129
|
+
// [cos(a) sin(a) -sin(a) cos(a) 0 0]
|
|
130
|
+
const [a, x = 0, y = 0] = args
|
|
131
|
+
const t1 = transformationToMatrix('translate', [x, y])
|
|
132
|
+
const t2 = transformationToMatrix('translate', [-x, -y])
|
|
133
|
+
// -args[0] -> the '-' operator is necessary because the pdf rotation system is inverted
|
|
134
|
+
const aRadians = degreesToRadians(-a)
|
|
135
|
+
const r: TransformationMatrix = [Math.cos(aRadians), Math.sin(aRadians), -Math.sin(aRadians), Math.cos(aRadians), 0, 0]
|
|
136
|
+
// rotation around a point is the combination of: translate * rotate * (-translate)
|
|
137
|
+
return combineMatrix(combineMatrix(t1, r), t2)
|
|
138
|
+
}
|
|
139
|
+
case 'skewY':
|
|
140
|
+
case 'skewX': {
|
|
141
|
+
// [1 tan(a) 0 1 0 0]
|
|
142
|
+
// [1 0 tan(a) 1 0 0]
|
|
143
|
+
// -args[0] -> the '-' operator is necessary because the pdf rotation system is inverted
|
|
144
|
+
const a = degreesToRadians(-args[0])
|
|
145
|
+
const skew = Math.tan(a)
|
|
146
|
+
const skewX = name === 'skewX' ? skew : 0
|
|
147
|
+
const skewY = name === 'skewY' ? skew : 0
|
|
148
|
+
return [1, skewY, skewX, 1, 0, 0]
|
|
149
|
+
}
|
|
150
|
+
case 'matrix': {
|
|
151
|
+
const [a, b, c, d, e, f] = args
|
|
152
|
+
const r = transformationToMatrix('scale', [1, -1])
|
|
153
|
+
const m: TransformationMatrix = [a, b, c, d, e, f]
|
|
154
|
+
return combineMatrix(combineMatrix(r, m), r)
|
|
155
|
+
}
|
|
156
|
+
default:
|
|
157
|
+
return identityMatrix
|
|
158
|
+
}
|
|
159
|
+
}
|
|
113
160
|
|
|
114
|
-
const
|
|
115
|
-
isEqual(0, distance(dot, rect.orthoProjection(dot)));
|
|
161
|
+
const combineTransformation = (matrix: TransformationMatrix, name: TransformationName, args: number[]) => combineMatrix(matrix, transformationToMatrix(name, args))
|
|
116
162
|
|
|
117
163
|
const StrokeLineCapMap: Record<string, LineCapStyle> = {
|
|
118
164
|
butt: LineCapStyle.Butt,
|
|
@@ -131,538 +177,6 @@ const StrokeLineJoinMap: Record<string, LineJoinStyle> = {
|
|
|
131
177
|
round: LineJoinStyle.Round,
|
|
132
178
|
};
|
|
133
179
|
|
|
134
|
-
const getInnerSegment = (start: Point, end: Point, rect: Rectangle) => {
|
|
135
|
-
const isStartInside = isCoordinateInsideTheRect(start, rect);
|
|
136
|
-
const isEndInside = isCoordinateInsideTheRect(end, rect);
|
|
137
|
-
let resultLineStart = start;
|
|
138
|
-
let resultLineEnd = end;
|
|
139
|
-
// it means that the segment is already inside the rect
|
|
140
|
-
if (isEndInside && isStartInside) return new Segment(start, end);
|
|
141
|
-
|
|
142
|
-
const line = new Segment(start, end);
|
|
143
|
-
const intersection = getIntersections([rect, line]);
|
|
144
|
-
|
|
145
|
-
// if there's no intersection it means that the line doesn't intersects the svgRect and isn't visible
|
|
146
|
-
if (intersection.length === 0) return;
|
|
147
|
-
|
|
148
|
-
if (!isStartInside) {
|
|
149
|
-
// replace the line start point by the nearest intersection
|
|
150
|
-
const nearestPoint = intersection.sort(
|
|
151
|
-
(p1, p2) => distanceCoords(start, p1) - distanceCoords(start, p2),
|
|
152
|
-
)[0];
|
|
153
|
-
resultLineStart = new Point(nearestPoint);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (!isEndInside) {
|
|
157
|
-
// replace the line start point by the nearest intersection
|
|
158
|
-
const nearestPoint = intersection.sort(
|
|
159
|
-
(p1, p2) => distanceCoords(end, p1) - distanceCoords(end, p2),
|
|
160
|
-
)[0];
|
|
161
|
-
resultLineEnd = new Point(nearestPoint);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return new Segment(resultLineStart, resultLineEnd);
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
const cropSvgElement = (
|
|
168
|
-
svgRect: Rectangle,
|
|
169
|
-
element: SVGElement,
|
|
170
|
-
): SVGElement => {
|
|
171
|
-
switch (element.tagName) {
|
|
172
|
-
case 'text': {
|
|
173
|
-
const fontSize = element.svgAttributes.fontSize || 12;
|
|
174
|
-
// TODO: compute the right font boundaries to know which characters should be drawn
|
|
175
|
-
// this is an workaround to draw text that are just a little outside the viewbox boundaries
|
|
176
|
-
const start = new Point({
|
|
177
|
-
x: element.svgAttributes.x || 0,
|
|
178
|
-
y: element.svgAttributes.y || 0,
|
|
179
|
-
});
|
|
180
|
-
const paddingRect = new Rectangle(
|
|
181
|
-
new Point({
|
|
182
|
-
x: svgRect.start.x - fontSize,
|
|
183
|
-
y: svgRect.start.y + fontSize,
|
|
184
|
-
}),
|
|
185
|
-
new Point({ x: svgRect.end.x + fontSize, y: svgRect.end.y - fontSize }),
|
|
186
|
-
);
|
|
187
|
-
if (!isCoordinateInsideTheRect(start, paddingRect)) {
|
|
188
|
-
element.set_content('');
|
|
189
|
-
}
|
|
190
|
-
break;
|
|
191
|
-
}
|
|
192
|
-
case 'line': {
|
|
193
|
-
const start = new Point({
|
|
194
|
-
x: element.svgAttributes.x1!,
|
|
195
|
-
y: element.svgAttributes.y1!,
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
const end = new Point({
|
|
199
|
-
x: element.svgAttributes.x2!,
|
|
200
|
-
y: element.svgAttributes.y2!,
|
|
201
|
-
});
|
|
202
|
-
const line = getInnerSegment(start, end, svgRect);
|
|
203
|
-
element.svgAttributes.x1 = line ? line.A.x : 0;
|
|
204
|
-
element.svgAttributes.x2 = line ? line.B.x : 0;
|
|
205
|
-
element.svgAttributes.y1 = line ? line.A.y : 0;
|
|
206
|
-
element.svgAttributes.y2 = line ? line.B.y : 0;
|
|
207
|
-
break;
|
|
208
|
-
}
|
|
209
|
-
case 'path': {
|
|
210
|
-
// the path origin coordinate
|
|
211
|
-
const basePoint = new Point({
|
|
212
|
-
x: element.svgAttributes.x || 0,
|
|
213
|
-
y: element.svgAttributes.y || 0,
|
|
214
|
-
});
|
|
215
|
-
const normalizePoint = (p: Point) =>
|
|
216
|
-
new Point({ x: p.x - basePoint.x, y: p.y - basePoint.y });
|
|
217
|
-
/**
|
|
218
|
-
*
|
|
219
|
-
* @param origin is the origin of the current drawing in the page coordinate system
|
|
220
|
-
* @param command the path instruction
|
|
221
|
-
* @param params the instruction params
|
|
222
|
-
* @returns the point where the next instruction starts and the new instruction text
|
|
223
|
-
*/
|
|
224
|
-
const handlePath = (origin: Point, command: string, params: number[]) => {
|
|
225
|
-
switch (command) {
|
|
226
|
-
case 'm':
|
|
227
|
-
case 'M': {
|
|
228
|
-
const isLocalInstruction = command === command.toLocaleLowerCase();
|
|
229
|
-
const nextPoint = new Point({
|
|
230
|
-
x: (isLocalInstruction ? origin.x : basePoint.x) + params[0],
|
|
231
|
-
y: (isLocalInstruction ? origin.y : basePoint.y) + params[1],
|
|
232
|
-
});
|
|
233
|
-
return {
|
|
234
|
-
point: nextPoint,
|
|
235
|
-
command: `${command}${params[0]},${params[1]}`,
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
case 'v':
|
|
239
|
-
case 'V':
|
|
240
|
-
case 'h':
|
|
241
|
-
case 'H':
|
|
242
|
-
case 'l':
|
|
243
|
-
case 'L': {
|
|
244
|
-
const isLocalInstruction = ['l', 'v', 'h'].includes(command);
|
|
245
|
-
const getNextPoint = () => {
|
|
246
|
-
switch (command.toLocaleLowerCase()) {
|
|
247
|
-
case 'l':
|
|
248
|
-
return new Point({
|
|
249
|
-
x:
|
|
250
|
-
(isLocalInstruction ? origin.x : basePoint.x) + params[0],
|
|
251
|
-
y:
|
|
252
|
-
(isLocalInstruction ? origin.y : basePoint.y) + params[1],
|
|
253
|
-
});
|
|
254
|
-
case 'v':
|
|
255
|
-
return new Point({
|
|
256
|
-
x: origin.x,
|
|
257
|
-
y:
|
|
258
|
-
(isLocalInstruction ? origin.y : basePoint.y) + params[0],
|
|
259
|
-
});
|
|
260
|
-
case 'h':
|
|
261
|
-
return new Point({
|
|
262
|
-
x:
|
|
263
|
-
(isLocalInstruction ? origin.x : basePoint.x) + params[0],
|
|
264
|
-
y: origin.y,
|
|
265
|
-
});
|
|
266
|
-
default:
|
|
267
|
-
return new Point({
|
|
268
|
-
x: 0,
|
|
269
|
-
y: 0,
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
};
|
|
273
|
-
const nextPoint = getNextPoint();
|
|
274
|
-
const normalizedNext = normalizePoint(nextPoint);
|
|
275
|
-
|
|
276
|
-
let endPoint = new Point({ x: nextPoint.x, y: nextPoint.y });
|
|
277
|
-
let startPoint = new Point({ x: origin.x, y: origin.y });
|
|
278
|
-
const result = getInnerSegment(startPoint, endPoint, svgRect);
|
|
279
|
-
if (!result) {
|
|
280
|
-
return {
|
|
281
|
-
point: nextPoint,
|
|
282
|
-
command: `M${normalizedNext.x},${normalizedNext.y}`,
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
|
-
// if the point wasn't moved it means that it's inside the rect
|
|
286
|
-
const isStartInside = result.A.isEqual(startPoint);
|
|
287
|
-
const isEndInside = result.B.isEqual(endPoint);
|
|
288
|
-
|
|
289
|
-
// the intersection points are referencing the pdf coordinates, it's necessary to convert these points to the path's origin point
|
|
290
|
-
endPoint = normalizePoint(new Point(result.B.toCoords()));
|
|
291
|
-
startPoint = normalizePoint(new Point(result.A.toCoords()));
|
|
292
|
-
const startInstruction = isStartInside
|
|
293
|
-
? ''
|
|
294
|
-
: `M${startPoint.x},${startPoint.y}`;
|
|
295
|
-
const endInstruction = isEndInside
|
|
296
|
-
? ''
|
|
297
|
-
: `M${normalizedNext.x},${normalizedNext.y}`
|
|
298
|
-
return {
|
|
299
|
-
point: nextPoint,
|
|
300
|
-
command: `${startInstruction} L${endPoint.x},${endPoint.y} ${endInstruction} `,
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
case 'a':
|
|
304
|
-
case 'A': {
|
|
305
|
-
const isLocalInstruction = command === 'a';
|
|
306
|
-
const [, , , , , x, y] = params;
|
|
307
|
-
const nextPoint = new Point({
|
|
308
|
-
x: (isLocalInstruction ? origin.x : basePoint.x) + x,
|
|
309
|
-
y: (isLocalInstruction ? origin.y : basePoint.y) + y,
|
|
310
|
-
});
|
|
311
|
-
// TODO: implement the code to fit the Elliptical Arc Curve instructions into the viewbox
|
|
312
|
-
return {
|
|
313
|
-
point: nextPoint,
|
|
314
|
-
command: `${command} ${params.map((p) => `${p}`).join()}`,
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
case 'c':
|
|
318
|
-
case 'C': {
|
|
319
|
-
const isLocalInstruction = command === 'c';
|
|
320
|
-
|
|
321
|
-
let x = 0;
|
|
322
|
-
let y = 0;
|
|
323
|
-
|
|
324
|
-
for (
|
|
325
|
-
let pendingParams = params;
|
|
326
|
-
pendingParams.length > 0;
|
|
327
|
-
pendingParams = pendingParams.slice(6)
|
|
328
|
-
) {
|
|
329
|
-
const [, , , , pendingX, pendingY] = pendingParams;
|
|
330
|
-
if (isLocalInstruction) {
|
|
331
|
-
x += pendingX;
|
|
332
|
-
y += pendingY;
|
|
333
|
-
} else {
|
|
334
|
-
x = pendingX;
|
|
335
|
-
y = pendingY;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
const nextPoint = new Point({
|
|
340
|
-
x: (isLocalInstruction ? origin.x : basePoint.x) + x,
|
|
341
|
-
y: (isLocalInstruction ? origin.y : basePoint.y) + y,
|
|
342
|
-
});
|
|
343
|
-
// TODO: implement the code to fit the Cubic Bézier Curve instructions into the viewbox
|
|
344
|
-
return {
|
|
345
|
-
point: nextPoint,
|
|
346
|
-
command: `${command} ${params.map((p) => `${p}`).join()}`,
|
|
347
|
-
};
|
|
348
|
-
}
|
|
349
|
-
case 's':
|
|
350
|
-
case 'S':
|
|
351
|
-
const isLocalInstruction = command === 's';
|
|
352
|
-
|
|
353
|
-
let x = 0;
|
|
354
|
-
let y = 0;
|
|
355
|
-
|
|
356
|
-
for (
|
|
357
|
-
let pendingParams = params;
|
|
358
|
-
pendingParams.length > 0;
|
|
359
|
-
pendingParams = pendingParams.slice(4)
|
|
360
|
-
) {
|
|
361
|
-
const [, , pendingX, pendingY] = pendingParams;
|
|
362
|
-
x += pendingX;
|
|
363
|
-
y += pendingY;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
const nextPoint = new Point({
|
|
367
|
-
x: (isLocalInstruction ? origin.x : basePoint.x) + x,
|
|
368
|
-
y: (isLocalInstruction ? origin.y : basePoint.y) + y,
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
return {
|
|
372
|
-
point: nextPoint,
|
|
373
|
-
command: `${command} ${params.map((p) => `${p}`).join()}`,
|
|
374
|
-
};
|
|
375
|
-
case 'q':
|
|
376
|
-
case 'Q': {
|
|
377
|
-
const isLocalInstruction = command === 'q';
|
|
378
|
-
const [, , x, y] = params;
|
|
379
|
-
const nextPoint = new Point({
|
|
380
|
-
x: (isLocalInstruction ? origin.x : basePoint.x) + x,
|
|
381
|
-
y: (isLocalInstruction ? origin.y : basePoint.y) + y,
|
|
382
|
-
});
|
|
383
|
-
// TODO: implement the code to fit the Quadratic Bézier Curve instructions into the viewbox
|
|
384
|
-
return {
|
|
385
|
-
point: nextPoint,
|
|
386
|
-
command: `${command} ${params.map((p) => `${p}`).join()}`,
|
|
387
|
-
};
|
|
388
|
-
}
|
|
389
|
-
// TODO: Handle the remaining svg instructions: t,q
|
|
390
|
-
default:
|
|
391
|
-
return {
|
|
392
|
-
point: origin,
|
|
393
|
-
command: `${command} ${params.map((p) => `${p}`).join()}`,
|
|
394
|
-
};
|
|
395
|
-
}
|
|
396
|
-
};
|
|
397
|
-
|
|
398
|
-
const commands = element.svgAttributes.d?.match(
|
|
399
|
-
/(v|h|a|l|t|m|q|c|s|z)([0-9,e\s.-]*)/gi,
|
|
400
|
-
);
|
|
401
|
-
let currentPoint = new Point({ x: basePoint.x, y: basePoint.y });
|
|
402
|
-
const newPath = commands
|
|
403
|
-
?.map((command) => {
|
|
404
|
-
const letter = command.match(/[a-z]/i)?.[0];
|
|
405
|
-
const params = command
|
|
406
|
-
.match(
|
|
407
|
-
/(-?[0-9]+\.[0-9]+(e[+-]?[0-9]+)?)|(-?\.[0-9]+(e[+-]?[0-9]+)?)|(-?[0-9]+)/gi,
|
|
408
|
-
)
|
|
409
|
-
?.filter((m) => m !== '')
|
|
410
|
-
.map((v) => parseFloat(v));
|
|
411
|
-
if (letter && params) {
|
|
412
|
-
const result = handlePath(currentPoint, letter, params);
|
|
413
|
-
if (result) {
|
|
414
|
-
currentPoint = result.point;
|
|
415
|
-
return result.command;
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
return command;
|
|
419
|
-
})
|
|
420
|
-
.join(' ');
|
|
421
|
-
element.svgAttributes.d = newPath;
|
|
422
|
-
break;
|
|
423
|
-
}
|
|
424
|
-
case 'ellipse':
|
|
425
|
-
case 'circle': {
|
|
426
|
-
if (
|
|
427
|
-
element.svgAttributes.cx === undefined ||
|
|
428
|
-
element.svgAttributes.cy === undefined ||
|
|
429
|
-
element.svgAttributes.rx === undefined ||
|
|
430
|
-
element.svgAttributes.ry === undefined
|
|
431
|
-
) {
|
|
432
|
-
break;
|
|
433
|
-
}
|
|
434
|
-
const { cx = 0, cy = 0, rx = 0, ry = 0 } = element.svgAttributes;
|
|
435
|
-
const center = new Point({
|
|
436
|
-
x: cx,
|
|
437
|
-
y: cy,
|
|
438
|
-
});
|
|
439
|
-
const rotation = element.svgAttributes.rotation?.angle || 0;
|
|
440
|
-
// these points are relative to the ellipse's center
|
|
441
|
-
const a = new Point(rotate({ x: -rx, y: 0 }, degreesToRadians(rotation)));
|
|
442
|
-
const b = new Point(rotate({ x: rx, y: 0 }, degreesToRadians(rotation)));
|
|
443
|
-
const c = new Point(rotate({ x: 0, y: ry }, degreesToRadians(rotation)));
|
|
444
|
-
// these points are relative to the real coordinate system
|
|
445
|
-
const A = center.plus(a);
|
|
446
|
-
const B = center.plus(b);
|
|
447
|
-
const C = center.plus(c);
|
|
448
|
-
const ellipse = new Ellipse(A, B, C);
|
|
449
|
-
const intersections = getIntersections([svgRect, ellipse]);
|
|
450
|
-
const isCenterInsideRect = isCoordinateInsideTheRect(center, svgRect);
|
|
451
|
-
/**
|
|
452
|
-
* if there are less than 2 intersection, there are two possibilities:
|
|
453
|
-
* - the ellipse is outside the viewbox and therefore isn't visible
|
|
454
|
-
* - the ellipse is inside the viewbox and don't need to be cropped
|
|
455
|
-
*/
|
|
456
|
-
if (intersections.length < 2) {
|
|
457
|
-
!isCenterInsideRect && element.setAttribute('rx', '0');
|
|
458
|
-
!isCenterInsideRect && (element.svgAttributes.rx = 0);
|
|
459
|
-
!isCenterInsideRect && element.setAttribute('ry', '0');
|
|
460
|
-
!isCenterInsideRect && (element.svgAttributes.ry = 0);
|
|
461
|
-
break;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// viewbox rectangle coordinates
|
|
465
|
-
const P1 = new Point(svgRect.getCoords());
|
|
466
|
-
const P3 = new Point(svgRect.getEnd());
|
|
467
|
-
const P2 = new Point({ x: P3.x, y: P1.y });
|
|
468
|
-
const P4 = new Point({ x: P1.x, y: P3.y });
|
|
469
|
-
const top = new Segment(P1, P2);
|
|
470
|
-
const right = new Segment(P2, P3);
|
|
471
|
-
const bottom = new Segment(P3, P4);
|
|
472
|
-
const left = new Segment(P4, P1);
|
|
473
|
-
// Warning: keep the order of the segments, it's important when building the path that will represent the ellipse
|
|
474
|
-
const rectSegments = [top, right, bottom, left];
|
|
475
|
-
|
|
476
|
-
const isPointInsideEllipse = (P: Point) =>
|
|
477
|
-
(P.x - cx) ** 2 / rx ** 2 + (P.y - cy) ** 2 / ry ** 2 <= 1;
|
|
478
|
-
// check if the rect boundaries are inside the circle
|
|
479
|
-
const isRectInsideEllipse =
|
|
480
|
-
isPointInsideEllipse(P1) &&
|
|
481
|
-
isPointInsideEllipse(P2) &&
|
|
482
|
-
isPointInsideEllipse(P3) &&
|
|
483
|
-
isPointInsideEllipse(P4);
|
|
484
|
-
|
|
485
|
-
// the segments that are intersecting the circle. And, therefore, are lines that are cropping the drawing
|
|
486
|
-
const circleSegments = isRectInsideEllipse
|
|
487
|
-
? rectSegments
|
|
488
|
-
: rectSegments.map((segment, i) => {
|
|
489
|
-
const [p1, p2] = getIntersections([segment, ellipse])
|
|
490
|
-
// it's important to sort the segment's point because it impacts the angle of the arc, the points are sorted on clockwise direction
|
|
491
|
-
.sort((p1, p2) => {
|
|
492
|
-
// top
|
|
493
|
-
if (i === 0) {
|
|
494
|
-
return p1.x - p2.x;
|
|
495
|
-
// right
|
|
496
|
-
} else if (i === 1) {
|
|
497
|
-
return p2.y - p1.y;
|
|
498
|
-
// bottom
|
|
499
|
-
} else if (i === 2) {
|
|
500
|
-
return p2.x - p1.x;
|
|
501
|
-
// left
|
|
502
|
-
} else {
|
|
503
|
-
return p1.y - p2.y;
|
|
504
|
-
}
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
if (p1 && p2) {
|
|
508
|
-
return new Segment(new Point(p1), new Point(p2));
|
|
509
|
-
// if the other point isn't inside the circle it means that the circle isn't cropped by the segment
|
|
510
|
-
} else if (
|
|
511
|
-
p1 &&
|
|
512
|
-
(isPointInsideEllipse(segment.A) ||
|
|
513
|
-
isPointInsideEllipse(segment.B))
|
|
514
|
-
) {
|
|
515
|
-
const intersectionPoint = new Point(p1);
|
|
516
|
-
const innerPoint = isPointInsideEllipse(segment.A)
|
|
517
|
-
? segment.A
|
|
518
|
-
: segment.B;
|
|
519
|
-
// ensures that the segment is always following the clockwise direction
|
|
520
|
-
const start = innerPoint.isEqual(segment.A)
|
|
521
|
-
? innerPoint
|
|
522
|
-
: intersectionPoint;
|
|
523
|
-
const end = innerPoint.isEqual(segment.A)
|
|
524
|
-
? intersectionPoint
|
|
525
|
-
: innerPoint;
|
|
526
|
-
return new Segment(start, end);
|
|
527
|
-
// 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
|
|
528
|
-
} else if (
|
|
529
|
-
!(p1 && p2) &&
|
|
530
|
-
isPointInsideEllipse(segment.A) &&
|
|
531
|
-
isPointInsideEllipse(segment.B)
|
|
532
|
-
) {
|
|
533
|
-
return segment;
|
|
534
|
-
}
|
|
535
|
-
return;
|
|
536
|
-
});
|
|
537
|
-
|
|
538
|
-
const inverseAngle = (angle: number) => (360 - angle) % 360;
|
|
539
|
-
const pointsAngle = (
|
|
540
|
-
p1: Point,
|
|
541
|
-
p2: Point,
|
|
542
|
-
direction: 'clockwise' | 'counter-clockwise' = 'clockwise',
|
|
543
|
-
) => {
|
|
544
|
-
const startAngle = radiansToDegrees(
|
|
545
|
-
Math.atan2(p1.y - center.y, p1.x - center.x),
|
|
546
|
-
);
|
|
547
|
-
const endAngle = radiansToDegrees(
|
|
548
|
-
Math.atan2(p2.y - center.y, p2.x - center.x),
|
|
549
|
-
);
|
|
550
|
-
const arcAngle = (endAngle + (360 - startAngle)) % 360;
|
|
551
|
-
return direction === 'clockwise' ? arcAngle : inverseAngle(arcAngle);
|
|
552
|
-
};
|
|
553
|
-
|
|
554
|
-
/**
|
|
555
|
-
* - draw a line for each segment
|
|
556
|
-
* - if two segments aren't connected draw an arc connecting them
|
|
557
|
-
*/
|
|
558
|
-
let startPoint: Point | undefined;
|
|
559
|
-
// the point where the pen is located
|
|
560
|
-
let currentPoint: Point | undefined;
|
|
561
|
-
let lastSegment: Segment | undefined;
|
|
562
|
-
let path = circleSegments.reduce((path, segment) => {
|
|
563
|
-
if (!segment) return path;
|
|
564
|
-
if (!startPoint) {
|
|
565
|
-
startPoint = segment.A;
|
|
566
|
-
path = `M ${segment.A.x},${segment.A.y}`;
|
|
567
|
-
}
|
|
568
|
-
// if the current segment isn't connected to the last one, connect both with an arc
|
|
569
|
-
if (lastSegment && !lastSegment.B.isEqual(segment.A)) {
|
|
570
|
-
const arcAngle = pointsAngle(segment.A, lastSegment.B);
|
|
571
|
-
// angles greater than 180 degrees are marked as large-arc-flag = 1
|
|
572
|
-
path += `A ${rx},${ry} ${rotation} ${arcAngle > 180 ? 1 : 0},0 ${
|
|
573
|
-
segment.A.x
|
|
574
|
-
}, ${segment.A.y}`;
|
|
575
|
-
}
|
|
576
|
-
path += ` L ${segment.B.x},${segment.B.y}`;
|
|
577
|
-
currentPoint = segment.B;
|
|
578
|
-
lastSegment = segment;
|
|
579
|
-
return path;
|
|
580
|
-
}, '');
|
|
581
|
-
|
|
582
|
-
// if the path isn't closed, close it by drawing an arc
|
|
583
|
-
if (startPoint && currentPoint && !startPoint.isEqual(currentPoint)) {
|
|
584
|
-
const arcAngle = pointsAngle(
|
|
585
|
-
currentPoint,
|
|
586
|
-
startPoint,
|
|
587
|
-
'counter-clockwise',
|
|
588
|
-
);
|
|
589
|
-
// angles greater than 180 degrees are marked as large-arc-flag = 1
|
|
590
|
-
path += `A ${rx},${ry} ${rotation} ${arcAngle > 180 ? 1 : 0},0 ${startPoint.x
|
|
591
|
-
}, ${startPoint.y}`;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
// create a new element that will represent the cropped ellipse
|
|
595
|
-
const newElement = parseHtml(`<path d="${path}" fill="red"/>`).firstChild;
|
|
596
|
-
const svgAttributes: SVGAttributes = {
|
|
597
|
-
...element.svgAttributes,
|
|
598
|
-
// the x and y values are 0 because all the path coordinates are global
|
|
599
|
-
x: 0,
|
|
600
|
-
y: 0,
|
|
601
|
-
// the path coordinates are already rotated
|
|
602
|
-
rotate: undefined,
|
|
603
|
-
d: path,
|
|
604
|
-
};
|
|
605
|
-
Object.assign(newElement, { svgAttributes });
|
|
606
|
-
return newElement as SVGElement;
|
|
607
|
-
}
|
|
608
|
-
case 'rect': {
|
|
609
|
-
const {
|
|
610
|
-
x = 0,
|
|
611
|
-
y = 0,
|
|
612
|
-
width = 0,
|
|
613
|
-
height = 0,
|
|
614
|
-
rotate: rawRotation,
|
|
615
|
-
} = element.svgAttributes;
|
|
616
|
-
const rotation = rawRotation?.angle || 0;
|
|
617
|
-
if (!(width && height)) return element;
|
|
618
|
-
// bottomLeft point
|
|
619
|
-
const origin = new Point({ x, y });
|
|
620
|
-
|
|
621
|
-
const rotateAroundOrigin = (p: Point) =>
|
|
622
|
-
new Point(rotate(normalize(p), degreesToRadians(rotation))).plus(
|
|
623
|
-
origin,
|
|
624
|
-
);
|
|
625
|
-
const normalize = (p: Point) => p.plus({ x: -origin.x, y: -origin.y });
|
|
626
|
-
|
|
627
|
-
const topLeft = rotateAroundOrigin(origin.plus({ x: 0, y: -height }));
|
|
628
|
-
const topRight = rotateAroundOrigin(
|
|
629
|
-
origin.plus({ x: width, y: -height }),
|
|
630
|
-
);
|
|
631
|
-
const bottomRight = rotateAroundOrigin(origin.plus({ x: width, y: 0 }));
|
|
632
|
-
|
|
633
|
-
const pointToString = (p: Point) => [p.x, p.y].join();
|
|
634
|
-
|
|
635
|
-
const d = `M${pointToString(topLeft)} L${pointToString(
|
|
636
|
-
topRight,
|
|
637
|
-
)} L${pointToString(bottomRight)} L${pointToString(
|
|
638
|
-
origin,
|
|
639
|
-
)} L${pointToString(topLeft)}`;
|
|
640
|
-
const el = parseHtml(`<path d="${d}"/>`).firstChild;
|
|
641
|
-
|
|
642
|
-
const newAttributes = {
|
|
643
|
-
...element.svgAttributes,
|
|
644
|
-
d,
|
|
645
|
-
x: 0,
|
|
646
|
-
y: 0,
|
|
647
|
-
};
|
|
648
|
-
// @ts-ignore
|
|
649
|
-
delete newAttributes.width;
|
|
650
|
-
// @ts-ignore
|
|
651
|
-
delete newAttributes.height;
|
|
652
|
-
delete newAttributes.rotate;
|
|
653
|
-
delete newAttributes.rotation;
|
|
654
|
-
Object.assign(el, {
|
|
655
|
-
svgAttributes: newAttributes,
|
|
656
|
-
});
|
|
657
|
-
return cropSvgElement(svgRect, el as unknown as SVGElement);
|
|
658
|
-
}
|
|
659
|
-
// TODO: implement the crop for the following elements
|
|
660
|
-
case 'image':
|
|
661
|
-
default:
|
|
662
|
-
return element;
|
|
663
|
-
}
|
|
664
|
-
return element;
|
|
665
|
-
};
|
|
666
180
|
// TODO: Improve type system to require the correct props for each tagName.
|
|
667
181
|
/** methods to draw SVGElements onto a PDFPage */
|
|
668
182
|
const runnersToPage = (
|
|
@@ -670,7 +184,7 @@ const runnersToPage = (
|
|
|
670
184
|
options: PDFPageDrawSVGElementOptions,
|
|
671
185
|
): SVGElementToDrawMap => ({
|
|
672
186
|
async text(element) {
|
|
673
|
-
|
|
187
|
+
const anchor = element.svgAttributes.textAnchor;
|
|
674
188
|
const dominantBaseline = element.svgAttributes.dominantBaseline
|
|
675
189
|
const text = element.text.trim().replace(/\s/g, ' ');
|
|
676
190
|
const fontSize = element.svgAttributes.fontSize || 12;
|
|
@@ -698,7 +212,7 @@ const runnersToPage = (
|
|
|
698
212
|
|
|
699
213
|
const font =
|
|
700
214
|
options.fonts && getBestFont(element.svgAttributes, options.fonts);
|
|
701
|
-
|
|
215
|
+
const textWidth = (font || page.getFont()[0]).widthOfTextAtSize(
|
|
702
216
|
text,
|
|
703
217
|
fontSize,
|
|
704
218
|
);
|
|
@@ -715,53 +229,52 @@ const runnersToPage = (
|
|
|
715
229
|
: dominantBaseline === 'middle'
|
|
716
230
|
? textHeight / 2
|
|
717
231
|
: 0
|
|
718
|
-
|
|
719
|
-
x: (element.svgAttributes.x || 0) - offsetX,
|
|
720
|
-
y: (element.svgAttributes.y || 0) - offsetY,
|
|
721
|
-
});
|
|
722
|
-
// TODO: compute the right font boundaries to know which characters should be drawed
|
|
723
|
-
// this is an workaround to draw text that are just a little outside the viewbox boundaries
|
|
232
|
+
|
|
724
233
|
page.drawText(text, {
|
|
725
|
-
x:
|
|
726
|
-
y:
|
|
234
|
+
x: -offsetX,
|
|
235
|
+
y: -offsetY,
|
|
727
236
|
font,
|
|
237
|
+
// TODO: the font size should be correctly scaled too
|
|
728
238
|
size: fontSize,
|
|
729
239
|
color: element.svgAttributes.fill,
|
|
730
240
|
opacity: element.svgAttributes.fillOpacity,
|
|
731
|
-
|
|
241
|
+
matrix: element.svgAttributes.matrix,
|
|
242
|
+
clipSpaces: element.svgAttributes.clipSpaces,
|
|
732
243
|
});
|
|
733
244
|
},
|
|
734
245
|
async line(element) {
|
|
735
246
|
page.drawLine({
|
|
736
247
|
start: {
|
|
737
|
-
x: element.svgAttributes.x1
|
|
738
|
-
y: element.svgAttributes.y1
|
|
248
|
+
x: element.svgAttributes.x1 || 0,
|
|
249
|
+
y: -element.svgAttributes.y1! || 0,
|
|
739
250
|
},
|
|
740
251
|
end: {
|
|
741
|
-
x: element.svgAttributes.x2
|
|
742
|
-
y: element.svgAttributes.y2
|
|
252
|
+
x: element.svgAttributes.x2! || 0,
|
|
253
|
+
y: -element.svgAttributes.y2! || 0,
|
|
743
254
|
},
|
|
744
255
|
thickness: element.svgAttributes.strokeWidth,
|
|
745
256
|
color: element.svgAttributes.stroke,
|
|
746
257
|
opacity: element.svgAttributes.strokeOpacity,
|
|
747
258
|
lineCap: element.svgAttributes.strokeLineCap,
|
|
259
|
+
matrix: element.svgAttributes.matrix,
|
|
260
|
+
clipSpaces: element.svgAttributes.clipSpaces,
|
|
748
261
|
});
|
|
749
262
|
},
|
|
750
263
|
async path(element) {
|
|
751
264
|
if (!element.svgAttributes.d) return
|
|
752
265
|
// See https://jsbin.com/kawifomupa/edit?html,output and
|
|
753
266
|
page.drawSvgPath(element.svgAttributes.d, {
|
|
754
|
-
x:
|
|
755
|
-
y:
|
|
267
|
+
x: 0,
|
|
268
|
+
y: 0,
|
|
756
269
|
borderColor: element.svgAttributes.stroke,
|
|
757
270
|
borderWidth: element.svgAttributes.strokeWidth,
|
|
758
271
|
borderOpacity: element.svgAttributes.strokeOpacity,
|
|
759
272
|
borderLineCap: element.svgAttributes.strokeLineCap,
|
|
760
273
|
color: element.svgAttributes.fill,
|
|
761
274
|
opacity: element.svgAttributes.fillOpacity,
|
|
762
|
-
scale: element.svgAttributes.scale,
|
|
763
|
-
rotate: element.svgAttributes.rotate,
|
|
764
275
|
fillRule: element.svgAttributes.fillRule,
|
|
276
|
+
matrix: element.svgAttributes.matrix,
|
|
277
|
+
clipSpaces: element.svgAttributes.clipSpaces,
|
|
765
278
|
});
|
|
766
279
|
},
|
|
767
280
|
async image(element) {
|
|
@@ -780,21 +293,20 @@ const runnersToPage = (
|
|
|
780
293
|
element.svgAttributes.preserveAspectRatio,
|
|
781
294
|
);
|
|
782
295
|
page.drawImage(img, {
|
|
783
|
-
x
|
|
784
|
-
y:
|
|
296
|
+
x,
|
|
297
|
+
y: -y - height,
|
|
785
298
|
width,
|
|
786
299
|
height,
|
|
787
300
|
opacity: element.svgAttributes.fillOpacity,
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
rotate: element.svgAttributes.rotate,
|
|
301
|
+
matrix: element.svgAttributes.matrix,
|
|
302
|
+
clipSpaces: element.svgAttributes.clipSpaces,
|
|
791
303
|
});
|
|
792
304
|
},
|
|
793
305
|
async rect(element) {
|
|
794
306
|
if (!element.svgAttributes.fill && !element.svgAttributes.stroke) return;
|
|
795
307
|
page.drawRectangle({
|
|
796
|
-
x:
|
|
797
|
-
y:
|
|
308
|
+
x: 0,
|
|
309
|
+
y: 0,
|
|
798
310
|
width: element.svgAttributes.width,
|
|
799
311
|
height: element.svgAttributes.height * -1,
|
|
800
312
|
borderColor: element.svgAttributes.stroke,
|
|
@@ -803,15 +315,14 @@ const runnersToPage = (
|
|
|
803
315
|
borderLineCap: element.svgAttributes.strokeLineCap,
|
|
804
316
|
color: element.svgAttributes.fill,
|
|
805
317
|
opacity: element.svgAttributes.fillOpacity,
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
rotate: element.svgAttributes.rotate,
|
|
318
|
+
matrix: element.svgAttributes.matrix,
|
|
319
|
+
clipSpaces: element.svgAttributes.clipSpaces,
|
|
809
320
|
});
|
|
810
321
|
},
|
|
811
322
|
async ellipse(element) {
|
|
812
323
|
page.drawEllipse({
|
|
813
|
-
x: element.svgAttributes.cx,
|
|
814
|
-
y: element.svgAttributes.cy,
|
|
324
|
+
x: element.svgAttributes.cx || 0,
|
|
325
|
+
y: -(element.svgAttributes.cy || 0),
|
|
815
326
|
xScale: element.svgAttributes.rx,
|
|
816
327
|
yScale: element.svgAttributes.ry,
|
|
817
328
|
borderColor: element.svgAttributes.stroke,
|
|
@@ -820,7 +331,8 @@ const runnersToPage = (
|
|
|
820
331
|
borderLineCap: element.svgAttributes.strokeLineCap,
|
|
821
332
|
color: element.svgAttributes.fill,
|
|
822
333
|
opacity: element.svgAttributes.fillOpacity,
|
|
823
|
-
|
|
334
|
+
matrix: element.svgAttributes.matrix,
|
|
335
|
+
clipSpaces: element.svgAttributes.clipSpaces,
|
|
824
336
|
});
|
|
825
337
|
},
|
|
826
338
|
async circle(element) {
|
|
@@ -828,86 +340,6 @@ const runnersToPage = (
|
|
|
828
340
|
},
|
|
829
341
|
});
|
|
830
342
|
|
|
831
|
-
const transform = (
|
|
832
|
-
converter: SVGSizeConverter,
|
|
833
|
-
name: string,
|
|
834
|
-
args: number[],
|
|
835
|
-
): SVGSizeConverter => {
|
|
836
|
-
switch (name) {
|
|
837
|
-
case 'scaleX':
|
|
838
|
-
return transform(converter, 'scale', [args[0], 0]);
|
|
839
|
-
case 'scaleY':
|
|
840
|
-
return transform(converter, 'scale', [0, args[0]]);
|
|
841
|
-
case 'scale':
|
|
842
|
-
const [xScale, yScale = xScale] = args;
|
|
843
|
-
return {
|
|
844
|
-
point: (x: number, y: number) =>
|
|
845
|
-
converter.point(x * xScale, y * yScale),
|
|
846
|
-
size: (w: number, h: number) => converter.size(w * xScale, h * yScale),
|
|
847
|
-
};
|
|
848
|
-
case 'translateX':
|
|
849
|
-
return transform(converter, 'translate', [args[0], 0]);
|
|
850
|
-
case 'translateY':
|
|
851
|
-
return transform(converter, 'translate', [0, args[0]]);
|
|
852
|
-
case 'translate':
|
|
853
|
-
const [dx, dy = dx] = args;
|
|
854
|
-
return {
|
|
855
|
-
point: (x: number, y: number) => converter.point(x + dx, y + dy),
|
|
856
|
-
size: converter.size,
|
|
857
|
-
};
|
|
858
|
-
case 'rotate': {
|
|
859
|
-
if (args.length > 1) {
|
|
860
|
-
const [a, x, y = x] = args;
|
|
861
|
-
let tempResult = transform(converter, 'translate', [x, y]);
|
|
862
|
-
tempResult = transform(tempResult, 'rotate', [a]);
|
|
863
|
-
return transform(tempResult, 'translate', [-x, -y]);
|
|
864
|
-
} else {
|
|
865
|
-
const [a] = args;
|
|
866
|
-
const angle = degreesToRadians(a);
|
|
867
|
-
return {
|
|
868
|
-
point: (x, y) =>
|
|
869
|
-
converter.point(
|
|
870
|
-
x * Math.cos(angle) - y * Math.sin(angle),
|
|
871
|
-
y * Math.cos(angle) + x * Math.sin(angle),
|
|
872
|
-
),
|
|
873
|
-
size: converter.size,
|
|
874
|
-
};
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
case 'matrix': {
|
|
878
|
-
const [scaleX, skewY, skewX, scaleY, translateX, translateY] = args;
|
|
879
|
-
return {
|
|
880
|
-
point: (x: number, y: number) =>
|
|
881
|
-
converter.point(
|
|
882
|
-
x * scaleX + y * skewX + translateX,
|
|
883
|
-
x * skewY + y * scaleY + translateY,
|
|
884
|
-
),
|
|
885
|
-
size: (w: number, h: number) => converter.size(w * scaleX, h * scaleY),
|
|
886
|
-
};
|
|
887
|
-
}
|
|
888
|
-
case 'skewX': {
|
|
889
|
-
const angle = degreesToRadians(args[0]);
|
|
890
|
-
return {
|
|
891
|
-
point: (x: number, y: number) =>
|
|
892
|
-
converter.point((1 + x) * Math.tan(angle), y),
|
|
893
|
-
size: converter.size,
|
|
894
|
-
};
|
|
895
|
-
}
|
|
896
|
-
case 'skewY': {
|
|
897
|
-
const angle = degreesToRadians(args[0]);
|
|
898
|
-
return {
|
|
899
|
-
point: (x: number, y: number) =>
|
|
900
|
-
converter.point(x, (1 + y) * Math.tan(angle)),
|
|
901
|
-
size: converter.size,
|
|
902
|
-
};
|
|
903
|
-
}
|
|
904
|
-
default: {
|
|
905
|
-
console.log('transformation unsupported:', name);
|
|
906
|
-
return converter;
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
};
|
|
910
|
-
|
|
911
343
|
const styleOrAttribute = (
|
|
912
344
|
attributes: Attributes,
|
|
913
345
|
style: SVGStyle,
|
|
@@ -946,15 +378,15 @@ const parseColor = (
|
|
|
946
378
|
|
|
947
379
|
type ParsedAttributes = {
|
|
948
380
|
inherited: InheritedAttributes;
|
|
949
|
-
converter: SVGSizeConverter;
|
|
950
381
|
tagName: string;
|
|
951
382
|
svgAttributes: SVGAttributes;
|
|
383
|
+
matrix: TransformationMatrix;
|
|
952
384
|
};
|
|
953
385
|
|
|
954
386
|
const parseAttributes = (
|
|
955
387
|
element: HTMLElement,
|
|
956
388
|
inherited: InheritedAttributes,
|
|
957
|
-
|
|
389
|
+
matrix: TransformationMatrix,
|
|
958
390
|
): ParsedAttributes => {
|
|
959
391
|
const attributes = element.attributes;
|
|
960
392
|
const style = parseStyles(attributes.style);
|
|
@@ -1038,8 +470,6 @@ const parseAttributes = (
|
|
|
1038
470
|
preserveAspectRatio: attributes.preserveAspectRatio,
|
|
1039
471
|
};
|
|
1040
472
|
|
|
1041
|
-
let newConverter = converter;
|
|
1042
|
-
|
|
1043
473
|
let transformList = attributes.transform || '';
|
|
1044
474
|
// Handle transformations set as direct attributes
|
|
1045
475
|
[
|
|
@@ -1058,26 +488,12 @@ const parseAttributes = (
|
|
|
1058
488
|
transformList = attributes[name] + ' ' + transformList;
|
|
1059
489
|
}
|
|
1060
490
|
});
|
|
1061
|
-
|
|
1062
|
-
(['skewX', 'skewY', 'rotate'] as const).forEach((name) => {
|
|
1063
|
-
if (attributes[name]) {
|
|
1064
|
-
const d = attributes[name].match(/-?(\d+\.?|\.)\d*/)?.[0];
|
|
1065
|
-
if (d !== undefined) {
|
|
1066
|
-
svgAttributes[name] = {
|
|
1067
|
-
angle: parseInt(d, 10),
|
|
1068
|
-
type: RotationTypes.Degrees,
|
|
1069
|
-
};
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
});
|
|
1073
|
-
if (attributes.scale) {
|
|
1074
|
-
const d = attributes.scale.match(/-?(\d+\.?|\.)\d*/)?.[0];
|
|
1075
|
-
if (d !== undefined) svgAttributes.scale = parseInt(d, 10);
|
|
1076
|
-
}
|
|
491
|
+
|
|
1077
492
|
// Convert x/y as if it was a translation
|
|
1078
493
|
if (x || y) {
|
|
1079
494
|
transformList = transformList + `translate(${x || 0} ${y || 0}) `;
|
|
1080
495
|
}
|
|
496
|
+
let newMatrix = matrix
|
|
1081
497
|
// Apply the transformations
|
|
1082
498
|
if (transformList) {
|
|
1083
499
|
const regexTransform = /(\w+)\((.+?)\)/g;
|
|
@@ -1088,335 +504,42 @@ const parseAttributes = (
|
|
|
1088
504
|
.split(/\s*,\s*|\s+/)
|
|
1089
505
|
.filter((value) => value.length > 0)
|
|
1090
506
|
.map((value) => parseFloat(value));
|
|
1091
|
-
|
|
1092
|
-
newConverter = transform(newConverter, name, args);
|
|
1093
|
-
const xAxisVector = minus(currentConverter.point(0, 0), currentConverter.point(1, 0))
|
|
1094
|
-
const xAxisVectorPostTransform = minus(newConverter.point(0, 0), newConverter.point(1, 0))
|
|
1095
|
-
// matrix transform may also represent rotations: https://www.w3.org/TR/SVGTiny12/coords.html
|
|
1096
|
-
if (name === 'rotate' || name === 'matrix') {
|
|
1097
|
-
// transformations over x and y axis might change the page coord direction
|
|
1098
|
-
const { width: xDirection, height: yDirection } = currentConverter.size(
|
|
1099
|
-
1,
|
|
1100
|
-
1,
|
|
1101
|
-
);
|
|
1102
|
-
const rotationAdded = name === 'rotate' ? args[0] : radiansToDegrees(angle(xAxisVectorPostTransform, xAxisVector))
|
|
1103
|
-
// the page Y coord is inverted so the angle rotation is inverted too
|
|
1104
|
-
const pageYDirection = -1;
|
|
1105
|
-
newInherited.rotation = degrees(
|
|
1106
|
-
pageYDirection * rotationAdded * Math.sign(xDirection * yDirection) +
|
|
1107
|
-
(inherited.rotation?.angle || 0),
|
|
1108
|
-
);
|
|
1109
|
-
svgAttributes.rotate = newInherited.rotation;
|
|
1110
|
-
}
|
|
507
|
+
newMatrix = combineTransformation(newMatrix, name as TransformationName, args)
|
|
1111
508
|
parsed = regexTransform.exec(transformList);
|
|
1112
509
|
}
|
|
1113
510
|
}
|
|
1114
511
|
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
svgAttributes.x = newX;
|
|
1118
|
-
svgAttributes.y = newY;
|
|
512
|
+
svgAttributes.x = x;
|
|
513
|
+
svgAttributes.y = y;
|
|
1119
514
|
|
|
1120
515
|
if (attributes.cx || attributes.cy) {
|
|
1121
|
-
|
|
1122
|
-
svgAttributes.
|
|
1123
|
-
svgAttributes.cy = newCY;
|
|
516
|
+
svgAttributes.cx = cx;
|
|
517
|
+
svgAttributes.cy = cy;
|
|
1124
518
|
}
|
|
1125
519
|
if (attributes.rx || attributes.ry || attributes.r) {
|
|
1126
|
-
|
|
1127
|
-
svgAttributes.
|
|
1128
|
-
svgAttributes.ry = newRY;
|
|
520
|
+
svgAttributes.rx = rx;
|
|
521
|
+
svgAttributes.ry = ry;
|
|
1129
522
|
}
|
|
1130
523
|
if (attributes.x1 || attributes.y1) {
|
|
1131
|
-
|
|
1132
|
-
svgAttributes.
|
|
1133
|
-
svgAttributes.y1 = newY1;
|
|
524
|
+
svgAttributes.x1 = x1;
|
|
525
|
+
svgAttributes.y1 = y1;
|
|
1134
526
|
}
|
|
1135
527
|
if (attributes.x2 || attributes.y2) {
|
|
1136
|
-
|
|
1137
|
-
svgAttributes.
|
|
1138
|
-
svgAttributes.y2 = newY2;
|
|
528
|
+
svgAttributes.x2 = x2;
|
|
529
|
+
svgAttributes.y2 = y2;
|
|
1139
530
|
}
|
|
1140
531
|
if (attributes.width || attributes.height) {
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
height ?? inherited.height,
|
|
1144
|
-
);
|
|
1145
|
-
svgAttributes.width = size.width;
|
|
1146
|
-
svgAttributes.height = size.height;
|
|
532
|
+
svgAttributes.width = width ?? inherited.width
|
|
533
|
+
svgAttributes.height = height ?? inherited.height
|
|
1147
534
|
}
|
|
1148
535
|
|
|
1149
|
-
// We convert all the points from the path
|
|
1150
536
|
if (attributes.d) {
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
let currentX = 0;
|
|
1154
|
-
let currentY = 0;
|
|
1155
|
-
svgAttributes.d = attributes.d?.replace(
|
|
1156
|
-
/(l|m|s|t|q|c|z|a|v|h)([0-9,e\s.-]*)/gi,
|
|
1157
|
-
(command) => {
|
|
1158
|
-
const letter = command.match(/[a-z]/i)?.[0];
|
|
1159
|
-
if (letter?.toLocaleLowerCase() === 'z') return letter;
|
|
1160
|
-
// const params = command.match(/([0-9e.-]+)/ig)?.filter(m => m !== '')//.map(v => parseFloat(v))
|
|
1161
|
-
const params = command
|
|
1162
|
-
.match(
|
|
1163
|
-
/(-?[0-9]+\.[0-9]+(e[+-]?[0-9]+)?)|(-?\.[0-9]+(e[+-]?[0-9]+)?)|(-?[0-9]+)/gi,
|
|
1164
|
-
)
|
|
1165
|
-
?.filter((m) => m !== ''); // .map(v => parseFloat(v))
|
|
1166
|
-
if (!params) return letter || '';
|
|
1167
|
-
switch (letter?.toLocaleLowerCase()) {
|
|
1168
|
-
case 'm':
|
|
1169
|
-
case 'l': {
|
|
1170
|
-
const groupedParams = groupBy<string>(params, 2);
|
|
1171
|
-
return groupedParams
|
|
1172
|
-
.map((pair, pairIndex) => {
|
|
1173
|
-
const [x, y] = pair;
|
|
1174
|
-
const xReal = parseFloatValue(x, inherited.width) || 0;
|
|
1175
|
-
const yReal = parseFloatValue(y, innerHeight) || 0;
|
|
1176
|
-
if (letter === letter.toLowerCase()) {
|
|
1177
|
-
currentX += xReal;
|
|
1178
|
-
currentY += yReal;
|
|
1179
|
-
} else {
|
|
1180
|
-
currentX = xReal;
|
|
1181
|
-
currentY = yReal;
|
|
1182
|
-
}
|
|
1183
|
-
const point = newConverter.point(currentX, currentY);
|
|
1184
|
-
return (
|
|
1185
|
-
(pairIndex > 0 || letter.toUpperCase() === 'L' ? 'L' : 'M') +
|
|
1186
|
-
[point.x - xOrigin, point.y - yOrigin].join(',')
|
|
1187
|
-
);
|
|
1188
|
-
})
|
|
1189
|
-
.join(' ');
|
|
1190
|
-
}
|
|
1191
|
-
case 'v': {
|
|
1192
|
-
return params
|
|
1193
|
-
.map((value) => {
|
|
1194
|
-
const coord = parseFloatValue(value) || 0;
|
|
1195
|
-
if (letter === letter.toLowerCase()) {
|
|
1196
|
-
currentY += coord;
|
|
1197
|
-
} else {
|
|
1198
|
-
currentY = coord;
|
|
1199
|
-
}
|
|
1200
|
-
const point = newConverter.point(currentX, currentY);
|
|
1201
|
-
// we can't use 'v' as the final command because rotations might require a different command after the path parsing
|
|
1202
|
-
// for instance, a 90 degree rotation would turn a 'v' into an 'h' command
|
|
1203
|
-
return `L${point.x - xOrigin} ${point.y - yOrigin}`;
|
|
1204
|
-
})
|
|
1205
|
-
.join(' ');
|
|
1206
|
-
}
|
|
1207
|
-
case 'h': {
|
|
1208
|
-
return params
|
|
1209
|
-
.map((value) => {
|
|
1210
|
-
const coord = parseFloatValue(value) || 0;
|
|
1211
|
-
if (letter === letter.toLowerCase()) {
|
|
1212
|
-
currentX += coord;
|
|
1213
|
-
} else {
|
|
1214
|
-
currentX = coord;
|
|
1215
|
-
}
|
|
1216
|
-
const point = newConverter.point(currentX, currentY);
|
|
1217
|
-
// we can't use 'h' as the final command because rotations might require a different command after the path parsing
|
|
1218
|
-
// for instance, a 90 degree rotation would turn a 'h' into an 'v' command
|
|
1219
|
-
return `L${point.x - xOrigin} ${point.y - yOrigin}`;
|
|
1220
|
-
})
|
|
1221
|
-
.join(' ');
|
|
1222
|
-
}
|
|
1223
|
-
case 'a': {
|
|
1224
|
-
const groupedParams = groupBy<string>(params, 7);
|
|
1225
|
-
return groupedParams
|
|
1226
|
-
.map((p) => {
|
|
1227
|
-
const [
|
|
1228
|
-
rxPixel,
|
|
1229
|
-
ryPixel,
|
|
1230
|
-
xAxisRotation = '0',
|
|
1231
|
-
largeArc = '0',
|
|
1232
|
-
sweepFlag = '0',
|
|
1233
|
-
xPixel,
|
|
1234
|
-
yPixel,
|
|
1235
|
-
] = p;
|
|
1236
|
-
const realRx = parseFloatValue(rxPixel, inherited.width) || 0;
|
|
1237
|
-
const realRy = parseFloatValue(ryPixel, inherited.height) || 0;
|
|
1238
|
-
const realX = parseFloatValue(xPixel, inherited.width) || 0;
|
|
1239
|
-
const realY = parseFloatValue(yPixel, inherited.height) || 0;
|
|
1240
|
-
const { width: newRx, height: newRy } = newConverter.size(
|
|
1241
|
-
realRx,
|
|
1242
|
-
realRy,
|
|
1243
|
-
);
|
|
1244
|
-
let point;
|
|
1245
|
-
if (letter === letter.toLowerCase()) {
|
|
1246
|
-
currentX += realX;
|
|
1247
|
-
currentY += realY;
|
|
1248
|
-
} else {
|
|
1249
|
-
currentX = realX;
|
|
1250
|
-
currentY = realY;
|
|
1251
|
-
}
|
|
1252
|
-
point = newConverter.point(currentX, currentY);
|
|
1253
|
-
// transformations over x and y axis might change the page coord direction
|
|
1254
|
-
const { width: xDirection, height: yDirection } = newConverter.size(
|
|
1255
|
-
1,
|
|
1256
|
-
1,
|
|
1257
|
-
);
|
|
1258
|
-
// -1 is the default direction
|
|
1259
|
-
const pageYDirection = -1 * Math.sign(xDirection * yDirection);
|
|
1260
|
-
const oppositeSweepFlag = sweepFlag === '0' ? '1' : '0'
|
|
1261
|
-
return [
|
|
1262
|
-
letter.toUpperCase(),
|
|
1263
|
-
newRx,
|
|
1264
|
-
newRy,
|
|
1265
|
-
xAxisRotation,
|
|
1266
|
-
largeArc,
|
|
1267
|
-
pageYDirection === -1 ? oppositeSweepFlag : sweepFlag,
|
|
1268
|
-
point.x - xOrigin,
|
|
1269
|
-
point.y - yOrigin,
|
|
1270
|
-
].join(' ');
|
|
1271
|
-
})
|
|
1272
|
-
.join(' ');
|
|
1273
|
-
}
|
|
1274
|
-
case 'c': {
|
|
1275
|
-
const groupedParams = groupBy<string>(params, 6);
|
|
1276
|
-
const result = groupedParams!
|
|
1277
|
-
.map(([c1X, c1Y, c2X, c2Y, xString, yString]) => [
|
|
1278
|
-
parseFloatValue(c1X, inherited.width) || 0,
|
|
1279
|
-
parseFloatValue(c1Y, inherited.height) || 0,
|
|
1280
|
-
parseFloatValue(c2X, inherited.width) || 0,
|
|
1281
|
-
parseFloatValue(c2Y, inherited.height) || 0,
|
|
1282
|
-
parseFloatValue(xString, inherited.width) || 0,
|
|
1283
|
-
parseFloatValue(yString, inherited.height) || 0,
|
|
1284
|
-
])
|
|
1285
|
-
.map(([c1X, c1Y, c2X, c2Y, xReal, yReal]) => {
|
|
1286
|
-
let controlPoint1X;
|
|
1287
|
-
let controlPoint1Y;
|
|
1288
|
-
let controlPoint2X;
|
|
1289
|
-
let controlPoint2Y;
|
|
1290
|
-
if (letter === letter!.toLowerCase()) {
|
|
1291
|
-
controlPoint1X = currentX + c1X;
|
|
1292
|
-
controlPoint1Y = currentY + c1Y;
|
|
1293
|
-
controlPoint2X = currentX + c2X;
|
|
1294
|
-
controlPoint2Y = currentY + c2Y;
|
|
1295
|
-
currentX += xReal;
|
|
1296
|
-
currentY += yReal;
|
|
1297
|
-
} else {
|
|
1298
|
-
controlPoint1X = c1X;
|
|
1299
|
-
controlPoint1Y = c1Y;
|
|
1300
|
-
controlPoint2X = c2X;
|
|
1301
|
-
controlPoint2Y = c2Y;
|
|
1302
|
-
currentX = xReal;
|
|
1303
|
-
currentY = yReal;
|
|
1304
|
-
}
|
|
1305
|
-
const controlPoint1 = newConverter.point(
|
|
1306
|
-
controlPoint1X,
|
|
1307
|
-
controlPoint1Y,
|
|
1308
|
-
);
|
|
1309
|
-
const controlPoint2 = newConverter.point(
|
|
1310
|
-
controlPoint2X,
|
|
1311
|
-
controlPoint2Y,
|
|
1312
|
-
);
|
|
1313
|
-
const point = newConverter.point(currentX, currentY);
|
|
1314
|
-
return [
|
|
1315
|
-
controlPoint1.x - xOrigin,
|
|
1316
|
-
controlPoint1.y - yOrigin,
|
|
1317
|
-
controlPoint2.x - xOrigin,
|
|
1318
|
-
controlPoint2.y - yOrigin,
|
|
1319
|
-
point.x - xOrigin,
|
|
1320
|
-
point.y - yOrigin,
|
|
1321
|
-
].join(',');
|
|
1322
|
-
})
|
|
1323
|
-
.join(' ');
|
|
1324
|
-
return letter?.toUpperCase() + '' + result;
|
|
1325
|
-
}
|
|
1326
|
-
case 's': {
|
|
1327
|
-
const groupedParams = groupBy<string>(params, 4);
|
|
1328
|
-
const result = groupedParams!
|
|
1329
|
-
// the control point 1 is omitted because it's the reflection of c2
|
|
1330
|
-
.map(([c2X, c2Y, xString, yString]) => [
|
|
1331
|
-
parseFloatValue(c2X, inherited.width) || 0,
|
|
1332
|
-
parseFloatValue(c2Y, inherited.height) || 0,
|
|
1333
|
-
parseFloatValue(xString, inherited.width) || 0,
|
|
1334
|
-
parseFloatValue(yString, inherited.height) || 0,
|
|
1335
|
-
])
|
|
1336
|
-
.map(([c2X, c2Y, xReal, yReal]) => {
|
|
1337
|
-
let controlPoint2X;
|
|
1338
|
-
let controlPoint2Y;
|
|
1339
|
-
if (letter === letter!.toLowerCase()) {
|
|
1340
|
-
controlPoint2X = currentX + c2X;
|
|
1341
|
-
controlPoint2Y = currentY + c2Y;
|
|
1342
|
-
currentX += xReal;
|
|
1343
|
-
currentY += yReal;
|
|
1344
|
-
} else {
|
|
1345
|
-
controlPoint2X = c2X;
|
|
1346
|
-
controlPoint2Y = c2Y;
|
|
1347
|
-
currentX = xReal;
|
|
1348
|
-
currentY = yReal;
|
|
1349
|
-
}
|
|
1350
|
-
const controlPoint2 = newConverter.point(
|
|
1351
|
-
controlPoint2X,
|
|
1352
|
-
controlPoint2Y,
|
|
1353
|
-
);
|
|
1354
|
-
const point = newConverter.point(currentX, currentY);
|
|
1355
|
-
return [
|
|
1356
|
-
controlPoint2.x - xOrigin,
|
|
1357
|
-
controlPoint2.y - yOrigin,
|
|
1358
|
-
point.x - xOrigin,
|
|
1359
|
-
point.y - yOrigin,
|
|
1360
|
-
].join(',');
|
|
1361
|
-
})
|
|
1362
|
-
.join(' ');
|
|
1363
|
-
return letter?.toUpperCase() + '' + result;
|
|
1364
|
-
}
|
|
1365
|
-
default: {
|
|
1366
|
-
const groupedParams = groupBy<string>(params, 2);
|
|
1367
|
-
const result = groupedParams!
|
|
1368
|
-
.map(([xString, yString]) => [
|
|
1369
|
-
parseFloatValue(xString, inherited.width) || 0,
|
|
1370
|
-
parseFloatValue(yString, inherited.height) || 0,
|
|
1371
|
-
])
|
|
1372
|
-
.map(([xReal, yReal]) => {
|
|
1373
|
-
if (letter === letter!.toLowerCase()) {
|
|
1374
|
-
currentX += xReal;
|
|
1375
|
-
currentY += yReal;
|
|
1376
|
-
} else {
|
|
1377
|
-
currentX = xReal;
|
|
1378
|
-
currentY = yReal;
|
|
1379
|
-
}
|
|
1380
|
-
const point = newConverter.point(currentX, currentY);
|
|
1381
|
-
return [point.x - xOrigin, point.y - yOrigin].join(',');
|
|
1382
|
-
})
|
|
1383
|
-
.join(' ');
|
|
1384
|
-
return letter?.toUpperCase() + '' + result;
|
|
1385
|
-
}
|
|
1386
|
-
}
|
|
1387
|
-
},
|
|
1388
|
-
);
|
|
537
|
+
newMatrix = combineTransformation(newMatrix, 'scale', [1, -1])
|
|
538
|
+
svgAttributes.d = attributes.d
|
|
1389
539
|
}
|
|
1390
|
-
if (attributes.viewBox) {
|
|
1391
|
-
const viewBox = parseViewBox(attributes.viewBox)!;
|
|
1392
|
-
const size = {
|
|
1393
|
-
width: width || inherited.viewBox.width,
|
|
1394
|
-
height: height || inherited.viewBox.height,
|
|
1395
|
-
};
|
|
1396
540
|
|
|
1397
|
-
const localConverter = getConverterWithAspectRatio(
|
|
1398
|
-
size,
|
|
1399
|
-
viewBox,
|
|
1400
|
-
attributes.preserveAspectRatio,
|
|
1401
|
-
);
|
|
1402
|
-
const oldConverter = newConverter;
|
|
1403
|
-
newConverter = {
|
|
1404
|
-
point: (px: number, py: number) => {
|
|
1405
|
-
const { x: localX, y: localY } = localConverter.point(px, py);
|
|
1406
|
-
return oldConverter.point(localX, localY);
|
|
1407
|
-
},
|
|
1408
|
-
size: (w: number, h: number) => {
|
|
1409
|
-
const { width: localWidth, height: localHeight } = localConverter.size(
|
|
1410
|
-
w,
|
|
1411
|
-
h,
|
|
1412
|
-
);
|
|
1413
|
-
return oldConverter.size(localWidth, localHeight);
|
|
1414
|
-
},
|
|
1415
|
-
};
|
|
1416
|
-
}
|
|
1417
|
-
// apply the converter only when there's a local fontSize instruction
|
|
1418
541
|
if (fontSizeRaw && newInherited.fontSize) {
|
|
1419
|
-
newInherited.fontSize =
|
|
542
|
+
newInherited.fontSize = newInherited.fontSize
|
|
1420
543
|
}
|
|
1421
544
|
if (newInherited.fontFamily) {
|
|
1422
545
|
// Handle complex fontFamily like `"Linux Libertine O", serif`
|
|
@@ -1425,62 +548,14 @@ const parseAttributes = (
|
|
|
1425
548
|
}
|
|
1426
549
|
|
|
1427
550
|
if (newInherited.strokeWidth) {
|
|
1428
|
-
|
|
1429
|
-
newInherited.strokeWidth,
|
|
1430
|
-
newInherited.strokeWidth,
|
|
1431
|
-
);
|
|
1432
|
-
svgAttributes.strokeWidth = Math.max(
|
|
1433
|
-
Math.min(Math.abs(result.width), Math.abs(result.height)),
|
|
1434
|
-
1,
|
|
1435
|
-
);
|
|
551
|
+
svgAttributes.strokeWidth = newInherited.strokeWidth
|
|
1436
552
|
}
|
|
1437
553
|
|
|
1438
554
|
return {
|
|
1439
555
|
inherited: newInherited,
|
|
1440
556
|
svgAttributes,
|
|
1441
|
-
converter: newConverter,
|
|
1442
557
|
tagName: element.tagName,
|
|
1443
|
-
|
|
1444
|
-
};
|
|
1445
|
-
|
|
1446
|
-
const getConverter = (box: Size, viewBox: Box): SVGSizeConverter => {
|
|
1447
|
-
const { width, height } = box;
|
|
1448
|
-
const { x: xMin, y: yMin, width: viewWidth, height: viewHeight } = viewBox;
|
|
1449
|
-
const converter = {
|
|
1450
|
-
point: (xReal: number, yReal: number) => ({
|
|
1451
|
-
x: ((xReal - xMin) / viewWidth) * (width || 0),
|
|
1452
|
-
y: ((yReal - yMin) / viewHeight) * (height || 0),
|
|
1453
|
-
}),
|
|
1454
|
-
size: (wReal: number, hReal: number) => ({
|
|
1455
|
-
width: (wReal / viewWidth) * (width || 0),
|
|
1456
|
-
height: (hReal / viewHeight) * (height || 0),
|
|
1457
|
-
}),
|
|
1458
|
-
};
|
|
1459
|
-
return converter;
|
|
1460
|
-
};
|
|
1461
|
-
|
|
1462
|
-
const getConverterWithAspectRatio = (
|
|
1463
|
-
size: Size,
|
|
1464
|
-
viewBox: Box,
|
|
1465
|
-
preserveAspectRatio?: string,
|
|
1466
|
-
) => {
|
|
1467
|
-
// explanation about how the svg attributes applies transformations to the child elements
|
|
1468
|
-
// https://www.w3.org/TR/SVG/coords.html#ComputingAViewportsTransform
|
|
1469
|
-
const { x, y, width, height } = getFittingRectangle(
|
|
1470
|
-
viewBox.width,
|
|
1471
|
-
viewBox.height,
|
|
1472
|
-
size.width,
|
|
1473
|
-
size.height,
|
|
1474
|
-
preserveAspectRatio,
|
|
1475
|
-
);
|
|
1476
|
-
const ratioConverter = getConverter({ width, height }, viewBox);
|
|
1477
|
-
// We translate the drawing in the page when the aspect ratio is different, according to the preserveAspectRatio instructions.
|
|
1478
|
-
return {
|
|
1479
|
-
point: (xReal: number, yReal: number) => {
|
|
1480
|
-
const P = ratioConverter.point(xReal, yReal);
|
|
1481
|
-
return { x: P.x + x, y: P.y + y };
|
|
1482
|
-
},
|
|
1483
|
-
size: ratioConverter.size,
|
|
558
|
+
matrix: newMatrix,
|
|
1484
559
|
};
|
|
1485
560
|
};
|
|
1486
561
|
|
|
@@ -1528,10 +603,85 @@ const getFittingRectangle = (
|
|
|
1528
603
|
return { x, y, width, height };
|
|
1529
604
|
};
|
|
1530
605
|
|
|
606
|
+
const getAspectRatioTransformation = (
|
|
607
|
+
matrix: TransformationMatrix,
|
|
608
|
+
originalWidth: number,
|
|
609
|
+
originalHeight: number,
|
|
610
|
+
targetWidth: number,
|
|
611
|
+
targetHeight: number,
|
|
612
|
+
preserveAspectRatio?: string,
|
|
613
|
+
): {
|
|
614
|
+
clipBox: TransformationMatrix
|
|
615
|
+
content: TransformationMatrix
|
|
616
|
+
} => {
|
|
617
|
+
const scaleX = targetWidth / originalWidth
|
|
618
|
+
const scaleY = targetHeight / originalHeight
|
|
619
|
+
const boxScale = combineTransformation(
|
|
620
|
+
matrix,
|
|
621
|
+
'scale',
|
|
622
|
+
[
|
|
623
|
+
scaleX,
|
|
624
|
+
scaleY
|
|
625
|
+
]
|
|
626
|
+
)
|
|
627
|
+
if (preserveAspectRatio === 'none') {
|
|
628
|
+
return {
|
|
629
|
+
clipBox: boxScale,
|
|
630
|
+
content: boxScale
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
// TODO: the following code works for the 'meet' param but not for the 'slice'
|
|
634
|
+
const scale =
|
|
635
|
+
targetWidth > targetHeight ? scaleY : scaleX
|
|
636
|
+
const dx = targetWidth - (originalWidth * scale)
|
|
637
|
+
const dy = targetHeight - (originalHeight * scale)
|
|
638
|
+
const [x, y] = (() => {
|
|
639
|
+
switch (preserveAspectRatio) {
|
|
640
|
+
case 'xMinYMin':
|
|
641
|
+
return [0, 0];
|
|
642
|
+
case 'xMidYMin':
|
|
643
|
+
return [dx / 2, 0];
|
|
644
|
+
case 'xMaxYMin':
|
|
645
|
+
return [dx, dy / 2];
|
|
646
|
+
case 'xMinYMid':
|
|
647
|
+
return [0, dy];
|
|
648
|
+
case 'xMaxYMid':
|
|
649
|
+
return [dx, dy / 2];
|
|
650
|
+
case 'xMinYMax':
|
|
651
|
+
return [0, dy];
|
|
652
|
+
case 'xMidYMax':
|
|
653
|
+
return [dx / 2, dy];
|
|
654
|
+
case 'xMaxYMax':
|
|
655
|
+
return [dx, dy];
|
|
656
|
+
case 'xMidYMid':
|
|
657
|
+
default:
|
|
658
|
+
return [dx / 2, dy / 2];
|
|
659
|
+
}
|
|
660
|
+
})();
|
|
661
|
+
|
|
662
|
+
const contentTransform = combineTransformation(
|
|
663
|
+
combineTransformation(
|
|
664
|
+
matrix,
|
|
665
|
+
'translate',
|
|
666
|
+
[x, y]
|
|
667
|
+
),
|
|
668
|
+
'scale',
|
|
669
|
+
[scale]
|
|
670
|
+
)
|
|
671
|
+
|
|
672
|
+
return {
|
|
673
|
+
clipBox: boxScale,
|
|
674
|
+
content: contentTransform
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
|
|
1531
680
|
const parseHTMLNode = (
|
|
1532
681
|
node: Node,
|
|
1533
682
|
inherited: InheritedAttributes,
|
|
1534
|
-
|
|
683
|
+
matrix: TransformationMatrix,
|
|
684
|
+
clipSpaces: Space[],
|
|
1535
685
|
): SVGElement[] => {
|
|
1536
686
|
if (node.nodeType === NodeType.COMMENT_NODE) return [];
|
|
1537
687
|
else if (node.nodeType === NodeType.TEXT_NODE) return [];
|
|
@@ -1539,24 +689,28 @@ const parseHTMLNode = (
|
|
|
1539
689
|
return parseGroupNode(
|
|
1540
690
|
node as HTMLElement & { tagName: 'g' },
|
|
1541
691
|
inherited,
|
|
1542
|
-
|
|
692
|
+
matrix,
|
|
693
|
+
clipSpaces,
|
|
1543
694
|
);
|
|
1544
695
|
} else if (node.tagName === 'svg') {
|
|
1545
696
|
return parseSvgNode(
|
|
1546
697
|
node as HTMLElement & { tagName: 'svg' },
|
|
1547
698
|
inherited,
|
|
1548
|
-
|
|
699
|
+
matrix,
|
|
700
|
+
clipSpaces,
|
|
1549
701
|
);
|
|
1550
702
|
} else {
|
|
1551
703
|
if (node.tagName === 'polygon') {
|
|
1552
704
|
node.tagName = 'path';
|
|
1553
|
-
node.attributes.d = `M${node.attributes.points}Z`;
|
|
705
|
+
node.attributes.d = `M${node.attributes.points}Z`;
|
|
1554
706
|
delete node.attributes.points;
|
|
1555
707
|
}
|
|
1556
|
-
const attributes = parseAttributes(node, inherited,
|
|
708
|
+
const attributes = parseAttributes(node, inherited, matrix);
|
|
1557
709
|
const svgAttributes = {
|
|
1558
710
|
...attributes.inherited,
|
|
1559
711
|
...attributes.svgAttributes,
|
|
712
|
+
matrix: attributes.matrix,
|
|
713
|
+
clipSpaces
|
|
1560
714
|
};
|
|
1561
715
|
Object.assign(node, { svgAttributes });
|
|
1562
716
|
return [node as SVGElement];
|
|
@@ -1566,35 +720,74 @@ const parseHTMLNode = (
|
|
|
1566
720
|
const parseSvgNode = (
|
|
1567
721
|
node: HTMLElement & { tagName: 'svg' },
|
|
1568
722
|
inherited: InheritedAttributes,
|
|
1569
|
-
|
|
723
|
+
matrix: TransformationMatrix,
|
|
724
|
+
clipSpaces: Space[],
|
|
1570
725
|
): SVGElement[] => {
|
|
1571
726
|
// if the width/height aren't set, the svg will have the same dimension as the current drawing space
|
|
1572
727
|
node.attributes.width ??
|
|
1573
728
|
node.setAttribute('width', inherited.viewBox.width + '');
|
|
1574
729
|
node.attributes.height ??
|
|
1575
730
|
node.setAttribute('height', inherited.viewBox.height + '');
|
|
1576
|
-
const attributes = parseAttributes(node, inherited,
|
|
731
|
+
const attributes = parseAttributes(node, inherited, matrix);
|
|
1577
732
|
const result: SVGElement[] = [];
|
|
1578
733
|
const viewBox = node.attributes.viewBox
|
|
1579
734
|
? parseViewBox(node.attributes.viewBox)!
|
|
1580
735
|
: node.attributes.width && node.attributes.height
|
|
1581
736
|
? parseViewBox(`0 0 ${node.attributes.width} ${node.attributes.height}`)!
|
|
1582
737
|
: inherited.viewBox;
|
|
1583
|
-
const
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
738
|
+
const x = parseFloat(node.attributes.x) || 0
|
|
739
|
+
const y = parseFloat(node.attributes.y) || 0
|
|
740
|
+
|
|
741
|
+
let newMatrix = combineTransformation(matrix, 'translate', [x, y])
|
|
742
|
+
|
|
743
|
+
const { clipBox: clipBoxTransform, content: contentTransform } =
|
|
744
|
+
getAspectRatioTransformation(
|
|
745
|
+
newMatrix,
|
|
746
|
+
viewBox.width,
|
|
747
|
+
viewBox.height,
|
|
748
|
+
parseFloat(node.attributes.width),
|
|
749
|
+
parseFloat(node.attributes.height),
|
|
750
|
+
node.attributes.preserveAspectRatio || 'xMidYMid'
|
|
751
|
+
)
|
|
752
|
+
|
|
753
|
+
const topLeft = applyTransformation(clipBoxTransform, {
|
|
754
|
+
x: 0,
|
|
755
|
+
y: 0,
|
|
756
|
+
})
|
|
757
|
+
|
|
758
|
+
const topRight = applyTransformation(clipBoxTransform, {
|
|
759
|
+
x: viewBox.width,
|
|
760
|
+
y: 0,
|
|
761
|
+
})
|
|
762
|
+
|
|
763
|
+
const bottomRight = applyTransformation(clipBoxTransform, {
|
|
764
|
+
x: viewBox.width,
|
|
765
|
+
y: -viewBox.height,
|
|
766
|
+
})
|
|
767
|
+
|
|
768
|
+
const bottomLeft = applyTransformation(clipBoxTransform, {
|
|
769
|
+
x: 0,
|
|
770
|
+
y: -viewBox.height,
|
|
771
|
+
})
|
|
772
|
+
|
|
773
|
+
const baseClipSpace: Space = {
|
|
774
|
+
topLeft,
|
|
775
|
+
topRight,
|
|
776
|
+
bottomRight,
|
|
777
|
+
bottomLeft
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// TODO: maybe this is the correct transformation
|
|
781
|
+
// newMatrix = combineTransformation(newMatrix, 'translate', [-baseClipSpace.xMin, -baseClipSpace.yMin])
|
|
782
|
+
newMatrix = combineTransformation(contentTransform, 'translate', [-viewBox.x, -viewBox.y])
|
|
783
|
+
|
|
1592
784
|
node.childNodes.forEach((child) => {
|
|
1593
785
|
const parsedNodes = parseHTMLNode(
|
|
1594
786
|
child,
|
|
1595
787
|
{ ...attributes.inherited, viewBox },
|
|
1596
|
-
|
|
1597
|
-
|
|
788
|
+
newMatrix,
|
|
789
|
+
[...clipSpaces, baseClipSpace],
|
|
790
|
+
)
|
|
1598
791
|
result.push(...parsedNodes);
|
|
1599
792
|
});
|
|
1600
793
|
return result;
|
|
@@ -1603,13 +796,14 @@ const parseSvgNode = (
|
|
|
1603
796
|
const parseGroupNode = (
|
|
1604
797
|
node: HTMLElement & { tagName: 'g' },
|
|
1605
798
|
inherited: InheritedAttributes,
|
|
1606
|
-
|
|
799
|
+
matrix: TransformationMatrix,
|
|
800
|
+
clipSpaces: Space[],
|
|
1607
801
|
): SVGElement[] => {
|
|
1608
|
-
const attributes = parseAttributes(node, inherited,
|
|
802
|
+
const attributes = parseAttributes(node, inherited, matrix);
|
|
1609
803
|
const result: SVGElement[] = [];
|
|
1610
804
|
node.childNodes.forEach((child) => {
|
|
1611
805
|
result.push(
|
|
1612
|
-
...parseHTMLNode(child, attributes.inherited, attributes.
|
|
806
|
+
...parseHTMLNode(child, attributes.inherited, attributes.matrix, clipSpaces),
|
|
1613
807
|
);
|
|
1614
808
|
});
|
|
1615
809
|
return result;
|
|
@@ -1640,15 +834,13 @@ const parseViewBox = (viewBox?: string): Box | undefined => {
|
|
|
1640
834
|
|
|
1641
835
|
const parse = (
|
|
1642
836
|
svg: string,
|
|
1643
|
-
{ width, height,
|
|
837
|
+
{ width, height, fontSize }: PDFPageDrawSVGElementOptions,
|
|
1644
838
|
size: Size,
|
|
1645
|
-
|
|
839
|
+
matrix: TransformationMatrix
|
|
1646
840
|
): SVGElement[] => {
|
|
1647
841
|
const htmlElement = parseHtml(svg).firstChild as HTMLElement;
|
|
1648
842
|
if (width) htmlElement.setAttribute('width', width + '');
|
|
1649
843
|
if (height) htmlElement.setAttribute('height', height + '');
|
|
1650
|
-
if (x !== undefined) htmlElement.setAttribute('x', x + '');
|
|
1651
|
-
if (y !== undefined) htmlElement.setAttribute('y', size.height - y + '');
|
|
1652
844
|
if (fontSize) htmlElement.setAttribute('font-size', fontSize + '');
|
|
1653
845
|
// TODO: what should be the default viewBox?
|
|
1654
846
|
return parseHTMLNode(
|
|
@@ -1657,7 +849,8 @@ const parse = (
|
|
|
1657
849
|
...size,
|
|
1658
850
|
viewBox: parseViewBox(htmlElement.attributes.viewBox || '0 0 1 1')!,
|
|
1659
851
|
},
|
|
1660
|
-
|
|
852
|
+
matrix,
|
|
853
|
+
[]
|
|
1661
854
|
);
|
|
1662
855
|
};
|
|
1663
856
|
|
|
@@ -1702,17 +895,43 @@ export const drawSvg = async (
|
|
|
1702
895
|
);
|
|
1703
896
|
}
|
|
1704
897
|
|
|
1705
|
-
|
|
1706
|
-
const defaultConverter = {
|
|
1707
|
-
point: (xP: number, yP: number) => ({ x: xP, y: size.height - yP }),
|
|
1708
|
-
size: (w: number, h: number) => ({ width: w, height: h }),
|
|
1709
|
-
};
|
|
898
|
+
const baseTransformation: TransformationMatrix = [1, 0, 0, 1, options.x || 0, options.y || 0]
|
|
1710
899
|
|
|
1711
900
|
const runners = runnersToPage(page, options);
|
|
1712
|
-
const elements = parse(firstChild.outerHTML, options, size,
|
|
901
|
+
const elements = parse(firstChild.outerHTML, options, size, baseTransformation);
|
|
1713
902
|
|
|
1714
903
|
await elements.reduce(async (prev, elt) => {
|
|
1715
904
|
await prev;
|
|
905
|
+
// uncomment these lines to draw the clipSpaces
|
|
906
|
+
// elt.svgAttributes.clipSpaces.forEach(space => {
|
|
907
|
+
// page.drawLine({
|
|
908
|
+
// start: space.topLeft,
|
|
909
|
+
// end: space.topRight,
|
|
910
|
+
// color: parseColor('#000000')?.rgb,
|
|
911
|
+
// thickness: 1
|
|
912
|
+
// })
|
|
913
|
+
|
|
914
|
+
// page.drawLine({
|
|
915
|
+
// start: space.topRight,
|
|
916
|
+
// end: space.bottomRight,
|
|
917
|
+
// color: parseColor('#000000')?.rgb,
|
|
918
|
+
// thickness: 1
|
|
919
|
+
// })
|
|
920
|
+
|
|
921
|
+
// page.drawLine({
|
|
922
|
+
// start: space.bottomRight,
|
|
923
|
+
// end: space.bottomLeft,
|
|
924
|
+
// color: parseColor('#000000')?.rgb,
|
|
925
|
+
// thickness: 1
|
|
926
|
+
// })
|
|
927
|
+
|
|
928
|
+
// page.drawLine({
|
|
929
|
+
// start: space.bottomLeft,
|
|
930
|
+
// end: space.topLeft,
|
|
931
|
+
// color: parseColor('#000000')?.rgb,
|
|
932
|
+
// thickness: 1
|
|
933
|
+
// })
|
|
934
|
+
// })
|
|
1716
935
|
return runners[elt.tagName]?.(elt);
|
|
1717
936
|
}, Promise.resolve());
|
|
1718
937
|
};
|