@svgedit/svgcanvas 7.2.7 → 7.4.1
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/common/browser.js +104 -37
- package/common/logger.js +151 -0
- package/common/util.js +96 -155
- package/core/blur-event.js +106 -42
- package/core/clear.js +13 -3
- package/core/coords.js +214 -90
- package/core/copy-elem.js +27 -13
- package/core/dataStorage.js +84 -21
- package/core/draw.js +80 -40
- package/core/elem-get-set.js +161 -77
- package/core/event.js +143 -28
- package/core/history.js +50 -30
- package/core/historyrecording.js +4 -2
- package/core/json.js +54 -12
- package/core/layer.js +11 -17
- package/core/math.js +102 -23
- package/core/namespaces.js +5 -5
- package/core/paint.js +95 -24
- package/core/paste-elem.js +58 -19
- package/core/path-actions.js +812 -791
- package/core/path-method.js +236 -37
- package/core/path.js +45 -10
- package/core/recalculate.js +410 -15
- package/core/sanitize.js +46 -14
- package/core/select.js +44 -20
- package/core/selected-elem.js +146 -31
- package/core/selection.js +16 -6
- package/core/svg-exec.js +99 -27
- package/core/svgroot.js +1 -1
- package/core/text-actions.js +327 -306
- package/core/undo.js +20 -5
- package/core/units.js +8 -6
- package/core/utilities.js +282 -170
- package/dist/svgcanvas.js +31590 -53383
- package/dist/svgcanvas.js.map +1 -1
- package/package.json +55 -54
- package/publish.md +1 -6
- package/svgcanvas.d.ts +225 -0
- package/svgcanvas.js +9 -9
- package/vite.config.mjs +20 -0
- package/rollup.config.mjs +0 -38
package/core/layer.js
CHANGED
|
@@ -40,19 +40,16 @@ class Layer {
|
|
|
40
40
|
const layerTitle = svgdoc.createElementNS(NS.SVG, 'title')
|
|
41
41
|
layerTitle.textContent = name
|
|
42
42
|
this.group_.append(layerTitle)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
} else {
|
|
46
|
-
svgElem.append(this.group_)
|
|
47
|
-
}
|
|
43
|
+
|
|
44
|
+
group ? group.insertAdjacentElement('afterend', this.group_) : svgElem.append(this.group_)
|
|
48
45
|
}
|
|
49
46
|
|
|
50
47
|
addLayerClass(this.group_)
|
|
51
48
|
walkTree(this.group_, function (e) {
|
|
52
|
-
e.
|
|
49
|
+
e.style.pointerEvents = 'inherit'
|
|
53
50
|
})
|
|
54
51
|
|
|
55
|
-
this.group_.
|
|
52
|
+
this.group_.style.pointerEvents = svgElem ? 'all' : 'none'
|
|
56
53
|
}
|
|
57
54
|
|
|
58
55
|
/**
|
|
@@ -76,7 +73,7 @@ class Layer {
|
|
|
76
73
|
* @returns {void}
|
|
77
74
|
*/
|
|
78
75
|
activate () {
|
|
79
|
-
this.group_.
|
|
76
|
+
this.group_.style.pointerEvents = 'all'
|
|
80
77
|
}
|
|
81
78
|
|
|
82
79
|
/**
|
|
@@ -84,7 +81,7 @@ class Layer {
|
|
|
84
81
|
* @returns {void}
|
|
85
82
|
*/
|
|
86
83
|
deactivate () {
|
|
87
|
-
this.group_.
|
|
84
|
+
this.group_.style.pointerEvents = 'none'
|
|
88
85
|
}
|
|
89
86
|
|
|
90
87
|
/**
|
|
@@ -93,7 +90,7 @@ class Layer {
|
|
|
93
90
|
* @returns {void}
|
|
94
91
|
*/
|
|
95
92
|
setVisible (visible) {
|
|
96
|
-
const expected = visible === undefined || visible ? 'inline' : 'none'
|
|
93
|
+
const expected = (visible === undefined || visible) ? 'inline' : 'none'
|
|
97
94
|
const oldDisplay = this.group_.getAttribute('display')
|
|
98
95
|
if (oldDisplay !== expected) {
|
|
99
96
|
this.group_.setAttribute('display', expected)
|
|
@@ -114,10 +111,7 @@ class Layer {
|
|
|
114
111
|
*/
|
|
115
112
|
getOpacity () {
|
|
116
113
|
const opacity = this.group_.getAttribute('opacity')
|
|
117
|
-
|
|
118
|
-
return 1
|
|
119
|
-
}
|
|
120
|
-
return Number.parseFloat(opacity)
|
|
114
|
+
return opacity ? Number.parseFloat(opacity) : 1
|
|
121
115
|
}
|
|
122
116
|
|
|
123
117
|
/**
|
|
@@ -208,7 +202,7 @@ Layer.CLASS_NAME = 'layer'
|
|
|
208
202
|
/**
|
|
209
203
|
* @property {RegExp} CLASS_REGEX - Used to test presence of class Layer.CLASS_NAME
|
|
210
204
|
*/
|
|
211
|
-
Layer.CLASS_REGEX = new RegExp(
|
|
205
|
+
Layer.CLASS_REGEX = new RegExp(`(\\s|^)${Layer.CLASS_NAME}(\\s|$)`)
|
|
212
206
|
|
|
213
207
|
/**
|
|
214
208
|
* Add class `Layer.CLASS_NAME` to the element (usually `class='layer'`).
|
|
@@ -216,12 +210,12 @@ Layer.CLASS_REGEX = new RegExp('(\\s|^)' + Layer.CLASS_NAME + '(\\s|$)')
|
|
|
216
210
|
* @param {SVGGElement} elem - The SVG element to update
|
|
217
211
|
* @returns {void}
|
|
218
212
|
*/
|
|
219
|
-
|
|
213
|
+
const addLayerClass = (elem) => {
|
|
220
214
|
const classes = elem.getAttribute('class')
|
|
221
215
|
if (!classes || !classes.length) {
|
|
222
216
|
elem.setAttribute('class', Layer.CLASS_NAME)
|
|
223
217
|
} else if (!Layer.CLASS_REGEX.test(classes)) {
|
|
224
|
-
elem.setAttribute('class', classes
|
|
218
|
+
elem.setAttribute('class', `${classes} ${Layer.CLASS_NAME}`)
|
|
225
219
|
}
|
|
226
220
|
}
|
|
227
221
|
|
package/core/math.js
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
import { NS } from './namespaces.js'
|
|
23
|
+
import { warn } from '../common/logger.js'
|
|
23
24
|
|
|
24
25
|
// Constants
|
|
25
26
|
const NEAR_ZERO = 1e-10
|
|
@@ -27,6 +28,38 @@ const NEAR_ZERO = 1e-10
|
|
|
27
28
|
// Create a throwaway SVG element for matrix operations
|
|
28
29
|
const svg = document.createElementNS(NS.SVG, 'svg')
|
|
29
30
|
|
|
31
|
+
const createTransformFromMatrix = (m) => {
|
|
32
|
+
const createFallback = (matrix) => {
|
|
33
|
+
const fallback = svg.createSVGMatrix()
|
|
34
|
+
Object.assign(fallback, {
|
|
35
|
+
a: matrix.a,
|
|
36
|
+
b: matrix.b,
|
|
37
|
+
c: matrix.c,
|
|
38
|
+
d: matrix.d,
|
|
39
|
+
e: matrix.e,
|
|
40
|
+
f: matrix.f
|
|
41
|
+
})
|
|
42
|
+
return fallback
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
return svg.createSVGTransformFromMatrix(m)
|
|
47
|
+
} catch (e) {
|
|
48
|
+
const t = svg.createSVGTransform()
|
|
49
|
+
try {
|
|
50
|
+
t.setMatrix(m)
|
|
51
|
+
return t
|
|
52
|
+
} catch (err) {
|
|
53
|
+
try {
|
|
54
|
+
return svg.createSVGTransformFromMatrix(createFallback(m))
|
|
55
|
+
} catch (e2) {
|
|
56
|
+
t.setMatrix(createFallback(m))
|
|
57
|
+
return t
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
30
63
|
/**
|
|
31
64
|
* Transforms a point by a given matrix without DOM calls.
|
|
32
65
|
* @function transformPoint
|
|
@@ -56,7 +89,7 @@ export const getTransformList = elem => {
|
|
|
56
89
|
if (elem.patternTransform?.baseVal) {
|
|
57
90
|
return elem.patternTransform.baseVal
|
|
58
91
|
}
|
|
59
|
-
|
|
92
|
+
warn('No transform list found. Check browser compatibility.', elem, 'math')
|
|
60
93
|
}
|
|
61
94
|
|
|
62
95
|
/**
|
|
@@ -66,7 +99,12 @@ export const getTransformList = elem => {
|
|
|
66
99
|
* @returns {boolean} True if it's an identity matrix (1,0,0,1,0,0)
|
|
67
100
|
*/
|
|
68
101
|
export const isIdentity = m =>
|
|
69
|
-
m.a
|
|
102
|
+
Math.abs(m.a - 1) < NEAR_ZERO &&
|
|
103
|
+
Math.abs(m.b) < NEAR_ZERO &&
|
|
104
|
+
Math.abs(m.c) < NEAR_ZERO &&
|
|
105
|
+
Math.abs(m.d - 1) < NEAR_ZERO &&
|
|
106
|
+
Math.abs(m.e) < NEAR_ZERO &&
|
|
107
|
+
Math.abs(m.f) < NEAR_ZERO
|
|
70
108
|
|
|
71
109
|
/**
|
|
72
110
|
* Multiplies multiple matrices together (m1 * m2 * ...).
|
|
@@ -76,22 +114,54 @@ export const isIdentity = m =>
|
|
|
76
114
|
* @returns {SVGMatrix} The resulting matrix
|
|
77
115
|
*/
|
|
78
116
|
export const matrixMultiply = (...args) => {
|
|
79
|
-
// If no matrices are given, return an identity matrix
|
|
80
117
|
if (args.length === 0) {
|
|
81
118
|
return svg.createSVGMatrix()
|
|
82
119
|
}
|
|
83
120
|
|
|
84
|
-
const
|
|
121
|
+
const normalizeNearZero = (matrix) => {
|
|
122
|
+
const props = ['a', 'b', 'c', 'd', 'e', 'f']
|
|
123
|
+
for (const prop of props) {
|
|
124
|
+
if (Math.abs(matrix[prop]) < NEAR_ZERO) {
|
|
125
|
+
matrix[prop] = 0
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return matrix
|
|
129
|
+
}
|
|
85
130
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (Math.abs(m.e) < NEAR_ZERO) m.e = 0
|
|
92
|
-
if (Math.abs(m.f) < NEAR_ZERO) m.f = 0
|
|
131
|
+
if (typeof DOMMatrix === 'function' && typeof DOMMatrix.fromMatrix === 'function') {
|
|
132
|
+
const result = args.reduce(
|
|
133
|
+
(acc, curr) => acc.multiply(DOMMatrix.fromMatrix(curr)),
|
|
134
|
+
new DOMMatrix()
|
|
135
|
+
)
|
|
93
136
|
|
|
94
|
-
|
|
137
|
+
const out = svg.createSVGMatrix()
|
|
138
|
+
Object.assign(out, {
|
|
139
|
+
a: result.a,
|
|
140
|
+
b: result.b,
|
|
141
|
+
c: result.c,
|
|
142
|
+
d: result.d,
|
|
143
|
+
e: result.e,
|
|
144
|
+
f: result.f
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
return normalizeNearZero(out)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
let m = svg.createSVGMatrix()
|
|
151
|
+
for (const curr of args) {
|
|
152
|
+
const next = svg.createSVGMatrix()
|
|
153
|
+
Object.assign(next, {
|
|
154
|
+
a: m.a * curr.a + m.c * curr.b,
|
|
155
|
+
b: m.b * curr.a + m.d * curr.b,
|
|
156
|
+
c: m.a * curr.c + m.c * curr.d,
|
|
157
|
+
d: m.b * curr.c + m.d * curr.d,
|
|
158
|
+
e: m.a * curr.e + m.c * curr.f + m.e,
|
|
159
|
+
f: m.b * curr.e + m.d * curr.f + m.f
|
|
160
|
+
})
|
|
161
|
+
m = next
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return normalizeNearZero(m)
|
|
95
165
|
}
|
|
96
166
|
|
|
97
167
|
/**
|
|
@@ -172,25 +242,34 @@ export const transformBox = (l, t, w, h, m) => {
|
|
|
172
242
|
*/
|
|
173
243
|
export const transformListToTransform = (tlist, min = 0, max = null) => {
|
|
174
244
|
if (!tlist) {
|
|
175
|
-
return
|
|
245
|
+
return createTransformFromMatrix(svg.createSVGMatrix())
|
|
176
246
|
}
|
|
177
247
|
|
|
178
248
|
const start = Number.parseInt(min, 10)
|
|
179
249
|
const end = Number.parseInt(max ?? tlist.numberOfItems - 1, 10)
|
|
180
|
-
const low = Math.min(start, end)
|
|
181
|
-
const high = Math.max(start, end)
|
|
250
|
+
const [low, high] = [Math.min(start, end), Math.max(start, end)]
|
|
182
251
|
|
|
183
|
-
|
|
252
|
+
const matrices = []
|
|
184
253
|
for (let i = low; i <= high; i++) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
: svg.createSVGMatrix()
|
|
190
|
-
combinedMatrix = matrixMultiply(combinedMatrix, currentMatrix)
|
|
254
|
+
const matrix = (i >= 0 && i < tlist.numberOfItems)
|
|
255
|
+
? tlist.getItem(i).matrix
|
|
256
|
+
: svg.createSVGMatrix()
|
|
257
|
+
matrices.push(matrix)
|
|
191
258
|
}
|
|
192
259
|
|
|
193
|
-
|
|
260
|
+
const combinedMatrix = matrixMultiply(...matrices)
|
|
261
|
+
|
|
262
|
+
const out = svg.createSVGMatrix()
|
|
263
|
+
Object.assign(out, {
|
|
264
|
+
a: combinedMatrix.a,
|
|
265
|
+
b: combinedMatrix.b,
|
|
266
|
+
c: combinedMatrix.c,
|
|
267
|
+
d: combinedMatrix.d,
|
|
268
|
+
e: combinedMatrix.e,
|
|
269
|
+
f: combinedMatrix.f
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
return createTransformFromMatrix(out)
|
|
194
273
|
}
|
|
195
274
|
|
|
196
275
|
/**
|
package/core/namespaces.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Common
|
|
8
|
+
* Common namespaces constants in alpha order.
|
|
9
9
|
* @enum {string}
|
|
10
10
|
* @type {PlainObject}
|
|
11
11
|
* @memberof module:namespaces
|
|
@@ -29,12 +29,12 @@ export const NS = {
|
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* @function module:namespaces.getReverseNS
|
|
32
|
-
* @returns {string} The
|
|
32
|
+
* @returns {PlainObject<string, string>} The namespace URI map with values swapped to their lowercase keys
|
|
33
33
|
*/
|
|
34
|
-
export const getReverseNS =
|
|
34
|
+
export const getReverseNS = () => {
|
|
35
35
|
const reverseNS = {}
|
|
36
|
-
|
|
36
|
+
for (const [name, URI] of Object.entries(NS)) {
|
|
37
37
|
reverseNS[URI] = name.toLowerCase()
|
|
38
|
-
}
|
|
38
|
+
}
|
|
39
39
|
return reverseNS
|
|
40
40
|
}
|
package/core/paint.js
CHANGED
|
@@ -2,12 +2,95 @@
|
|
|
2
2
|
*
|
|
3
3
|
*/
|
|
4
4
|
export default class Paint {
|
|
5
|
+
static #normalizeAlpha (alpha) {
|
|
6
|
+
const numeric = Number(alpha)
|
|
7
|
+
if (!Number.isFinite(numeric)) return 100
|
|
8
|
+
return Math.min(100, Math.max(0, numeric))
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
static #normalizeSolidColor (color) {
|
|
12
|
+
if (color === null || color === undefined) return null
|
|
13
|
+
const str = String(color).trim()
|
|
14
|
+
if (!str) return null
|
|
15
|
+
if (str === 'none') return 'none'
|
|
16
|
+
return str.startsWith('#') ? str.slice(1) : str
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static #extractHrefId (hrefAttr) {
|
|
20
|
+
if (!hrefAttr) return null
|
|
21
|
+
const href = String(hrefAttr).trim()
|
|
22
|
+
if (!href) return null
|
|
23
|
+
if (href.startsWith('#')) return href.slice(1)
|
|
24
|
+
const urlMatch = href.match(/url\(\s*['"]?#([^'")\s]+)['"]?\s*\)/)
|
|
25
|
+
if (urlMatch?.[1]) return urlMatch[1]
|
|
26
|
+
const hashIndex = href.lastIndexOf('#')
|
|
27
|
+
if (hashIndex >= 0 && hashIndex < href.length - 1) {
|
|
28
|
+
return href.slice(hashIndex + 1)
|
|
29
|
+
}
|
|
30
|
+
return null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static #resolveGradient (gradient) {
|
|
34
|
+
if (!gradient?.cloneNode) return null
|
|
35
|
+
const doc = gradient.ownerDocument || document
|
|
36
|
+
const visited = new Set()
|
|
37
|
+
const clone = gradient.cloneNode(true)
|
|
38
|
+
|
|
39
|
+
let refId = Paint.#extractHrefId(
|
|
40
|
+
clone.getAttribute('href') || clone.getAttribute('xlink:href')
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
while (refId && !visited.has(refId)) {
|
|
44
|
+
visited.add(refId)
|
|
45
|
+
|
|
46
|
+
const referenced = doc.getElementById(refId)
|
|
47
|
+
if (!referenced?.getAttribute) break
|
|
48
|
+
|
|
49
|
+
const cloneTag = String(clone.tagName || '').toLowerCase()
|
|
50
|
+
const referencedTag = String(referenced.tagName || '').toLowerCase()
|
|
51
|
+
if (
|
|
52
|
+
!['lineargradient', 'radialgradient'].includes(referencedTag) ||
|
|
53
|
+
referencedTag !== cloneTag
|
|
54
|
+
) {
|
|
55
|
+
break
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Copy missing attributes from referenced gradient (matches SVG href inheritance).
|
|
59
|
+
for (const attr of referenced.attributes || []) {
|
|
60
|
+
const name = attr.name
|
|
61
|
+
if (name === 'id' || name === 'href' || name === 'xlink:href') continue
|
|
62
|
+
const current = clone.getAttribute(name)
|
|
63
|
+
if (current === null || current === '') {
|
|
64
|
+
clone.setAttribute(name, attr.value)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// If the referencing gradient has no stops, inherit stops from the referenced gradient.
|
|
69
|
+
if (clone.querySelectorAll('stop').length === 0) {
|
|
70
|
+
for (const stop of referenced.querySelectorAll?.('stop') || []) {
|
|
71
|
+
clone.append(stop.cloneNode(true))
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Prepare to continue resolving deeper links if present.
|
|
76
|
+
refId = Paint.#extractHrefId(
|
|
77
|
+
referenced.getAttribute('href') || referenced.getAttribute('xlink:href')
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// The clone is now self-contained; remove any href.
|
|
82
|
+
clone.removeAttribute('href')
|
|
83
|
+
clone.removeAttribute('xlink:href')
|
|
84
|
+
|
|
85
|
+
return clone
|
|
86
|
+
}
|
|
87
|
+
|
|
5
88
|
/**
|
|
6
89
|
* @param {module:jGraduate.jGraduatePaintOptions} [opt]
|
|
7
90
|
*/
|
|
8
91
|
constructor (opt) {
|
|
9
92
|
const options = opt || {}
|
|
10
|
-
this.alpha =
|
|
93
|
+
this.alpha = Paint.#normalizeAlpha(options.alpha)
|
|
11
94
|
// copy paint object
|
|
12
95
|
if (options.copy) {
|
|
13
96
|
/**
|
|
@@ -20,7 +103,7 @@ export default class Paint {
|
|
|
20
103
|
* @name module:jGraduate~Paint#alpha
|
|
21
104
|
* @type {Float}
|
|
22
105
|
*/
|
|
23
|
-
this.alpha = options.copy.alpha
|
|
106
|
+
this.alpha = Paint.#normalizeAlpha(options.copy.alpha)
|
|
24
107
|
/**
|
|
25
108
|
* Represents #RRGGBB hex of color.
|
|
26
109
|
* @name module:jGraduate~Paint#solidColor
|
|
@@ -42,13 +125,17 @@ export default class Paint {
|
|
|
42
125
|
case 'none':
|
|
43
126
|
break
|
|
44
127
|
case 'solidColor':
|
|
45
|
-
this.solidColor = options.copy.solidColor
|
|
128
|
+
this.solidColor = Paint.#normalizeSolidColor(options.copy.solidColor)
|
|
46
129
|
break
|
|
47
130
|
case 'linearGradient':
|
|
48
|
-
this.linearGradient = options.copy.linearGradient
|
|
131
|
+
this.linearGradient = options.copy.linearGradient?.cloneNode
|
|
132
|
+
? options.copy.linearGradient.cloneNode(true)
|
|
133
|
+
: null
|
|
49
134
|
break
|
|
50
135
|
case 'radialGradient':
|
|
51
|
-
this.radialGradient = options.copy.radialGradient
|
|
136
|
+
this.radialGradient = options.copy.radialGradient?.cloneNode
|
|
137
|
+
? options.copy.radialGradient.cloneNode(true)
|
|
138
|
+
: null
|
|
52
139
|
break
|
|
53
140
|
}
|
|
54
141
|
// create linear gradient paint
|
|
@@ -56,33 +143,17 @@ export default class Paint {
|
|
|
56
143
|
this.type = 'linearGradient'
|
|
57
144
|
this.solidColor = null
|
|
58
145
|
this.radialGradient = null
|
|
59
|
-
|
|
60
|
-
options.linearGradient.getAttribute('href') ||
|
|
61
|
-
options.linearGradient.getAttribute('xlink:href')
|
|
62
|
-
if (hrefAttr) {
|
|
63
|
-
const xhref = document.getElementById(hrefAttr.replace(/^#/, ''))
|
|
64
|
-
this.linearGradient = xhref.cloneNode(true)
|
|
65
|
-
} else {
|
|
66
|
-
this.linearGradient = options.linearGradient.cloneNode(true)
|
|
67
|
-
}
|
|
146
|
+
this.linearGradient = Paint.#resolveGradient(options.linearGradient)
|
|
68
147
|
// create linear gradient paint
|
|
69
148
|
} else if (options.radialGradient) {
|
|
70
149
|
this.type = 'radialGradient'
|
|
71
150
|
this.solidColor = null
|
|
72
151
|
this.linearGradient = null
|
|
73
|
-
|
|
74
|
-
options.radialGradient.getAttribute('href') ||
|
|
75
|
-
options.radialGradient.getAttribute('xlink:href')
|
|
76
|
-
if (hrefAttr) {
|
|
77
|
-
const xhref = document.getElementById(hrefAttr.replace(/^#/, ''))
|
|
78
|
-
this.radialGradient = xhref.cloneNode(true)
|
|
79
|
-
} else {
|
|
80
|
-
this.radialGradient = options.radialGradient.cloneNode(true)
|
|
81
|
-
}
|
|
152
|
+
this.radialGradient = Paint.#resolveGradient(options.radialGradient)
|
|
82
153
|
// create solid color paint
|
|
83
154
|
} else if (options.solidColor) {
|
|
84
155
|
this.type = 'solidColor'
|
|
85
|
-
this.solidColor = options.solidColor
|
|
156
|
+
this.solidColor = Paint.#normalizeSolidColor(options.solidColor)
|
|
86
157
|
// create empty paint
|
|
87
158
|
} else {
|
|
88
159
|
this.type = 'none'
|
package/core/paste-elem.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
-
getStrokedBBoxDefaultVisible
|
|
2
|
+
getStrokedBBoxDefaultVisible,
|
|
3
|
+
getUrlFromAttr
|
|
3
4
|
} from './utilities.js'
|
|
4
5
|
import * as hstry from './history.js'
|
|
5
6
|
|
|
@@ -27,11 +28,15 @@ export const init = (canvas) => {
|
|
|
27
28
|
* @fires module:svgcanvas.SvgCanvas#event:ext_IDsUpdated
|
|
28
29
|
* @returns {void}
|
|
29
30
|
*/
|
|
30
|
-
export const pasteElementsMethod =
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
export const pasteElementsMethod = (type, x, y) => {
|
|
32
|
+
const rawClipboard = sessionStorage.getItem(svgCanvas.getClipboardID())
|
|
33
|
+
let clipb
|
|
34
|
+
try {
|
|
35
|
+
clipb = JSON.parse(rawClipboard)
|
|
36
|
+
} catch {
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
if (!Array.isArray(clipb) || !clipb.length) return
|
|
35
40
|
|
|
36
41
|
const pasted = []
|
|
37
42
|
const batchCmd = new BatchCommand('Paste elements')
|
|
@@ -50,7 +55,7 @@ export const pasteElementsMethod = function (type, x, y) {
|
|
|
50
55
|
* @param {module:svgcanvas.SVGAsJSON} elem
|
|
51
56
|
* @returns {void}
|
|
52
57
|
*/
|
|
53
|
-
|
|
58
|
+
const checkIDs = (elem) => {
|
|
54
59
|
if (elem.attr?.id) {
|
|
55
60
|
changedIDs[elem.attr.id] = svgCanvas.getNextId()
|
|
56
61
|
elem.attr.id = changedIDs[elem.attr.id]
|
|
@@ -59,6 +64,35 @@ export const pasteElementsMethod = function (type, x, y) {
|
|
|
59
64
|
}
|
|
60
65
|
clipb.forEach((elem) => checkIDs(elem))
|
|
61
66
|
|
|
67
|
+
// Update any internal references in the clipboard to match the new IDs.
|
|
68
|
+
/**
|
|
69
|
+
* @param {module:svgcanvas.SVGAsJSON} elem
|
|
70
|
+
* @returns {void}
|
|
71
|
+
*/
|
|
72
|
+
const remapReferences = (elem) => {
|
|
73
|
+
const attrs = elem?.attr
|
|
74
|
+
if (attrs) {
|
|
75
|
+
for (const [attrName, attrVal] of Object.entries(attrs)) {
|
|
76
|
+
if (typeof attrVal !== 'string' || !attrVal) continue
|
|
77
|
+
if ((attrName === 'href' || attrName === 'xlink:href') && attrVal.startsWith('#')) {
|
|
78
|
+
const refId = attrVal.slice(1)
|
|
79
|
+
if (refId in changedIDs) {
|
|
80
|
+
attrs[attrName] = `#${changedIDs[refId]}`
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const url = getUrlFromAttr(attrVal)
|
|
84
|
+
if (url) {
|
|
85
|
+
const refId = url.slice(1)
|
|
86
|
+
if (refId in changedIDs) {
|
|
87
|
+
attrs[attrName] = attrVal.replace(url, `#${changedIDs[refId]}`)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (elem.children) elem.children.forEach((child) => remapReferences(child))
|
|
93
|
+
}
|
|
94
|
+
clipb.forEach((elem) => remapReferences(elem))
|
|
95
|
+
|
|
62
96
|
// Give extensions like the connector extension a chance to reflect new IDs and remove invalid elements
|
|
63
97
|
/**
|
|
64
98
|
* Triggered when `pasteElements` is called from a paste action (context menu or key).
|
|
@@ -77,12 +111,14 @@ export const pasteElementsMethod = function (type, x, y) {
|
|
|
77
111
|
|
|
78
112
|
extChanges.remove.forEach(function (removeID) {
|
|
79
113
|
clipb = clipb.filter(function (clipBoardItem) {
|
|
80
|
-
return clipBoardItem
|
|
114
|
+
return clipBoardItem?.attr?.id !== removeID
|
|
81
115
|
})
|
|
82
116
|
})
|
|
83
117
|
})
|
|
84
118
|
|
|
85
119
|
// Move elements to lastClickPoint
|
|
120
|
+
let len = clipb.length
|
|
121
|
+
if (!len) return
|
|
86
122
|
while (len--) {
|
|
87
123
|
const elem = clipb[len]
|
|
88
124
|
if (!elem) { continue }
|
|
@@ -94,6 +130,7 @@ export const pasteElementsMethod = function (type, x, y) {
|
|
|
94
130
|
svgCanvas.restoreRefElements(copy)
|
|
95
131
|
}
|
|
96
132
|
|
|
133
|
+
if (!pasted.length) return
|
|
97
134
|
svgCanvas.selectOnly(pasted)
|
|
98
135
|
|
|
99
136
|
if (type !== 'in_place') {
|
|
@@ -108,18 +145,20 @@ export const pasteElementsMethod = function (type, x, y) {
|
|
|
108
145
|
}
|
|
109
146
|
|
|
110
147
|
const bbox = getStrokedBBoxDefaultVisible(pasted)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
148
|
+
if (bbox && Number.isFinite(ctrX) && Number.isFinite(ctrY)) {
|
|
149
|
+
const cx = ctrX - (bbox.x + bbox.width / 2)
|
|
150
|
+
const cy = ctrY - (bbox.y + bbox.height / 2)
|
|
151
|
+
const dx = []
|
|
152
|
+
const dy = []
|
|
153
|
+
|
|
154
|
+
pasted.forEach(function (_item) {
|
|
155
|
+
dx.push(cx)
|
|
156
|
+
dy.push(cy)
|
|
157
|
+
})
|
|
120
158
|
|
|
121
|
-
|
|
122
|
-
|
|
159
|
+
const cmd = svgCanvas.moveSelectedElements(dx, dy, false)
|
|
160
|
+
if (cmd) batchCmd.addSubCommand(cmd)
|
|
161
|
+
}
|
|
123
162
|
}
|
|
124
163
|
|
|
125
164
|
svgCanvas.addCommandToHistory(batchCmd)
|