@svgedit/svgcanvas 7.2.6 → 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.
@@ -16,44 +16,92 @@ export const init = (canvas) => {
16
16
  svgCanvas = canvas
17
17
  }
18
18
 
19
+ /**
20
+ * @param {Element} filterElem
21
+ * @returns {?Element}
22
+ */
23
+ const getFeGaussianBlurElem = (filterElem) => {
24
+ if (!filterElem || filterElem.nodeType !== 1) return null
25
+ return filterElem.querySelector('feGaussianBlur') || filterElem.firstElementChild
26
+ }
27
+
19
28
  /**
20
29
  * Sets the `stdDeviation` blur value on the selected element without being undoable.
21
30
  * @function module:svgcanvas.SvgCanvas#setBlurNoUndo
22
31
  * @param {Float} val - The new `stdDeviation` value
23
32
  * @returns {void}
24
33
  */
25
- export const setBlurNoUndo = function (val) {
34
+ export const setBlurNoUndo = (val) => {
26
35
  const selectedElements = svgCanvas.getSelectedElements()
27
- if (!svgCanvas.getFilter()) {
28
- svgCanvas.setBlur(val)
29
- return
36
+ const elem = selectedElements[0]
37
+ if (!elem) return
38
+
39
+ let filter = svgCanvas.getFilter()
40
+ if (!filter) {
41
+ filter = svgCanvas.getElement(`${elem.id}_blur`)
30
42
  }
43
+
31
44
  if (val === 0) {
32
45
  // Don't change the StdDev, as that will hide the element.
33
46
  // Instead, just remove the value for "filter"
34
47
  svgCanvas.changeSelectedAttributeNoUndo('filter', '')
35
48
  svgCanvas.setFilterHidden(true)
36
49
  } else {
37
- const elem = selectedElements[0]
38
- if (svgCanvas.getFilterHidden()) {
39
- svgCanvas.changeSelectedAttributeNoUndo('filter', 'url(#' + elem.id + '_blur)')
50
+ if (!filter) {
51
+ // Create the filter if missing, but don't add history.
52
+ const blurElem = svgCanvas.addSVGElementsFromJson({
53
+ element: 'feGaussianBlur',
54
+ attr: {
55
+ in: 'SourceGraphic',
56
+ stdDeviation: val
57
+ }
58
+ })
59
+ filter = svgCanvas.addSVGElementsFromJson({
60
+ element: 'filter',
61
+ attr: {
62
+ id: `${elem.id}_blur`
63
+ }
64
+ })
65
+ filter.append(blurElem)
66
+ svgCanvas.findDefs().append(filter)
40
67
  }
41
- const filter = svgCanvas.getFilter()
42
- svgCanvas.changeSelectedAttributeNoUndo('stdDeviation', val, [filter.firstChild])
68
+
69
+ if (svgCanvas.getFilterHidden() || !elem.getAttribute('filter')) {
70
+ svgCanvas.changeSelectedAttributeNoUndo('filter', `url(#${filter.id})`)
71
+ svgCanvas.setFilterHidden(false)
72
+ }
73
+
74
+ const blurElem = getFeGaussianBlurElem(filter)
75
+ if (!blurElem) {
76
+ return
77
+ }
78
+ svgCanvas.changeSelectedAttributeNoUndo('stdDeviation', val, [blurElem])
43
79
  svgCanvas.setBlurOffsets(filter, val)
44
80
  }
45
81
  }
46
82
 
47
83
  /**
48
- *
84
+ * Finishes the blur change command and adds it to history if not empty.
49
85
  * @returns {void}
50
86
  */
51
- function finishChange () {
87
+ const finishChange = () => {
88
+ const curCommand = svgCanvas.getCurCommand()
89
+ if (!curCommand) {
90
+ svgCanvas.setCurCommand(null)
91
+ svgCanvas.setFilter(null)
92
+ svgCanvas.setFilterHidden(false)
93
+ return
94
+ }
52
95
  const bCmd = svgCanvas.undoMgr.finishUndoableChange()
53
- svgCanvas.getCurCommand().addSubCommand(bCmd)
54
- svgCanvas.addCommandToHistory(svgCanvas.getCurCommand())
96
+ if (!bCmd.isEmpty()) {
97
+ curCommand.addSubCommand(bCmd)
98
+ }
99
+ if (!curCommand.isEmpty()) {
100
+ svgCanvas.addCommandToHistory(curCommand)
101
+ }
55
102
  svgCanvas.setCurCommand(null)
56
103
  svgCanvas.setFilter(null)
104
+ svgCanvas.setFilterHidden(false)
57
105
  }
58
106
 
59
107
  /**
@@ -64,7 +112,13 @@ function finishChange () {
64
112
  * @param {Float} stdDev - The standard deviation value on which to base the offset size
65
113
  * @returns {void}
66
114
  */
67
- export const setBlurOffsets = function (filterElem, stdDev) {
115
+ export const setBlurOffsets = (filterElem, stdDev) => {
116
+ if (!filterElem || filterElem.nodeType !== 1) {
117
+ return
118
+ }
119
+
120
+ stdDev = Number(stdDev) || 0
121
+
68
122
  if (stdDev > 3) {
69
123
  // TODO: Create algorithm here where size is based on expected blur
70
124
  svgCanvas.assignAttributes(filterElem, {
@@ -88,7 +142,7 @@ export const setBlurOffsets = function (filterElem, stdDev) {
88
142
  * @param {boolean} complete - Whether or not the action should be completed (to add to the undo manager)
89
143
  * @returns {void}
90
144
  */
91
- export const setBlur = function (val, complete) {
145
+ export const setBlur = (val, complete) => {
92
146
  const {
93
147
  InsertElementCommand, ChangeElementCommand, BatchCommand
94
148
  } = svgCanvas.history
@@ -101,20 +155,33 @@ export const setBlur = function (val, complete) {
101
155
 
102
156
  // Looks for associated blur, creates one if not found
103
157
  const elem = selectedElements[0]
158
+ if (!elem) {
159
+ return
160
+ }
104
161
  const elemId = elem.id
105
- svgCanvas.setFilter(svgCanvas.getElement(elemId + '_blur'))
162
+ let filter = svgCanvas.getElement(`${elemId}_blur`)
163
+ svgCanvas.setFilter(filter)
106
164
 
107
- val -= 0
165
+ val = Number(val) || 0
108
166
 
109
- const batchCmd = new BatchCommand()
167
+ const batchCmd = new BatchCommand('Change blur')
110
168
 
111
- // Blur found!
112
- if (svgCanvas.getFilter()) {
113
- if (val === 0) {
114
- svgCanvas.setFilter(null)
169
+ if (val === 0) {
170
+ const oldFilter = elem.getAttribute('filter')
171
+ if (!oldFilter) {
172
+ return
115
173
  }
116
- } else {
117
- // Not found, so create
174
+ const changes = { filter: oldFilter }
175
+ elem.removeAttribute('filter')
176
+ batchCmd.addSubCommand(new ChangeElementCommand(elem, changes))
177
+ svgCanvas.addCommandToHistory(batchCmd)
178
+ svgCanvas.setFilter(null)
179
+ svgCanvas.setFilterHidden(true)
180
+ return
181
+ }
182
+
183
+ // Ensure blur filter exists.
184
+ if (!filter) {
118
185
  const newblur = svgCanvas.addSVGElementsFromJson({
119
186
  element: 'feGaussianBlur',
120
187
  attr: {
@@ -123,32 +190,29 @@ export const setBlur = function (val, complete) {
123
190
  }
124
191
  })
125
192
 
126
- svgCanvas.setFilter(svgCanvas.addSVGElementsFromJson({
193
+ filter = svgCanvas.addSVGElementsFromJson({
127
194
  element: 'filter',
128
195
  attr: {
129
- id: elemId + '_blur'
196
+ id: `${elemId}_blur`
130
197
  }
131
- }))
132
- svgCanvas.getFilter().append(newblur)
133
- svgCanvas.findDefs().append(svgCanvas.getFilter())
134
-
135
- batchCmd.addSubCommand(new InsertElementCommand(svgCanvas.getFilter()))
198
+ })
199
+ filter.append(newblur)
200
+ const defs = svgCanvas.findDefs()
201
+ if (defs && defs.ownerDocument === filter.ownerDocument) {
202
+ defs.append(filter)
203
+ }
204
+ svgCanvas.setFilter(filter)
205
+ batchCmd.addSubCommand(new InsertElementCommand(filter))
136
206
  }
137
207
 
138
208
  const changes = { filter: elem.getAttribute('filter') }
139
-
140
- if (val === 0) {
141
- elem.removeAttribute('filter')
142
- batchCmd.addSubCommand(new ChangeElementCommand(elem, changes))
143
- return
144
- }
145
-
146
- svgCanvas.changeSelectedAttribute('filter', 'url(#' + elemId + '_blur)')
209
+ svgCanvas.changeSelectedAttributeNoUndo('filter', `url(#${filter.id})`)
147
210
  batchCmd.addSubCommand(new ChangeElementCommand(elem, changes))
148
- svgCanvas.setBlurOffsets(svgCanvas.getFilter(), val)
149
- const filter = svgCanvas.getFilter()
211
+ svgCanvas.setBlurOffsets(filter, val)
150
212
  svgCanvas.setCurCommand(batchCmd)
151
- svgCanvas.undoMgr.beginUndoableChange('stdDeviation', [filter ? filter.firstChild : null])
213
+
214
+ const blurElem = getFeGaussianBlurElem(filter)
215
+ svgCanvas.undoMgr.beginUndoableChange('stdDeviation', [blurElem])
152
216
  if (complete) {
153
217
  svgCanvas.setBlurNoUndo(val)
154
218
  finishChange()
package/core/clear.js CHANGED
@@ -24,7 +24,15 @@ export const clearSvgContentElementInit = () => {
24
24
  // empty
25
25
  while (el.firstChild) { el.removeChild(el.firstChild) }
26
26
 
27
- // TODO: Clear out all other attributes first?
27
+ // Reset any stale attributes from the previous document.
28
+ for (const attr of Array.from(el.attributes)) {
29
+ if (attr.namespaceURI) {
30
+ el.removeAttributeNS(attr.namespaceURI, attr.localName)
31
+ } else {
32
+ el.removeAttribute(attr.name)
33
+ }
34
+ }
35
+
28
36
  const pel = svgCanvas.getSvgRoot()
29
37
  el.setAttribute('id', 'svgcontent')
30
38
  el.setAttribute('width', dimensions[0])
@@ -35,9 +43,11 @@ export const clearSvgContentElementInit = () => {
35
43
  el.setAttribute('xmlns', NS.SVG)
36
44
  el.setAttribute('xmlns:se', NS.SE)
37
45
  el.setAttribute('xmlns:xlink', NS.XLINK)
38
- pel.appendChild(el)
46
+ if (el.parentNode !== pel) {
47
+ pel.appendChild(el)
48
+ }
39
49
 
40
50
  // TODO: make this string optional and set by the client
41
51
  const comment = svgCanvas.getDOMDocument().createComment(' Created with SVG-edit - https://github.com/SVG-Edit/svgedit')
42
- svgCanvas.getSvgContent().append(comment)
52
+ el.append(comment)
43
53
  }
package/core/coords.js CHANGED
@@ -4,6 +4,8 @@
4
4
  * @license MIT
5
5
  */
6
6
 
7
+ import { warn } from '../common/logger.js'
8
+
7
9
  import {
8
10
  snapToGrid,
9
11
  assignAttributes,
@@ -22,6 +24,30 @@ import { convertToNum } from './units.js'
22
24
 
23
25
  let svgCanvas = null
24
26
 
27
+ const flipBoxCoordinate = (value) => {
28
+ if (value === null || value === undefined) return null
29
+ const str = String(value).trim()
30
+ if (!str) return null
31
+
32
+ if (str.endsWith('%')) {
33
+ const num = Number.parseFloat(str.slice(0, -1))
34
+ return Number.isNaN(num) ? str : `${100 - num}%`
35
+ }
36
+
37
+ const num = Number.parseFloat(str)
38
+ return Number.isNaN(num) ? str : String(1 - num)
39
+ }
40
+
41
+ const flipAttributeInBoxUnits = (elem, attr) => {
42
+ const value = elem.getAttribute(attr)
43
+ if (value === null || value === undefined) return
44
+
45
+ const flipped = flipBoxCoordinate(value)
46
+ if (flipped !== null && flipped !== undefined) {
47
+ elem.setAttribute(attr, flipped)
48
+ }
49
+ }
50
+
25
51
  /**
26
52
  * Initialize the coords module with the SVG canvas.
27
53
  * @function module:coords.init
@@ -32,28 +58,9 @@ export const init = canvas => {
32
58
  svgCanvas = canvas
33
59
  }
34
60
 
35
- // This is how we map path segment types to their corresponding commands
61
+ // Map path segment types to their corresponding commands
36
62
  const pathMap = [
37
- 0,
38
- 'z',
39
- 'M',
40
- 'm',
41
- 'L',
42
- 'l',
43
- 'C',
44
- 'c',
45
- 'Q',
46
- 'q',
47
- 'A',
48
- 'a',
49
- 'H',
50
- 'h',
51
- 'V',
52
- 'v',
53
- 'S',
54
- 's',
55
- 'T',
56
- 't'
63
+ 0, 'z', 'M', 'm', 'L', 'l', 'C', 'c', 'Q', 'q', 'A', 'a', 'H', 'h', 'V', 'v', 'S', 's', 'T', 't'
57
64
  ]
58
65
 
59
66
  /**
@@ -66,19 +73,21 @@ const pathMap = [
66
73
  */
67
74
  export const remapElement = (selected, changes, m) => {
68
75
  const remap = (x, y) => transformPoint(x, y, m)
69
- const scalew = w => m.a * w
70
- const scaleh = h => m.d * h
76
+ const scalew = (w) => m.a * w
77
+ const scaleh = (h) => m.d * h
71
78
  const doSnapping =
72
- svgCanvas.getGridSnapping() &&
73
- selected.parentNode.parentNode.localName === 'svg'
79
+ svgCanvas.getGridSnapping?.() &&
80
+ selected?.parentNode?.parentNode?.localName === 'svg'
81
+
74
82
  const finishUp = () => {
75
83
  if (doSnapping) {
76
- Object.entries(changes).forEach(([attr, value]) => {
84
+ for (const [attr, value] of Object.entries(changes)) {
77
85
  changes[attr] = snapToGrid(value)
78
- })
86
+ }
79
87
  }
80
88
  assignAttributes(selected, changes, 1000, true)
81
89
  }
90
+
82
91
  const box = getBBox(selected)
83
92
 
84
93
  // Handle gradients and patterns
@@ -86,25 +95,47 @@ export const remapElement = (selected, changes, m) => {
86
95
  const attrVal = selected.getAttribute(type)
87
96
  if (attrVal?.startsWith('url(') && (m.a < 0 || m.d < 0)) {
88
97
  const grad = getRefElem(attrVal)
98
+ if (!grad) return
99
+
100
+ const tagName = (grad.tagName || '').toLowerCase()
101
+ if (!['lineargradient', 'radialgradient'].includes(tagName)) return
102
+
103
+ // userSpaceOnUse gradients do not need object-bounding-box correction.
104
+ if (grad.getAttribute('gradientUnits') === 'userSpaceOnUse') return
105
+
89
106
  const newgrad = grad.cloneNode(true)
90
107
  if (m.a < 0) {
91
108
  // Flip x
92
- const x1 = newgrad.getAttribute('x1')
93
- const x2 = newgrad.getAttribute('x2')
94
- newgrad.setAttribute('x1', -(x1 - 1))
95
- newgrad.setAttribute('x2', -(x2 - 1))
109
+ if (tagName === 'lineargradient') {
110
+ flipAttributeInBoxUnits(newgrad, 'x1')
111
+ flipAttributeInBoxUnits(newgrad, 'x2')
112
+ } else {
113
+ flipAttributeInBoxUnits(newgrad, 'cx')
114
+ flipAttributeInBoxUnits(newgrad, 'fx')
115
+ }
96
116
  }
97
117
 
98
118
  if (m.d < 0) {
99
119
  // Flip y
100
- const y1 = newgrad.getAttribute('y1')
101
- const y2 = newgrad.getAttribute('y2')
102
- newgrad.setAttribute('y1', -(y1 - 1))
103
- newgrad.setAttribute('y2', -(y2 - 1))
120
+ if (tagName === 'lineargradient') {
121
+ flipAttributeInBoxUnits(newgrad, 'y1')
122
+ flipAttributeInBoxUnits(newgrad, 'y2')
123
+ } else {
124
+ flipAttributeInBoxUnits(newgrad, 'cy')
125
+ flipAttributeInBoxUnits(newgrad, 'fy')
126
+ }
104
127
  }
105
- newgrad.id = svgCanvas.getCurrentDrawing().getNextId()
128
+
129
+ const drawing = svgCanvas.getCurrentDrawing?.() || svgCanvas.getDrawing?.()
130
+ const generatedId = drawing?.getNextId?.() ??
131
+ (grad.id ? `${grad.id}-mirrored` : `mirrored-grad-${Date.now()}`)
132
+ if (!generatedId) {
133
+ warn('Unable to mirror gradient: no drawing context available', null, 'coords')
134
+ return
135
+ }
136
+ newgrad.id = generatedId
106
137
  findDefs().append(newgrad)
107
- selected.setAttribute(type, 'url(#' + newgrad.id + ')')
138
+ selected.setAttribute(type, `url(#${newgrad.id})`)
108
139
  }
109
140
  })
110
141
 
@@ -265,25 +296,79 @@ export const remapElement = (selected, changes, m) => {
265
296
  break
266
297
  }
267
298
  case 'path': {
299
+ const supportsPathData =
300
+ typeof selected.getPathData === 'function' &&
301
+ typeof selected.setPathData === 'function'
302
+
268
303
  // Handle path segments
269
- const segList = selected.pathSegList
270
- const len = segList.numberOfItems
304
+ const segList = supportsPathData ? null : selected.pathSegList
305
+ const len = supportsPathData ? selected.getPathData().length : segList.numberOfItems
306
+ const det = m.a * m.d - m.b * m.c
307
+ const shouldToggleArcSweep = det < 0
271
308
  changes.d = []
272
- for (let i = 0; i < len; ++i) {
273
- const seg = segList.getItem(i)
274
- changes.d[i] = {
275
- type: seg.pathSegType,
276
- x: seg.x,
277
- y: seg.y,
278
- x1: seg.x1,
279
- y1: seg.y1,
280
- x2: seg.x2,
281
- y2: seg.y2,
282
- r1: seg.r1,
283
- r2: seg.r2,
284
- angle: seg.angle,
285
- largeArcFlag: seg.largeArcFlag,
286
- sweepFlag: seg.sweepFlag
309
+ if (supportsPathData) {
310
+ const pathDataSegments = selected.getPathData()
311
+ for (let i = 0; i < len; ++i) {
312
+ const seg = pathDataSegments[i]
313
+ const t = seg.type
314
+ const type = pathMap.indexOf(t)
315
+ if (type === -1) continue
316
+ const values = seg.values || []
317
+ const entry = { type }
318
+ switch (t.toUpperCase()) {
319
+ case 'M':
320
+ case 'L':
321
+ case 'T':
322
+ [entry.x, entry.y] = values
323
+ break
324
+ case 'H':
325
+ [entry.x] = values
326
+ break
327
+ case 'V':
328
+ [entry.y] = values
329
+ break
330
+ case 'C':
331
+ [entry.x1, entry.y1, entry.x2, entry.y2, entry.x, entry.y] = values
332
+ break
333
+ case 'S':
334
+ [entry.x2, entry.y2, entry.x, entry.y] = values
335
+ break
336
+ case 'Q':
337
+ [entry.x1, entry.y1, entry.x, entry.y] = values
338
+ break
339
+ case 'A':
340
+ [
341
+ entry.r1,
342
+ entry.r2,
343
+ entry.angle,
344
+ entry.largeArcFlag,
345
+ entry.sweepFlag,
346
+ entry.x,
347
+ entry.y
348
+ ] = values
349
+ break
350
+ default:
351
+ break
352
+ }
353
+ changes.d[i] = entry
354
+ }
355
+ } else {
356
+ for (let i = 0; i < len; ++i) {
357
+ const seg = segList.getItem(i)
358
+ changes.d[i] = {
359
+ type: seg.pathSegType,
360
+ x: seg.x,
361
+ y: seg.y,
362
+ x1: seg.x1,
363
+ y1: seg.y1,
364
+ x2: seg.x2,
365
+ y2: seg.y2,
366
+ r1: seg.r1,
367
+ r2: seg.r2,
368
+ angle: seg.angle,
369
+ largeArcFlag: seg.largeArcFlag,
370
+ sweepFlag: seg.sweepFlag
371
+ }
287
372
  }
288
373
  }
289
374
 
@@ -302,41 +387,65 @@ export const remapElement = (selected, changes, m) => {
302
387
  const thisx = seg.x !== undefined ? seg.x : currentpt.x // For V commands
303
388
  const thisy = seg.y !== undefined ? seg.y : currentpt.y // For H commands
304
389
  const pt = remap(thisx, thisy)
305
- const pt1 = remap(seg.x1, seg.y1)
306
- const pt2 = remap(seg.x2, seg.y2)
307
390
  seg.x = pt.x
308
391
  seg.y = pt.y
309
- seg.x1 = pt1.x
310
- seg.y1 = pt1.y
311
- seg.x2 = pt2.x
312
- seg.y2 = pt2.y
313
- seg.r1 = scalew(seg.r1)
314
- seg.r2 = scaleh(seg.r2)
392
+ if (seg.x1 !== undefined && seg.y1 !== undefined) {
393
+ const pt1 = remap(seg.x1, seg.y1)
394
+ seg.x1 = pt1.x
395
+ seg.y1 = pt1.y
396
+ }
397
+ if (seg.x2 !== undefined && seg.y2 !== undefined) {
398
+ const pt2 = remap(seg.x2, seg.y2)
399
+ seg.x2 = pt2.x
400
+ seg.y2 = pt2.y
401
+ }
402
+ if (type === 10) {
403
+ seg.r1 = Math.abs(scalew(seg.r1))
404
+ seg.r2 = Math.abs(scaleh(seg.r2))
405
+ if (shouldToggleArcSweep) {
406
+ seg.sweepFlag = Number(seg.sweepFlag) ? 0 : 1
407
+ if (typeof seg.angle === 'number') {
408
+ seg.angle = -seg.angle
409
+ }
410
+ }
411
+ }
315
412
  } else {
316
413
  // For relative segments, scale x, y, x1, y1, x2, y2
317
- seg.x = scalew(seg.x)
318
- seg.y = scaleh(seg.y)
319
- seg.x1 = scalew(seg.x1)
320
- seg.y1 = scaleh(seg.y1)
321
- seg.x2 = scalew(seg.x2)
322
- seg.y2 = scaleh(seg.y2)
323
- seg.r1 = scalew(seg.r1)
324
- seg.r2 = scaleh(seg.r2)
414
+ if (seg.x !== undefined) seg.x = scalew(seg.x)
415
+ if (seg.y !== undefined) seg.y = scaleh(seg.y)
416
+ if (seg.x1 !== undefined) seg.x1 = scalew(seg.x1)
417
+ if (seg.y1 !== undefined) seg.y1 = scaleh(seg.y1)
418
+ if (seg.x2 !== undefined) seg.x2 = scalew(seg.x2)
419
+ if (seg.y2 !== undefined) seg.y2 = scaleh(seg.y2)
420
+ if (type === 11) {
421
+ seg.r1 = Math.abs(scalew(seg.r1))
422
+ seg.r2 = Math.abs(scaleh(seg.r2))
423
+ if (shouldToggleArcSweep) {
424
+ seg.sweepFlag = Number(seg.sweepFlag) ? 0 : 1
425
+ if (typeof seg.angle === 'number') {
426
+ seg.angle = -seg.angle
427
+ }
428
+ }
429
+ }
325
430
  }
326
431
  }
327
432
 
328
433
  let dstr = ''
434
+ const newPathData = []
329
435
  changes.d.forEach(seg => {
330
436
  const { type } = seg
331
- dstr += pathMap[type]
437
+ const letter = pathMap[type]
438
+ dstr += letter
332
439
  switch (type) {
333
440
  case 13: // relative horizontal line (h)
334
441
  case 12: // absolute horizontal line (H)
335
- dstr += seg.x + ' '
442
+ dstr += `${seg.x} `
443
+ newPathData.push({ type: letter, values: [seg.x] })
336
444
  break
337
445
  case 15: // relative vertical line (v)
338
446
  case 14: // absolute vertical line (V)
339
- dstr += seg.y + ' '
447
+ dstr += `${seg.y} `
448
+ newPathData.push({ type: letter, values: [seg.y] })
340
449
  break
341
450
  case 3: // relative move (m)
342
451
  case 5: // relative line (l)
@@ -344,27 +453,21 @@ export const remapElement = (selected, changes, m) => {
344
453
  case 2: // absolute move (M)
345
454
  case 4: // absolute line (L)
346
455
  case 18: // absolute smooth quad (T)
347
- dstr += seg.x + ',' + seg.y + ' '
456
+ dstr += `${seg.x},${seg.y} `
457
+ newPathData.push({ type: letter, values: [seg.x, seg.y] })
348
458
  break
349
459
  case 7: // relative cubic (c)
350
460
  case 6: // absolute cubic (C)
351
- dstr +=
352
- seg.x1 +
353
- ',' +
354
- seg.y1 +
355
- ' ' +
356
- seg.x2 +
357
- ',' +
358
- seg.y2 +
359
- ' ' +
360
- seg.x +
361
- ',' +
362
- seg.y +
363
- ' '
461
+ dstr += `${seg.x1},${seg.y1} ${seg.x2},${seg.y2} ${seg.x},${seg.y} `
462
+ newPathData.push({
463
+ type: letter,
464
+ values: [seg.x1, seg.y1, seg.x2, seg.y2, seg.x, seg.y]
465
+ })
364
466
  break
365
467
  case 9: // relative quad (q)
366
468
  case 8: // absolute quad (Q)
367
- dstr += seg.x1 + ',' + seg.y1 + ' ' + seg.x + ',' + seg.y + ' '
469
+ dstr += `${seg.x1},${seg.y1} ${seg.x},${seg.y} `
470
+ newPathData.push({ type: letter, values: [seg.x1, seg.y1, seg.x, seg.y] })
368
471
  break
369
472
  case 11: // relative elliptical arc (a)
370
473
  case 10: // absolute elliptical arc (A)
@@ -383,17 +486,38 @@ export const remapElement = (selected, changes, m) => {
383
486
  ',' +
384
487
  seg.y +
385
488
  ' '
489
+ newPathData.push({
490
+ type: letter,
491
+ values: [
492
+ seg.r1,
493
+ seg.r2,
494
+ seg.angle,
495
+ Number(seg.largeArcFlag),
496
+ Number(seg.sweepFlag),
497
+ seg.x,
498
+ seg.y
499
+ ]
500
+ })
386
501
  break
387
502
  case 17: // relative smooth cubic (s)
388
503
  case 16: // absolute smooth cubic (S)
389
- dstr += seg.x2 + ',' + seg.y2 + ' ' + seg.x + ',' + seg.y + ' '
504
+ dstr += `${seg.x2},${seg.y2} ${seg.x},${seg.y} `
505
+ newPathData.push({ type: letter, values: [seg.x2, seg.y2, seg.x, seg.y] })
390
506
  break
391
507
  default:
392
508
  break
393
509
  }
394
510
  })
395
511
 
396
- selected.setAttribute('d', dstr.trim())
512
+ const d = dstr.trim()
513
+ selected.setAttribute('d', d)
514
+ if (supportsPathData) {
515
+ try {
516
+ selected.setPathData(newPathData)
517
+ } catch (e) {
518
+ // Fallback to 'd' attribute if setPathData is unavailable or throws.
519
+ }
520
+ }
397
521
  break
398
522
  }
399
523
  default: