@svgedit/svgcanvas 7.2.3 → 7.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGES.md +4 -0
- package/core/coords.js +203 -99
- package/core/draw.js +129 -56
- package/core/event.js +1 -0
- package/core/math.js +138 -154
- package/core/recalculate.js +232 -613
- package/core/sanitize.js +3 -3
- package/core/selected-elem.js +1 -1
- package/core/selection.js +1 -1
- package/core/svg-exec.js +163 -140
- package/core/text-actions.js +160 -129
- package/dist/svgcanvas.js +20310 -19109
- package/dist/svgcanvas.js.map +1 -1
- package/package.json +1 -1
- package/svgcanvas.js +1 -1
package/core/math.js
CHANGED
|
@@ -3,117 +3,111 @@
|
|
|
3
3
|
* @module math
|
|
4
4
|
* @license MIT
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* ©2010 Alexis Deveria, ©2010 Jeff Schiller
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* @typedef {
|
|
11
|
-
* @property {
|
|
12
|
-
* @property {
|
|
13
|
-
* @property {
|
|
10
|
+
* @typedef {Object} AngleCoord45
|
|
11
|
+
* @property {number} x - The angle-snapped x value
|
|
12
|
+
* @property {number} y - The angle-snapped y value
|
|
13
|
+
* @property {number} a - The angle (in radians) at which to snap
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* @typedef {
|
|
18
|
-
* @property {
|
|
19
|
-
* @property {
|
|
17
|
+
* @typedef {Object} XYObject
|
|
18
|
+
* @property {number} x
|
|
19
|
+
* @property {number} y
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
import { NS } from './namespaces.js'
|
|
23
23
|
|
|
24
24
|
// Constants
|
|
25
|
-
const NEAR_ZERO = 1e-
|
|
25
|
+
const NEAR_ZERO = 1e-10
|
|
26
26
|
|
|
27
|
-
//
|
|
27
|
+
// Create a throwaway SVG element for matrix operations
|
|
28
28
|
const svg = document.createElementNS(NS.SVG, 'svg')
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* @
|
|
34
|
-
* @param {
|
|
35
|
-
* @param {
|
|
36
|
-
* @
|
|
37
|
-
* @returns {module:math.XYObject} An x, y object representing the transformed point
|
|
31
|
+
* Transforms a point by a given matrix without DOM calls.
|
|
32
|
+
* @function transformPoint
|
|
33
|
+
* @param {number} x - The x coordinate
|
|
34
|
+
* @param {number} y - The y coordinate
|
|
35
|
+
* @param {SVGMatrix} m - The transformation matrix
|
|
36
|
+
* @returns {XYObject} The transformed point
|
|
38
37
|
*/
|
|
39
|
-
export const transformPoint =
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
export const transformPoint = (x, y, m) => ({
|
|
39
|
+
x: m.a * x + m.c * y + m.e,
|
|
40
|
+
y: m.b * x + m.d * y + m.f
|
|
41
|
+
})
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Gets the transform list (baseVal) from an element if it exists.
|
|
45
|
+
* @function getTransformList
|
|
46
|
+
* @param {Element} elem - An SVG element or element with a transform list
|
|
47
|
+
* @returns {SVGTransformList|undefined} The transform list, if any
|
|
48
|
+
*/
|
|
49
|
+
export const getTransformList = elem => {
|
|
50
|
+
if (elem.transform?.baseVal) {
|
|
51
|
+
return elem.transform.baseVal
|
|
46
52
|
}
|
|
47
|
-
if (elem.gradientTransform) {
|
|
48
|
-
return elem.gradientTransform
|
|
53
|
+
if (elem.gradientTransform?.baseVal) {
|
|
54
|
+
return elem.gradientTransform.baseVal
|
|
49
55
|
}
|
|
50
|
-
if (elem.patternTransform) {
|
|
51
|
-
return elem.patternTransform
|
|
56
|
+
if (elem.patternTransform?.baseVal) {
|
|
57
|
+
return elem.patternTransform.baseVal
|
|
52
58
|
}
|
|
53
|
-
console.warn('
|
|
59
|
+
console.warn('No transform list found. Check browser compatibility.', elem)
|
|
54
60
|
}
|
|
55
61
|
|
|
56
62
|
/**
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* @
|
|
60
|
-
* @
|
|
61
|
-
* @returns {boolean} Indicates whether or not the matrix is 1,0,0,1,0,0
|
|
63
|
+
* Checks if a matrix is the identity matrix.
|
|
64
|
+
* @function isIdentity
|
|
65
|
+
* @param {SVGMatrix} m - The matrix to check
|
|
66
|
+
* @returns {boolean} True if it's an identity matrix (1,0,0,1,0,0)
|
|
62
67
|
*/
|
|
63
|
-
export const isIdentity =
|
|
64
|
-
|
|
65
|
-
m.a === 1 && m.b === 0 && m.c === 0 && m.d === 1 && m.e === 0 && m.f === 0
|
|
66
|
-
)
|
|
67
|
-
}
|
|
68
|
+
export const isIdentity = m =>
|
|
69
|
+
m.a === 1 && m.b === 0 && m.c === 0 && m.d === 1 && m.e === 0 && m.f === 0
|
|
68
70
|
|
|
69
71
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* @function
|
|
73
|
-
* @param {...SVGMatrix} args -
|
|
74
|
-
* @returns {SVGMatrix} The
|
|
72
|
+
* Multiplies multiple matrices together (m1 * m2 * ...).
|
|
73
|
+
* Near-zero values are rounded to zero.
|
|
74
|
+
* @function matrixMultiply
|
|
75
|
+
* @param {...SVGMatrix} args - The matrices to multiply
|
|
76
|
+
* @returns {SVGMatrix} The resulting matrix
|
|
75
77
|
*/
|
|
76
|
-
export const matrixMultiply =
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (Math.abs(m.a) < NEAR_ZERO) {
|
|
82
|
-
m.a = 0
|
|
83
|
-
}
|
|
84
|
-
if (Math.abs(m.b) < NEAR_ZERO) {
|
|
85
|
-
m.b = 0
|
|
86
|
-
}
|
|
87
|
-
if (Math.abs(m.c) < NEAR_ZERO) {
|
|
88
|
-
m.c = 0
|
|
89
|
-
}
|
|
90
|
-
if (Math.abs(m.d) < NEAR_ZERO) {
|
|
91
|
-
m.d = 0
|
|
92
|
-
}
|
|
93
|
-
if (Math.abs(m.e) < NEAR_ZERO) {
|
|
94
|
-
m.e = 0
|
|
95
|
-
}
|
|
96
|
-
if (Math.abs(m.f) < NEAR_ZERO) {
|
|
97
|
-
m.f = 0
|
|
78
|
+
export const matrixMultiply = (...args) => {
|
|
79
|
+
// If no matrices are given, return an identity matrix
|
|
80
|
+
if (args.length === 0) {
|
|
81
|
+
return svg.createSVGMatrix()
|
|
98
82
|
}
|
|
99
83
|
|
|
84
|
+
const m = args.reduceRight((prev, curr) => curr.multiply(prev))
|
|
85
|
+
|
|
86
|
+
// Round near-zero values to zero
|
|
87
|
+
if (Math.abs(m.a) < NEAR_ZERO) m.a = 0
|
|
88
|
+
if (Math.abs(m.b) < NEAR_ZERO) m.b = 0
|
|
89
|
+
if (Math.abs(m.c) < NEAR_ZERO) m.c = 0
|
|
90
|
+
if (Math.abs(m.d) < NEAR_ZERO) m.d = 0
|
|
91
|
+
if (Math.abs(m.e) < NEAR_ZERO) m.e = 0
|
|
92
|
+
if (Math.abs(m.f) < NEAR_ZERO) m.f = 0
|
|
93
|
+
|
|
100
94
|
return m
|
|
101
95
|
}
|
|
102
96
|
|
|
103
97
|
/**
|
|
104
|
-
*
|
|
105
|
-
* @function
|
|
106
|
-
* @param {SVGTransformList} [tlist] - The
|
|
107
|
-
* @returns {boolean}
|
|
98
|
+
* Checks if a transform list includes a non-identity matrix transform.
|
|
99
|
+
* @function hasMatrixTransform
|
|
100
|
+
* @param {SVGTransformList} [tlist] - The transform list to check
|
|
101
|
+
* @returns {boolean} True if a matrix transform is found
|
|
108
102
|
*/
|
|
109
|
-
export const hasMatrixTransform =
|
|
110
|
-
if (!tlist)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
103
|
+
export const hasMatrixTransform = tlist => {
|
|
104
|
+
if (!tlist) return false
|
|
105
|
+
for (let i = 0; i < tlist.numberOfItems; i++) {
|
|
106
|
+
const xform = tlist.getItem(i)
|
|
107
|
+
if (
|
|
108
|
+
xform.type === SVGTransform.SVG_TRANSFORM_MATRIX &&
|
|
109
|
+
!isIdentity(xform.matrix)
|
|
110
|
+
) {
|
|
117
111
|
return true
|
|
118
112
|
}
|
|
119
113
|
}
|
|
@@ -121,29 +115,29 @@ export const hasMatrixTransform = function (tlist) {
|
|
|
121
115
|
}
|
|
122
116
|
|
|
123
117
|
/**
|
|
124
|
-
* @typedef {
|
|
125
|
-
* @property {
|
|
126
|
-
* @property {
|
|
127
|
-
* @property {
|
|
128
|
-
* @property {
|
|
129
|
-
* @property {
|
|
130
|
-
* @property {
|
|
131
|
-
* @property {
|
|
132
|
-
* @property {
|
|
133
|
-
* @property {
|
|
118
|
+
* @typedef {Object} TransformedBox
|
|
119
|
+
* @property {XYObject} tl - Top-left coordinate
|
|
120
|
+
* @property {XYObject} tr - Top-right coordinate
|
|
121
|
+
* @property {XYObject} bl - Bottom-left coordinate
|
|
122
|
+
* @property {XYObject} br - Bottom-right coordinate
|
|
123
|
+
* @property {Object} aabox
|
|
124
|
+
* @property {number} aabox.x - Axis-aligned x
|
|
125
|
+
* @property {number} aabox.y - Axis-aligned y
|
|
126
|
+
* @property {number} aabox.width - Axis-aligned width
|
|
127
|
+
* @property {number} aabox.height - Axis-aligned height
|
|
134
128
|
*/
|
|
135
129
|
|
|
136
130
|
/**
|
|
137
|
-
* Transforms a
|
|
138
|
-
* @function
|
|
139
|
-
* @param {
|
|
140
|
-
* @param {
|
|
141
|
-
* @param {
|
|
142
|
-
* @param {
|
|
143
|
-
* @param {SVGMatrix} m -
|
|
144
|
-
* @returns {
|
|
131
|
+
* Transforms a rectangular box using a given matrix.
|
|
132
|
+
* @function transformBox
|
|
133
|
+
* @param {number} l - Left coordinate
|
|
134
|
+
* @param {number} t - Top coordinate
|
|
135
|
+
* @param {number} w - Width
|
|
136
|
+
* @param {number} h - Height
|
|
137
|
+
* @param {SVGMatrix} m - Transformation matrix
|
|
138
|
+
* @returns {TransformedBox} The transformed box information
|
|
145
139
|
*/
|
|
146
|
-
export const transformBox =
|
|
140
|
+
export const transformBox = (l, t, w, h, m) => {
|
|
147
141
|
const tl = transformPoint(l, t, m)
|
|
148
142
|
const tr = transformPoint(l + w, t, m)
|
|
149
143
|
const bl = transformPoint(l, t + h, m)
|
|
@@ -169,91 +163,81 @@ export const transformBox = function (l, t, w, h, m) {
|
|
|
169
163
|
}
|
|
170
164
|
|
|
171
165
|
/**
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
* @
|
|
177
|
-
* @
|
|
178
|
-
* @param {Integer} [min=0] - Optional integer indicating start transform position
|
|
179
|
-
* @param {Integer} [max] - Optional integer indicating end transform position;
|
|
180
|
-
* defaults to one less than the tlist's `numberOfItems`
|
|
181
|
-
* @returns {SVGTransform} A single matrix transform object
|
|
166
|
+
* Consolidates a transform list into a single matrix transform without modifying the original list.
|
|
167
|
+
* @function transformListToTransform
|
|
168
|
+
* @param {SVGTransformList} tlist - The transform list
|
|
169
|
+
* @param {number} [min=0] - Optional start index
|
|
170
|
+
* @param {number} [max] - Optional end index, defaults to tlist length-1
|
|
171
|
+
* @returns {SVGTransform} A single transform from the combined matrices
|
|
182
172
|
*/
|
|
183
|
-
export const transformListToTransform =
|
|
173
|
+
export const transformListToTransform = (tlist, min = 0, max = null) => {
|
|
184
174
|
if (!tlist) {
|
|
185
|
-
// Or should tlist = null have been prevented before this?
|
|
186
175
|
return svg.createSVGTransformFromMatrix(svg.createSVGMatrix())
|
|
187
176
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
for (let i = min; i <= max; ++i) {
|
|
199
|
-
// if our indices are out of range, just use a harmless identity matrix
|
|
200
|
-
const mtom =
|
|
177
|
+
|
|
178
|
+
const start = Number.parseInt(min, 10)
|
|
179
|
+
const end = Number.parseInt(max ?? tlist.numberOfItems - 1, 10)
|
|
180
|
+
const low = Math.min(start, end)
|
|
181
|
+
const high = Math.max(start, end)
|
|
182
|
+
|
|
183
|
+
let combinedMatrix = svg.createSVGMatrix()
|
|
184
|
+
for (let i = low; i <= high; i++) {
|
|
185
|
+
// If out of range, use identity
|
|
186
|
+
const currentMatrix =
|
|
201
187
|
i >= 0 && i < tlist.numberOfItems
|
|
202
188
|
? tlist.getItem(i).matrix
|
|
203
189
|
: svg.createSVGMatrix()
|
|
204
|
-
|
|
190
|
+
combinedMatrix = matrixMultiply(combinedMatrix, currentMatrix)
|
|
205
191
|
}
|
|
206
|
-
|
|
192
|
+
|
|
193
|
+
return svg.createSVGTransformFromMatrix(combinedMatrix)
|
|
207
194
|
}
|
|
208
195
|
|
|
209
196
|
/**
|
|
210
|
-
*
|
|
211
|
-
* @function
|
|
212
|
-
* @param {Element} elem - The
|
|
213
|
-
* @returns {SVGMatrix} The matrix
|
|
197
|
+
* Gets the matrix of a given element's transform list.
|
|
198
|
+
* @function getMatrix
|
|
199
|
+
* @param {Element} elem - The element to check
|
|
200
|
+
* @returns {SVGMatrix} The transformation matrix
|
|
214
201
|
*/
|
|
215
|
-
export const getMatrix =
|
|
202
|
+
export const getMatrix = elem => {
|
|
216
203
|
const tlist = getTransformList(elem)
|
|
217
204
|
return transformListToTransform(tlist).matrix
|
|
218
205
|
}
|
|
219
206
|
|
|
220
207
|
/**
|
|
221
|
-
* Returns a
|
|
222
|
-
*
|
|
223
|
-
* @
|
|
224
|
-
* @param {
|
|
225
|
-
* @param {
|
|
226
|
-
* @param {
|
|
227
|
-
* @
|
|
228
|
-
* @returns {module:math.AngleCoord45}
|
|
208
|
+
* Returns a coordinate snapped to the nearest 45-degree angle.
|
|
209
|
+
* @function snapToAngle
|
|
210
|
+
* @param {number} x1 - First point's x
|
|
211
|
+
* @param {number} y1 - First point's y
|
|
212
|
+
* @param {number} x2 - Second point's x
|
|
213
|
+
* @param {number} y2 - Second point's y
|
|
214
|
+
* @returns {AngleCoord45} The angle-snapped coordinates and angle
|
|
229
215
|
*/
|
|
230
216
|
export const snapToAngle = (x1, y1, x2, y2) => {
|
|
231
217
|
const snap = Math.PI / 4 // 45 degrees
|
|
232
218
|
const dx = x2 - x1
|
|
233
219
|
const dy = y2 - y1
|
|
234
220
|
const angle = Math.atan2(dy, dx)
|
|
235
|
-
const dist = Math.
|
|
236
|
-
const
|
|
221
|
+
const dist = Math.hypot(dx, dy)
|
|
222
|
+
const snapAngle = Math.round(angle / snap) * snap
|
|
237
223
|
|
|
238
224
|
return {
|
|
239
|
-
x: x1 + dist * Math.cos(
|
|
240
|
-
y: y1 + dist * Math.sin(
|
|
241
|
-
a:
|
|
225
|
+
x: x1 + dist * Math.cos(snapAngle),
|
|
226
|
+
y: y1 + dist * Math.sin(snapAngle),
|
|
227
|
+
a: snapAngle
|
|
242
228
|
}
|
|
243
229
|
}
|
|
244
230
|
|
|
245
231
|
/**
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
* @
|
|
249
|
-
* @param {
|
|
250
|
-
* @
|
|
232
|
+
* Checks if two rectangles intersect.
|
|
233
|
+
* Both r1 and r2 are expected to have {x, y, width, height}.
|
|
234
|
+
* @function rectsIntersect
|
|
235
|
+
* @param {{x:number,y:number,width:number,height:number}} r1 - First rectangle
|
|
236
|
+
* @param {{x:number,y:number,width:number,height:number}} r2 - Second rectangle
|
|
237
|
+
* @returns {boolean} True if the rectangles intersect
|
|
251
238
|
*/
|
|
252
|
-
export const rectsIntersect = (r1, r2) =>
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
r2.y + r2.height > r1.y
|
|
258
|
-
)
|
|
259
|
-
}
|
|
239
|
+
export const rectsIntersect = (r1, r2) =>
|
|
240
|
+
r2.x < r1.x + r1.width &&
|
|
241
|
+
r2.x + r2.width > r1.x &&
|
|
242
|
+
r2.y < r1.y + r1.height &&
|
|
243
|
+
r2.y + r2.height > r1.y
|