@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.
@@ -122,25 +122,36 @@ const setGroupTitleMethod = (val) => {
122
122
  const selectedElements = svgCanvas.getSelectedElements()
123
123
  const dataStorage = svgCanvas.getDataStorage()
124
124
  let elem = selectedElements[0]
125
+ if (!elem) { return }
125
126
  if (dataStorage.has(elem, 'gsvg')) {
126
127
  elem = dataStorage.get(elem, 'gsvg')
128
+ } else if (dataStorage.has(elem, 'symbol')) {
129
+ elem = dataStorage.get(elem, 'symbol')
127
130
  }
128
-
129
- const ts = elem.querySelectorAll('title')
131
+ if (!elem) { return }
130
132
 
131
133
  const batchCmd = new BatchCommand('Set Label')
132
134
 
133
- let title
135
+ let title = null
136
+ for (const child of elem.childNodes) {
137
+ if (child.nodeName === 'title') {
138
+ title = child
139
+ break
140
+ }
141
+ }
142
+
134
143
  if (val.length === 0) {
144
+ if (!title) { return }
135
145
  // Remove title element
136
- const tsNextSibling = ts.nextSibling
137
- batchCmd.addSubCommand(new RemoveElementCommand(ts[0], tsNextSibling, elem))
138
- ts.remove()
139
- } else if (ts.length) {
146
+ const { nextSibling } = title
147
+ title.remove()
148
+ batchCmd.addSubCommand(new RemoveElementCommand(title, nextSibling, elem))
149
+ } else if (title) {
140
150
  // Change title contents
141
- title = ts[0]
142
- batchCmd.addSubCommand(new ChangeElementCommand(title, { '#text': title.textContent }))
151
+ const oldText = title.textContent
152
+ if (oldText === val) { return }
143
153
  title.textContent = val
154
+ batchCmd.addSubCommand(new ChangeElementCommand(title, { '#text': oldText }))
144
155
  } else {
145
156
  // Add title element
146
157
  title = svgCanvas.getDOMDocument().createElementNS(NS.SVG, 'title')
@@ -149,7 +160,9 @@ const setGroupTitleMethod = (val) => {
149
160
  batchCmd.addSubCommand(new InsertElementCommand(title))
150
161
  }
151
162
 
152
- svgCanvas.addCommandToHistory(batchCmd)
163
+ if (!batchCmd.isEmpty()) {
164
+ svgCanvas.addCommandToHistory(batchCmd)
165
+ }
153
166
  }
154
167
 
155
168
  /**
@@ -160,33 +173,44 @@ const setGroupTitleMethod = (val) => {
160
173
  * @returns {void}
161
174
  */
162
175
  const setDocumentTitleMethod = (newTitle) => {
163
- const { ChangeElementCommand, BatchCommand } = svgCanvas.history
164
- const childs = svgCanvas.getSvgContent().childNodes
165
- let docTitle = false; let oldTitle = ''
176
+ const {
177
+ InsertElementCommand, RemoveElementCommand,
178
+ ChangeElementCommand, BatchCommand
179
+ } = svgCanvas.history
180
+ const svgContent = svgCanvas.getSvgContent()
166
181
 
167
182
  const batchCmd = new BatchCommand('Change Image Title')
168
183
 
169
- for (const child of childs) {
184
+ /** @type {Element|null} */
185
+ let docTitle = null
186
+ for (const child of svgContent.childNodes) {
170
187
  if (child.nodeName === 'title') {
171
188
  docTitle = child
172
- oldTitle = docTitle.textContent
173
189
  break
174
190
  }
175
191
  }
192
+
176
193
  if (!docTitle) {
194
+ if (!newTitle.length) { return }
177
195
  docTitle = svgCanvas.getDOMDocument().createElementNS(NS.SVG, 'title')
178
- svgCanvas.getSvgContent().insertBefore(docTitle, svgCanvas.getSvgContent().firstChild)
179
- // svgContent.firstChild.before(docTitle); // Ok to replace above with this?
180
- }
181
-
182
- if (newTitle.length) {
183
196
  docTitle.textContent = newTitle
197
+ svgContent.insertBefore(docTitle, svgContent.firstChild)
198
+ batchCmd.addSubCommand(new InsertElementCommand(docTitle))
199
+ } else if (newTitle.length) {
200
+ const oldTitle = docTitle.textContent
201
+ if (oldTitle === newTitle) { return }
202
+ docTitle.textContent = newTitle
203
+ batchCmd.addSubCommand(new ChangeElementCommand(docTitle, { '#text': oldTitle }))
184
204
  } else {
185
205
  // No title given, so element is not necessary
206
+ const { nextSibling } = docTitle
186
207
  docTitle.remove()
208
+ batchCmd.addSubCommand(new RemoveElementCommand(docTitle, nextSibling, svgContent))
209
+ }
210
+
211
+ if (!batchCmd.isEmpty()) {
212
+ svgCanvas.addCommandToHistory(batchCmd)
187
213
  }
188
- batchCmd.addSubCommand(new ChangeElementCommand(docTitle, { '#text': oldTitle }))
189
- svgCanvas.addCommandToHistory(batchCmd)
190
214
  }
191
215
 
192
216
  /**
@@ -201,7 +225,6 @@ const setDocumentTitleMethod = (newTitle) => {
201
225
  */
202
226
  const setResolutionMethod = (x, y) => {
203
227
  const { ChangeElementCommand, BatchCommand } = svgCanvas.history
204
- const zoom = svgCanvas.getZoom()
205
228
  const res = svgCanvas.getResolution()
206
229
  const { w, h } = res
207
230
  let batchCmd
@@ -220,8 +243,10 @@ const setResolutionMethod = (x, y) => {
220
243
  dy.push(bbox.y * -1)
221
244
  })
222
245
 
223
- const cmd = svgCanvas.moveSelectedElements(dx, dy, true)
224
- batchCmd.addSubCommand(cmd)
246
+ const cmd = svgCanvas.moveSelectedElements(dx, dy, false)
247
+ if (cmd) {
248
+ batchCmd.addSubCommand(cmd)
249
+ }
225
250
  svgCanvas.clearSelection()
226
251
 
227
252
  x = Math.round(bbox.width)
@@ -230,26 +255,25 @@ const setResolutionMethod = (x, y) => {
230
255
  return false
231
256
  }
232
257
  }
233
- if (x !== w || y !== h) {
258
+ const newW = convertToNum('width', x)
259
+ const newH = convertToNum('height', y)
260
+ if (newW !== w || newH !== h) {
234
261
  if (!batchCmd) {
235
262
  batchCmd = new BatchCommand('Change Image Dimensions')
236
263
  }
264
+ const svgContent = svgCanvas.getSvgContent()
265
+ const oldViewBox = svgContent.getAttribute('viewBox')
237
266
 
238
- x = convertToNum('width', x)
239
- y = convertToNum('height', y)
240
-
241
- svgCanvas.getSvgContent().setAttribute('width', x)
242
- svgCanvas.getSvgContent().setAttribute('height', y)
267
+ svgContent.setAttribute('width', newW)
268
+ svgContent.setAttribute('height', newH)
243
269
 
244
- svgCanvas.contentW = x
245
- svgCanvas.contentH = y
246
- batchCmd.addSubCommand(new ChangeElementCommand(svgCanvas.getSvgContent(), { width: w, height: h }))
247
-
248
- svgCanvas.getSvgContent().setAttribute('viewBox', [0, 0, x / zoom, y / zoom].join(' '))
249
- batchCmd.addSubCommand(new ChangeElementCommand(svgCanvas.getSvgContent(), { viewBox: ['0 0', w, h].join(' ') }))
270
+ svgCanvas.contentW = newW
271
+ svgCanvas.contentH = newH
272
+ svgContent.setAttribute('viewBox', [0, 0, newW, newH].join(' '))
273
+ batchCmd.addSubCommand(new ChangeElementCommand(svgContent, { width: w, height: h, viewBox: oldViewBox }))
250
274
 
251
275
  svgCanvas.addCommandToHistory(batchCmd)
252
- svgCanvas.call('changed', [svgCanvas.getSvgContent()])
276
+ svgCanvas.call('changed', [svgContent])
253
277
  }
254
278
  return true
255
279
  }
@@ -286,20 +310,36 @@ const setBBoxZoomMethod = (val, editorW, editorH) => {
286
310
  let spacer = 0.85
287
311
  let bb
288
312
  const calcZoom = (bb) => {
289
- if (!bb) { return false }
313
+ if (!bb) { return undefined }
314
+ if (!Number.isFinite(editorW) || !Number.isFinite(editorH) || editorW <= 0 || editorH <= 0) {
315
+ return undefined
316
+ }
317
+ if (!Number.isFinite(bb.width) || !Number.isFinite(bb.height) || bb.width <= 0 || bb.height <= 0) {
318
+ return undefined
319
+ }
290
320
  const wZoom = Math.round((editorW / bb.width) * 100 * spacer) / 100
291
321
  const hZoom = Math.round((editorH / bb.height) * 100 * spacer) / 100
292
322
  const zoom = Math.min(wZoom, hZoom)
323
+ if (!Number.isFinite(zoom) || zoom <= 0) {
324
+ return undefined
325
+ }
293
326
  svgCanvas.setZoom(zoom)
294
327
  return { zoom, bbox: bb }
295
328
  }
296
329
 
297
- if (typeof val === 'object') {
330
+ if (val && typeof val === 'object') {
298
331
  bb = val
299
332
  if (bb.width === 0 || bb.height === 0) {
300
- const newzoom = bb.zoom ? bb.zoom : zoom * bb.factor
301
- svgCanvas.setZoom(newzoom)
302
- return { zoom, bbox: bb }
333
+ let newzoom = zoom
334
+ if (Number.isFinite(bb.zoom) && bb.zoom > 0) {
335
+ newzoom = bb.zoom
336
+ } else if (Number.isFinite(bb.factor) && bb.factor > 0) {
337
+ newzoom = zoom * bb.factor
338
+ }
339
+ if (Number.isFinite(newzoom) && newzoom > 0) {
340
+ svgCanvas.setZoom(newzoom)
341
+ }
342
+ return { zoom: newzoom, bbox: bb }
303
343
  }
304
344
  return calcZoom(bb)
305
345
  }
@@ -307,12 +347,7 @@ const setBBoxZoomMethod = (val, editorW, editorH) => {
307
347
  switch (val) {
308
348
  case 'selection': {
309
349
  if (!selectedElements[0]) { return undefined }
310
- const selectedElems = selectedElements.map((n, _) => {
311
- if (n) {
312
- return n
313
- }
314
- return undefined
315
- })
350
+ const selectedElems = selectedElements.filter(Boolean)
316
351
  bb = getStrokedBBoxDefaultVisible(selectedElems)
317
352
  break
318
353
  } case 'canvas': {
@@ -340,13 +375,22 @@ const setBBoxZoomMethod = (val, editorW, editorH) => {
340
375
  * @returns {void}
341
376
  */
342
377
  const setZoomMethod = (zoomLevel) => {
378
+ if (!Number.isFinite(zoomLevel) || zoomLevel <= 0) {
379
+ return
380
+ }
343
381
  const selectedElements = svgCanvas.getSelectedElements()
344
382
  const res = svgCanvas.getResolution()
345
- svgCanvas.getSvgContent().setAttribute('viewBox', '0 0 ' + res.w / zoomLevel + ' ' + res.h / zoomLevel)
383
+ const w = res.w / zoomLevel
384
+ const h = res.h / zoomLevel
385
+ if (!Number.isFinite(w) || !Number.isFinite(h) || w <= 0 || h <= 0) {
386
+ return
387
+ }
388
+ svgCanvas.getSvgContent().setAttribute('viewBox', `0 0 ${w} ${h}`)
346
389
  svgCanvas.setZoom(zoomLevel)
347
390
  selectedElements.forEach((elem) => {
348
391
  if (!elem) { return }
349
- svgCanvas.selectorManager.requestSelector(elem).resize()
392
+ const selector = svgCanvas.selectorManager.requestSelector(elem)
393
+ selector && selector.resize()
350
394
  })
351
395
  svgCanvas.pathActions.zoomChange()
352
396
  svgCanvas.runExtensions('zoomChanged', zoomLevel)
@@ -364,7 +408,7 @@ const setZoomMethod = (zoomLevel) => {
364
408
  const setColorMethod = (type, val, preventUndo) => {
365
409
  const selectedElements = svgCanvas.getSelectedElements()
366
410
  svgCanvas.setCurShape(type, val)
367
- svgCanvas.setCurProperties(type + '_paint', { type: 'solidColor' })
411
+ svgCanvas.setCurProperties(`${type}_paint`, { type: 'solidColor' })
368
412
  const elems = []
369
413
  /**
370
414
  *
@@ -408,10 +452,11 @@ const setColorMethod = (type, val, preventUndo) => {
408
452
  * @returns {void}
409
453
  */
410
454
  const setGradientMethod = (type) => {
411
- if (!svgCanvas.getCurProperties(type + '_paint') ||
412
- svgCanvas.getCurProperties(type + '_paint').type === 'solidColor') { return }
455
+ if (!svgCanvas.getCurProperties(`${type}_paint`) ||
456
+ svgCanvas.getCurProperties(`${type}_paint`).type === 'solidColor') { return }
413
457
  const canvas = svgCanvas
414
458
  let grad = canvas[type + 'Grad']
459
+ if (!grad) { return }
415
460
  // find out if there is a duplicate gradient already in the defs
416
461
  const duplicateGrad = findDuplicateGradient(grad)
417
462
  const defs = findDefs()
@@ -425,7 +470,7 @@ const setGradientMethod = (type) => {
425
470
  } else { // use existing gradient
426
471
  grad = duplicateGrad
427
472
  }
428
- svgCanvas.setColor(type, 'url(#' + grad.id + ')')
473
+ svgCanvas.setColor(type, `url(#${grad.id})`)
429
474
  }
430
475
 
431
476
  /**
@@ -435,12 +480,21 @@ const setGradientMethod = (type) => {
435
480
  * @returns {SVGGradientElement} The existing gradient if found, `null` if not
436
481
  */
437
482
  const findDuplicateGradient = (grad) => {
483
+ if (!grad) {
484
+ return null
485
+ }
486
+ if (!['linearGradient', 'radialGradient'].includes(grad.tagName)) {
487
+ return null
488
+ }
438
489
  const defs = findDefs()
439
490
  const existingGrads = defs.querySelectorAll('linearGradient, radialGradient')
440
491
  let i = existingGrads.length
441
492
  const radAttrs = ['r', 'cx', 'cy', 'fx', 'fy']
442
493
  while (i--) {
443
494
  const og = existingGrads[i]
495
+ if (og.tagName !== grad.tagName) {
496
+ continue
497
+ }
444
498
  if (grad.tagName === 'linearGradient') {
445
499
  if (grad.getAttribute('x1') !== og.getAttribute('x1') ||
446
500
  grad.getAttribute('y1') !== og.getAttribute('y1') ||
@@ -514,10 +568,10 @@ const setPaintMethod = (type, paint) => {
514
568
  svgCanvas.setPaintOpacity(type, p.alpha / 100, true)
515
569
 
516
570
  // now set the current paint object
517
- svgCanvas.setCurProperties(type + '_paint', p)
571
+ svgCanvas.setCurProperties(`${type}_paint`, p)
518
572
  switch (p.type) {
519
573
  case 'solidColor':
520
- svgCanvas.setColor(type, p.solidColor !== 'none' ? '#' + p.solidColor : 'none')
574
+ svgCanvas.setColor(type, p.solidColor !== 'none' ? `#${p.solidColor}` : 'none')
521
575
  break
522
576
  case 'linearGradient':
523
577
  case 'radialGradient':
@@ -653,7 +707,7 @@ const addTextDecorationMethod = (value) => {
653
707
  // Add the new text decoration value if it did not exist
654
708
  if (!oldValue.includes(value)) {
655
709
  batchCmd.addSubCommand(new ChangeElementCommand(elem, { 'text-decoration': oldValue }))
656
- svgCanvas.changeSelectedAttributeNoUndo('text-decoration', (oldValue + ' ' + value).trim(), [elem])
710
+ svgCanvas.changeSelectedAttributeNoUndo('text-decoration', `${oldValue} ${value}`.trim(), [elem])
657
711
  }
658
712
  })
659
713
  if (!batchCmd.isEmpty()) {
@@ -892,32 +946,48 @@ const setImageURLMethod = (val) => {
892
946
  const setsize = (!attrs.width || !attrs.height)
893
947
 
894
948
  const curHref = getHref(elem)
949
+ const hrefChanged = curHref !== val
895
950
 
896
951
  // Do nothing if no URL change or size change
897
- if (curHref === val && !setsize) {
952
+ if (!hrefChanged && !setsize) {
898
953
  return
899
954
  }
900
955
 
901
956
  const batchCmd = new BatchCommand('Change Image URL')
902
957
 
903
- setHref(elem, val)
904
- batchCmd.addSubCommand(new ChangeElementCommand(elem, {
905
- '#href': curHref
906
- }))
958
+ if (hrefChanged) {
959
+ setHref(elem, val)
960
+ batchCmd.addSubCommand(new ChangeElementCommand(elem, {
961
+ '#href': curHref
962
+ }))
963
+ }
964
+
965
+ let finalized = false
966
+ const finalize = () => {
967
+ if (finalized) { return }
968
+ finalized = true
969
+ if (batchCmd.isEmpty()) { return }
970
+ svgCanvas.addCommandToHistory(batchCmd)
971
+ svgCanvas.call('changed', [elem])
972
+ }
973
+
907
974
  const img = new Image()
908
- img.onload = function () {
975
+ img.onload = () => {
909
976
  const changes = {
910
977
  width: elem.getAttribute('width'),
911
978
  height: elem.getAttribute('height')
912
979
  }
913
- elem.setAttribute('width', this.width)
914
- elem.setAttribute('height', this.height)
980
+ elem.setAttribute('width', img.width)
981
+ elem.setAttribute('height', img.height)
915
982
 
916
- svgCanvas.selectorManager.requestSelector(elem).resize()
983
+ const selector = svgCanvas.selectorManager.requestSelector(elem)
984
+ selector && selector.resize()
917
985
 
918
986
  batchCmd.addSubCommand(new ChangeElementCommand(elem, changes))
919
- svgCanvas.addCommandToHistory(batchCmd)
920
- svgCanvas.call('changed', [elem])
987
+ finalize()
988
+ }
989
+ img.onerror = () => {
990
+ finalize()
921
991
  }
922
992
  img.src = val
923
993
  }
@@ -969,15 +1039,27 @@ const setRectRadiusMethod = (val) => {
969
1039
  const { ChangeElementCommand } = svgCanvas.history
970
1040
  const selectedElements = svgCanvas.getSelectedElements()
971
1041
  const selected = selectedElements[0]
972
- if (selected?.tagName === 'rect') {
973
- const r = Number(selected.getAttribute('rx'))
974
- if (r !== val) {
975
- selected.setAttribute('rx', val)
976
- selected.setAttribute('ry', val)
977
- svgCanvas.addCommandToHistory(new ChangeElementCommand(selected, { rx: r, ry: r }, 'Radius'))
978
- svgCanvas.call('changed', [selected])
979
- }
1042
+ if (selected?.tagName !== 'rect') { return }
1043
+
1044
+ const radius = Number(val)
1045
+ if (!Number.isFinite(radius) || radius < 0) {
1046
+ return
980
1047
  }
1048
+
1049
+ const oldRx = selected.getAttribute('rx')
1050
+ const oldRy = selected.getAttribute('ry')
1051
+ const currentRx = Number(oldRx)
1052
+ const currentRy = Number(oldRy)
1053
+ const hasCurrentRx = oldRx !== null && Number.isFinite(currentRx)
1054
+ const hasCurrentRy = oldRy !== null && Number.isFinite(currentRy)
1055
+ const already = (radius === 0 && oldRx === null && oldRy === null) ||
1056
+ (hasCurrentRx && hasCurrentRy && currentRx === radius && currentRy === radius)
1057
+ if (already) { return }
1058
+
1059
+ selected.setAttribute('rx', radius)
1060
+ selected.setAttribute('ry', radius)
1061
+ svgCanvas.addCommandToHistory(new ChangeElementCommand(selected, { rx: oldRx, ry: oldRy }, 'Radius'))
1062
+ svgCanvas.call('changed', [selected])
981
1063
  }
982
1064
 
983
1065
  /**
@@ -1021,7 +1103,9 @@ const setSegTypeMethod = (newType) => {
1021
1103
  */
1022
1104
  const setBackgroundMethod = (color, url) => {
1023
1105
  const bg = getElement('canvasBackground')
1106
+ if (!bg) { return }
1024
1107
  const border = bg.querySelector('rect')
1108
+ if (!border) { return }
1025
1109
  let bgImg = getElement('background_image')
1026
1110
  let bgPattern = getElement('background_pattern')
1027
1111
  border.setAttribute('fill', color === 'chessboard' ? '#fff' : color)