@svgedit/svgcanvas 7.1.4
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/blur-event.js +156 -0
- package/clear.js +43 -0
- package/coords.js +298 -0
- package/copy-elem.js +45 -0
- package/dataStorage.js +28 -0
- package/dist/svgcanvas.js +515 -0
- package/dist/svgcanvas.js.map +1 -0
- package/draw.js +1064 -0
- package/elem-get-set.js +1077 -0
- package/event.js +1388 -0
- package/history.js +619 -0
- package/historyrecording.js +161 -0
- package/json.js +110 -0
- package/layer.js +228 -0
- package/math.js +221 -0
- package/namespaces.js +40 -0
- package/package.json +54 -0
- package/paint.js +88 -0
- package/paste-elem.js +127 -0
- package/path-actions.js +1237 -0
- package/path-method.js +1012 -0
- package/path.js +781 -0
- package/recalculate.js +794 -0
- package/rollup.config.js +40 -0
- package/sanitize.js +252 -0
- package/select.js +543 -0
- package/selected-elem.js +1297 -0
- package/selection.js +482 -0
- package/svg-exec.js +1289 -0
- package/svgcanvas.js +1347 -0
- package/svgroot.js +36 -0
- package/text-actions.js +530 -0
- package/touch.js +51 -0
- package/undo.js +279 -0
- package/utilities.js +1214 -0
package/path-actions.js
ADDED
|
@@ -0,0 +1,1237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path functionality.
|
|
3
|
+
* @module path
|
|
4
|
+
* @license MIT
|
|
5
|
+
*
|
|
6
|
+
* @copyright 2011 Alexis Deveria, 2011 Jeff Schiller
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { NS } from './namespaces.js'
|
|
10
|
+
import { shortFloat } from '../../src/common/units.js'
|
|
11
|
+
import { ChangeElementCommand, BatchCommand } from './history.js'
|
|
12
|
+
import {
|
|
13
|
+
transformPoint, snapToAngle, rectsIntersect,
|
|
14
|
+
transformListToTransform
|
|
15
|
+
} from './math.js'
|
|
16
|
+
import {
|
|
17
|
+
assignAttributes, getElement, getRotationAngle, snapToGrid,
|
|
18
|
+
getBBox
|
|
19
|
+
} from './utilities.js'
|
|
20
|
+
|
|
21
|
+
let svgCanvas = null
|
|
22
|
+
let path = null
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @function module:path-actions.init
|
|
26
|
+
* @param {module:path-actions.svgCanvas} pathActionsContext
|
|
27
|
+
* @returns {void}
|
|
28
|
+
*/
|
|
29
|
+
export const init = (canvas) => {
|
|
30
|
+
svgCanvas = canvas
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Convert a path to one with only absolute or relative values.
|
|
35
|
+
* @todo move to pathActions.js
|
|
36
|
+
* @function module:path.convertPath
|
|
37
|
+
* @param {SVGPathElement} pth - the path to convert
|
|
38
|
+
* @param {boolean} toRel - true of convert to relative
|
|
39
|
+
* @returns {string}
|
|
40
|
+
*/
|
|
41
|
+
export const convertPath = function (pth, toRel) {
|
|
42
|
+
const { pathSegList } = pth
|
|
43
|
+
const len = pathSegList.numberOfItems
|
|
44
|
+
let curx = 0; let cury = 0
|
|
45
|
+
let d = ''
|
|
46
|
+
let lastM = null
|
|
47
|
+
|
|
48
|
+
for (let i = 0; i < len; ++i) {
|
|
49
|
+
const seg = pathSegList.getItem(i)
|
|
50
|
+
// if these properties are not in the segment, set them to zero
|
|
51
|
+
let x = seg.x || 0
|
|
52
|
+
let y = seg.y || 0
|
|
53
|
+
let x1 = seg.x1 || 0
|
|
54
|
+
let y1 = seg.y1 || 0
|
|
55
|
+
let x2 = seg.x2 || 0
|
|
56
|
+
let y2 = seg.y2 || 0
|
|
57
|
+
|
|
58
|
+
// const type = seg.pathSegType;
|
|
59
|
+
// const pathMap = svgCanvas.getPathMap();
|
|
60
|
+
// let letter = pathMap[type][toRel ? 'toLowerCase' : 'toUpperCase']();
|
|
61
|
+
let letter = seg.pathSegTypeAsLetter
|
|
62
|
+
|
|
63
|
+
switch (letter) {
|
|
64
|
+
case 'z': // z,Z closepath (Z/z)
|
|
65
|
+
case 'Z':
|
|
66
|
+
d += 'z'
|
|
67
|
+
if (lastM && !toRel) {
|
|
68
|
+
curx = lastM[0]
|
|
69
|
+
cury = lastM[1]
|
|
70
|
+
}
|
|
71
|
+
break
|
|
72
|
+
case 'H': // absolute horizontal line (H)
|
|
73
|
+
x -= curx
|
|
74
|
+
// Fallthrough
|
|
75
|
+
case 'h': // relative horizontal line (h)
|
|
76
|
+
if (toRel) {
|
|
77
|
+
y = 0
|
|
78
|
+
curx += x
|
|
79
|
+
letter = 'l'
|
|
80
|
+
} else {
|
|
81
|
+
y = cury
|
|
82
|
+
x += curx
|
|
83
|
+
curx = x
|
|
84
|
+
letter = 'L'
|
|
85
|
+
}
|
|
86
|
+
// Convert to "line" for easier editing
|
|
87
|
+
d += pathDSegment(letter, [[x, y]])
|
|
88
|
+
break
|
|
89
|
+
case 'V': // absolute vertical line (V)
|
|
90
|
+
y -= cury
|
|
91
|
+
// Fallthrough
|
|
92
|
+
case 'v': // relative vertical line (v)
|
|
93
|
+
if (toRel) {
|
|
94
|
+
x = 0
|
|
95
|
+
cury += y
|
|
96
|
+
letter = 'l'
|
|
97
|
+
} else {
|
|
98
|
+
x = curx
|
|
99
|
+
y += cury
|
|
100
|
+
cury = y
|
|
101
|
+
letter = 'L'
|
|
102
|
+
}
|
|
103
|
+
// Convert to "line" for easier editing
|
|
104
|
+
d += pathDSegment(letter, [[x, y]])
|
|
105
|
+
break
|
|
106
|
+
case 'M': // absolute move (M)
|
|
107
|
+
case 'L': // absolute line (L)
|
|
108
|
+
case 'T': // absolute smooth quad (T)
|
|
109
|
+
x -= curx
|
|
110
|
+
y -= cury
|
|
111
|
+
// Fallthrough
|
|
112
|
+
case 'l': // relative line (l)
|
|
113
|
+
case 'm': // relative move (m)
|
|
114
|
+
case 't': // relative smooth quad (t)
|
|
115
|
+
if (toRel) {
|
|
116
|
+
curx += x
|
|
117
|
+
cury += y
|
|
118
|
+
letter = letter.toLowerCase()
|
|
119
|
+
} else {
|
|
120
|
+
x += curx
|
|
121
|
+
y += cury
|
|
122
|
+
curx = x
|
|
123
|
+
cury = y
|
|
124
|
+
letter = letter.toUpperCase()
|
|
125
|
+
}
|
|
126
|
+
if (letter === 'm' || letter === 'M') { lastM = [curx, cury] }
|
|
127
|
+
|
|
128
|
+
d += pathDSegment(letter, [[x, y]])
|
|
129
|
+
break
|
|
130
|
+
case 'C': // absolute cubic (C)
|
|
131
|
+
x -= curx; x1 -= curx; x2 -= curx
|
|
132
|
+
y -= cury; y1 -= cury; y2 -= cury
|
|
133
|
+
// Fallthrough
|
|
134
|
+
case 'c': // relative cubic (c)
|
|
135
|
+
if (toRel) {
|
|
136
|
+
curx += x
|
|
137
|
+
cury += y
|
|
138
|
+
letter = 'c'
|
|
139
|
+
} else {
|
|
140
|
+
x += curx; x1 += curx; x2 += curx
|
|
141
|
+
y += cury; y1 += cury; y2 += cury
|
|
142
|
+
curx = x
|
|
143
|
+
cury = y
|
|
144
|
+
letter = 'C'
|
|
145
|
+
}
|
|
146
|
+
d += pathDSegment(letter, [[x1, y1], [x2, y2], [x, y]])
|
|
147
|
+
break
|
|
148
|
+
case 'Q': // absolute quad (Q)
|
|
149
|
+
x -= curx; x1 -= curx
|
|
150
|
+
y -= cury; y1 -= cury
|
|
151
|
+
// Fallthrough
|
|
152
|
+
case 'q': // relative quad (q)
|
|
153
|
+
if (toRel) {
|
|
154
|
+
curx += x
|
|
155
|
+
cury += y
|
|
156
|
+
letter = 'q'
|
|
157
|
+
} else {
|
|
158
|
+
x += curx; x1 += curx
|
|
159
|
+
y += cury; y1 += cury
|
|
160
|
+
curx = x
|
|
161
|
+
cury = y
|
|
162
|
+
letter = 'Q'
|
|
163
|
+
}
|
|
164
|
+
d += pathDSegment(letter, [[x1, y1], [x, y]])
|
|
165
|
+
break
|
|
166
|
+
case 'A':
|
|
167
|
+
x -= curx
|
|
168
|
+
y -= cury
|
|
169
|
+
// fallthrough
|
|
170
|
+
case 'a': // relative elliptical arc (a)
|
|
171
|
+
if (toRel) {
|
|
172
|
+
curx += x
|
|
173
|
+
cury += y
|
|
174
|
+
letter = 'a'
|
|
175
|
+
} else {
|
|
176
|
+
x += curx
|
|
177
|
+
y += cury
|
|
178
|
+
curx = x
|
|
179
|
+
cury = y
|
|
180
|
+
letter = 'A'
|
|
181
|
+
}
|
|
182
|
+
d += pathDSegment(letter, [[seg.r1, seg.r2]], [
|
|
183
|
+
seg.angle,
|
|
184
|
+
(seg.largeArcFlag ? 1 : 0),
|
|
185
|
+
(seg.sweepFlag ? 1 : 0)
|
|
186
|
+
], [x, y])
|
|
187
|
+
break
|
|
188
|
+
case 'S': // absolute smooth cubic (S)
|
|
189
|
+
x -= curx; x2 -= curx
|
|
190
|
+
y -= cury; y2 -= cury
|
|
191
|
+
// Fallthrough
|
|
192
|
+
case 's': // relative smooth cubic (s)
|
|
193
|
+
if (toRel) {
|
|
194
|
+
curx += x
|
|
195
|
+
cury += y
|
|
196
|
+
letter = 's'
|
|
197
|
+
} else {
|
|
198
|
+
x += curx; x2 += curx
|
|
199
|
+
y += cury; y2 += cury
|
|
200
|
+
curx = x
|
|
201
|
+
cury = y
|
|
202
|
+
letter = 'S'
|
|
203
|
+
}
|
|
204
|
+
d += pathDSegment(letter, [[x2, y2], [x, y]])
|
|
205
|
+
break
|
|
206
|
+
} // switch on path segment type
|
|
207
|
+
} // for each segment
|
|
208
|
+
return d
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* TODO: refactor callers in `convertPath` to use `getPathDFromSegments` instead of this function.
|
|
213
|
+
* Legacy code refactored from `svgcanvas.pathActions.convertPath`.
|
|
214
|
+
* @param {string} letter - path segment command (letter in potentially either case from {@link module:path.pathMap}; see [SVGPathSeg#pathSegTypeAsLetter]{@link https://www.w3.org/TR/SVG/single-page.html#paths-__svg__SVGPathSeg__pathSegTypeAsLetter})
|
|
215
|
+
* @param {GenericArray<GenericArray<Integer>>} points - x,y points
|
|
216
|
+
* @param {GenericArray<GenericArray<Integer>>} [morePoints] - x,y points
|
|
217
|
+
* @param {Integer[]} [lastPoint] - x,y point
|
|
218
|
+
* @returns {string}
|
|
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
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Group: Path edit functions.
|
|
236
|
+
* Functions relating to editing path elements.
|
|
237
|
+
* @namespace {PlainObject} pathActions
|
|
238
|
+
* @memberof module:path
|
|
239
|
+
*/
|
|
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);
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* This function converts a polyline (created by the fh_path tool) into
|
|
251
|
+
* a path element and coverts every three line segments into a single bezier
|
|
252
|
+
* curve in an attempt to smooth out the free-hand.
|
|
253
|
+
* @function smoothPolylineIntoPath
|
|
254
|
+
* @param {Element} element
|
|
255
|
+
* @returns {Element}
|
|
256
|
+
*/
|
|
257
|
+
const smoothPolylineIntoPath = function (element) {
|
|
258
|
+
let i
|
|
259
|
+
const { points } = element
|
|
260
|
+
const N = points.numberOfItems
|
|
261
|
+
if (N >= 4) {
|
|
262
|
+
// loop through every 3 points and convert to a cubic bezier curve segment
|
|
263
|
+
//
|
|
264
|
+
// NOTE: this is cheating, it means that every 3 points has the potential to
|
|
265
|
+
// be a corner instead of treating each point in an equal manner. In general,
|
|
266
|
+
// this technique does not look that good.
|
|
267
|
+
//
|
|
268
|
+
// I am open to better ideas!
|
|
269
|
+
//
|
|
270
|
+
// Reading:
|
|
271
|
+
// - http://www.efg2.com/Lab/Graphics/Jean-YvesQueinecBezierCurves.htm
|
|
272
|
+
// - https://www.codeproject.com/KB/graphics/BezierSpline.aspx?msg=2956963
|
|
273
|
+
// - https://www.ian-ko.com/ET_GeoWizards/UserGuide/smooth.htm
|
|
274
|
+
// - https://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/Bezier/bezier-der.html
|
|
275
|
+
let curpos = points.getItem(0); let prevCtlPt = null
|
|
276
|
+
let d = []
|
|
277
|
+
d.push(['M', curpos.x, ',', curpos.y, ' C'].join(''))
|
|
278
|
+
for (i = 1; i <= (N - 4); i += 3) {
|
|
279
|
+
let ct1 = points.getItem(i)
|
|
280
|
+
const ct2 = points.getItem(i + 1)
|
|
281
|
+
const end = points.getItem(i + 2)
|
|
282
|
+
|
|
283
|
+
// if the previous segment had a control point, we want to smooth out
|
|
284
|
+
// the control points on both sides
|
|
285
|
+
if (prevCtlPt) {
|
|
286
|
+
const newpts = svgCanvas.smoothControlPoints(prevCtlPt, ct1, curpos)
|
|
287
|
+
if (newpts?.length === 2) {
|
|
288
|
+
const prevArr = d[d.length - 1].split(',')
|
|
289
|
+
prevArr[2] = newpts[0].x
|
|
290
|
+
prevArr[3] = newpts[0].y
|
|
291
|
+
d[d.length - 1] = prevArr.join(',')
|
|
292
|
+
ct1 = newpts[1]
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
d.push([ct1.x, ct1.y, ct2.x, ct2.y, end.x, end.y].join(','))
|
|
297
|
+
|
|
298
|
+
curpos = end
|
|
299
|
+
prevCtlPt = ct2
|
|
300
|
+
}
|
|
301
|
+
// handle remaining line segments
|
|
302
|
+
d.push('L')
|
|
303
|
+
while (i < N) {
|
|
304
|
+
const pt = points.getItem(i)
|
|
305
|
+
d.push([pt.x, pt.y].join(','))
|
|
306
|
+
i++
|
|
307
|
+
}
|
|
308
|
+
d = d.join(' ')
|
|
309
|
+
|
|
310
|
+
element = svgCanvas.addSVGElementsFromJson({
|
|
311
|
+
element: 'path',
|
|
312
|
+
curStyles: true,
|
|
313
|
+
attr: {
|
|
314
|
+
id: svgCanvas.getId(),
|
|
315
|
+
d,
|
|
316
|
+
fill: 'none'
|
|
317
|
+
}
|
|
318
|
+
})
|
|
319
|
+
// No need to call "changed", as this is already done under mouseUp
|
|
320
|
+
}
|
|
321
|
+
return element
|
|
322
|
+
}
|
|
323
|
+
|
|
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
|
+
}
|
|
350
|
+
|
|
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) &&
|
|
394
|
+
y >= (py - FUZZ) && y <= (py + FUZZ)
|
|
395
|
+
) {
|
|
396
|
+
clickOnPoint = true
|
|
397
|
+
break
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
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
|
|
434
|
+
}
|
|
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
|
|
457
|
+
}
|
|
458
|
+
// 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
|
|
471
|
+
|
|
472
|
+
if (evt.shiftKey) {
|
|
473
|
+
const xya = snapToAngle(lastx, lasty, x, y);
|
|
474
|
+
({ x, y } = xya)
|
|
475
|
+
}
|
|
476
|
+
|
|
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)
|
|
500
|
+
}
|
|
501
|
+
// keep = true;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
return undefined
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// TODO: Make sure currentPath isn't null at this point
|
|
508
|
+
if (!path) { return undefined }
|
|
509
|
+
|
|
510
|
+
path.storeD();
|
|
511
|
+
|
|
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]
|
|
519
|
+
|
|
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)
|
|
531
|
+
}
|
|
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)
|
|
539
|
+
}
|
|
540
|
+
|
|
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)
|
|
557
|
+
}
|
|
558
|
+
return undefined
|
|
559
|
+
},
|
|
560
|
+
/**
|
|
561
|
+
* @param {Float} mouseX
|
|
562
|
+
* @param {Float} mouseY
|
|
563
|
+
* @returns {void}
|
|
564
|
+
*/
|
|
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
|
+
}
|
|
626
|
+
} 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
|
+
}
|
|
644
|
+
}
|
|
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)
|
|
666
|
+
}
|
|
667
|
+
} 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
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
const sel = rectsIntersect(rbb, ptBb)
|
|
686
|
+
|
|
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
|
+
/**
|
|
694
|
+
* @typedef module:path.keepElement
|
|
695
|
+
* @type {PlainObject}
|
|
696
|
+
* @property {boolean} keep
|
|
697
|
+
* @property {Element} element
|
|
698
|
+
*/
|
|
699
|
+
/**
|
|
700
|
+
* @param {Event} evt
|
|
701
|
+
* @param {Element} element
|
|
702
|
+
* @param {Float} _mouseX
|
|
703
|
+
* @param {Float} _mouseY
|
|
704
|
+
* @returns {module:path.keepElement|void}
|
|
705
|
+
*/
|
|
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
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// Edit mode
|
|
724
|
+
const rubberBox = svgCanvas.getRubberBox()
|
|
725
|
+
if (path.dragging) {
|
|
726
|
+
const lastPt = path.cur_pt
|
|
727
|
+
|
|
728
|
+
path.dragging = false
|
|
729
|
+
path.dragctrl = false
|
|
730
|
+
path.update()
|
|
731
|
+
|
|
732
|
+
if (hasMoved) {
|
|
733
|
+
path.endChanges('Move path point(s)')
|
|
734
|
+
}
|
|
735
|
+
|
|
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')
|
|
742
|
+
|
|
743
|
+
if (rubberBox.getAttribute('width') <= 2 && rubberBox.getAttribute('height') <= 2) {
|
|
744
|
+
pathActionsMethod.toSelectMode(evt.target)
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// else, move back to select mode
|
|
748
|
+
} else {
|
|
749
|
+
pathActionsMethod.toSelectMode(evt.target)
|
|
750
|
+
}
|
|
751
|
+
hasMoved = false
|
|
752
|
+
return undefined
|
|
753
|
+
},
|
|
754
|
+
/**
|
|
755
|
+
* @param {Element} element
|
|
756
|
+
* @returns {void}
|
|
757
|
+
*/
|
|
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
|
+
/**
|
|
768
|
+
* @param {Element} elem
|
|
769
|
+
* @fires module:svgcanvas.SvgCanvas#event:selected
|
|
770
|
+
* @returns {void}
|
|
771
|
+
*/
|
|
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
|
+
}
|
|
784
|
+
|
|
785
|
+
if (selPath) {
|
|
786
|
+
svgCanvas.call('selected', [elem])
|
|
787
|
+
svgCanvas.addToSelection([elem], true)
|
|
788
|
+
}
|
|
789
|
+
},
|
|
790
|
+
/**
|
|
791
|
+
* @param {boolean} on
|
|
792
|
+
* @returns {void}
|
|
793
|
+
*/
|
|
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
|
+
/**
|
|
806
|
+
* @param {Element} target
|
|
807
|
+
* @returns {void}
|
|
808
|
+
*/
|
|
809
|
+
select (target) {
|
|
810
|
+
if (currentPath === target) {
|
|
811
|
+
pathActionsMethod.toEditMode(target)
|
|
812
|
+
svgCanvas.setCurrentMode('pathedit')
|
|
813
|
+
// going into pathedit mode
|
|
814
|
+
} else {
|
|
815
|
+
currentPath = target
|
|
816
|
+
}
|
|
817
|
+
},
|
|
818
|
+
/**
|
|
819
|
+
* @fires module:svgcanvas.SvgCanvas#event:changed
|
|
820
|
+
* @returns {void}
|
|
821
|
+
*/
|
|
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)
|
|
836
|
+
|
|
837
|
+
svgCanvas.addCommandToHistory(batchCmd)
|
|
838
|
+
|
|
839
|
+
// Set matrix to null
|
|
840
|
+
svgCanvas.getPath_(elem).show(false).matrix = null
|
|
841
|
+
|
|
842
|
+
this.clear()
|
|
843
|
+
|
|
844
|
+
svgCanvas.addToSelection([elem], true)
|
|
845
|
+
svgCanvas.call('changed', svgCanvas.getSelectedElements())
|
|
846
|
+
},
|
|
847
|
+
|
|
848
|
+
/**
|
|
849
|
+
* @param {boolean} remove Not in use
|
|
850
|
+
* @returns {void}
|
|
851
|
+
*/
|
|
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()
|
|
870
|
+
}
|
|
871
|
+
if (path) { path.init().show(false) }
|
|
872
|
+
},
|
|
873
|
+
/**
|
|
874
|
+
* @param {?(Element|SVGPathElement)} pth
|
|
875
|
+
* @returns {false|void}
|
|
876
|
+
*/
|
|
877
|
+
resetOrientation (pth) {
|
|
878
|
+
if (pth?.nodeName !== 'path') { return false }
|
|
879
|
+
const tlist = pth.transform.baseVal
|
|
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)
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
svgCanvas.reorientGrads(pth, m)
|
|
914
|
+
return undefined
|
|
915
|
+
},
|
|
916
|
+
/**
|
|
917
|
+
* @returns {void}
|
|
918
|
+
*/
|
|
919
|
+
zoomChange () {
|
|
920
|
+
if (svgCanvas.getCurrentMode() === 'pathedit') {
|
|
921
|
+
path.update()
|
|
922
|
+
}
|
|
923
|
+
},
|
|
924
|
+
/**
|
|
925
|
+
* @typedef {PlainObject} module:path.NodePoint
|
|
926
|
+
* @property {Float} x
|
|
927
|
+
* @property {Float} y
|
|
928
|
+
* @property {Integer} type
|
|
929
|
+
*/
|
|
930
|
+
/**
|
|
931
|
+
* @returns {module:path.NodePoint}
|
|
932
|
+
*/
|
|
933
|
+
getNodePoint () {
|
|
934
|
+
const selPt = path.selected_pts.length ? path.selected_pts[0] : 1
|
|
935
|
+
|
|
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
|
+
/**
|
|
944
|
+
* @param {boolean} linkPoints
|
|
945
|
+
* @returns {void}
|
|
946
|
+
*/
|
|
947
|
+
linkControlPoints (linkPoints) {
|
|
948
|
+
svgCanvas.setLinkControlPoints(linkPoints)
|
|
949
|
+
},
|
|
950
|
+
/**
|
|
951
|
+
* @returns {void}
|
|
952
|
+
*/
|
|
953
|
+
clonePathNode () {
|
|
954
|
+
path.storeD()
|
|
955
|
+
|
|
956
|
+
const selPts = path.selected_pts
|
|
957
|
+
// const {segs} = path;
|
|
958
|
+
|
|
959
|
+
let i = selPts.length
|
|
960
|
+
const nums = []
|
|
961
|
+
|
|
962
|
+
while (i--) {
|
|
963
|
+
const pt = selPts[i]
|
|
964
|
+
path.addSeg(pt)
|
|
965
|
+
|
|
966
|
+
nums.push(pt + i)
|
|
967
|
+
nums.push(pt + i + 1)
|
|
968
|
+
}
|
|
969
|
+
path.init().addPtsToSelection(nums)
|
|
970
|
+
|
|
971
|
+
path.endChanges('Clone path node(s)')
|
|
972
|
+
},
|
|
973
|
+
/**
|
|
974
|
+
* @returns {void}
|
|
975
|
+
*/
|
|
976
|
+
opencloseSubPath () {
|
|
977
|
+
const selPts = path.selected_pts
|
|
978
|
+
// Only allow one selected node for now
|
|
979
|
+
if (selPts.length !== 1) { return }
|
|
980
|
+
|
|
981
|
+
const { elem } = path
|
|
982
|
+
const list = elem.pathSegList
|
|
983
|
+
|
|
984
|
+
// const len = list.numberOfItems;
|
|
985
|
+
|
|
986
|
+
const index = selPts[0]
|
|
987
|
+
|
|
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
|
+
})
|
|
1009
|
+
|
|
1010
|
+
if (!openPt) {
|
|
1011
|
+
// Single path, so close last seg
|
|
1012
|
+
openPt = path.segs.length - 1
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
if (openPt !== false) {
|
|
1016
|
+
// Close this path
|
|
1017
|
+
|
|
1018
|
+
// Create a line going to the previous "M"
|
|
1019
|
+
const newseg = elem.createSVGPathSegLinetoAbs(startItem.x, startItem.y)
|
|
1020
|
+
|
|
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
|
+
}
|
|
1029
|
+
|
|
1030
|
+
path.init().selectPt(openPt + 1)
|
|
1031
|
+
return
|
|
1032
|
+
}
|
|
1033
|
+
|
|
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
|
|
1036
|
+
|
|
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
|
|
1039
|
+
|
|
1040
|
+
const seg = path.segs[index]
|
|
1041
|
+
|
|
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
|
+
}
|
|
1048
|
+
|
|
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
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
let num = (index - lastM) - 1
|
|
1071
|
+
|
|
1072
|
+
while (num--) {
|
|
1073
|
+
list.insertItemBefore(list.getItem(lastM), zSeg)
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
const pt = list.getItem(lastM)
|
|
1077
|
+
|
|
1078
|
+
// Make this point the new "M"
|
|
1079
|
+
svgCanvas.replacePathSeg(2, lastM, [pt.x, pt.y])
|
|
1080
|
+
|
|
1081
|
+
// i = index; // i is local here, so has no effect; what was the intent for this?
|
|
1082
|
+
|
|
1083
|
+
path.init().selectPt(0)
|
|
1084
|
+
},
|
|
1085
|
+
/**
|
|
1086
|
+
* @returns {void}
|
|
1087
|
+
*/
|
|
1088
|
+
deletePathNode () {
|
|
1089
|
+
if (!pathActionsMethod.canDeleteNodes) { return }
|
|
1090
|
+
path.storeD()
|
|
1091
|
+
|
|
1092
|
+
const selPts = path.selected_pts
|
|
1093
|
+
|
|
1094
|
+
let i = selPts.length
|
|
1095
|
+
while (i--) {
|
|
1096
|
+
const pt = selPts[i]
|
|
1097
|
+
path.deleteSeg(pt)
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
// Cleanup
|
|
1101
|
+
const cleanup = function () {
|
|
1102
|
+
const segList = path.elem.pathSegList
|
|
1103
|
+
let len = segList.numberOfItems
|
|
1104
|
+
|
|
1105
|
+
const remItems = function (pos, count) {
|
|
1106
|
+
while (count--) {
|
|
1107
|
+
segList.removeItem(pos)
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
|
|
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
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
return false
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
cleanup()
|
|
1146
|
+
|
|
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
|
+
}
|
|
1153
|
+
|
|
1154
|
+
path.init()
|
|
1155
|
+
path.clearSelection()
|
|
1156
|
+
|
|
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 }
|
|
1187
|
+
|
|
1188
|
+
path.storeD()
|
|
1189
|
+
|
|
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]
|
|
1194
|
+
|
|
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
|
+
}
|
|
1214
|
+
|
|
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
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
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
|
+
})()
|
|
1237
|
+
// end pathActions
|