svg-path-simplify 0.3.6 → 0.4.0

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.
@@ -0,0 +1,307 @@
1
+ import { svgArcToCenterParam } from "./geometry";
2
+
3
+ /**
4
+ * scale pathData
5
+ */
6
+ export function transformPathData(pathData, matrix) {
7
+
8
+ // new pathdata
9
+ let pathDataTrans = [];
10
+
11
+ // transform point by 2d matrix
12
+ const transformPoint = (pt, matrix) => {
13
+ let { a, b, c, d, e, f } = matrix;
14
+ let { x, y } = pt;
15
+ return { x: a * x + c * y + e, y: b * x + d * y + f };
16
+ }
17
+
18
+ //normalize matrix notations object, array or css matrix string
19
+ const normalizeMatrix = (matrix) => {
20
+ matrix =
21
+ typeof matrix === "string"
22
+ ? (matrix = matrix
23
+ .replace(/^matrix\(|\)$/g, "")
24
+ .split(",")
25
+ .map(Number))
26
+ : matrix;
27
+ matrix = !Array.isArray(matrix)
28
+ ? {
29
+ a: matrix.a,
30
+ b: matrix.b,
31
+ c: matrix.c,
32
+ d: matrix.d,
33
+ e: matrix.e,
34
+ f: matrix.f
35
+ }
36
+ : {
37
+ a: matrix[0],
38
+ b: matrix[1],
39
+ c: matrix[2],
40
+ d: matrix[3],
41
+ e: matrix[4],
42
+ f: matrix[5]
43
+ };
44
+ return matrix;
45
+ }
46
+
47
+
48
+ const transformArc = (p0, values, matrix) => {
49
+ let [rx, ry, angle, largeArc, sweep, x, y] = values;
50
+
51
+ /**
52
+ * parametrize arc command
53
+ * to get the actual arc params
54
+ */
55
+ let arcData = svgArcToCenterParam(
56
+ p0.x,
57
+ p0.y,
58
+ values[0],
59
+ values[1],
60
+ angle,
61
+ largeArc,
62
+ sweep,
63
+ x,
64
+ y
65
+ );
66
+ ({ rx, ry } = arcData);
67
+ let { a, b, c, d, e, f } = matrix;
68
+
69
+ let ellipsetr = transformEllipse(rx, ry, angle, matrix);
70
+ let p = transformPoint({ x: x, y: y }, matrix);
71
+
72
+
73
+ // adjust sweep if flipped
74
+ let denom = a * a + b * b;
75
+ let scaleX = Math.sqrt(denom)
76
+ let scaleY = (a * d - c * b) / scaleX
77
+
78
+ let flipX = scaleX < 0 ? true : false;
79
+ let flipY = scaleY < 0 ? true : false;
80
+
81
+
82
+ // adjust sweep
83
+ if (flipX || flipY) {
84
+ sweep = sweep === 0 ? 1 : 0;
85
+ }
86
+
87
+ return {
88
+ type: 'A',
89
+ values: [
90
+ ellipsetr.rx,
91
+ ellipsetr.ry,
92
+ ellipsetr.ax,
93
+ largeArc,
94
+ sweep,
95
+ p.x,
96
+ p.y]
97
+ };
98
+ }
99
+
100
+ // normalize matrix input
101
+ matrix = normalizeMatrix(matrix);
102
+
103
+ let matrixStr = [matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f]
104
+ .map((val) => {
105
+ return +val.toFixed(1);
106
+ })
107
+ .join("");
108
+
109
+ // no transform: quit
110
+ if (matrixStr === "100100") {
111
+ //console.log("no transform");
112
+ return pathData;
113
+ }
114
+
115
+
116
+ pathData.forEach((com, i) => {
117
+ let { type, values } = com;
118
+ let typeRel = type.toLowerCase();
119
+ let comPrev = i > 0 ? pathData[i - 1] : pathData[i];
120
+ let comPrevValues = comPrev.values;
121
+ let comPrevValuesL = comPrevValues.length;
122
+ let p0 = {
123
+ x: comPrevValues[comPrevValuesL - 2],
124
+ y: comPrevValues[comPrevValuesL - 1]
125
+ };
126
+ let p = { x: values[values.length - 2], y: values[values.length - 1] };
127
+ let comT = { type: type, values: [] };
128
+
129
+ switch (typeRel) {
130
+ case "a":
131
+ comT = transformArc(p0, values, matrix)
132
+ break;
133
+
134
+ default:
135
+ // all other point based commands
136
+ if (values.length) {
137
+ for (let i = 0; i < values.length; i += 2) {
138
+ let ptTrans = transformPoint(
139
+ { x: com.values[i], y: com.values[i + 1] },
140
+ matrix
141
+ );
142
+
143
+ comT.values[i] = ptTrans.x;
144
+ comT.values[i + 1] = ptTrans.y;
145
+ }
146
+ }
147
+ }
148
+
149
+ pathDataTrans.push(comT);
150
+ });
151
+
152
+
153
+ //console.log('pathDataTrans', pathDataTrans);
154
+
155
+ return pathDataTrans;
156
+ }
157
+
158
+
159
+
160
+
161
+
162
+
163
+ /**
164
+ * Based on: https://github.com/fontello/svgpath/blob/master/lib/ellipse.js
165
+ * and fork: https://github.com/kpym/SVGPathy/blob/master/lib/ellipse.js
166
+ */
167
+
168
+ function transformEllipse(rx, ry, ax, matrix) {
169
+ const torad = Math.PI / 180;
170
+ const epsilon = 1e-7;
171
+
172
+ //normalize matrix object or array notations
173
+ matrix = !Array.isArray(matrix)
174
+ ? matrix
175
+ : {
176
+ a: matrix[0],
177
+ b: matrix[1],
178
+ c: matrix[2],
179
+ d: matrix[3],
180
+ e: matrix[4],
181
+ f: matrix[5]
182
+ };
183
+
184
+ // We consider the current ellipse as image of the unit circle
185
+ // by first scale(rx,ry) and then rotate(ax) ...
186
+ // So we apply ma = m x rotate(ax) x scale(rx,ry) to the unit circle.
187
+ let c = Math.cos(ax * torad),
188
+ s = Math.sin(ax * torad);
189
+ let ma = [
190
+ rx * (matrix.a * c + matrix.c * s),
191
+ rx * (matrix.b * c + matrix.d * s),
192
+ ry * (-matrix.a * s + matrix.c * c),
193
+ ry * (-matrix.b * s + matrix.d * c)
194
+ ];
195
+
196
+ // ma * transpose(ma) = [ J L ]
197
+ // [ L K ]
198
+ // L is calculated later (if the image is not a circle)
199
+ let J = ma[0] * ma[0] + ma[2] * ma[2],
200
+ K = ma[1] * ma[1] + ma[3] * ma[3];
201
+
202
+ // the sqrt of the discriminant of the characteristic polynomial of ma * transpose(ma)
203
+ // this is also the geometric mean of the eigenvalues
204
+ let D = Math.sqrt(
205
+ ((ma[0] - ma[3]) * (ma[0] - ma[3]) + (ma[2] + ma[1]) * (ma[2] + ma[1])) *
206
+ ((ma[0] + ma[3]) * (ma[0] + ma[3]) + (ma[2] - ma[1]) * (ma[2] - ma[1]))
207
+ );
208
+
209
+ // the arithmetic mean of the eigenvalues
210
+ let JK = (J + K) / 2;
211
+
212
+ // check if the image is (almost) a circle
213
+ if (D <= epsilon) {
214
+ rx = ry = Math.sqrt(JK);
215
+ ax = 0;
216
+ return { rx: rx, ry: ry, ax: ax };
217
+ }
218
+
219
+ // check if ma * transpose(ma) is (almost) diagonal
220
+ if (Math.abs(D - Math.abs(J - K)) <= epsilon) {
221
+ rx = Math.sqrt(J);
222
+ ry = Math.sqrt(K);
223
+ ax = 0;
224
+ return { rx: rx, ry: ry, ax: ax };
225
+ }
226
+
227
+ // if it is not a circle, nor diagonal
228
+ let L = ma[0] * ma[1] + ma[2] * ma[3];
229
+
230
+ // {l1,l2} = the two eigen values of ma * transpose(ma)
231
+ let l1 = JK + D / 2,
232
+ l2 = JK - D / 2;
233
+
234
+ // the x - axis - rotation angle is the argument of the l1 - eigenvector
235
+ if (Math.abs(L) <= epsilon && Math.abs(l1 - K) <= epsilon) {
236
+ // if (ax == 90) => ax = 0 and exchange axes
237
+ ax = 0;
238
+ rx = Math.sqrt(l2);
239
+ ry = Math.sqrt(l1);
240
+ return { rx: rx, ry: ry, ax: ax };
241
+ }
242
+
243
+ ax =
244
+ Math.atan(Math.abs(L) > Math.abs(l1 - K) ? (l1 - J) / L : L / (l1 - K)) /
245
+ torad; // the angle in degree
246
+
247
+ // if ax > 0 => rx = sqrt(l1), ry = sqrt(l2), else exchange axes and ax += 90
248
+ if (ax >= 0) {
249
+ // if ax in [0,90]
250
+ rx = Math.sqrt(l1);
251
+ ry = Math.sqrt(l2);
252
+ } else {
253
+ // if ax in ]-90,0[ => exchange axes
254
+ ax += 90;
255
+ rx = Math.sqrt(l2);
256
+ ry = Math.sqrt(l1);
257
+ }
258
+
259
+ return { rx: rx, ry: ry, ax: ax };
260
+ }
261
+
262
+
263
+
264
+
265
+
266
+ /**
267
+ * scale pathData
268
+ */
269
+
270
+ export function scalePathData(pathData, scaleX, scaleY) {
271
+ pathData.forEach((com, i) => {
272
+ let { type, values } = com;
273
+ let typeRel = type.toLowerCase();
274
+
275
+ switch (typeRel) {
276
+ case "a":
277
+ com.values = [
278
+ values[0] * scaleX,
279
+ values[1] * scaleY,
280
+ values[2],
281
+ values[3],
282
+ values[4],
283
+ values[5] * scaleX,
284
+ values[6] * scaleY
285
+ ];
286
+ break;
287
+
288
+ case "h":
289
+ com.values = [values[0] * scaleX];
290
+ break;
291
+
292
+ case "v":
293
+ com.values = [values[0] * scaleY];
294
+ break;
295
+
296
+ default:
297
+ if (values.length) {
298
+ for (let i = 0; i < values.length; i += 2) {
299
+ com.values[i] *= scaleX;
300
+ com.values[i + 1] *= scaleY;
301
+ }
302
+ }
303
+ }
304
+
305
+ });
306
+ return pathData;
307
+ }
@@ -3,6 +3,8 @@
3
3
  * transform property object
