@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.
@@ -38,7 +38,7 @@ export const init = (canvas) => {
38
38
  * @param {boolean} toRel - true of convert to relative
39
39
  * @returns {string}
40
40
  */
41
- export const convertPath = function (pth, toRel) {
41
+ export const convertPath = (pth, toRel) => {
42
42
  const { pathSegList } = pth
43
43
  const len = pathSegList.numberOfItems
44
44
  let curx = 0; let cury = 0
@@ -64,7 +64,7 @@ export const convertPath = function (pth, toRel) {
64
64
  case 'z': // z,Z closepath (Z/z)
65
65
  case 'Z':
66
66
  d += 'z'
67
- if (lastM && !toRel) {
67
+ if (lastM) {
68
68
  curx = lastM[0]
69
69
  cury = lastM[1]
70
70
  }
@@ -217,34 +217,27 @@ export const convertPath = function (pth, toRel) {
217
217
  * @param {Integer[]} [lastPoint] - x,y point
218
218
  * @returns {string}
219
219
  */
220
- function pathDSegment (letter, points, morePoints, lastPoint) {
221
- points.forEach(function (pnt, i) {
222
- points[i] = shortFloat(pnt)
223
- })
224
- let segment = letter + points.join(' ')
225
- if (morePoints) {
226
- segment += ' ' + morePoints.join(' ')
227
- }
228
- if (lastPoint) {
229
- segment += ' ' + shortFloat(lastPoint)
230
- }
231
- return segment
220
+ const pathDSegment = (letter, points, morePoints, lastPoint) => {
221
+ const parts = [
222
+ letter + points.map(pnt => shortFloat(pnt)).join(' '),
223
+ morePoints ? morePoints.join(' ') : null,
224
+ lastPoint ? shortFloat(lastPoint) : null
225
+ ].filter(Boolean)
226
+ return parts.join(' ')
232
227
  }
233
228
 
234
229
  /**
235
230
  * Group: Path edit functions.
236
231
  * Functions relating to editing path elements.
237
- * @namespace {PlainObject} pathActions
232
+ * @class PathActions
238
233
  * @memberof module:path
239
234
  */
240
- export const pathActionsMethod = (function () {
241
- let subpath = false
242
- let newPoint; let firstCtrl
243
-
244
- let currentPath = null
245
- let hasMoved = false
246
- // No `svgCanvas` yet but should be ok as is `null` by default
247
- // svgCanvas.setDrawnPath(null);
235
+ class PathActions {
236
+ #subpath = false
237
+ #newPoint = null
238
+ #firstCtrl = null
239
+ #currentPath = null
240
+ #hasMoved = false
248
241
 
249
242
  /**
250
243
  * This function converts a polyline (created by the fh_path tool) into
@@ -253,9 +246,9 @@ export const pathActionsMethod = (function () {
253
246
  * @function smoothPolylineIntoPath
254
247
  * @param {Element} element
255
248
  * @returns {Element}
249
+ * @private
256
250
  */
257
- const smoothPolylineIntoPath = function (element) {
258
- let i
251
+ #smoothPolylineIntoPath = (element) => {
259
252
  const { points } = element
260
253
  const N = points.numberOfItems
261
254
  if (N >= 4) {
@@ -272,9 +265,11 @@ export const pathActionsMethod = (function () {
272
265
  // - https://www.codeproject.com/KB/graphics/BezierSpline.aspx?msg=2956963
273
266
  // - https://www.ian-ko.com/ET_GeoWizards/UserGuide/smooth.htm
274
267
  // - https://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/Bezier/bezier-der.html
275
- let curpos = points.getItem(0); let prevCtlPt = null
268
+ let curpos = points.getItem(0)
269
+ let prevCtlPt = null
276
270
  let d = []
277
- d.push(['M', curpos.x, ',', curpos.y, ' C'].join(''))
271
+ d.push(`M${curpos.x},${curpos.y} C`)
272
+ let i
278
273
  for (i = 1; i <= (N - 4); i += 3) {
279
274
  let ct1 = points.getItem(i)
280
275
  const ct2 = points.getItem(i + 1)
@@ -321,917 +316,943 @@ export const pathActionsMethod = (function () {
321
316
  return element
322
317
  }
323
318
 
324
- return (/** @lends module:path.pathActions */ {
325
- /**
326
- * @param {MouseEvent} evt
327
- * @param {Element} mouseTarget
328
- * @param {Float} startX
329
- * @param {Float} startY
330
- * @returns {boolean|void}
331
- */
332
- mouseDown (evt, mouseTarget, startX, startY) {
333
- let id
334
- if (svgCanvas.getCurrentMode() === 'path') {
335
- let mouseX = startX // Was this meant to work with the other `mouseX`? (was defined globally so adding `let` to at least avoid a global)
336
- let mouseY = startY // Was this meant to work with the other `mouseY`? (was defined globally so adding `let` to at least avoid a global)
337
-
338
- const zoom = svgCanvas.getZoom()
339
- let x = mouseX / zoom
340
- let y = mouseY / zoom
341
- let stretchy = getElement('path_stretch_line')
342
- newPoint = [x, y]
343
-
344
- if (svgCanvas.getGridSnapping()) {
345
- x = snapToGrid(x)
346
- y = snapToGrid(y)
347
- mouseX = snapToGrid(mouseX)
348
- mouseY = snapToGrid(mouseY)
349
- }
319
+ /**
320
+ * @param {MouseEvent} evt
321
+ * @param {Element} mouseTarget
322
+ * @param {Float} startX
323
+ * @param {Float} startY
324
+ * @returns {boolean|void}
325
+ */
326
+ mouseDown (evt, mouseTarget, startX, startY) {
327
+ let id
328
+ if (svgCanvas.getCurrentMode() === 'path') {
329
+ let mouseX = startX // Was this meant to work with the other `mouseX`? (was defined globally so adding `let` to at least avoid a global)
330
+ let mouseY = startY // Was this meant to work with the other `mouseY`? (was defined globally so adding `let` to at least avoid a global)
350
331
 
351
- if (!stretchy) {
352
- stretchy = document.createElementNS(NS.SVG, 'path')
353
- assignAttributes(stretchy, {
354
- id: 'path_stretch_line',
355
- stroke: '#22C',
356
- 'stroke-width': '0.5',
357
- fill: 'none'
358
- })
359
- getElement('selectorParentGroup').append(stretchy)
360
- }
361
- stretchy.setAttribute('display', 'inline')
362
-
363
- let keep = null
364
- let index
365
- // if pts array is empty, create path element with M at current point
366
- const drawnPath = svgCanvas.getDrawnPath()
367
- if (!drawnPath) {
368
- const dAttr = 'M' + x + ',' + y + ' ' // Was this meant to work with the other `dAttr`? (was defined globally so adding `var` to at least avoid a global)
369
- /* drawnPath = */ svgCanvas.setDrawnPath(svgCanvas.addSVGElementsFromJson({
370
- element: 'path',
371
- curStyles: true,
372
- attr: {
373
- d: dAttr,
374
- id: svgCanvas.getNextId(),
375
- opacity: svgCanvas.getOpacity() / 2
376
- }
377
- }))
378
- // set stretchy line to first point
379
- stretchy.setAttribute('d', ['M', mouseX, mouseY, mouseX, mouseY].join(' '))
380
- index = subpath ? path.segs.length : 0
381
- svgCanvas.addPointGrip(index, mouseX, mouseY)
382
- } else {
383
- // determine if we clicked on an existing point
384
- const seglist = drawnPath.pathSegList
385
- let i = seglist.numberOfItems
386
- const FUZZ = 6 / zoom
387
- let clickOnPoint = false
388
- while (i) {
389
- i--
390
- const item = seglist.getItem(i)
391
- const px = item.x; const py = item.y
392
- // found a matching point
393
- if (x >= (px - FUZZ) && x <= (px + FUZZ) &&
332
+ const zoom = svgCanvas.getZoom()
333
+ let x = mouseX / zoom
334
+ let y = mouseY / zoom
335
+ let stretchy = getElement('path_stretch_line')
336
+ this.#newPoint = [x, y]
337
+
338
+ if (svgCanvas.getGridSnapping()) {
339
+ x = snapToGrid(x)
340
+ y = snapToGrid(y)
341
+ mouseX = snapToGrid(mouseX)
342
+ mouseY = snapToGrid(mouseY)
343
+ }
344
+
345
+ if (!stretchy) {
346
+ stretchy = document.createElementNS(NS.SVG, 'path')
347
+ assignAttributes(stretchy, {
348
+ id: 'path_stretch_line',
349
+ stroke: '#22C',
350
+ 'stroke-width': '0.5',
351
+ fill: 'none'
352
+ })
353
+ getElement('selectorParentGroup').append(stretchy)
354
+ }
355
+ stretchy.setAttribute('display', 'inline')
356
+
357
+ let keep = null
358
+ let index
359
+ // if pts array is empty, create path element with M at current point
360
+ const drawnPath = svgCanvas.getDrawnPath()
361
+ if (!drawnPath) {
362
+ const dAttr = `M${x},${y} `
363
+ /* drawnPath = */ svgCanvas.setDrawnPath(svgCanvas.addSVGElementsFromJson({
364
+ element: 'path',
365
+ curStyles: true,
366
+ attr: {
367
+ d: dAttr,
368
+ id: svgCanvas.getNextId(),
369
+ opacity: svgCanvas.getOpacity() / 2
370
+ }
371
+ }))
372
+ // set stretchy line to first point
373
+ stretchy.setAttribute('d', `M${mouseX} ${mouseY} ${mouseX} ${mouseY}`)
374
+ index = this.#subpath ? path.segs.length : 0
375
+ svgCanvas.addPointGrip(index, mouseX, mouseY)
376
+ } else {
377
+ // determine if we clicked on an existing point
378
+ const seglist = drawnPath.pathSegList
379
+ let i = seglist.numberOfItems
380
+ const FUZZ = 6 / zoom
381
+ let clickOnPoint = false
382
+ while (i) {
383
+ i--
384
+ const item = seglist.getItem(i)
385
+ const px = item.x; const py = item.y
386
+ // found a matching point
387
+ if (x >= (px - FUZZ) && x <= (px + FUZZ) &&
394
388
  y >= (py - FUZZ) && y <= (py + FUZZ)
395
- ) {
396
- clickOnPoint = true
397
- break
398
- }
389
+ ) {
390
+ clickOnPoint = true
391
+ break
392
+ }
393
+ }
394
+
395
+ // get path element that we are in the process of creating
396
+ id = svgCanvas.getId()
397
+
398
+ // Remove previous path object if previously created
399
+ svgCanvas.removePath_(id)
400
+
401
+ const newpath = getElement(id)
402
+ let newseg
403
+ let sSeg
404
+ const len = seglist.numberOfItems
405
+ // if we clicked on an existing point, then we are done this path, commit it
406
+ // (i, i+1) are the x,y that were clicked on
407
+ if (clickOnPoint) {
408
+ // if clicked on any other point but the first OR
409
+ // the first point was clicked on and there are less than 3 points
410
+ // then leave the path open
411
+ // otherwise, close the path
412
+ if (i <= 1 && len >= 2) {
413
+ // Create end segment
414
+ const absX = seglist.getItem(0).x
415
+ const absY = seglist.getItem(0).y
416
+
417
+ sSeg = stretchy.pathSegList.getItem(1)
418
+ newseg = sSeg.pathSegType === 4
419
+ ? drawnPath.createSVGPathSegLinetoAbs(absX, absY)
420
+ : drawnPath.createSVGPathSegCurvetoCubicAbs(absX, absY, sSeg.x1 / zoom, sSeg.y1 / zoom, absX, absY)
421
+
422
+ const endseg = drawnPath.createSVGPathSegClosePath()
423
+ seglist.appendItem(newseg)
424
+ seglist.appendItem(endseg)
425
+ } else if (len < 3) {
426
+ keep = false
427
+ return keep
399
428
  }
429
+ stretchy.remove()
400
430
 
401
- // get path element that we are in the process of creating
402
- id = svgCanvas.getId()
403
-
404
- // Remove previous path object if previously created
405
- svgCanvas.removePath_(id)
406
-
407
- const newpath = getElement(id)
408
- let newseg
409
- let sSeg
410
- const len = seglist.numberOfItems
411
- // if we clicked on an existing point, then we are done this path, commit it
412
- // (i, i+1) are the x,y that were clicked on
413
- if (clickOnPoint) {
414
- // if clicked on any other point but the first OR
415
- // the first point was clicked on and there are less than 3 points
416
- // then leave the path open
417
- // otherwise, close the path
418
- if (i <= 1 && len >= 2) {
419
- // Create end segment
420
- const absX = seglist.getItem(0).x
421
- const absY = seglist.getItem(0).y
422
-
423
- sSeg = stretchy.pathSegList.getItem(1)
424
- newseg = sSeg.pathSegType === 4
425
- ? drawnPath.createSVGPathSegLinetoAbs(absX, absY)
426
- : drawnPath.createSVGPathSegCurvetoCubicAbs(absX, absY, sSeg.x1 / zoom, sSeg.y1 / zoom, absX, absY)
427
-
428
- const endseg = drawnPath.createSVGPathSegClosePath()
429
- seglist.appendItem(newseg)
430
- seglist.appendItem(endseg)
431
- } else if (len < 3) {
432
- keep = false
433
- return keep
431
+ // This will signal to commit the path
432
+ // const element = newpath; // Other event handlers define own `element`, so this was probably not meant to interact with them or one which shares state (as there were none); I therefore adding a missing `var` to avoid a global
433
+ /* drawnPath = */ svgCanvas.setDrawnPath(null)
434
+ svgCanvas.setStarted(false)
435
+
436
+ if (this.#subpath) {
437
+ if (path.matrix) {
438
+ svgCanvas.remapElement(newpath, {}, path.matrix.inverse())
434
439
  }
435
- stretchy.remove()
436
-
437
- // This will signal to commit the path
438
- // const element = newpath; // Other event handlers define own `element`, so this was probably not meant to interact with them or one which shares state (as there were none); I therefore adding a missing `var` to avoid a global
439
- /* drawnPath = */ svgCanvas.setDrawnPath(null)
440
- svgCanvas.setStarted(false)
441
-
442
- if (subpath) {
443
- if (path.matrix) {
444
- svgCanvas.remapElement(newpath, {}, path.matrix.inverse())
445
- }
446
-
447
- const newD = newpath.getAttribute('d')
448
- const origD = path.elem.getAttribute('d')
449
- path.elem.setAttribute('d', origD + newD)
450
- newpath.parentNode.removeChild(newpath)
451
- if (path.matrix) {
452
- svgCanvas.recalcRotatedPath()
453
- }
454
- pathActionsMethod.toEditMode(path.elem)
455
- path.selectPt()
456
- return false
440
+
441
+ const newD = newpath.getAttribute('d')
442
+ const origD = path.elem.getAttribute('d')
443
+ path.elem.setAttribute('d', origD + newD)
444
+ newpath.parentNode.removeChild(newpath)
445
+ if (path.matrix) {
446
+ svgCanvas.recalcRotatedPath()
457
447
  }
448
+ pathActionsMethod.toEditMode(path.elem)
449
+ path.selectPt()
450
+ return false
451
+ }
458
452
  // else, create a new point, update path element
459
- } else {
460
- // Checks if current target or parents are #svgcontent
461
- if (!(svgCanvas.getContainer() !== svgCanvas.getMouseTarget(evt) && svgCanvas.getContainer().contains(
462
- svgCanvas.getMouseTarget(evt)
463
- ))) {
464
- // Clicked outside canvas, so don't make point
465
- return false
466
- }
467
-
468
- const num = drawnPath.pathSegList.numberOfItems
469
- const last = drawnPath.pathSegList.getItem(num - 1)
470
- const lastx = last.x; const lasty = last.y
453
+ } else {
454
+ // Checks if current target or parents are #svgcontent
455
+ if (!(svgCanvas.getContainer() !== svgCanvas.getMouseTarget(evt) && svgCanvas.getContainer().contains(
456
+ svgCanvas.getMouseTarget(evt)
457
+ ))) {
458
+ // Clicked outside canvas, so don't make point
459
+ return false
460
+ }
471
461
 
472
- if (evt.shiftKey) {
473
- const xya = snapToAngle(lastx, lasty, x, y);
474
- ({ x, y } = xya)
475
- }
462
+ const num = drawnPath.pathSegList.numberOfItems
463
+ const last = drawnPath.pathSegList.getItem(num - 1)
464
+ const lastx = last.x; const lasty = last.y
476
465
 
477
- // Use the segment defined by stretchy
478
- sSeg = stretchy.pathSegList.getItem(1)
479
- newseg = sSeg.pathSegType === 4
480
- ? drawnPath.createSVGPathSegLinetoAbs(svgCanvas.round(x), svgCanvas.round(y))
481
- : drawnPath.createSVGPathSegCurvetoCubicAbs(
482
- svgCanvas.round(x),
483
- svgCanvas.round(y),
484
- sSeg.x1 / zoom,
485
- sSeg.y1 / zoom,
486
- sSeg.x2 / zoom,
487
- sSeg.y2 / zoom
488
- )
489
-
490
- drawnPath.pathSegList.appendItem(newseg)
491
-
492
- x *= zoom
493
- y *= zoom
494
-
495
- // set stretchy line to latest point
496
- stretchy.setAttribute('d', ['M', x, y, x, y].join(' '))
497
- index = num
498
- if (subpath) { index += path.segs.length }
499
- svgCanvas.addPointGrip(index, x, y)
466
+ if (evt.shiftKey) {
467
+ const xya = snapToAngle(lastx, lasty, x, y);
468
+ ({ x, y } = xya)
500
469
  }
501
- // keep = true;
502
- }
503
470
 
504
- return undefined
471
+ // Use the segment defined by stretchy
472
+ sSeg = stretchy.pathSegList.getItem(1)
473
+ newseg = sSeg.pathSegType === 4
474
+ ? drawnPath.createSVGPathSegLinetoAbs(svgCanvas.round(x), svgCanvas.round(y))
475
+ : drawnPath.createSVGPathSegCurvetoCubicAbs(
476
+ svgCanvas.round(x),
477
+ svgCanvas.round(y),
478
+ sSeg.x1 / zoom,
479
+ sSeg.y1 / zoom,
480
+ sSeg.x2 / zoom,
481
+ sSeg.y2 / zoom
482
+ )
483
+
484
+ drawnPath.pathSegList.appendItem(newseg)
485
+
486
+ x *= zoom
487
+ y *= zoom
488
+
489
+ // set stretchy line to latest point
490
+ stretchy.setAttribute('d', ['M', x, y, x, y].join(' '))
491
+ index = num
492
+ if (this.#subpath) { index += path.segs.length }
493
+ svgCanvas.addPointGrip(index, x, y)
494
+ }
495
+ // keep = true;
505
496
  }
506
497
 
507
- // TODO: Make sure currentPath isn't null at this point
508
- if (!path) { return undefined }
498
+ return undefined
499
+ }
500
+
501
+ // TODO: Make sure currentPath isn't null at this point
502
+ if (!path) { return undefined }
509
503
 
510
- path.storeD();
504
+ path.storeD();
511
505
 
512
- ({ id } = evt.target)
513
- let curPt
514
- if (id.substr(0, 14) === 'pathpointgrip_') {
515
- // Select this point
516
- curPt = path.cur_pt = Number.parseInt(id.substr(14))
517
- path.dragging = [startX, startY]
518
- const seg = path.segs[curPt]
506
+ ({ id } = evt.target)
507
+ let curPt
508
+ if (id.startsWith('pathpointgrip_')) {
509
+ // Select this point
510
+ curPt = path.cur_pt = Number.parseInt(id.slice(14))
511
+ path.dragging = [startX, startY]
512
+ const seg = path.segs[curPt]
519
513
 
520
- // only clear selection if shift is not pressed (otherwise, add
521
- // node to selection)
522
- if (!evt.shiftKey) {
523
- if (path.selected_pts.length <= 1 || !seg.selected) {
524
- path.clearSelection()
525
- }
526
- path.addPtsToSelection(curPt)
527
- } else if (seg.selected) {
528
- path.removePtFromSelection(curPt)
529
- } else {
530
- path.addPtsToSelection(curPt)
514
+ // only clear selection if shift is not pressed (otherwise, add
515
+ // node to selection)
516
+ if (!evt.shiftKey) {
517
+ if (path.selected_pts.length <= 1 || !seg.selected) {
518
+ path.clearSelection()
531
519
  }
532
- } else if (id.startsWith('ctrlpointgrip_')) {
533
- path.dragging = [startX, startY]
534
-
535
- const parts = id.split('_')[1].split('c')
536
- curPt = Number(parts[0])
537
- const ctrlNum = Number(parts[1])
538
- path.selectPt(curPt, ctrlNum)
520
+ path.addPtsToSelection(curPt)
521
+ } else if (seg.selected) {
522
+ path.removePtFromSelection(curPt)
523
+ } else {
524
+ path.addPtsToSelection(curPt)
539
525
  }
526
+ } else if (id.startsWith('ctrlpointgrip_')) {
527
+ path.dragging = [startX, startY]
540
528
 
541
- // Start selection box
542
- if (!path.dragging) {
543
- let rubberBox = svgCanvas.getRubberBox()
544
- if (!rubberBox) {
545
- rubberBox = svgCanvas.setRubberBox(
546
- svgCanvas.selectorManager.getRubberBandBox()
547
- )
548
- }
549
- const zoom = svgCanvas.getZoom()
550
- assignAttributes(rubberBox, {
551
- x: startX * zoom,
552
- y: startY * zoom,
553
- width: 0,
554
- height: 0,
555
- display: 'inline'
556
- }, 100)
529
+ const parts = id.split('_')[1].split('c')
530
+ curPt = Number(parts[0])
531
+ const ctrlNum = Number(parts[1])
532
+ path.selectPt(curPt, ctrlNum)
533
+ }
534
+
535
+ // Start selection box
536
+ if (!path.dragging) {
537
+ let rubberBox = svgCanvas.getRubberBox()
538
+ if (!rubberBox) {
539
+ rubberBox = svgCanvas.setRubberBox(
540
+ svgCanvas.selectorManager.getRubberBandBox()
541
+ )
557
542
  }
558
- return undefined
559
- },
560
- /**
543
+ const zoom = svgCanvas.getZoom()
544
+ assignAttributes(rubberBox, {
545
+ x: startX * zoom,
546
+ y: startY * zoom,
547
+ width: 0,
548
+ height: 0,
549
+ display: 'inline'
550
+ }, 100)
551
+ }
552
+ return undefined
553
+ }
554
+
555
+ /**
561
556
  * @param {Float} mouseX
562
557
  * @param {Float} mouseY
563
558
  * @returns {void}
564
559
  */
565
- mouseMove (mouseX, mouseY) {
566
- const zoom = svgCanvas.getZoom()
567
- hasMoved = true
568
- const drawnPath = svgCanvas.getDrawnPath()
569
- if (svgCanvas.getCurrentMode() === 'path') {
570
- if (!drawnPath) { return }
571
- const seglist = drawnPath.pathSegList
572
- const index = seglist.numberOfItems - 1
573
-
574
- if (newPoint) {
575
- // First point
576
- // if (!index) { return; }
577
-
578
- // Set control points
579
- const pointGrip1 = svgCanvas.addCtrlGrip('1c1')
580
- const pointGrip2 = svgCanvas.addCtrlGrip('0c2')
581
-
582
- // dragging pointGrip1
583
- pointGrip1.setAttribute('cx', mouseX)
584
- pointGrip1.setAttribute('cy', mouseY)
585
- pointGrip1.setAttribute('display', 'inline')
586
-
587
- const ptX = newPoint[0]
588
- const ptY = newPoint[1]
589
-
590
- // set curve
591
- // const seg = seglist.getItem(index);
592
- const curX = mouseX / zoom
593
- const curY = mouseY / zoom
594
- const altX = (ptX + (ptX - curX))
595
- const altY = (ptY + (ptY - curY))
596
-
597
- pointGrip2.setAttribute('cx', altX * zoom)
598
- pointGrip2.setAttribute('cy', altY * zoom)
599
- pointGrip2.setAttribute('display', 'inline')
600
-
601
- const ctrlLine = svgCanvas.getCtrlLine(1)
602
- assignAttributes(ctrlLine, {
603
- x1: mouseX,
604
- y1: mouseY,
605
- x2: altX * zoom,
606
- y2: altY * zoom,
607
- display: 'inline'
608
- })
609
-
610
- if (index === 0) {
611
- firstCtrl = [mouseX, mouseY]
612
- } else {
613
- const last = seglist.getItem(index - 1)
614
- let lastX = last.x
615
- let lastY = last.y
616
-
617
- if (last.pathSegType === 6) {
618
- lastX += (lastX - last.x2)
619
- lastY += (lastY - last.y2)
620
- } else if (firstCtrl) {
621
- lastX = firstCtrl[0] / zoom
622
- lastY = firstCtrl[1] / zoom
623
- }
624
- svgCanvas.replacePathSeg(6, index, [ptX, ptY, lastX, lastY, altX, altY], drawnPath)
625
- }
560
+ mouseMove (mouseX, mouseY) {
561
+ const zoom = svgCanvas.getZoom()
562
+ this.#hasMoved = true
563
+ const drawnPath = svgCanvas.getDrawnPath()
564
+ if (svgCanvas.getCurrentMode() === 'path') {
565
+ if (!drawnPath) { return }
566
+ const seglist = drawnPath.pathSegList
567
+ const index = seglist.numberOfItems - 1
568
+
569
+ if (this.#newPoint) {
570
+ // First point
571
+ // if (!index) { return; }
572
+
573
+ // Set control points
574
+ const pointGrip1 = svgCanvas.addCtrlGrip('1c1')
575
+ const pointGrip2 = svgCanvas.addCtrlGrip('0c2')
576
+
577
+ // dragging pointGrip1
578
+ pointGrip1.setAttribute('cx', mouseX)
579
+ pointGrip1.setAttribute('cy', mouseY)
580
+ pointGrip1.setAttribute('display', 'inline')
581
+
582
+ const ptX = this.#newPoint[0]
583
+ const ptY = this.#newPoint[1]
584
+
585
+ // set curve
586
+ // const seg = seglist.getItem(index);
587
+ const curX = mouseX / zoom
588
+ const curY = mouseY / zoom
589
+ const altX = (ptX + (ptX - curX))
590
+ const altY = (ptY + (ptY - curY))
591
+
592
+ pointGrip2.setAttribute('cx', altX * zoom)
593
+ pointGrip2.setAttribute('cy', altY * zoom)
594
+ pointGrip2.setAttribute('display', 'inline')
595
+
596
+ const ctrlLine = svgCanvas.getCtrlLine(1)
597
+ assignAttributes(ctrlLine, {
598
+ x1: mouseX,
599
+ y1: mouseY,
600
+ x2: altX * zoom,
601
+ y2: altY * zoom,
602
+ display: 'inline'
603
+ })
604
+
605
+ if (index === 0) {
606
+ this.#firstCtrl = [mouseX, mouseY]
626
607
  } else {
627
- const stretchy = getElement('path_stretch_line')
628
- if (stretchy) {
629
- const prev = seglist.getItem(index)
630
- if (prev.pathSegType === 6) {
631
- const prevX = prev.x + (prev.x - prev.x2)
632
- const prevY = prev.y + (prev.y - prev.y2)
633
- svgCanvas.replacePathSeg(
634
- 6,
635
- 1,
636
- [mouseX, mouseY, prevX * zoom, prevY * zoom, mouseX, mouseY],
637
- stretchy
638
- )
639
- } else if (firstCtrl) {
640
- svgCanvas.replacePathSeg(6, 1, [mouseX, mouseY, firstCtrl[0], firstCtrl[1], mouseX, mouseY], stretchy)
641
- } else {
642
- svgCanvas.replacePathSeg(4, 1, [mouseX, mouseY], stretchy)
643
- }
608
+ const last = seglist.getItem(index - 1)
609
+ let lastX = last.x
610
+ let lastY = last.y
611
+
612
+ if (last.pathSegType === 6) {
613
+ lastX += (lastX - last.x2)
614
+ lastY += (lastY - last.y2)
615
+ } else if (this.#firstCtrl) {
616
+ lastX = this.#firstCtrl[0] / zoom
617
+ lastY = this.#firstCtrl[1] / zoom
644
618
  }
645
- }
646
- return
647
- }
648
- // if we are dragging a point, let's move it
649
- if (path.dragging) {
650
- const pt = svgCanvas.getPointFromGrip({
651
- x: path.dragging[0],
652
- y: path.dragging[1]
653
- }, path)
654
- const mpt = svgCanvas.getPointFromGrip({
655
- x: mouseX,
656
- y: mouseY
657
- }, path)
658
- const diffX = mpt.x - pt.x
659
- const diffY = mpt.y - pt.y
660
- path.dragging = [mouseX, mouseY]
661
-
662
- if (path.dragctrl) {
663
- path.moveCtrl(diffX, diffY)
664
- } else {
665
- path.movePts(diffX, diffY)
619
+ svgCanvas.replacePathSeg(6, index, [ptX, ptY, lastX, lastY, altX, altY], drawnPath)
666
620
  }
667
621
  } else {
668
- path.selected_pts = []
669
- path.eachSeg(function (_i) {
670
- const seg = this
671
- if (!seg.next && !seg.prev) { return }
672
-
673
- // const {item} = seg;
674
- const rubberBox = svgCanvas.getRubberBox()
675
- const rbb = getBBox(rubberBox)
676
-
677
- const pt = svgCanvas.getGripPt(seg)
678
- const ptBb = {
679
- x: pt.x,
680
- y: pt.y,
681
- width: 0,
682
- height: 0
622
+ const stretchy = getElement('path_stretch_line')
623
+ if (stretchy) {
624
+ const prev = seglist.getItem(index)
625
+ if (prev.pathSegType === 6) {
626
+ const prevX = prev.x + (prev.x - prev.x2)
627
+ const prevY = prev.y + (prev.y - prev.y2)
628
+ svgCanvas.replacePathSeg(
629
+ 6,
630
+ 1,
631
+ [mouseX, mouseY, prevX * zoom, prevY * zoom, mouseX, mouseY],
632
+ stretchy
633
+ )
634
+ } else if (this.#firstCtrl) {
635
+ svgCanvas.replacePathSeg(6, 1, [mouseX, mouseY, this.#firstCtrl[0], this.#firstCtrl[1], mouseX, mouseY], stretchy)
636
+ } else {
637
+ svgCanvas.replacePathSeg(4, 1, [mouseX, mouseY], stretchy)
683
638
  }
639
+ }
640
+ }
641
+ return
642
+ }
643
+ // if we are dragging a point, let's move it
644
+ if (path.dragging) {
645
+ const pt = svgCanvas.getPointFromGrip({
646
+ x: path.dragging[0],
647
+ y: path.dragging[1]
648
+ }, path)
649
+ const mpt = svgCanvas.getPointFromGrip({
650
+ x: mouseX,
651
+ y: mouseY
652
+ }, path)
653
+ const diffX = mpt.x - pt.x
654
+ const diffY = mpt.y - pt.y
655
+ path.dragging = [mouseX, mouseY]
656
+
657
+ if (path.dragctrl) {
658
+ path.moveCtrl(diffX, diffY)
659
+ } else {
660
+ path.movePts(diffX, diffY)
661
+ }
662
+ } else {
663
+ path.selected_pts = []
664
+ path.eachSeg(function (_i) {
665
+ const seg = this
666
+ if (!seg.next && !seg.prev) return
667
+
668
+ // const {item} = seg;
669
+ const rubberBox = svgCanvas.getRubberBox()
670
+ const rbb = getBBox(rubberBox)
671
+
672
+ const pt = svgCanvas.getGripPt(seg)
673
+ const ptBb = {
674
+ x: pt.x,
675
+ y: pt.y,
676
+ width: 0,
677
+ height: 0
678
+ }
684
679
 
685
- const sel = rectsIntersect(rbb, ptBb)
680
+ const sel = rectsIntersect(rbb, ptBb)
686
681
 
687
- this.select(sel)
688
- // Note that addPtsToSelection is not being run
689
- if (sel) { path.selected_pts.push(seg.index) }
690
- })
691
- }
692
- },
693
- /**
682
+ this.select(sel)
683
+ // Note that addPtsToSelection is not being run
684
+ if (sel) { path.selected_pts.push(seg.index) }
685
+ })
686
+ }
687
+ }
688
+
689
+ /**
694
690
  * @typedef module:path.keepElement
695
691
  * @type {PlainObject}
696
692
  * @property {boolean} keep
697
693
  * @property {Element} element
698
694
  */
699
- /**
695
+ /**
700
696
  * @param {Event} evt
701
697
  * @param {Element} element
702
698
  * @param {Float} _mouseX
703
699
  * @param {Float} _mouseY
704
700
  * @returns {module:path.keepElement|void}
705
701
  */
706
- mouseUp (evt, element, _mouseX, _mouseY) {
707
- const drawnPath = svgCanvas.getDrawnPath()
708
- // Create mode
709
- if (svgCanvas.getCurrentMode() === 'path') {
710
- newPoint = null
711
- if (!drawnPath) {
712
- element = getElement(svgCanvas.getId())
713
- svgCanvas.setStarted(false)
714
- firstCtrl = null
715
- }
716
-
717
- return {
718
- keep: true,
719
- element
720
- }
702
+ mouseUp (evt, element, _mouseX, _mouseY) {
703
+ const drawnPath = svgCanvas.getDrawnPath()
704
+ // Create mode
705
+ if (svgCanvas.getCurrentMode() === 'path') {
706
+ this.#newPoint = null
707
+ if (!drawnPath) {
708
+ element = getElement(svgCanvas.getId())
709
+ svgCanvas.setStarted(false)
710
+ this.#firstCtrl = null
721
711
  }
722
712
 
723
- // Edit mode
724
- const rubberBox = svgCanvas.getRubberBox()
725
- if (path.dragging) {
726
- const lastPt = path.cur_pt
713
+ return {
714
+ keep: true,
715
+ element
716
+ }
717
+ }
727
718
 
728
- path.dragging = false
729
- path.dragctrl = false
730
- path.update()
719
+ // Edit mode
720
+ const rubberBox = svgCanvas.getRubberBox()
721
+ if (path.dragging) {
722
+ const lastPt = path.cur_pt
731
723
 
732
- if (hasMoved) {
733
- path.endChanges('Move path point(s)')
734
- }
724
+ path.dragging = false
725
+ path.dragctrl = false
726
+ path.update()
735
727
 
736
- if (!evt.shiftKey && !hasMoved) {
737
- path.selectPt(lastPt)
738
- }
739
- } else if (rubberBox?.getAttribute('display') !== 'none') {
740
- // Done with multi-node-select
741
- rubberBox.setAttribute('display', 'none')
728
+ if (this.#hasMoved) {
729
+ path.endChanges('Move path point(s)')
730
+ }
742
731
 
743
- if (rubberBox.getAttribute('width') <= 2 && rubberBox.getAttribute('height') <= 2) {
744
- pathActionsMethod.toSelectMode(evt.target)
745
- }
732
+ if (!evt.shiftKey && !this.#hasMoved) {
733
+ path.selectPt(lastPt)
734
+ }
735
+ } else if (rubberBox?.getAttribute('display') !== 'none') {
736
+ // Done with multi-node-select
737
+ rubberBox.setAttribute('display', 'none')
746
738
 
747
- // else, move back to select mode
748
- } else {
739
+ if (rubberBox.getAttribute('width') <= 2 && rubberBox.getAttribute('height') <= 2) {
749
740
  pathActionsMethod.toSelectMode(evt.target)
750
741
  }
751
- hasMoved = false
752
- return undefined
753
- },
754
- /**
742
+
743
+ // else, move back to select mode
744
+ } else {
745
+ pathActionsMethod.toSelectMode(evt.target)
746
+ }
747
+ this.#hasMoved = false
748
+ return undefined
749
+ }
750
+
751
+ /**
755
752
  * @param {Element} element
756
753
  * @returns {void}
757
754
  */
758
- toEditMode (element) {
759
- path = svgCanvas.getPath_(element)
760
- svgCanvas.setCurrentMode('pathedit')
761
- svgCanvas.clearSelection()
762
- path.setPathContext()
763
- path.show(true).update()
764
- path.oldbbox = getBBox(path.elem)
765
- subpath = false
766
- },
767
- /**
755
+ toEditMode (element) {
756
+ path = svgCanvas.getPath_(element)
757
+ svgCanvas.setCurrentMode('pathedit')
758
+ svgCanvas.clearSelection()
759
+ path.setPathContext()
760
+ path.show(true).update()
761
+ path.oldbbox = getBBox(path.elem)
762
+ this.#subpath = false
763
+ }
764
+
765
+ /**
768
766
  * @param {Element} elem
769
767
  * @fires module:svgcanvas.SvgCanvas#event:selected
770
768
  * @returns {void}
771
769
  */
772
- toSelectMode (elem) {
773
- const selPath = (elem === path.elem)
774
- svgCanvas.setCurrentMode('select')
775
- path.setPathContext()
776
- path.show(false)
777
- currentPath = false
778
- svgCanvas.clearSelection()
779
-
780
- if (path.matrix) {
781
- // Rotated, so may need to re-calculate the center
782
- svgCanvas.recalcRotatedPath()
783
- }
770
+ toSelectMode (elem) {
771
+ const selPath = (elem === path.elem)
772
+ svgCanvas.setCurrentMode('select')
773
+ path.setPathContext()
774
+ path.show(false)
775
+ this.#currentPath = false
776
+ svgCanvas.clearSelection()
777
+
778
+ if (path.matrix) {
779
+ // Rotated, so may need to re-calculate the center
780
+ svgCanvas.recalcRotatedPath()
781
+ }
784
782
 
785
- if (selPath) {
786
- svgCanvas.call('selected', [elem])
787
- svgCanvas.addToSelection([elem], true)
788
- }
789
- },
790
- /**
783
+ if (selPath) {
784
+ svgCanvas.call('selected', [elem])
785
+ svgCanvas.addToSelection([elem], true)
786
+ }
787
+ }
788
+
789
+ /**
791
790
  * @param {boolean} on
792
791
  * @returns {void}
793
792
  */
794
- addSubPath (on) {
795
- if (on) {
796
- // Internally we go into "path" mode, but in the UI it will
797
- // still appear as if in "pathedit" mode.
798
- svgCanvas.setCurrentMode('path')
799
- subpath = true
800
- } else {
801
- pathActionsMethod.clear(true)
802
- pathActionsMethod.toEditMode(path.elem)
803
- }
804
- },
805
- /**
793
+ addSubPath (on) {
794
+ if (on) {
795
+ // Internally we go into "path" mode, but in the UI it will
796
+ // still appear as if in "pathedit" mode.
797
+ svgCanvas.setCurrentMode('path')
798
+ this.#subpath = true
799
+ } else {
800
+ pathActionsMethod.clear(true)
801
+ pathActionsMethod.toEditMode(path.elem)
802
+ }
803
+ }
804
+
805
+ /**
806
806
  * @param {Element} target
807
807
  * @returns {void}
808
808
  */
809
- select (target) {
810
- if (currentPath === target) {
811
- pathActionsMethod.toEditMode(target)
812
- svgCanvas.setCurrentMode('pathedit')
809
+ select (target) {
810
+ if (this.#currentPath === target) {
811
+ pathActionsMethod.toEditMode(target)
812
+ svgCanvas.setCurrentMode('pathedit')
813
813
  // going into pathedit mode
814
- } else {
815
- currentPath = target
816
- }
817
- },
818
- /**
814
+ } else {
815
+ this.#currentPath = target
816
+ }
817
+ }
818
+
819
+ /**
819
820
  * @fires module:svgcanvas.SvgCanvas#event:changed
820
821
  * @returns {void}
821
822
  */
822
- reorient () {
823
- const elem = svgCanvas.getSelectedElements()[0]
824
- if (!elem) { return }
825
- const angl = getRotationAngle(elem)
826
- if (angl === 0) { return }
827
-
828
- const batchCmd = new BatchCommand('Reorient path')
829
- const changes = {
830
- d: elem.getAttribute('d'),
831
- transform: elem.getAttribute('transform')
832
- }
833
- batchCmd.addSubCommand(new ChangeElementCommand(elem, changes))
834
- svgCanvas.clearSelection()
835
- this.resetOrientation(elem)
823
+ reorient () {
824
+ const elem = svgCanvas.getSelectedElements()[0]
825
+ if (!elem) { return }
826
+ if (elem.nodeName !== 'path') { return }
827
+ const angl = getRotationAngle(elem)
828
+ if (angl === 0) { return }
829
+
830
+ const batchCmd = new BatchCommand('Reorient path')
831
+ const changes = {
832
+ d: elem.getAttribute('d'),
833
+ transform: elem.getAttribute('transform')
834
+ }
835
+ batchCmd.addSubCommand(new ChangeElementCommand(elem, changes))
836
+ svgCanvas.clearSelection()
837
+ this.resetOrientation(elem)
836
838
 
837
- svgCanvas.addCommandToHistory(batchCmd)
839
+ svgCanvas.addCommandToHistory(batchCmd)
838
840
 
839
- // Set matrix to null
840
- svgCanvas.getPath_(elem).show(false).matrix = null
841
+ // Set matrix to null
842
+ svgCanvas.getPath_(elem).show(false).matrix = null
841
843
 
842
- this.clear()
844
+ this.clear()
843
845
 
844
- svgCanvas.addToSelection([elem], true)
845
- svgCanvas.call('changed', svgCanvas.getSelectedElements())
846
- },
846
+ svgCanvas.addToSelection([elem], true)
847
+ svgCanvas.call('changed', svgCanvas.getSelectedElements())
848
+ }
847
849
 
848
- /**
850
+ /**
849
851
  * @param {boolean} remove Not in use
850
852
  * @returns {void}
851
853
  */
852
- clear () {
853
- const drawnPath = svgCanvas.getDrawnPath()
854
- currentPath = null
855
- if (drawnPath) {
856
- const elem = getElement(svgCanvas.getId())
857
- const psl = getElement('path_stretch_line')
858
- psl.parentNode.removeChild(psl)
859
- elem.parentNode.removeChild(elem)
860
- const pathpointgripContainer = getElement('pathpointgrip_container')
861
- const elements = pathpointgripContainer.querySelectorAll('*')
862
- Array.prototype.forEach.call(elements, function (el) {
863
- el.setAttribute('display', 'none')
864
- })
865
- firstCtrl = null
866
- svgCanvas.setDrawnPath(null)
867
- svgCanvas.setStarted(false)
868
- } else if (svgCanvas.getCurrentMode() === 'pathedit') {
869
- this.toSelectMode()
854
+ clear () {
855
+ const drawnPath = svgCanvas.getDrawnPath()
856
+ this.#currentPath = null
857
+ if (drawnPath) {
858
+ const elem = getElement(svgCanvas.getId())
859
+ const psl = getElement('path_stretch_line')
860
+ psl.parentNode.removeChild(psl)
861
+ elem.parentNode.removeChild(elem)
862
+ const pathpointgripContainer = getElement('pathpointgrip_container')
863
+ const elements = pathpointgripContainer.querySelectorAll('*')
864
+ for (const el of elements) {
865
+ el.setAttribute('display', 'none')
870
866
  }
871
- if (path) { path.init().show(false) }
872
- },
873
- /**
867
+ this.#firstCtrl = null
868
+ svgCanvas.setDrawnPath(null)
869
+ svgCanvas.setStarted(false)
870
+ } else if (svgCanvas.getCurrentMode() === 'pathedit') {
871
+ this.toSelectMode()
872
+ }
873
+ if (path) { path.init().show(false) }
874
+ }
875
+
876
+ /**
874
877
  * @param {?(Element|SVGPathElement)} pth
875
878
  * @returns {false|void}
876
879
  */
877
- resetOrientation (pth) {
878
- if (pth?.nodeName !== 'path') { return false }
879
- const tlist = getTransformList(pth)
880
- const m = transformListToTransform(tlist).matrix
881
- tlist.clear()
882
- pth.removeAttribute('transform')
883
- const segList = pth.pathSegList
884
-
885
- // Opera/win/non-EN throws an error here.
886
- // TODO: Find out why!
887
- // Presumed fixed in Opera 10.5, so commented out for now
888
-
889
- // try {
890
- const len = segList.numberOfItems
891
- // } catch(err) {
892
- // const fixed_d = pathActions.convertPath(pth);
893
- // pth.setAttribute('d', fixed_d);
894
- // segList = pth.pathSegList;
895
- // const len = segList.numberOfItems;
896
- // }
897
- // let lastX, lastY;
898
- for (let i = 0; i < len; ++i) {
899
- const seg = segList.getItem(i)
900
- const type = seg.pathSegType
901
- if (type === 1) { continue }
902
- const pts = [];
903
- ['', 1, 2].forEach(function (n) {
904
- const x = seg['x' + n]; const y = seg['y' + n]
905
- if (x !== undefined && y !== undefined) {
906
- const pt = transformPoint(x, y, m)
907
- pts.splice(pts.length, 0, pt.x, pt.y)
908
- }
909
- })
910
- svgCanvas.replacePathSeg(type, i, pts, pth)
880
+ resetOrientation (pth) {
881
+ if (pth?.nodeName !== 'path') { return false }
882
+ const tlist = getTransformList(pth)
883
+ const m = transformListToTransform(tlist).matrix
884
+ tlist.clear()
885
+ pth.removeAttribute('transform')
886
+ const segList = pth.pathSegList
887
+
888
+ // Opera/win/non-EN throws an error here.
889
+ // TODO: Find out why!
890
+ // Presumed fixed in Opera 10.5, so commented out for now
891
+
892
+ // try {
893
+ const len = segList.numberOfItems
894
+ // } catch(err) {
895
+ // const fixed_d = pathActions.convertPath(pth);
896
+ // pth.setAttribute('d', fixed_d);
897
+ // segList = pth.pathSegList;
898
+ // const len = segList.numberOfItems;
899
+ // }
900
+ // let lastX, lastY;
901
+ for (let i = 0; i < len; ++i) {
902
+ const seg = segList.getItem(i)
903
+ const type = seg.pathSegType
904
+ if (type === 1) { continue }
905
+ const pts = []
906
+ for (const n of ['', 1, 2]) {
907
+ const x = seg['x' + n]
908
+ const y = seg['y' + n]
909
+ if (x !== undefined && y !== undefined) {
910
+ const pt = transformPoint(x, y, m)
911
+ pts.push(pt.x, pt.y)
912
+ }
911
913
  }
914
+ svgCanvas.replacePathSeg(type, i, pts, pth)
915
+ }
912
916
 
913
- svgCanvas.reorientGrads(pth, m)
914
- return undefined
915
- },
916
- /**
917
+ svgCanvas.reorientGrads(pth, m)
918
+ return undefined
919
+ }
920
+
921
+ /**
917
922
  * @returns {void}
918
923
  */
919
- zoomChange () {
920
- if (svgCanvas.getCurrentMode() === 'pathedit') {
921
- path.update()
922
- }
923
- },
924
- /**
924
+ zoomChange () {
925
+ if (svgCanvas.getCurrentMode() === 'pathedit') {
926
+ path.update()
927
+ }
928
+ }
929
+
930
+ /**
925
931
  * @typedef {PlainObject} module:path.NodePoint
926
932
  * @property {Float} x
927
933
  * @property {Float} y
928
934
  * @property {Integer} type
929
935
  */
930
- /**
936
+ /**
931
937
  * @returns {module:path.NodePoint}
932
938
  */
933
- getNodePoint () {
934
- const selPt = path.selected_pts.length ? path.selected_pts[0] : 1
939
+ getNodePoint () {
940
+ const selPt = path.selected_pts.length ? path.selected_pts[0] : 1
941
+
942
+ const seg = path.segs[selPt]
943
+ return {
944
+ x: seg.item.x,
945
+ y: seg.item.y,
946
+ type: seg.type
947
+ }
948
+ }
935
949
 
936
- const seg = path.segs[selPt]
937
- return {
938
- x: seg.item.x,
939
- y: seg.item.y,
940
- type: seg.type
941
- }
942
- },
943
- /**
950
+ /**
944
951
  * @param {boolean} linkPoints
945
952
  * @returns {void}
946
953
  */
947
- linkControlPoints (linkPoints) {
948
- svgCanvas.setLinkControlPoints(linkPoints)
949
- },
950
- /**
954
+ linkControlPoints (linkPoints) {
955
+ svgCanvas.setLinkControlPoints(linkPoints)
956
+ }
957
+
958
+ /**
951
959
  * @returns {void}
952
960
  */
953
- clonePathNode () {
954
- path.storeD()
961
+ clonePathNode () {
962
+ path.storeD()
955
963
 
956
- const selPts = path.selected_pts
957
- // const {segs} = path;
964
+ const selPts = path.selected_pts
965
+ // const {segs} = path;
958
966
 
959
- let i = selPts.length
960
- const nums = []
967
+ let i = selPts.length
968
+ const nums = []
961
969
 
962
- while (i--) {
963
- const pt = selPts[i]
964
- path.addSeg(pt)
970
+ while (i--) {
971
+ const pt = selPts[i]
972
+ path.addSeg(pt)
965
973
 
966
- nums.push(pt + i)
967
- nums.push(pt + i + 1)
968
- }
969
- path.init().addPtsToSelection(nums)
974
+ nums.push(pt + i)
975
+ nums.push(pt + i + 1)
976
+ }
977
+ path.init().addPtsToSelection(nums)
978
+
979
+ path.endChanges('Clone path node(s)')
980
+ }
970
981
 
971
- path.endChanges('Clone path node(s)')
972
- },
973
- /**
982
+ /**
974
983
  * @returns {void}
975
984
  */
976
- opencloseSubPath () {
977
- const selPts = path.selected_pts
978
- // Only allow one selected node for now
979
- if (selPts.length !== 1) { return }
985
+ opencloseSubPath () {
986
+ const selPts = path.selected_pts
987
+ // Only allow one selected node for now
988
+ if (selPts.length !== 1) { return }
980
989
 
981
- const { elem } = path
982
- const list = elem.pathSegList
990
+ const { elem } = path
991
+ const list = elem.pathSegList
983
992
 
984
- // const len = list.numberOfItems;
993
+ // const len = list.numberOfItems;
985
994
 
986
- const index = selPts[0]
995
+ const index = selPts[0]
987
996
 
988
- let openPt = null
989
- let startItem = null
990
-
991
- // Check if subpath is already open
992
- path.eachSeg(function (i) {
993
- if (this.type === 2 && i <= index) {
994
- startItem = this.item
995
- }
996
- if (i <= index) { return true }
997
- if (this.type === 2) {
998
- // Found M first, so open
999
- openPt = i
1000
- return false
1001
- }
1002
- if (this.type === 1) {
1003
- // Found Z first, so closed
1004
- openPt = false
1005
- return false
1006
- }
1007
- return true
1008
- })
997
+ let openPt = null
998
+ let startItem = null
1009
999
 
1010
- if (!openPt) {
1011
- // Single path, so close last seg
1012
- openPt = path.segs.length - 1
1000
+ // Check if subpath is already open
1001
+ path.eachSeg(function (i) {
1002
+ if (this.type === 2 && i <= index) {
1003
+ startItem = this.item
1004
+ }
1005
+ if (i <= index) return true
1006
+ if (this.type === 2) {
1007
+ // Found M first, so open
1008
+ openPt = i
1009
+ return false
1010
+ }
1011
+ if (this.type === 1) {
1012
+ // Found Z first, so closed
1013
+ openPt = false
1014
+ return false
1013
1015
  }
1016
+ return true
1017
+ })
1014
1018
 
1015
- if (openPt !== false) {
1016
- // Close this path
1019
+ if (!openPt) {
1020
+ // Single path, so close last seg
1021
+ openPt = path.segs.length - 1
1022
+ }
1017
1023
 
1018
- // Create a line going to the previous "M"
1019
- const newseg = elem.createSVGPathSegLinetoAbs(startItem.x, startItem.y)
1024
+ if (openPt !== false) {
1025
+ // Close this path
1020
1026
 
1021
- const closer = elem.createSVGPathSegClosePath()
1022
- if (openPt === path.segs.length - 1) {
1023
- list.appendItem(newseg)
1024
- list.appendItem(closer)
1025
- } else {
1026
- list.insertItemBefore(closer, openPt)
1027
- list.insertItemBefore(newseg, openPt)
1028
- }
1027
+ // Create a line going to the previous "M"
1028
+ const newseg = elem.createSVGPathSegLinetoAbs(startItem.x, startItem.y)
1029
1029
 
1030
- path.init().selectPt(openPt + 1)
1031
- return
1030
+ const closer = elem.createSVGPathSegClosePath()
1031
+ if (openPt === path.segs.length - 1) {
1032
+ list.appendItem(newseg)
1033
+ list.appendItem(closer)
1034
+ } else {
1035
+ list.insertItemBefore(closer, openPt)
1036
+ list.insertItemBefore(newseg, openPt)
1032
1037
  }
1033
1038
 
1034
- // M 1,1 L 2,2 L 3,3 L 1,1 z // open at 2,2
1035
- // M 2,2 L 3,3 L 1,1
1039
+ path.init().selectPt(openPt + 1)
1040
+ return
1041
+ }
1036
1042
 
1037
- // M 1,1 L 2,2 L 1,1 z M 4,4 L 5,5 L6,6 L 5,5 z
1038
- // M 1,1 L 2,2 L 1,1 z [M 4,4] L 5,5 L(M)6,6 L 5,5 z
1043
+ // M 1,1 L 2,2 L 3,3 L 1,1 z // open at 2,2
1044
+ // M 2,2 L 3,3 L 1,1
1039
1045
 
1040
- const seg = path.segs[index]
1046
+ // M 1,1 L 2,2 L 1,1 z M 4,4 L 5,5 L6,6 L 5,5 z
1047
+ // M 1,1 L 2,2 L 1,1 z [M 4,4] L 5,5 L(M)6,6 L 5,5 z
1041
1048
 
1042
- if (seg.mate) {
1043
- list.removeItem(index) // Removes last "L"
1044
- list.removeItem(index) // Removes the "Z"
1045
- path.init().selectPt(index - 1)
1046
- return
1047
- }
1049
+ const seg = path.segs[index]
1048
1050
 
1049
- let lastM; let zSeg
1050
-
1051
- // Find this sub-path's closing point and remove
1052
- for (let i = 0; i < list.numberOfItems; i++) {
1053
- const item = list.getItem(i)
1054
-
1055
- if (item.pathSegType === 2) {
1056
- // Find the preceding M
1057
- lastM = i
1058
- } else if (i === index) {
1059
- // Remove it
1060
- list.removeItem(lastM)
1061
- // index--;
1062
- } else if (item.pathSegType === 1 && index < i) {
1063
- // Remove the closing seg of this subpath
1064
- zSeg = i - 1
1065
- list.removeItem(i)
1066
- break
1067
- }
1051
+ if (seg.mate) {
1052
+ list.removeItem(index) // Removes last "L"
1053
+ list.removeItem(index) // Removes the "Z"
1054
+ path.init().selectPt(index - 1)
1055
+ return
1056
+ }
1057
+
1058
+ let lastM; let zSeg
1059
+
1060
+ // Find this sub-path's closing point and remove
1061
+ for (let i = 0; i < list.numberOfItems; i++) {
1062
+ const item = list.getItem(i)
1063
+
1064
+ if (item.pathSegType === 2) {
1065
+ // Find the preceding M
1066
+ lastM = i
1067
+ } else if (i === index) {
1068
+ // Remove it
1069
+ list.removeItem(lastM)
1070
+ // index--;
1071
+ } else if (item.pathSegType === 1 && index < i) {
1072
+ // Remove the closing seg of this subpath
1073
+ zSeg = i - 1
1074
+ list.removeItem(i)
1075
+ break
1068
1076
  }
1077
+ }
1069
1078
 
1070
- let num = (index - lastM) - 1
1079
+ let num = (index - lastM) - 1
1071
1080
 
1072
- while (num--) {
1073
- list.insertItemBefore(list.getItem(lastM), zSeg)
1074
- }
1081
+ while (num--) {
1082
+ list.insertItemBefore(list.getItem(lastM), zSeg)
1083
+ }
1084
+
1085
+ const pt = list.getItem(lastM)
1075
1086
 
1076
- const pt = list.getItem(lastM)
1087
+ // Make this point the new "M"
1088
+ svgCanvas.replacePathSeg(2, lastM, [pt.x, pt.y])
1077
1089
 
1078
- // Make this point the new "M"
1079
- svgCanvas.replacePathSeg(2, lastM, [pt.x, pt.y])
1090
+ // i = index; // i is local here, so has no effect; what was the intent for this?
1080
1091
 
1081
- // i = index; // i is local here, so has no effect; what was the intent for this?
1092
+ path.init().selectPt(0)
1093
+ }
1082
1094
 
1083
- path.init().selectPt(0)
1084
- },
1085
- /**
1095
+ /**
1086
1096
  * @returns {void}
1087
1097
  */
1088
- deletePathNode () {
1089
- if (!pathActionsMethod.canDeleteNodes) { return }
1090
- path.storeD()
1098
+ deletePathNode () {
1099
+ if (!pathActionsMethod.canDeleteNodes) { return }
1100
+ path.storeD()
1091
1101
 
1092
- const selPts = path.selected_pts
1102
+ const selPts = path.selected_pts
1093
1103
 
1094
- let i = selPts.length
1095
- while (i--) {
1096
- const pt = selPts[i]
1097
- path.deleteSeg(pt)
1098
- }
1104
+ let i = selPts.length
1105
+ while (i--) {
1106
+ const pt = selPts[i]
1107
+ path.deleteSeg(pt)
1108
+ }
1099
1109
 
1100
- // Cleanup
1101
- const cleanup = function () {
1102
- const segList = path.elem.pathSegList
1103
- let len = segList.numberOfItems
1110
+ // Cleanup
1111
+ const cleanup = () => {
1112
+ const segList = path.elem.pathSegList
1113
+ let len = segList.numberOfItems
1104
1114
 
1105
- const remItems = function (pos, count) {
1106
- while (count--) {
1107
- segList.removeItem(pos)
1108
- }
1115
+ const remItems = (pos, count) => {
1116
+ while (count--) {
1117
+ segList.removeItem(pos)
1109
1118
  }
1119
+ }
1110
1120
 
1111
- if (len <= 1) { return true }
1112
-
1113
- while (len--) {
1114
- const item = segList.getItem(len)
1115
- if (item.pathSegType === 1) {
1116
- const prev = segList.getItem(len - 1)
1117
- const nprev = segList.getItem(len - 2)
1118
- if (prev.pathSegType === 2) {
1119
- remItems(len - 1, 2)
1120
- cleanup()
1121
- break
1122
- } else if (nprev.pathSegType === 2) {
1123
- remItems(len - 2, 3)
1124
- cleanup()
1125
- break
1126
- }
1127
- } else if (item.pathSegType === 2 && len > 0) {
1128
- const prevType = segList.getItem(len - 1).pathSegType
1129
- // Path has M M
1130
- if (prevType === 2) {
1131
- remItems(len - 1, 1)
1132
- cleanup()
1133
- break
1134
- // Entire path ends with Z M
1135
- } else if (prevType === 1 && segList.numberOfItems - 1 === len) {
1136
- remItems(len, 1)
1137
- cleanup()
1138
- break
1139
- }
1121
+ if (len <= 1) { return true }
1122
+
1123
+ while (len--) {
1124
+ const item = segList.getItem(len)
1125
+ if (item.pathSegType === 1) {
1126
+ const prev = segList.getItem(len - 1)
1127
+ const nprev = segList.getItem(len - 2)
1128
+ if (prev.pathSegType === 2) {
1129
+ remItems(len - 1, 2)
1130
+ cleanup()
1131
+ break
1132
+ } else if (nprev.pathSegType === 2) {
1133
+ remItems(len - 2, 3)
1134
+ cleanup()
1135
+ break
1136
+ }
1137
+ } else if (item.pathSegType === 2 && len > 0) {
1138
+ const prevType = segList.getItem(len - 1).pathSegType
1139
+ // Path has M M
1140
+ if (prevType === 2) {
1141
+ remItems(len - 1, 1)
1142
+ cleanup()
1143
+ break
1144
+ // Entire path ends with Z M
1145
+ } else if (prevType === 1 && segList.numberOfItems - 1 === len) {
1146
+ remItems(len, 1)
1147
+ cleanup()
1148
+ break
1140
1149
  }
1141
1150
  }
1142
- return false
1143
1151
  }
1152
+ return false
1153
+ }
1144
1154
 
1145
- cleanup()
1155
+ cleanup()
1146
1156
 
1147
- // Completely delete a path with 1 or 0 segments
1148
- if (path.elem.pathSegList.numberOfItems <= 1) {
1149
- pathActionsMethod.toSelectMode(path.elem)
1150
- svgCanvas.canvas.deleteSelectedElements()
1151
- return
1152
- }
1157
+ // Completely delete a path with 1 or 0 segments
1158
+ if (path.elem.pathSegList.numberOfItems <= 1) {
1159
+ pathActionsMethod.toSelectMode(path.elem)
1160
+ svgCanvas.canvas.deleteSelectedElements()
1161
+ return
1162
+ }
1153
1163
 
1154
- path.init()
1155
- path.clearSelection()
1164
+ path.init()
1165
+ path.clearSelection()
1156
1166
 
1157
- // TODO: Find right way to select point now
1158
- // path.selectPt(selPt);
1159
- if (window.opera) { // Opera repaints incorrectly
1160
- path.elem.setAttribute('d', path.elem.getAttribute('d'))
1161
- }
1162
- path.endChanges('Delete path node(s)')
1163
- },
1164
- // Can't seem to use `@borrows` here, so using `@see`
1165
- /**
1166
- * Smooth polyline into path.
1167
- * @function module:path.pathActions.smoothPolylineIntoPath
1168
- * @see module:path~smoothPolylineIntoPath
1169
- */
1170
- smoothPolylineIntoPath,
1171
- /* eslint-enable */
1172
- /**
1173
- * @param {?Integer} v See {@link https://www.w3.org/TR/SVG/single-page.html#paths-InterfaceSVGPathSeg}
1174
- * @returns {void}
1175
- */
1176
- setSegType (v) {
1177
- path?.setSegType(v)
1178
- },
1179
- /**
1180
- * @param {string} attr
1181
- * @param {Float} newValue
1182
- * @returns {void}
1183
- */
1184
- moveNode (attr, newValue) {
1185
- const selPts = path.selected_pts
1186
- if (!selPts.length) { return }
1167
+ // TODO: Find right way to select point now
1168
+ // path.selectPt(selPt);
1169
+ if (window.opera) { // Opera repaints incorrectly
1170
+ path.elem.setAttribute('d', path.elem.getAttribute('d'))
1171
+ }
1172
+ path.endChanges('Delete path node(s)')
1173
+ }
1187
1174
 
1188
- path.storeD()
1175
+ // Can't seem to use `@borrows` here, so using `@see`
1176
+ /**
1177
+ * Smooth polyline into path.
1178
+ * @function module:path.pathActions.smoothPolylineIntoPath
1179
+ * @see module:path~smoothPolylineIntoPath
1180
+ */
1181
+ smoothPolylineIntoPath (element) {
1182
+ return this.#smoothPolylineIntoPath(element)
1183
+ }
1189
1184
 
1190
- // Get first selected point
1191
- const seg = path.segs[selPts[0]]
1192
- const diff = { x: 0, y: 0 }
1193
- diff[attr] = newValue - seg.item[attr]
1185
+ /* eslint-enable */
1186
+ /**
1187
+ * @param {?Integer} v See {@link https://www.w3.org/TR/SVG/single-page.html#paths-InterfaceSVGPathSeg}
1188
+ * @returns {void}
1189
+ */
1190
+ setSegType (v) {
1191
+ path?.setSegType(v)
1192
+ }
1194
1193
 
1195
- seg.move(diff.x, diff.y)
1196
- path.endChanges('Move path point')
1197
- },
1198
- /**
1199
- * @param {Element} elem
1200
- * @returns {void}
1201
- */
1202
- fixEnd (elem) {
1203
- // Adds an extra segment if the last seg before a Z doesn't end
1204
- // at its M point
1205
- // M0,0 L0,100 L100,100 z
1206
- const segList = elem.pathSegList
1207
- const len = segList.numberOfItems
1208
- let lastM
1209
- for (let i = 0; i < len; ++i) {
1210
- const item = segList.getItem(i)
1211
- if (item.pathSegType === 2) { // 2 => M segment type (move to)
1212
- lastM = item
1213
- }
1194
+ /**
1195
+ * @param {string} attr
1196
+ * @param {Float} newValue
1197
+ * @returns {void}
1198
+ */
1199
+ moveNode (attr, newValue) {
1200
+ const selPts = path.selected_pts
1201
+ if (!selPts.length) { return }
1214
1202
 
1215
- if (item.pathSegType === 1) { // 1 => Z segment type (close path)
1216
- const prev = segList.getItem(i - 1)
1217
- if (prev.x !== lastM.x || prev.y !== lastM.y) {
1218
- // Add an L segment here
1219
- const newseg = elem.createSVGPathSegLinetoAbs(lastM.x, lastM.y)
1220
- segList.insertItemBefore(newseg, i)
1221
- // Can this be done better?
1222
- pathActionsMethod.fixEnd(elem)
1223
- break
1224
- }
1203
+ path.storeD()
1204
+
1205
+ // Get first selected point
1206
+ const seg = path.segs[selPts[0]]
1207
+ const diff = { x: 0, y: 0 }
1208
+ diff[attr] = newValue - seg.item[attr]
1209
+
1210
+ seg.move(diff.x, diff.y)
1211
+ path.endChanges('Move path point')
1212
+ }
1213
+
1214
+ /**
1215
+ * @param {Element} elem
1216
+ * @returns {void}
1217
+ */
1218
+ fixEnd (elem) {
1219
+ // Adds an extra segment if the last seg before a Z doesn't end
1220
+ // at its M point
1221
+ // M0,0 L0,100 L100,100 z
1222
+ const segList = elem.pathSegList
1223
+ const len = segList.numberOfItems
1224
+ let lastM
1225
+ for (let i = 0; i < len; ++i) {
1226
+ const item = segList.getItem(i)
1227
+ if (item.pathSegType === 2) { // 2 => M segment type (move to)
1228
+ lastM = item
1229
+ }
1230
+
1231
+ if (item.pathSegType === 1) { // 1 => Z segment type (close path)
1232
+ const prev = segList.getItem(i - 1)
1233
+ if (prev.x !== lastM.x || prev.y !== lastM.y) {
1234
+ // Add an L segment here
1235
+ const newseg = elem.createSVGPathSegLinetoAbs(lastM.x, lastM.y)
1236
+ segList.insertItemBefore(newseg, i)
1237
+ // Can this be done better?
1238
+ pathActionsMethod.fixEnd(elem)
1239
+ break
1225
1240
  }
1226
1241
  }
1227
- },
1228
- // Can't seem to use `@borrows` here, so using `@see`
1229
- /**
1230
- * Convert a path to one with only absolute or relative values.
1231
- * @function module:path.pathActions.convertPath
1232
- * @see module:path.convertPath
1233
- */
1234
- convertPath
1235
- })
1236
- })()
1242
+ }
1243
+ }
1244
+
1245
+ // Can't seem to use `@borrows` here, so using `@see`
1246
+ /**
1247
+ * Convert a path to one with only absolute or relative values.
1248
+ * @function module:path.pathActions.convertPath
1249
+ * @see module:path.convertPath
1250
+ */
1251
+ convertPath (pth, toRel) {
1252
+ return convertPath(pth, toRel)
1253
+ }
1254
+ }
1255
+
1256
+ // Export singleton instance for backward compatibility
1257
+ export const pathActionsMethod = new PathActions()
1237
1258
  // end pathActions