@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/core/svg-exec.js CHANGED
@@ -8,6 +8,7 @@
8
8
  import { jsPDF as JsPDF } from 'jspdf'
9
9
  import 'svg2pdf.js'
10
10
  import * as history from './history.js'
11
+ import { error } from '../common/logger.js'
11
12
  import {
12
13
  text2xml,
13
14
  cleanupElement,
@@ -131,7 +132,7 @@ const svgToString = (elem, indent) => {
131
132
  const nsMap = svgCanvas.getNsMap()
132
133
  const out = []
133
134
  const unit = curConfig.baseUnit
134
- const unitRe = new RegExp('^-?[\\d\\.]+' + unit + '$')
135
+ const unitRe = new RegExp(`^-?[\\d\\.]+${unit}$`)
135
136
 
136
137
  if (elem) {
137
138
  cleanupElement(elem)
@@ -164,7 +165,10 @@ const svgToString = (elem, indent) => {
164
165
  // }
165
166
  if (curConfig.dynamicOutput) {
166
167
  vb = elem.getAttribute('viewBox')
167
- out.push(' viewBox="' + vb + '" xmlns="' + NS.SVG + '"')
168
+ if (!vb) {
169
+ vb = [0, 0, res.w, res.h].join(' ')
170
+ }
171
+ out.push(` viewBox="${vb}" xmlns="${NS.SVG}"`)
168
172
  } else {
169
173
  if (unit !== 'px') {
170
174
  res.w = convertUnit(res.w, unit) + unit
@@ -193,14 +197,14 @@ const svgToString = (elem, indent) => {
193
197
  nsMap[uri] !== 'xml'
194
198
  ) {
195
199
  nsuris[uri] = true
196
- out.push(' xmlns:' + nsMap[uri] + '="' + uri + '"')
200
+ out.push(` xmlns:${nsMap[uri]}="${uri}"`)
197
201
  }
198
202
  if (el.attributes.length > 0) {
199
203
  for (const [, attr] of Object.entries(el.attributes)) {
200
204
  const u = attr.namespaceURI
201
205
  if (u && !nsuris[u] && nsMap[u] !== 'xmlns' && nsMap[u] !== 'xml') {
202
206
  nsuris[u] = true
203
- out.push(' xmlns:' + nsMap[u] + '="' + u + '"')
207
+ out.push(` xmlns:${nsMap[u]}="${u}"`)
204
208
  }
205
209
  }
206
210
  }
@@ -469,7 +473,7 @@ const setSvgString = (xmlString, preventUndo) => {
469
473
 
470
474
  Object.entries(ids).forEach(([key, value]) => {
471
475
  if (value > 1) {
472
- const nodes = content.querySelectorAll('[id="' + key + '"]')
476
+ const nodes = content.querySelectorAll(`[id="${key}"]`)
473
477
  for (let i = 1; i < nodes.length; i++) {
474
478
  nodes[i].setAttribute('id', svgCanvas.getNextId())
475
479
  }
@@ -525,14 +529,20 @@ const setSvgString = (xmlString, preventUndo) => {
525
529
  if (content.getAttribute('viewBox')) {
526
530
  const viBox = content.getAttribute('viewBox')
527
531
  const vb = viBox.split(/[ ,]+/)
528
- attrs.width = vb[2]
529
- attrs.height = vb[3]
532
+ const vbWidth = Number(vb[2])
533
+ const vbHeight = Number(vb[3])
534
+ if (Number.isFinite(vbWidth)) {
535
+ attrs.width = vbWidth
536
+ }
537
+ if (Number.isFinite(vbHeight)) {
538
+ attrs.height = vbHeight
539
+ }
530
540
  // handle content that doesn't have a viewBox
531
541
  } else {
532
542
  ;['width', 'height'].forEach(dim => {
533
543
  // Set to 100 if not given
534
544
  const val = content.getAttribute(dim) || '100%'
535
- if (String(val).substr(-1) === '%') {
545
+ if (String(val).slice(-1) === '%') {
536
546
  // Use user units if percentage given
537
547
  percs = true
538
548
  } else {
@@ -558,16 +568,25 @@ const setSvgString = (xmlString, preventUndo) => {
558
568
  // Percentage width/height, so let's base it on visible elements
559
569
  if (percs) {
560
570
  const bb = getStrokedBBoxDefaultVisible()
561
- attrs.width = bb.width + bb.x
562
- attrs.height = bb.height + bb.y
571
+ if (bb && typeof bb === 'object') {
572
+ attrs.width = bb.width + bb.x
573
+ attrs.height = bb.height + bb.y
574
+ } else {
575
+ if (attrs.width === null || attrs.width === undefined) {
576
+ attrs.width = 100
577
+ }
578
+ if (attrs.height === null || attrs.height === undefined) {
579
+ attrs.height = 100
580
+ }
581
+ }
563
582
  }
564
583
 
565
584
  // Just in case negative numbers are given or
566
585
  // result from the percs calculation
567
- if (attrs.width <= 0) {
586
+ if (!Number.isFinite(attrs.width) || attrs.width <= 0) {
568
587
  attrs.width = 100
569
588
  }
570
- if (attrs.height <= 0) {
589
+ if (!Number.isFinite(attrs.height) || attrs.height <= 0) {
571
590
  attrs.height = 100
572
591
  }
573
592
 
@@ -596,7 +615,7 @@ const setSvgString = (xmlString, preventUndo) => {
596
615
  if (!preventUndo) svgCanvas.addCommandToHistory(batchCmd)
597
616
  svgCanvas.call('sourcechanged', [svgCanvas.getSvgContent()])
598
617
  } catch (e) {
599
- console.error(e)
618
+ error('Error setting SVG string', e, 'svg-exec')
600
619
  return false
601
620
  }
602
621
 
@@ -666,16 +685,26 @@ const importSvgString = (xmlString, preserveDimension) => {
666
685
 
667
686
  // TODO: properly handle preserveAspectRatio
668
687
  const // canvasw = +svgContent.getAttribute('width'),
669
- canvash = Number(svgCanvas.getSvgContent().getAttribute('height'))
688
+ rawCanvash = Number(svgCanvas.getSvgContent().getAttribute('height'))
689
+ const canvash =
690
+ Number.isFinite(rawCanvash) && rawCanvash > 0
691
+ ? rawCanvash
692
+ : (Number(svgCanvas.getCurConfig().dimensions?.[1]) || 100)
670
693
  // imported content should be 1/3 of the canvas on its largest dimension
671
694
 
695
+ const vbWidth = vb[2]
696
+ const vbHeight = vb[3]
697
+ const importW = Number.isFinite(vbWidth) && vbWidth > 0 ? vbWidth : (innerw > 0 ? innerw : 100)
698
+ const importH = Number.isFinite(vbHeight) && vbHeight > 0 ? vbHeight : (innerh > 0 ? innerh : 100)
699
+ const safeImportW = Number.isFinite(importW) && importW > 0 ? importW : 100
700
+ const safeImportH = Number.isFinite(importH) && importH > 0 ? importH : 100
672
701
  ts =
673
- innerh > innerw
674
- ? 'scale(' + canvash / 3 / vb[3] + ')'
675
- : 'scale(' + canvash / 3 / vb[2] + ')'
702
+ safeImportH > safeImportW
703
+ ? 'scale(' + canvash / 3 / safeImportH + ')'
704
+ : 'scale(' + canvash / 3 / safeImportW + ')'
676
705
 
677
706
  // Hack to make recalculateDimensions understand how to scale
678
- ts = 'translate(0) ' + ts + ' translate(0)'
707
+ ts = `translate(0) ${ts} translate(0)`
679
708
 
680
709
  symbol = svgCanvas.getDOMDocument().createElementNS(NS.SVG, 'symbol')
681
710
  const defs = findDefs()
@@ -738,7 +767,7 @@ const importSvgString = (xmlString, preserveDimension) => {
738
767
  svgCanvas.addCommandToHistory(batchCmd)
739
768
  svgCanvas.call('changed', [svgCanvas.getSvgContent()])
740
769
  } catch (e) {
741
- console.error(e)
770
+ error('Error importing SVG string', e, 'svg-exec')
742
771
  return null
743
772
  }
744
773
 
@@ -865,8 +894,8 @@ const convertImagesToBase64 = async svgElement => {
865
894
  }
866
895
  reader.readAsDataURL(blob)
867
896
  })
868
- } catch (error) {
869
- console.error('Failed to fetch image:', error)
897
+ } catch (err) {
898
+ error('Failed to fetch image', err, 'svg-exec')
870
899
  }
871
900
  }
872
901
  })
@@ -905,10 +934,14 @@ const rasterExport = (
905
934
 
906
935
  const canvas = document.createElement('canvas')
907
936
  const ctx = canvas.getContext('2d')
937
+ if (!ctx) {
938
+ reject(new Error('Canvas 2D context not available'))
939
+ return
940
+ }
908
941
 
909
- const width = svgElement.clientWidth || svgElement.getAttribute('width')
910
- const height =
911
- svgElement.clientHeight || svgElement.getAttribute('height')
942
+ const res = svgCanvas.getResolution()
943
+ const width = res.w
944
+ const height = res.h
912
945
  canvas.width = width
913
946
  canvas.height = height
914
947
 
@@ -1013,7 +1046,7 @@ const exportPDF = (
1013
1046
  }
1014
1047
 
1015
1048
  img.onerror = err => {
1016
- console.error('Failed to load SVG into image element:', err)
1049
+ error('Failed to load SVG into image element', err, 'svg-exec')
1017
1050
  reject(err)
1018
1051
  }
1019
1052
 
@@ -1112,7 +1145,7 @@ const uniquifyElemsMethod = g => {
1112
1145
  let j = attrs.length
1113
1146
  while (j--) {
1114
1147
  const attr = attrs[j]
1115
- attr.ownerElement.setAttribute(attr.name, 'url(#' + newid + ')')
1148
+ attr.ownerElement.setAttribute(attr.name, `url(#${newid})`)
1116
1149
  }
1117
1150
 
1118
1151
  // remap all href attributes
@@ -1142,7 +1175,11 @@ const setUseDataMethod = parent => {
1142
1175
 
1143
1176
  Array.prototype.forEach.call(elems, (el, _) => {
1144
1177
  const dataStorage = svgCanvas.getDataStorage()
1145
- const id = svgCanvas.getHref(el).substr(1)
1178
+ const href = svgCanvas.getHref(el)
1179
+ if (!href || !href.startsWith('#')) {
1180
+ return
1181
+ }
1182
+ const id = href.substr(1)
1146
1183
  const refElem = svgCanvas.getElement(id)
1147
1184
  if (!refElem) {
1148
1185
  return
@@ -1301,6 +1338,41 @@ const convertGradientsMethod = elem => {
1301
1338
  grad.setAttribute('x2', (gCoords.x2 - bb.x) / bb.width)
1302
1339
  grad.setAttribute('y2', (gCoords.y2 - bb.y) / bb.height)
1303
1340
  grad.removeAttribute('gradientUnits')
1341
+ } else if (grad.tagName === 'radialGradient') {
1342
+ const getNum = (value, fallback) => {
1343
+ const num = Number(value)
1344
+ return Number.isFinite(num) ? num : fallback
1345
+ }
1346
+ let cx = getNum(grad.getAttribute('cx'), 0.5)
1347
+ let cy = getNum(grad.getAttribute('cy'), 0.5)
1348
+ let r = getNum(grad.getAttribute('r'), 0.5)
1349
+ let fx = getNum(grad.getAttribute('fx'), cx)
1350
+ let fy = getNum(grad.getAttribute('fy'), cy)
1351
+
1352
+ // If has transform, convert
1353
+ const tlist = getTransformList(grad)
1354
+ if (tlist?.numberOfItems > 0) {
1355
+ const m = transformListToTransform(tlist).matrix
1356
+ const cpt = transformPoint(cx, cy, m)
1357
+ const fpt = transformPoint(fx, fy, m)
1358
+ const rpt = transformPoint(cx + r, cy, m)
1359
+ cx = cpt.x
1360
+ cy = cpt.y
1361
+ fx = fpt.x
1362
+ fy = fpt.y
1363
+ r = Math.hypot(rpt.x - cpt.x, rpt.y - cpt.y)
1364
+ grad.removeAttribute('gradientTransform')
1365
+ }
1366
+
1367
+ if (!bb.width || !bb.height) {
1368
+ return
1369
+ }
1370
+ grad.setAttribute('cx', (cx - bb.x) / bb.width)
1371
+ grad.setAttribute('cy', (cy - bb.y) / bb.height)
1372
+ grad.setAttribute('fx', (fx - bb.x) / bb.width)
1373
+ grad.setAttribute('fy', (fy - bb.y) / bb.height)
1374
+ grad.setAttribute('r', r / Math.max(bb.width, bb.height))
1375
+ grad.removeAttribute('gradientUnits')
1304
1376
  }
1305
1377
  }
1306
1378
  })
package/core/svgroot.js CHANGED
@@ -14,7 +14,7 @@ import { text2xml } from './utilities.js'
14
14
  * @param {ArgumentsArray} dimensions - dimensions of width and height
15
15
  * @returns {svgRootElement}
16
16
  */
17
- export const svgRootElement = function (svgdoc, dimensions) {
17
+ export const svgRootElement = (svgdoc, dimensions) => {
18
18
  return svgdoc.importNode(
19
19
  text2xml(
20
20
  `<svg id="svgroot" xmlns="${NS.SVG}" xlinkns="${NS.XLINK}" width="${dimensions[0]}"