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