4
4
  */
5
5
 
6
+ import { deg2rad } from "../constants";
7
+
6
8
  export function parseTransform(transformString, transformOrigin = { x: 0, y: 0 }) {
7
9
 
8
10
  //let regex = /(\w+)\(([^)]+)\)/g;
@@ -18,12 +20,12 @@ export function parseTransform(transformString, transformOrigin = { x: 0, y: 0 }
18
20
  console.log('trans', prop, vals);
19
21
 
20
22
  // rotate has origin
21
- if(prop==='rotate' && vals.length===3){
22
- transforms.push({prop:'translate', values:[vals[1], vals[2]]})
23
- transforms.push({prop:'rotate', values:[vals[0]]})
24
- transforms.push({prop:'translate', values:[-vals[1], -vals[2]]})
25
- }else{
26
- transforms.push({prop, values:[vals[0]]})
23
+ if (prop === 'rotate' && vals.length === 3) {
24
+ transforms.push({ prop: 'translate', values: [vals[1], vals[2]] })
25
+ transforms.push({ prop: 'rotate', values: [vals[0]] })
26
+ transforms.push({ prop: 'translate', values: [-vals[1], -vals[2]] })
27
+ } else {
28
+ transforms.push({ prop, values: [vals[0]] })
27
29
  }
28
30
  })
29
31
 
@@ -89,8 +91,7 @@ export function parseCSSTransform(transformString, transformOrigin = { x: 0, y:
89
91
  break;
90
92
 
91
93
  case 'rotate':
92
-
93
- console.log('rotate', values);
94
+ //console.log('rotate', values);
94
95
 
95
96
  transformOptions.transforms.push({ rotate: [0, 0, values[0] || 0] });
96
97
  break;
@@ -139,6 +140,116 @@ export function getMatrix({
139
140
 
140
141
 
141
142
 
143
+
144
+ export function getMatrixFromTransform(transformations = []) {
145
+
146
+ //console.log('getMatrix2D', transformations, origin);
147
+ // Helper function to multiply two 2D matrices
148
+
149
+ const multiply = (m1, m2) => {
150
+ let mtxN = {
151
+ a: m1.a * m2.a + m1.c * m2.b,
152
+ b: m1.b * m2.a + m1.d * m2.b,
153
+ c: m1.a * m2.c + m1.c * m2.d,
154
+ d: m1.b * m2.c + m1.d * m2.d,
155
+ e: m1.a * m2.e + m1.c * m2.f + m1.e,
156
+ f: m1.b * m2.e + m1.d * m2.f + m1.f
157
+ }
158
+
159
+ //console.log('m1', m1, 'm2', m2, 'mtxN', mtxN);
160
+ return mtxN;
161
+ };
162
+
163
+
164
+ // Helper function to create a translation matrix
165
+ const translationMatrix = (x, y) => {
166
+ let mtx ={a: 1, b: 0, c: 0, d: 1, e: x, f: y}
167
+ return mtx
168
+ };
169
+
170
+ // Helper function to create a scaling matrix
171
+ const scalingMatrix = (x, y) => ({
172
+ a: x, b: 0, c: 0, d: y, e: 0, f: 0
173
+ });
174
+
175
+
176
+ // get skew or rotation axis matrix
177
+ const angleMatrix = (angles, type) => {
178
+ //const toRad = (angle) => angle * Math.PI / 180;
179
+ let [angleX, angleY=0] = angles.map(ang => ang*deg2rad)
180
+ let m = {}
181
+ //console.log('angles', angles);
182
+
183
+ if (type === 'rot') {
184
+ let cosX = Math.cos(angleX), sinX = Math.sin(angleX);
185
+ m = { a: cosX, b: sinX, c: -sinX, d: cosX, e: 0, f: 0 }
186
+ } else if (type === 'skew') {
187
+ let tanX = Math.tan(angleX), tanY = Math.tan(angleY);
188
+ m = {
189
+ a: 1, b: tanY, c: tanX, d: 1, e: 0, f: 0
190
+ };
191
+ }
192
+ return m
193
+ };
194
+
195
+
196
+ // Start with an identity matrix
197
+ let matrix = { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 };
198
+
199
+
200
+ // Process transformations in the provided order (right-to-left)
201
+ for (let i = 0; i < transformations.length; i++) {
202
+
203
+ let transform = transformations[i];
204
+
205
+ // Get the transformation type (e.g., "translate")
206
+ let type = Object.keys(transform)[0];
207
+
208
+ let values = transform[type];
209
+ //console.log('transform', transform, type, values);
210
+
211
+ let [x, y] = values
212
+
213
+
214
+ switch (type) {
215
+ case "matrix":
216
+ let keys = ['a', 'b', 'c', 'd', 'e', 'f'];
217
+ let obj = Object.fromEntries(keys.map((key, i) => [key, values[i]]));
218
+ //console.log('mtx',obj );
219
+ matrix = multiply(matrix, obj);
220
+ break;
221
+ case "translate":
222
+ matrix = multiply(matrix, translationMatrix(x, y));
223
+ break;
224
+ case "skew":
225
+ matrix = multiply(matrix, angleMatrix([x, y], 'skew'));
226
+ break;
227
+ case "rotate":
228
+ matrix = multiply(matrix, angleMatrix([x], 'rot'));
229
+ //console.log('rot', matrix);
230
+ break;
231
+ case "scale":
232
+ matrix = multiply(matrix, scalingMatrix(x, y));
233
+ break;
234
+
235
+ default:
236
+ throw new Error(`Unknown transformation type: ${type}`);
237
+ }
238
+
239
+
240
+ //let mtxTmp = JSON.parse(JSON.stringify(matrix))
241
+ //console.log('??? mtxTmp', type, mtxTmp, 'transform', transform);
242
+ }
243
+
244
+
245
+ //console.log('matrix2D', matrix);
246
+ return matrix;
247
+ }
248
+
249
+
250
+
251
+
252
+
142
253
  export function getMatrix2D(transformations = [], origin = { x: 0, y: 0 }) {
143
254
 
144
255
  //console.log('getMatrix2D', transformations, origin);
@@ -15,20 +15,35 @@ export const shapeEls = [
15
15
 
16
16
  export const horizontalProps = ['x', 'cx', 'rx', 'dx', 'width', 'translateX'];
17
17
  export const verticalProps = ['y', 'cy', 'ry', 'dy', 'height', 'translateY'];
18
+ export const transHorizontal = ['scaleX', 'translateX', 'skewX'];
19
+ export const transVertical = ['scaleY', 'translateY', 'skewY'];
20
+
21
+ export const colorProps = ['fill', 'stroke', 'stop-color'];
22
+
18
23
 
19
24
  export const geometryEls = [
20
25
  "path",
21
26
  ...shapeEls
22
- ]
27
+ ];
28
+
29
+ export const renderedEls = [
30
+ "text",
31
+ "textPath",
32
+ "tspan",
33
+ ...geometryEls
34
+ ];
23
35
 
24
36
  export const textEls = [
25
37
  "textPath",
26
38
  "text",
27
39
  "tspan",
28
- ]
40
+ ];
41
+
42
+
29
43
 
30
44
  export const strokeAtts = ['stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin','stroke-linecap', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-miterlimit', 'stroke-opacity' ];
31
45
 
46
+
32
47
  export const attLookup = {
33
48
 
34
49
  atts: {
@@ -306,9 +321,9 @@ export const attLookup = {
306
321
  "color": ["black", "rgb(0, 0, 0)", "rgba(0, 0, 0, 0)", "#000", "#000000"],
307
322
 
308
323
  stroke: ["none"],
309
- "stroke-width": ["1", "1px"],
310
324
  opacity: ["1"],
311
325
  "fill-opacity": ["1"],
326
+ "stroke-width": ["1", "1px"],
312
327
  "stroke-opacity": ["1"],
313
328
  "stroke-linecap": ["butt"],
314
329
  "stroke-miterlimit": ["4"],