@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.
@@ -16,6 +16,190 @@ import {
16
16
  getElement
17
17
  } from './utilities.js'
18
18
 
19
+ const TYPE_TO_CMD = {
20
+ 1: 'Z',
21
+ 2: 'M',
22
+ 3: 'm',
23
+ 4: 'L',
24
+ 5: 'l',
25
+ 6: 'C',
26
+ 7: 'c',
27
+ 8: 'Q',
28
+ 9: 'q',
29
+ 10: 'A',
30
+ 11: 'a',
31
+ 12: 'H',
32
+ 13: 'h',
33
+ 14: 'V',
34
+ 15: 'v',
35
+ 16: 'S',
36
+ 17: 's',
37
+ 18: 'T',
38
+ 19: 't'
39
+ }
40
+
41
+ const CMD_TO_TYPE = Object.fromEntries(
42
+ Object.entries(TYPE_TO_CMD).map(([k, v]) => [v, Number(k)])
43
+ )
44
+
45
+ class PathDataListShim {
46
+ constructor (elem) {
47
+ this.elem = elem
48
+ }
49
+
50
+ _getData () {
51
+ return this.elem.getPathData()
52
+ }
53
+
54
+ _setData (data) {
55
+ this.elem.setPathData(data)
56
+ }
57
+
58
+ get numberOfItems () {
59
+ return this._getData().length
60
+ }
61
+
62
+ _entryToSeg (entry) {
63
+ const { type, values = [] } = entry
64
+ const cmd = CMD_TO_TYPE[type] || CMD_TO_TYPE[type?.toUpperCase?.()]
65
+ const seg = { pathSegType: cmd }
66
+ const U = String(type).toUpperCase()
67
+ switch (U) {
68
+ case 'H':
69
+ [seg.x] = values
70
+ break
71
+ case 'V':
72
+ [seg.y] = values
73
+ break
74
+ case 'M':
75
+ case 'L':
76
+ case 'T':
77
+ [seg.x, seg.y] = values
78
+ break
79
+ case 'S':
80
+ [seg.x2, seg.y2, seg.x, seg.y] = values
81
+ break
82
+ case 'C':
83
+ [seg.x1, seg.y1, seg.x2, seg.y2, seg.x, seg.y] = values
84
+ break
85
+ case 'Q':
86
+ [seg.x1, seg.y1, seg.x, seg.y] = values
87
+ break
88
+ case 'A':
89
+ [
90
+ seg.r1,
91
+ seg.r2,
92
+ seg.angle,
93
+ seg.largeArcFlag,
94
+ seg.sweepFlag,
95
+ seg.x,
96
+ seg.y
97
+ ] = values
98
+ break
99
+ default:
100
+ break
101
+ }
102
+ return seg
103
+ }
104
+
105
+ _segToEntry (seg) {
106
+ const type = TYPE_TO_CMD[seg.pathSegType] || seg.type
107
+ if (!type) {
108
+ return { type: 'Z', values: [] }
109
+ }
110
+ const U = String(type).toUpperCase()
111
+ let values = []
112
+ switch (U) {
113
+ case 'H':
114
+ values = [seg.x]
115
+ break
116
+ case 'V':
117
+ values = [seg.y]
118
+ break
119
+ case 'M':
120
+ case 'L':
121
+ case 'T':
122
+ values = [seg.x, seg.y]
123
+ break
124
+ case 'S':
125
+ values = [seg.x2, seg.y2, seg.x, seg.y]
126
+ break
127
+ case 'C':
128
+ values = [seg.x1, seg.y1, seg.x2, seg.y2, seg.x, seg.y]
129
+ break
130
+ case 'Q':
131
+ values = [seg.x1, seg.y1, seg.x, seg.y]
132
+ break
133
+ case 'A':
134
+ values = [
135
+ seg.r1,
136
+ seg.r2,
137
+ seg.angle,
138
+ Number(seg.largeArcFlag),
139
+ Number(seg.sweepFlag),
140
+ seg.x,
141
+ seg.y
142
+ ]
143
+ break
144
+ default:
145
+ values = []
146
+ }
147
+ return { type, values }
148
+ }
149
+
150
+ getItem (index) {
151
+ const entry = this._getData()[index]
152
+ return entry ? this._entryToSeg(entry) : null
153
+ }
154
+
155
+ replaceItem (seg, index) {
156
+ const data = this._getData()
157
+ data[index] = this._segToEntry(seg)
158
+ this._setData(data)
159
+ return seg
160
+ }
161
+
162
+ insertItemBefore (seg, index) {
163
+ const data = this._getData()
164
+ data.splice(index, 0, this._segToEntry(seg))
165
+ this._setData(data)
166
+ return seg
167
+ }
168
+
169
+ appendItem (seg) {
170
+ const data = this._getData()
171
+ data.push(this._segToEntry(seg))
172
+ this._setData(data)
173
+ return seg
174
+ }
175
+
176
+ removeItem (index) {
177
+ const data = this._getData()
178
+ data.splice(index, 1)
179
+ this._setData(data)
180
+ }
181
+
182
+ clear () {
183
+ this._setData([])
184
+ }
185
+ }
186
+
187
+ if (
188
+ typeof SVGPathElement !== 'undefined' &&
189
+ typeof SVGPathElement.prototype.getPathData === 'function' &&
190
+ typeof SVGPathElement.prototype.setPathData === 'function' &&
191
+ !('pathSegList' in SVGPathElement.prototype)
192
+ ) {
193
+ Object.defineProperty(SVGPathElement.prototype, 'pathSegList', {
194
+ get () {
195
+ if (!this._pathSegListShim) {
196
+ this._pathSegListShim = new PathDataListShim(this)
197
+ }
198
+ return this._pathSegListShim
199
+ }
200
+ })
201
+ }
202
+
19
203
  let svgCanvas = null
