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