20
204
 
21
205
  /**
@@ -36,7 +220,7 @@ export const init = (canvas) => {
36
220
  * @returns {ArgumentsArray}
37
221
  */
38
222
  /* eslint-enable max-len */
39
- export const ptObjToArrMethod = function (type, segItem) {
223
+ export const ptObjToArrMethod = (type, segItem) => {
40
224
  const segData = svgCanvas.getSegData()
41
225
  const props = segData[type]
42
226
  return props.map((prop) => {
@@ -50,7 +234,7 @@ export const ptObjToArrMethod = function (type, segItem) {
50
234
  * @param {module:math.XYObject} altPt
51
235
  * @returns {module:math.XYObject}
52
236
  */
53
- export const getGripPtMethod = function (seg, altPt) {
237
+ export const getGripPtMethod = (seg, altPt) => {
54
238
  const { path: pth } = seg
55
239
  let out = {
56
240
  x: altPt ? altPt.x : seg.item.x,
@@ -73,7 +257,7 @@ export const getGripPtMethod = function (seg, altPt) {
73
257
  * @param {module:path.Path} pth
74
258
  * @returns {module:math.XYObject}
75
259
  */
76
- export const getPointFromGripMethod = function (pt, pth) {
260
+ export const getPointFromGripMethod = (pt, pth) => {
77
261
  const out = {
78
262
  x: pt.x,
79
263
  y: pt.y
@@ -94,7 +278,7 @@ export const getPointFromGripMethod = function (pt, pth) {
94
278
  * @function module:path.getGripContainer
95
279
  * @returns {Element}
96
280
  */
97
- export const getGripContainerMethod = function () {
281
+ export const getGripContainerMethod = () => {
98
282
  let c = getElement('pathpointgrip_container')
99
283
  if (!c) {
100
284
  const parentElement = getElement('selectorParentGroup')
@@ -113,16 +297,16 @@ export const getGripContainerMethod = function () {
113
297
  * @param {Integer} y
114
298
  * @returns {SVGCircleElement}
115
299
  */
116
- export const addPointGripMethod = function (index, x, y) {
300
+ export const addPointGripMethod = (index, x, y) => {
117
301
  // create the container of all the point grips
118
302
  const pointGripContainer = getGripContainerMethod()
119
303
 
120
- let pointGrip = getElement('pathpointgrip_' + index)
304
+ let pointGrip = getElement(`pathpointgrip_${index}`)
121
305
  // create it
122
306
  if (!pointGrip) {
123
307
  pointGrip = document.createElementNS(NS.SVG, 'circle')
124
308
  const atts = {
125
- id: 'pathpointgrip_' + index,
309
+ id: `pathpointgrip_${index}`,
126
310
  display: 'none',
127
311
  r: 4,
128
312
  fill: '#0FF',
@@ -163,7 +347,7 @@ export const addPointGripMethod = function (index, x, y) {
163
347
  * @param {string} id
164
348
  * @returns {SVGCircleElement}
165
349
  */
166
- export const addCtrlGripMethod = function (id) {
350
+ export const addCtrlGripMethod = (id) => {
167
351
  let pointGrip = getElement('ctrlpointgrip_' + id)
168
352
  if (pointGrip) { return pointGrip }
169
353
 
@@ -191,7 +375,7 @@ export const addCtrlGripMethod = function (id) {
191
375
  * @param {string} id
192
376
  * @returns {SVGLineElement}
193
377
  */
194
- export const getCtrlLineMethod = function (id) {
378
+ export const getCtrlLineMethod = (id) => {
195
379
  let ctrlLine = getElement('ctrlLine_' + id)
196
380
  if (ctrlLine) { return ctrlLine }
197
381
 
@@ -211,7 +395,7 @@ export const getCtrlLineMethod = function (id) {
211
395
  * @param {boolean} update
212
396
  * @returns {SVGCircleElement}
213
397
  */
214
- export const getPointGripMethod = function (seg, update) {
398
+ export const getPointGripMethod = (seg, update) => {
215
399
  const { index } = seg
216
400
  const pointGrip = addPointGripMethod(index)
217
401
 
@@ -231,7 +415,7 @@ export const getPointGripMethod = function (seg, update) {
231
415
  * @param {Segment} seg
232
416
  * @returns {PlainObject<string, SVGLineElement|SVGCircleElement>}
233
417
  */
234
- export const getControlPointsMethod = function (seg) {
418
+ export const getControlPointsMethod = (seg) => {
235
419
  const { item, index } = seg
236
420
  if (!('x1' in item) || !('x2' in item)) { return null }
237
421
  const cpt = {}
@@ -246,7 +430,7 @@ export const getControlPointsMethod = function (seg) {
246
430
  for (let i = 1; i < 3; i++) {
247
431
  const id = index + 'c' + i
248
432
 
249
- const ctrlLine = cpt['c' + i + '_line'] = getCtrlLineMethod(id)
433
+ const ctrlLine = cpt[`c${i}_line`] = getCtrlLineMethod(id)
250
434
 
251
435
  const pt = getGripPtMethod(seg, { x: item['x' + i], y: item['y' + i] })
252
436
  const gpt = getGripPtMethod(seg, { x: segItems[i - 1].x, y: segItems[i - 1].y })
@@ -259,10 +443,10 @@ export const getControlPointsMethod = function (seg) {
259
443
  display: 'inline'
260
444
  })
261
445
 
262
- cpt['c' + i + '_line'] = ctrlLine
446
+ cpt[`c${i}_line`] = ctrlLine
263
447
 
264
448
  // create it
265
- const pointGrip = cpt['c' + i] = addCtrlGripMethod(id)
449
+ const pointGrip = cpt[`c${i}`] = addCtrlGripMethod(id)
266
450
 
267
451
  assignAttributes(pointGrip, {
268
452
  cx: pt.x,
@@ -282,12 +466,29 @@ export const getControlPointsMethod = function (seg) {
282
466
  * @param {SVGPathElement} elem
283
467
  * @returns {void}
284
468
  */
285
- export const replacePathSegMethod = function (type, index, pts, elem) {
469
+ export const replacePathSegMethod = (type, index, pts, elem) => {
286
470
  const path = svgCanvas.getPathObj()
287
471
  const pth = elem || path.elem
288
472
  const pathFuncs = svgCanvas.getPathFuncs()
289
473
  const func = 'createSVGPathSeg' + pathFuncs[type]
290
- const seg = pth[func](...pts)
474
+ const segData = svgCanvas.getSegData?.()
475
+ const props = segData?.[type] || segData?.[type - 1]
476
+ if (props && pts.length < props.length) {
477
+ const currentSeg = pth.pathSegList?.getItem?.(index)
478
+ if (currentSeg) {
479
+ pts = props.map((prop, i) => (pts[i] !== undefined ? pts[i] : currentSeg[prop]))
480
+ }
481
+ }
482
+ let seg
483
+ if (typeof pth[func] === 'function') {
484
+ seg = pth[func](...pts)
485
+ } else {
486
+ const safeProps = props || []
487
+ seg = { pathSegType: type }
488
+ safeProps.forEach((prop, i) => {
489
+ seg[prop] = pts[i]
490
+ })
491
+ }
291
492
 
292
493
  pth.pathSegList.replaceItem(seg, index)
293
494
  }
@@ -297,15 +498,15 @@ export const replacePathSegMethod = function (type, index, pts, elem) {
297
498
  * @param {boolean} update
298
499
  * @returns {SVGPathElement}
299
500
  */
300
- export const getSegSelectorMethod = function (seg, update) {
501
+ export const getSegSelectorMethod = (seg, update) => {
301
502
  const { index } = seg
302
- let segLine = getElement('segline_' + index)
503
+ let segLine = getElement(`segline_${index}`)
303
504
  if (!segLine) {
304
505
  const pointGripContainer = getGripContainerMethod()
305
506
  // create segline
306
507
  segLine = document.createElementNS(NS.SVG, 'path')
307
508
  assignAttributes(segLine, {
308
- id: 'segline_' + index,
509
+ id: `segline_${index}`,
309
510
  display: 'none',
310
511
  fill: 'none',
311
512
  stroke: '#0FF',
@@ -353,7 +554,7 @@ export class Segment {
353
554
  this.item = item
354
555
  this.type = item.pathSegType
355
556
 
356
- this.ctrlpts = []
557
+ this.ctrlpts = null
357
558
  this.ptgrip = null
358
559
  this.segsel = null
359
560
  }
@@ -375,8 +576,8 @@ export class Segment {
375
576
  * @returns {void}
376
577
  */
377
578
  selectCtrls (y) {
378
- document.getElementById('ctrlpointgrip_' + this.index + 'c1').setAttribute('fill', y ? '#0FF' : '#EEE')
379
- document.getElementById('ctrlpointgrip_' + this.index + 'c2').setAttribute('fill', y ? '#0FF' : '#EEE')
579
+ document.getElementById(`ctrlpointgrip_${this.index}c1`)?.setAttribute('fill', y ? '#0FF' : '#EEE')
580
+ document.getElementById(`ctrlpointgrip_${this.index}c2`)?.setAttribute('fill', y ? '#0FF' : '#EEE')
380
581
  }
381
582
 
382
583
  /**
@@ -450,27 +651,25 @@ export class Segment {
450
651
  move (dx, dy) {
451
652
  const { item } = this
452
653
 
453
- const curPts = this.ctrlpts
454
- ? [
455
- item.x += dx, item.y += dy,
456
- item.x1, item.y1, item.x2 += dx, item.y2 += dy
457
- ]
458
- : [item.x += dx, item.y += dy]
654
+ item.x += dx
655
+ item.y += dy
656
+
657
+ // `x2/y2` are the control point attached to this node (when present)
658
+ if ('x2' in item) { item.x2 += dx }
659
+ if ('y2' in item) { item.y2 += dy }
459
660
 
460
661
  replacePathSegMethod(
461
662
  this.type,
462
663
  this.index,
463
- // type 10 means ARC
464
- this.type === 10 ? ptObjToArrMethod(this.type, item) : curPts
664
+ ptObjToArrMethod(this.type, item)
465
665
  )
466
666
 
467
- if (this.next?.ctrlpts) {
468
- const next = this.next.item
469
- const nextPts = [
470
- next.x, next.y,
471
- next.x1 += dx, next.y1 += dy, next.x2, next.y2
472
- ]
473
- replacePathSegMethod(this.next.type, this.next.index, nextPts)
667
+ const next = this.next?.item
668
+ // `x1/y1` are the control point attached to this node on the next segment (when present)
669
+ if (next && 'x1' in next && 'y1' in next) {
670
+ next.x1 += dx
671
+ next.y1 += dy
672
+ replacePathSegMethod(this.next.type, this.next.index, ptObjToArrMethod(this.next.type, next))
474
673
  }
475
674
 
476
675
  if (this.mate) {
package/core/path.js CHANGED
@@ -236,6 +236,7 @@ export const init = (canvas) => {
236
236
  svgCanvas.getPointFromGrip = getPointFromGripMethod
237
237
  svgCanvas.setLinkControlPoints = setLinkControlPoints
238
238
  svgCanvas.reorientGrads = reorientGrads
239
+ svgCanvas.recalcRotatedPath = recalcRotatedPath
239
240
  svgCanvas.getSegData = () => { return segData }
240
241
  svgCanvas.getUIStrings = () => { return uiStrings }
241
242
  svgCanvas.getPathObj = () => { return path }
@@ -466,14 +467,17 @@ const getRotVals = (x, y) => {
466
467
  * @returns {void}
467
468
  */
468
469
  export const recalcRotatedPath = () => {
469
- const currentPath = path.elem
470
+ const currentPath = path?.elem
471
+ if (!currentPath) { return }
470
472
  angle = getRotationAngle(currentPath, true)
471
473
  if (!angle) { return }
472
474
  // selectedBBoxes[0] = path.oldbbox;
473
475
  const oldbox = path.oldbbox // selectedBBoxes[0],
476
+ if (!oldbox) { return }
474
477
  oldcx = oldbox.x + oldbox.width / 2
475
478
  oldcy = oldbox.y + oldbox.height / 2
476
479
  const box = getBBox(currentPath)
480
+ if (!box) { return }
477
481
  newcx = box.x + box.width / 2
478
482
  newcy = box.y + box.height / 2
479
483
 
@@ -487,6 +491,7 @@ export const recalcRotatedPath = () => {
487
491
  newcy = r * Math.sin(theta) + oldcy
488
492
 
489
493
  const list = currentPath.pathSegList
494
+ if (!list) { return }
490
495
 
491
496
  let i = list.numberOfItems
492
497
  while (i) {
@@ -495,13 +500,33 @@ export const recalcRotatedPath = () => {
495
500
  const type = seg.pathSegType
496
501
  if (type === 1) { continue }
497
502
 
498
- const rvals = getRotVals(seg.x, seg.y)
499
- const points = [rvals.x, rvals.y]
500
- if (seg.x1 && seg.x2) {
503
+ const props = segData[type]
504
+ if (!props) { continue }
505
+
506
+ const newVals = {}
507
+ if (seg.x !== null && seg.x !== undefined && seg.y !== null && seg.y !== undefined) {
508
+ const rvals = getRotVals(seg.x, seg.y)
509
+ newVals.x = rvals.x
510
+ newVals.y = rvals.y
511
+ }
512
+ if (seg.x1 !== null && seg.x1 !== undefined && seg.y1 !== null && seg.y1 !== undefined) {
501
513
  const cVals1 = getRotVals(seg.x1, seg.y1)
514
+ newVals.x1 = cVals1.x
515
+ newVals.y1 = cVals1.y
516
+ }
517
+ if (seg.x2 !== null && seg.x2 !== undefined && seg.y2 !== null && seg.y2 !== undefined) {
502
518
  const cVals2 = getRotVals(seg.x2, seg.y2)
503
- points.splice(points.length, 0, cVals1.x, cVals1.y, cVals2.x, cVals2.y)
519
+ newVals.x2 = cVals2.x
520
+ newVals.y2 = cVals2.y
504
521
  }
522
+
523
+ const points = props.map((prop) => {
524
+ if (Object.prototype.hasOwnProperty.call(newVals, prop)) {
525
+ return newVals[prop]
526
+ }
527
+ const val = seg[prop]
528
+ return val === null || val === undefined ? 0 : val
529
+ })
505
530
  replacePathSeg(type, i, points)
506
531
  } // loop for each point
507
532
 
@@ -512,8 +537,18 @@ export const recalcRotatedPath = () => {
512
537
  // now we must set the new transform to be rotated around the new center
513
538
  const Rnc = svgCanvas.getSvgRoot().createSVGTransform()
514
539
  const tlist = getTransformList(currentPath)
540
+ if (!tlist) { return }
515
541
  Rnc.setRotate((angle * 180.0 / Math.PI), newcx, newcy)
516
- tlist.replaceItem(Rnc, 0)
542
+ if (tlist.numberOfItems) {
543
+ if (typeof tlist.replaceItem === 'function') {
544
+ tlist.replaceItem(Rnc, 0)
545
+ } else {
546
+ tlist.removeItem(0)
547
+ tlist.insertItemBefore(Rnc, 0)
548
+ }
549
+ } else {
550
+ tlist.appendItem(Rnc)
551
+ }
517
552
  }
518
553
 
519
554
  // ====================================
@@ -571,7 +606,7 @@ export const reorientGrads = (elem, m) => {
571
606
  }
572
607
  newgrad.id = svgCanvas.getNextId()
573
608
  findDefs().append(newgrad)
574
- elem.setAttribute(type, 'url(#' + newgrad.id + ')')
609
+ elem.setAttribute(type, `url(#${newgrad.id})`)
575
610
  }
576
611
  }
577
612
  }
@@ -618,7 +653,7 @@ export const convertPath = (pth, toRel) => {
618
653
  switch (type) {
619
654
  case 1: // z,Z closepath (Z/z)
620
655
  d += 'z'
621
- if (lastM && !toRel) {
656
+ if (lastM) {
622
657
  curx = lastM[0]
623
658
  cury = lastM[1]
624
659
  }
@@ -765,10 +800,10 @@ const pathDSegment = (letter, points, morePoints, lastPoint) => {
765
800
  })
766
801
  let segment = letter + points.join(' ')
767
802
  if (morePoints) {
768
- segment += ' ' + morePoints.join(' ')
803
+ segment += ` ${morePoints.join(' ')}`
769
804
  }
770
805
  if (lastPoint) {
771
- segment += ' ' + shortFloat(lastPoint)
806
+ segment += ` ${shortFloat(lastPoint)}`
772
807
  }
773
808
  return segment
774
809
  }