@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-method.js
ADDED
|
@@ -0,0 +1,1012 @@
|
|
|
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 { ChangeElementCommand } from './history.js'
|
|
11
|
+
import {
|
|
12
|
+
transformPoint, getMatrix
|
|
13
|
+
} from './math.js'
|
|
14
|
+
import {
|
|
15
|
+
assignAttributes, getRotationAngle,
|
|
16
|
+
getElement
|
|
17
|
+
} from './utilities.js'
|
|
18
|
+
|
|
19
|
+
let svgCanvas = null
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @function module:path-actions.init
|
|
23
|
+
* @param {module:path-actions.svgCanvas} pathMethodsContext
|
|
24
|
+
* @returns {void}
|
|
25
|
+
*/
|
|
26
|
+
export const init = (canvas) => {
|
|
27
|
+
svgCanvas = canvas
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* eslint-disable max-len */
|
|
31
|
+
/**
|
|
32
|
+
* @function module:path.ptObjToArr
|
|
33
|
+
* @todo See if this should just live in `replacePathSeg`
|
|
34
|
+
* @param {string} type
|
|
35
|
+
* @param {SVGPathSegMovetoAbs|SVGPathSegLinetoAbs|SVGPathSegCurvetoCubicAbs|SVGPathSegCurvetoQuadraticAbs|SVGPathSegArcAbs|SVGPathSegLinetoHorizontalAbs|SVGPathSegLinetoVerticalAbs|SVGPathSegCurvetoCubicSmoothAbs|SVGPathSegCurvetoQuadraticSmoothAbs} segItem
|
|
36
|
+
* @returns {ArgumentsArray}
|
|
37
|
+
*/
|
|
38
|
+
/* eslint-enable max-len */
|
|
39
|
+
export const ptObjToArrMethod = function (type, segItem) {
|
|
40
|
+
const segData = svgCanvas.getSegData()
|
|
41
|
+
const props = segData[type]
|
|
42
|
+
return props.map((prop) => {
|
|
43
|
+
return segItem[prop]
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @function module:path.getGripPt
|
|
49
|
+
* @param {Segment} seg
|
|
50
|
+
* @param {module:math.XYObject} altPt
|
|
51
|
+
* @returns {module:math.XYObject}
|
|
52
|
+
*/
|
|
53
|
+
export const getGripPtMethod = function (seg, altPt) {
|
|
54
|
+
const { path: pth } = seg
|
|
55
|
+
let out = {
|
|
56
|
+
x: altPt ? altPt.x : seg.item.x,
|
|
57
|
+
y: altPt ? altPt.y : seg.item.y
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (pth.matrix) {
|
|
61
|
+
const pt = transformPoint(out.x, out.y, pth.matrix)
|
|
62
|
+
out = pt
|
|
63
|
+
}
|
|
64
|
+
const zoom = svgCanvas.getZoom()
|
|
65
|
+
out.x *= zoom
|
|
66
|
+
out.y *= zoom
|
|
67
|
+
|
|
68
|
+
return out
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* @function module:path.getPointFromGrip
|
|
72
|
+
* @param {module:math.XYObject} pt
|
|
73
|
+
* @param {module:path.Path} pth
|
|
74
|
+
* @returns {module:math.XYObject}
|
|
75
|
+
*/
|
|
76
|
+
export const getPointFromGripMethod = function (pt, pth) {
|
|
77
|
+
const out = {
|
|
78
|
+
x: pt.x,
|
|
79
|
+
y: pt.y
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (pth.matrix) {
|
|
83
|
+
pt = transformPoint(out.x, out.y, pth.imatrix)
|
|
84
|
+
out.x = pt.x
|
|
85
|
+
out.y = pt.y
|
|
86
|
+
}
|
|
87
|
+
const zoom = svgCanvas.getZoom()
|
|
88
|
+
out.x /= zoom
|
|
89
|
+
out.y /= zoom
|
|
90
|
+
|
|
91
|
+
return out
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* @function module:path.getGripContainer
|
|
95
|
+
* @returns {Element}
|
|
96
|
+
*/
|
|
97
|
+
export const getGripContainerMethod = function () {
|
|
98
|
+
let c = getElement('pathpointgrip_container')
|
|
99
|
+
if (!c) {
|
|
100
|
+
const parentElement = getElement('selectorParentGroup')
|
|
101
|
+
c = document.createElementNS(NS.SVG, 'g')
|
|
102
|
+
parentElement.append(c)
|
|
103
|
+
c.id = 'pathpointgrip_container'
|
|
104
|
+
}
|
|
105
|
+
return c
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Requires prior call to `setUiStrings` if `xlink:title`
|
|
109
|
+
* to be set on the grip.
|
|
110
|
+
* @function module:path.addPointGrip
|
|
111
|
+
* @param {Integer} index
|
|
112
|
+
* @param {Integer} x
|
|
113
|
+
* @param {Integer} y
|
|
114
|
+
* @returns {SVGCircleElement}
|
|
115
|
+
*/
|
|
116
|
+
export const addPointGripMethod = function (index, x, y) {
|
|
117
|
+
// create the container of all the point grips
|
|
118
|
+
const pointGripContainer = getGripContainerMethod()
|
|
119
|
+
|
|
120
|
+
let pointGrip = getElement('pathpointgrip_' + index)
|
|
121
|
+
// create it
|
|
122
|
+
if (!pointGrip) {
|
|
123
|
+
pointGrip = document.createElementNS(NS.SVG, 'circle')
|
|
124
|
+
const atts = {
|
|
125
|
+
id: 'pathpointgrip_' + index,
|
|
126
|
+
display: 'none',
|
|
127
|
+
r: 4,
|
|
128
|
+
fill: '#0FF',
|
|
129
|
+
stroke: '#00F',
|
|
130
|
+
'stroke-width': 2,
|
|
131
|
+
cursor: 'move',
|
|
132
|
+
style: 'pointer-events:all'
|
|
133
|
+
}
|
|
134
|
+
const uiStrings = svgCanvas.getUIStrings()
|
|
135
|
+
if ('pathNodeTooltip' in uiStrings) { // May be empty if running path.js without svg-editor
|
|
136
|
+
atts['xlink:title'] = uiStrings.pathNodeTooltip
|
|
137
|
+
}
|
|
138
|
+
assignAttributes(pointGrip, atts)
|
|
139
|
+
pointGripContainer.append(pointGrip)
|
|
140
|
+
|
|
141
|
+
const grip = document.getElementById('pathpointgrip_' + index)
|
|
142
|
+
grip?.addEventListener('dblclick', () => {
|
|
143
|
+
const path = svgCanvas.getPathObj()
|
|
144
|
+
if (path) {
|
|
145
|
+
path.setSegType()
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
if (x && y) {
|
|
150
|
+
// set up the point grip element and display it
|
|
151
|
+
assignAttributes(pointGrip, {
|
|
152
|
+
cx: x,
|
|
153
|
+
cy: y,
|
|
154
|
+
display: 'inline'
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
return pointGrip
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Requires prior call to `setUiStrings` if `xlink:title`
|
|
161
|
+
* to be set on the grip.
|
|
162
|
+
* @function module:path.addCtrlGrip
|
|
163
|
+
* @param {string} id
|
|
164
|
+
* @returns {SVGCircleElement}
|
|
165
|
+
*/
|
|
166
|
+
export const addCtrlGripMethod = function (id) {
|
|
167
|
+
let pointGrip = getElement('ctrlpointgrip_' + id)
|
|
168
|
+
if (pointGrip) { return pointGrip }
|
|
169
|
+
|
|
170
|
+
pointGrip = document.createElementNS(NS.SVG, 'circle')
|
|
171
|
+
const atts = {
|
|
172
|
+
id: 'ctrlpointgrip_' + id,
|
|
173
|
+
display: 'none',
|
|
174
|
+
r: 4,
|
|
175
|
+
fill: '#0FF',
|
|
176
|
+
stroke: '#55F',
|
|
177
|
+
'stroke-width': 1,
|
|
178
|
+
cursor: 'move',
|
|
179
|
+
style: 'pointer-events:all'
|
|
180
|
+
}
|
|
181
|
+
const uiStrings = svgCanvas.getUIStrings()
|
|
182
|
+
if ('pathCtrlPtTooltip' in uiStrings) { // May be empty if running path.js without svg-editor
|
|
183
|
+
atts['xlink:title'] = uiStrings.pathCtrlPtTooltip
|
|
184
|
+
}
|
|
185
|
+
assignAttributes(pointGrip, atts)
|
|
186
|
+
getGripContainerMethod().append(pointGrip)
|
|
187
|
+
return pointGrip
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* @function module:path.getCtrlLine
|
|
191
|
+
* @param {string} id
|
|
192
|
+
* @returns {SVGLineElement}
|
|
193
|
+
*/
|
|
194
|
+
export const getCtrlLineMethod = function (id) {
|
|
195
|
+
let ctrlLine = getElement('ctrlLine_' + id)
|
|
196
|
+
if (ctrlLine) { return ctrlLine }
|
|
197
|
+
|
|
198
|
+
ctrlLine = document.createElementNS(NS.SVG, 'line')
|
|
199
|
+
assignAttributes(ctrlLine, {
|
|
200
|
+
id: 'ctrlLine_' + id,
|
|
201
|
+
stroke: '#555',
|
|
202
|
+
'stroke-width': 1,
|
|
203
|
+
style: 'pointer-events:none'
|
|
204
|
+
})
|
|
205
|
+
getGripContainerMethod().append(ctrlLine)
|
|
206
|
+
return ctrlLine
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* @function module:path.getPointGrip
|
|
210
|
+
* @param {Segment} seg
|
|
211
|
+
* @param {boolean} update
|
|
212
|
+
* @returns {SVGCircleElement}
|
|
213
|
+
*/
|
|
214
|
+
export const getPointGripMethod = function (seg, update) {
|
|
215
|
+
const { index } = seg
|
|
216
|
+
const pointGrip = addPointGripMethod(index)
|
|
217
|
+
|
|
218
|
+
if (update) {
|
|
219
|
+
const pt = getGripPtMethod(seg)
|
|
220
|
+
assignAttributes(pointGrip, {
|
|
221
|
+
cx: pt.x,
|
|
222
|
+
cy: pt.y,
|
|
223
|
+
display: 'inline'
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return pointGrip
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* @function module:path.getControlPoints
|
|
231
|
+
* @param {Segment} seg
|
|
232
|
+
* @returns {PlainObject<string, SVGLineElement|SVGCircleElement>}
|
|
233
|
+
*/
|
|
234
|
+
export const getControlPointsMethod = function (seg) {
|
|
235
|
+
const { item, index } = seg
|
|
236
|
+
if (!('x1' in item) || !('x2' in item)) { return null }
|
|
237
|
+
const cpt = {}
|
|
238
|
+
/* const pointGripContainer = */ getGripContainerMethod()
|
|
239
|
+
|
|
240
|
+
// Note that this is intentionally not seg.prev.item
|
|
241
|
+
const path = svgCanvas.getPathObj()
|
|
242
|
+
const prev = path.segs[index - 1].item
|
|
243
|
+
|
|
244
|
+
const segItems = [prev, item]
|
|
245
|
+
|
|
246
|
+
for (let i = 1; i < 3; i++) {
|
|
247
|
+
const id = index + 'c' + i
|
|
248
|
+
|
|
249
|
+
const ctrlLine = cpt['c' + i + '_line'] = getCtrlLineMethod(id)
|
|
250
|
+
|
|
251
|
+
const pt = getGripPtMethod(seg, { x: item['x' + i], y: item['y' + i] })
|
|
252
|
+
const gpt = getGripPtMethod(seg, { x: segItems[i - 1].x, y: segItems[i - 1].y })
|
|
253
|
+
|
|
254
|
+
assignAttributes(ctrlLine, {
|
|
255
|
+
x1: pt.x,
|
|
256
|
+
y1: pt.y,
|
|
257
|
+
x2: gpt.x,
|
|
258
|
+
y2: gpt.y,
|
|
259
|
+
display: 'inline'
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
cpt['c' + i + '_line'] = ctrlLine
|
|
263
|
+
|
|
264
|
+
// create it
|
|
265
|
+
const pointGrip = cpt['c' + i] = addCtrlGripMethod(id)
|
|
266
|
+
|
|
267
|
+
assignAttributes(pointGrip, {
|
|
268
|
+
cx: pt.x,
|
|
269
|
+
cy: pt.y,
|
|
270
|
+
display: 'inline'
|
|
271
|
+
})
|
|
272
|
+
cpt['c' + i] = pointGrip
|
|
273
|
+
}
|
|
274
|
+
return cpt
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* This replaces the segment at the given index. Type is given as number.
|
|
278
|
+
* @function module:path.replacePathSeg
|
|
279
|
+
* @param {Integer} type Possible values set during {@link module:path.init}
|
|
280
|
+
* @param {Integer} index
|
|
281
|
+
* @param {ArgumentsArray} pts
|
|
282
|
+
* @param {SVGPathElement} elem
|
|
283
|
+
* @returns {void}
|
|
284
|
+
*/
|
|
285
|
+
export const replacePathSegMethod = function (type, index, pts, elem) {
|
|
286
|
+
const path = svgCanvas.getPathObj()
|
|
287
|
+
const pth = elem || path.elem
|
|
288
|
+
const pathFuncs = svgCanvas.getPathFuncs()
|
|
289
|
+
const func = 'createSVGPathSeg' + pathFuncs[type]
|
|
290
|
+
const seg = pth[func](...pts)
|
|
291
|
+
|
|
292
|
+
pth.pathSegList.replaceItem(seg, index)
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* @function module:path.getSegSelector
|
|
296
|
+
* @param {Segment} seg
|
|
297
|
+
* @param {boolean} update
|
|
298
|
+
* @returns {SVGPathElement}
|
|
299
|
+
*/
|
|
300
|
+
export const getSegSelectorMethod = function (seg, update) {
|
|
301
|
+
const { index } = seg
|
|
302
|
+
let segLine = getElement('segline_' + index)
|
|
303
|
+
if (!segLine) {
|
|
304
|
+
const pointGripContainer = getGripContainerMethod()
|
|
305
|
+
// create segline
|
|
306
|
+
segLine = document.createElementNS(NS.SVG, 'path')
|
|
307
|
+
assignAttributes(segLine, {
|
|
308
|
+
id: 'segline_' + index,
|
|
309
|
+
display: 'none',
|
|
310
|
+
fill: 'none',
|
|
311
|
+
stroke: '#0FF',
|
|
312
|
+
'stroke-width': 2,
|
|
313
|
+
style: 'pointer-events:none',
|
|
314
|
+
d: 'M0,0 0,0'
|
|
315
|
+
})
|
|
316
|
+
pointGripContainer.append(segLine)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (update) {
|
|
320
|
+
const { prev } = seg
|
|
321
|
+
if (!prev) {
|
|
322
|
+
segLine.setAttribute('display', 'none')
|
|
323
|
+
return segLine
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const pt = getGripPtMethod(prev)
|
|
327
|
+
// Set start point
|
|
328
|
+
replacePathSegMethod(2, 0, [pt.x, pt.y], segLine)
|
|
329
|
+
|
|
330
|
+
const pts = ptObjToArrMethod(seg.type, seg.item) // , true);
|
|
331
|
+
for (let i = 0; i < pts.length; i += 2) {
|
|
332
|
+
const point = getGripPtMethod(seg, { x: pts[i], y: pts[i + 1] })
|
|
333
|
+
pts[i] = point.x
|
|
334
|
+
pts[i + 1] = point.y
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
replacePathSegMethod(seg.type, 1, pts, segLine)
|
|
338
|
+
}
|
|
339
|
+
return segLine
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
*
|
|
343
|
+
*/
|
|
344
|
+
export class Segment {
|
|
345
|
+
/**
|
|
346
|
+
* @param {Integer} index
|
|
347
|
+
* @param {SVGPathSeg} item
|
|
348
|
+
* @todo Is `item` be more constrained here?
|
|
349
|
+
*/
|
|
350
|
+
constructor (index, item) {
|
|
351
|
+
this.selected = false
|
|
352
|
+
this.index = index
|
|
353
|
+
this.item = item
|
|
354
|
+
this.type = item.pathSegType
|
|
355
|
+
|
|
356
|
+
this.ctrlpts = []
|
|
357
|
+
this.ptgrip = null
|
|
358
|
+
this.segsel = null
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* @param {boolean} y
|
|
363
|
+
* @returns {void}
|
|
364
|
+
*/
|
|
365
|
+
showCtrlPts (y) {
|
|
366
|
+
for (const i in this.ctrlpts) {
|
|
367
|
+
if ({}.hasOwnProperty.call(this.ctrlpts, i)) {
|
|
368
|
+
this.ctrlpts[i].setAttribute('display', y ? 'inline' : 'none')
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* @param {boolean} y
|
|
375
|
+
* @returns {void}
|
|
376
|
+
*/
|
|
377
|
+
selectCtrls (y) {
|
|
378
|
+
document.getElementById('ctrlpointgrip_' + this.index + 'c1').setAttribute('fill', y ? '#0FF' : '#EEE')
|
|
379
|
+
document.getElementById('ctrlpointgrip_' + this.index + 'c2').setAttribute('fill', y ? '#0FF' : '#EEE')
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* @param {boolean} y
|
|
384
|
+
* @returns {void}
|
|
385
|
+
*/
|
|
386
|
+
show (y) {
|
|
387
|
+
if (this.ptgrip) {
|
|
388
|
+
this.ptgrip.setAttribute('display', y ? 'inline' : 'none')
|
|
389
|
+
this.segsel.setAttribute('display', y ? 'inline' : 'none')
|
|
390
|
+
// Show/hide all control points if available
|
|
391
|
+
this.showCtrlPts(y)
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* @param {boolean} y
|
|
397
|
+
* @returns {void}
|
|
398
|
+
*/
|
|
399
|
+
select (y) {
|
|
400
|
+
if (this.ptgrip) {
|
|
401
|
+
this.ptgrip.setAttribute('stroke', y ? '#0FF' : '#00F')
|
|
402
|
+
this.segsel.setAttribute('display', y ? 'inline' : 'none')
|
|
403
|
+
if (this.ctrlpts) {
|
|
404
|
+
this.selectCtrls(y)
|
|
405
|
+
}
|
|
406
|
+
this.selected = y
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* @returns {void}
|
|
412
|
+
*/
|
|
413
|
+
addGrip () {
|
|
414
|
+
this.ptgrip = getPointGripMethod(this, true)
|
|
415
|
+
this.ctrlpts = getControlPointsMethod(this) // , true);
|
|
416
|
+
this.segsel = getSegSelectorMethod(this, true)
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* @param {boolean} full
|
|
421
|
+
* @returns {void}
|
|
422
|
+
*/
|
|
423
|
+
update (full) {
|
|
424
|
+
if (this.ptgrip) {
|
|
425
|
+
const pt = getGripPtMethod(this)
|
|
426
|
+
assignAttributes(this.ptgrip, {
|
|
427
|
+
cx: pt.x,
|
|
428
|
+
cy: pt.y
|
|
429
|
+
})
|
|
430
|
+
|
|
431
|
+
getSegSelectorMethod(this, true)
|
|
432
|
+
|
|
433
|
+
if (this.ctrlpts) {
|
|
434
|
+
if (full) {
|
|
435
|
+
const path = svgCanvas.getPathObj()
|
|
436
|
+
this.item = path.elem.pathSegList.getItem(this.index)
|
|
437
|
+
this.type = this.item.pathSegType
|
|
438
|
+
}
|
|
439
|
+
getControlPointsMethod(this)
|
|
440
|
+
}
|
|
441
|
+
// this.segsel.setAttribute('display', y ? 'inline' : 'none');
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* @param {Integer} dx
|
|
447
|
+
* @param {Integer} dy
|
|
448
|
+
* @returns {void}
|
|
449
|
+
*/
|
|
450
|
+
move (dx, dy) {
|
|
451
|
+
const { item } = this
|
|
452
|
+
|
|
453
|
+
const curPts = this.ctrlpts
|
|
454
|
+
? [
|
|
455
|
+
item.x += dx, item.y += dy,
|
|
456
|
+
item.x1, item.y1, item.x2 += dx, item.y2 += dy
|
|
457
|
+
]
|
|
458
|
+
: [item.x += dx, item.y += dy]
|
|
459
|
+
|
|
460
|
+
replacePathSegMethod(
|
|
461
|
+
this.type,
|
|
462
|
+
this.index,
|
|
463
|
+
// type 10 means ARC
|
|
464
|
+
this.type === 10 ? ptObjToArrMethod(this.type, item) : curPts
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
if (this.next?.ctrlpts) {
|
|
468
|
+
const next = this.next.item
|
|
469
|
+
const nextPts = [
|
|
470
|
+
next.x, next.y,
|
|
471
|
+
next.x1 += dx, next.y1 += dy, next.x2, next.y2
|
|
472
|
+
]
|
|
473
|
+
replacePathSegMethod(this.next.type, this.next.index, nextPts)
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (this.mate) {
|
|
477
|
+
// The last point of a closed subpath has a 'mate',
|
|
478
|
+
// which is the 'M' segment of the subpath
|
|
479
|
+
const { item: itm } = this.mate
|
|
480
|
+
const pts = [itm.x += dx, itm.y += dy]
|
|
481
|
+
replacePathSegMethod(this.mate.type, this.mate.index, pts)
|
|
482
|
+
// Has no grip, so does not need 'updating'?
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
this.update(true)
|
|
486
|
+
if (this.next) { this.next.update(true) }
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* @param {Integer} num
|
|
491
|
+
* @returns {void}
|
|
492
|
+
*/
|
|
493
|
+
setLinked (num) {
|
|
494
|
+
let seg; let anum; let pt
|
|
495
|
+
if (num === 2) {
|
|
496
|
+
anum = 1
|
|
497
|
+
seg = this.next
|
|
498
|
+
if (!seg) { return }
|
|
499
|
+
pt = this.item
|
|
500
|
+
} else {
|
|
501
|
+
anum = 2
|
|
502
|
+
seg = this.prev
|
|
503
|
+
if (!seg) { return }
|
|
504
|
+
pt = seg.item
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const { item } = seg
|
|
508
|
+
item['x' + anum] = pt.x + (pt.x - this.item['x' + num])
|
|
509
|
+
item['y' + anum] = pt.y + (pt.y - this.item['y' + num])
|
|
510
|
+
|
|
511
|
+
const pts = [
|
|
512
|
+
item.x, item.y,
|
|
513
|
+
item.x1, item.y1,
|
|
514
|
+
item.x2, item.y2
|
|
515
|
+
]
|
|
516
|
+
|
|
517
|
+
replacePathSegMethod(seg.type, seg.index, pts)
|
|
518
|
+
seg.update(true)
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* @param {Integer} num
|
|
523
|
+
* @param {Integer} dx
|
|
524
|
+
* @param {Integer} dy
|
|
525
|
+
* @returns {void}
|
|
526
|
+
*/
|
|
527
|
+
moveCtrl (num, dx, dy) {
|
|
528
|
+
const { item } = this
|
|
529
|
+
item['x' + num] += dx
|
|
530
|
+
item['y' + num] += dy
|
|
531
|
+
|
|
532
|
+
const pts = [
|
|
533
|
+
item.x, item.y,
|
|
534
|
+
item.x1, item.y1, item.x2, item.y2
|
|
535
|
+
]
|
|
536
|
+
|
|
537
|
+
replacePathSegMethod(this.type, this.index, pts)
|
|
538
|
+
this.update(true)
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* @param {Integer} newType Possible values set during {@link module:path.init}
|
|
543
|
+
* @param {ArgumentsArray} pts
|
|
544
|
+
* @returns {void}
|
|
545
|
+
*/
|
|
546
|
+
setType (newType, pts) {
|
|
547
|
+
replacePathSegMethod(newType, this.index, pts)
|
|
548
|
+
this.type = newType
|
|
549
|
+
const path = svgCanvas.getPathObj()
|
|
550
|
+
this.item = path.elem.pathSegList.getItem(this.index)
|
|
551
|
+
this.showCtrlPts(newType === 6)
|
|
552
|
+
this.ctrlpts = getControlPointsMethod(this)
|
|
553
|
+
this.update(true)
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
*
|
|
559
|
+
*/
|
|
560
|
+
export class Path {
|
|
561
|
+
/**
|
|
562
|
+
* @param {SVGPathElement} elem
|
|
563
|
+
* @throws {Error} If constructed without a path element
|
|
564
|
+
*/
|
|
565
|
+
constructor (elem) {
|
|
566
|
+
if (!elem || elem.tagName !== 'path') {
|
|
567
|
+
throw new Error('svgedit.path.Path constructed without a <path> element')
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
this.elem = elem
|
|
571
|
+
this.segs = []
|
|
572
|
+
this.selected_pts = []
|
|
573
|
+
svgCanvas.setPathObj(this)
|
|
574
|
+
// path = this;
|
|
575
|
+
|
|
576
|
+
this.init()
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
setPathContext () {
|
|
580
|
+
svgCanvas.setPathObj(this)
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Reset path data.
|
|
585
|
+
* @returns {module:path.Path}
|
|
586
|
+
*/
|
|
587
|
+
init () {
|
|
588
|
+
// Hide all grips, etc
|
|
589
|
+
|
|
590
|
+
// fixed, needed to work on all found elements, not just first
|
|
591
|
+
const pointGripContainer = getGripContainerMethod()
|
|
592
|
+
const elements = pointGripContainer.querySelectorAll('*')
|
|
593
|
+
Array.prototype.forEach.call(elements, function (el) {
|
|
594
|
+
el.setAttribute('display', 'none')
|
|
595
|
+
})
|
|
596
|
+
|
|
597
|
+
const segList = this.elem.pathSegList
|
|
598
|
+
const len = segList.numberOfItems
|
|
599
|
+
this.segs = []
|
|
600
|
+
this.selected_pts = []
|
|
601
|
+
this.first_seg = null
|
|
602
|
+
|
|
603
|
+
// Set up segs array
|
|
604
|
+
for (let i = 0; i < len; i++) {
|
|
605
|
+
const item = segList.getItem(i)
|
|
606
|
+
const segment = new Segment(i, item)
|
|
607
|
+
segment.path = this
|
|
608
|
+
this.segs.push(segment)
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
const { segs } = this
|
|
612
|
+
|
|
613
|
+
let startI = null
|
|
614
|
+
for (let i = 0; i < len; i++) {
|
|
615
|
+
const seg = segs[i]
|
|
616
|
+
const nextSeg = (i + 1) >= len ? null : segs[i + 1]
|
|
617
|
+
const prevSeg = (i - 1) < 0 ? null : segs[i - 1]
|
|
618
|
+
if (seg.type === 2) {
|
|
619
|
+
if (prevSeg && prevSeg.type !== 1) {
|
|
620
|
+
// New sub-path, last one is open,
|
|
621
|
+
// so add a grip to last sub-path's first point
|
|
622
|
+
const startSeg = segs[startI]
|
|
623
|
+
startSeg.next = segs[startI + 1]
|
|
624
|
+
startSeg.next.prev = startSeg
|
|
625
|
+
startSeg.addGrip()
|
|
626
|
+
}
|
|
627
|
+
// Remember that this is a starter seg
|
|
628
|
+
startI = i
|
|
629
|
+
} else if (nextSeg?.type === 1) {
|
|
630
|
+
// This is the last real segment of a closed sub-path
|
|
631
|
+
// Next is first seg after "M"
|
|
632
|
+
seg.next = segs[startI + 1]
|
|
633
|
+
|
|
634
|
+
// First seg after "M"'s prev is this
|
|
635
|
+
seg.next.prev = seg
|
|
636
|
+
seg.mate = segs[startI]
|
|
637
|
+
seg.addGrip()
|
|
638
|
+
if (!this.first_seg) {
|
|
639
|
+
this.first_seg = seg
|
|
640
|
+
}
|
|
641
|
+
} else if (!nextSeg) {
|
|
642
|
+
if (seg.type !== 1) {
|
|
643
|
+
// Last seg, doesn't close so add a grip
|
|
644
|
+
// to last sub-path's first point
|
|
645
|
+
const startSeg = segs[startI]
|
|
646
|
+
startSeg.next = segs[startI + 1]
|
|
647
|
+
startSeg.next.prev = startSeg
|
|
648
|
+
startSeg.addGrip()
|
|
649
|
+
seg.addGrip()
|
|
650
|
+
|
|
651
|
+
if (!this.first_seg) {
|
|
652
|
+
// Open path, so set first as real first and add grip
|
|
653
|
+
this.first_seg = segs[startI]
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
} else if (seg.type !== 1) {
|
|
657
|
+
// Regular segment, so add grip and its "next"
|
|
658
|
+
seg.addGrip()
|
|
659
|
+
|
|
660
|
+
// Don't set its "next" if it's an "M"
|
|
661
|
+
if (nextSeg && nextSeg.type !== 2) {
|
|
662
|
+
seg.next = nextSeg
|
|
663
|
+
seg.next.prev = seg
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return this
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* @callback module:path.PathEachSegCallback
|
|
672
|
+
* @this module:path.Segment
|
|
673
|
+
* @param {Integer} i The index of the seg being iterated
|
|
674
|
+
* @returns {boolean|void} Will stop execution of `eachSeg` if returns `false`
|
|
675
|
+
*/
|
|
676
|
+
/**
|
|
677
|
+
* @param {module:path.PathEachSegCallback} fn
|
|
678
|
+
* @returns {void}
|
|
679
|
+
*/
|
|
680
|
+
eachSeg (fn) {
|
|
681
|
+
const len = this.segs.length
|
|
682
|
+
for (let i = 0; i < len; i++) {
|
|
683
|
+
const ret = fn.call(this.segs[i], i)
|
|
684
|
+
if (ret === false) { break }
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* @param {Integer} index
|
|
690
|
+
* @returns {void}
|
|
691
|
+
*/
|
|
692
|
+
addSeg (index) {
|
|
693
|
+
// Adds a new segment
|
|
694
|
+
const seg = this.segs[index]
|
|
695
|
+
if (!seg.prev) { return }
|
|
696
|
+
|
|
697
|
+
const { prev } = seg
|
|
698
|
+
let newseg; let newX; let newY
|
|
699
|
+
switch (seg.item.pathSegType) {
|
|
700
|
+
case 4: {
|
|
701
|
+
newX = (seg.item.x + prev.item.x) / 2
|
|
702
|
+
newY = (seg.item.y + prev.item.y) / 2
|
|
703
|
+
newseg = this.elem.createSVGPathSegLinetoAbs(newX, newY)
|
|
704
|
+
break
|
|
705
|
+
} case 6: { // make it a curved segment to preserve the shape (WRS)
|
|
706
|
+
// https://en.wikipedia.org/wiki/De_Casteljau%27s_algorithm#Geometric_interpretation
|
|
707
|
+
const p0x = (prev.item.x + seg.item.x1) / 2
|
|
708
|
+
const p1x = (seg.item.x1 + seg.item.x2) / 2
|
|
709
|
+
const p2x = (seg.item.x2 + seg.item.x) / 2
|
|
710
|
+
const p01x = (p0x + p1x) / 2
|
|
711
|
+
const p12x = (p1x + p2x) / 2
|
|
712
|
+
newX = (p01x + p12x) / 2
|
|
713
|
+
const p0y = (prev.item.y + seg.item.y1) / 2
|
|
714
|
+
const p1y = (seg.item.y1 + seg.item.y2) / 2
|
|
715
|
+
const p2y = (seg.item.y2 + seg.item.y) / 2
|
|
716
|
+
const p01y = (p0y + p1y) / 2
|
|
717
|
+
const p12y = (p1y + p2y) / 2
|
|
718
|
+
newY = (p01y + p12y) / 2
|
|
719
|
+
newseg = this.elem.createSVGPathSegCurvetoCubicAbs(newX, newY, p0x, p0y, p01x, p01y)
|
|
720
|
+
const pts = [seg.item.x, seg.item.y, p12x, p12y, p2x, p2y]
|
|
721
|
+
replacePathSegMethod(seg.type, index, pts)
|
|
722
|
+
break
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
const list = this.elem.pathSegList
|
|
726
|
+
list.insertItemBefore(newseg, index)
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* @param {Integer} index
|
|
731
|
+
* @returns {void}
|
|
732
|
+
*/
|
|
733
|
+
deleteSeg (index) {
|
|
734
|
+
const seg = this.segs[index]
|
|
735
|
+
const list = this.elem.pathSegList
|
|
736
|
+
|
|
737
|
+
seg.show(false)
|
|
738
|
+
const { next } = seg
|
|
739
|
+
if (seg.mate) {
|
|
740
|
+
// Make the next point be the "M" point
|
|
741
|
+
const pt = [next.item.x, next.item.y]
|
|
742
|
+
replacePathSegMethod(2, next.index, pt)
|
|
743
|
+
|
|
744
|
+
// Reposition last node
|
|
745
|
+
replacePathSegMethod(4, seg.index, pt)
|
|
746
|
+
|
|
747
|
+
list.removeItem(seg.mate.index)
|
|
748
|
+
} else if (!seg.prev) {
|
|
749
|
+
// First node of open path, make next point the M
|
|
750
|
+
// const {item} = seg;
|
|
751
|
+
const pt = [next.item.x, next.item.y]
|
|
752
|
+
replacePathSegMethod(2, seg.next.index, pt)
|
|
753
|
+
list.removeItem(index)
|
|
754
|
+
} else {
|
|
755
|
+
list.removeItem(index)
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* @param {Integer} index
|
|
761
|
+
* @returns {void}
|
|
762
|
+
*/
|
|
763
|
+
removePtFromSelection (index) {
|
|
764
|
+
const pos = this.selected_pts.indexOf(index)
|
|
765
|
+
if (pos === -1) {
|
|
766
|
+
return
|
|
767
|
+
}
|
|
768
|
+
this.segs[index].select(false)
|
|
769
|
+
this.selected_pts.splice(pos, 1)
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* @returns {void}
|
|
774
|
+
*/
|
|
775
|
+
clearSelection () {
|
|
776
|
+
this.eachSeg(function () {
|
|
777
|
+
// 'this' is the segment here
|
|
778
|
+
this.select(false)
|
|
779
|
+
})
|
|
780
|
+
this.selected_pts = []
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* @returns {void}
|
|
785
|
+
*/
|
|
786
|
+
storeD () {
|
|
787
|
+
this.last_d = this.elem.getAttribute('d')
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* @param {Integer} y
|
|
792
|
+
* @returns {Path}
|
|
793
|
+
*/
|
|
794
|
+
show (y) {
|
|
795
|
+
// Shows this path's segment grips
|
|
796
|
+
this.eachSeg(function () {
|
|
797
|
+
// 'this' is the segment here
|
|
798
|
+
this.show(y)
|
|
799
|
+
})
|
|
800
|
+
if (y) {
|
|
801
|
+
this.selectPt(this.first_seg.index)
|
|
802
|
+
}
|
|
803
|
+
return this
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* Move selected points.
|
|
808
|
+
* @param {Integer} dx
|
|
809
|
+
* @param {Integer} dy
|
|
810
|
+
* @returns {void}
|
|
811
|
+
*/
|
|
812
|
+
movePts (dx, dy) {
|
|
813
|
+
let i = this.selected_pts.length
|
|
814
|
+
while (i--) {
|
|
815
|
+
const seg = this.segs[this.selected_pts[i]]
|
|
816
|
+
seg.move(dx, dy)
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/**
|
|
821
|
+
* @param {Integer} dx
|
|
822
|
+
* @param {Integer} dy
|
|
823
|
+
* @returns {void}
|
|
824
|
+
*/
|
|
825
|
+
moveCtrl (dx, dy) {
|
|
826
|
+
const seg = this.segs[this.selected_pts[0]]
|
|
827
|
+
seg.moveCtrl(this.dragctrl, dx, dy)
|
|
828
|
+
if (svgCanvas.getLinkControlPts()) {
|
|
829
|
+
seg.setLinked(this.dragctrl)
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* @param {?Integer} newType See {@link https://www.w3.org/TR/SVG/single-page.html#paths-InterfaceSVGPathSeg}
|
|
835
|
+
* @returns {void}
|
|
836
|
+
*/
|
|
837
|
+
setSegType (newType) {
|
|
838
|
+
this.storeD()
|
|
839
|
+
let i = this.selected_pts.length
|
|
840
|
+
let text
|
|
841
|
+
while (i--) {
|
|
842
|
+
const selPt = this.selected_pts[i]
|
|
843
|
+
|
|
844
|
+
// Selected seg
|
|
845
|
+
const cur = this.segs[selPt]
|
|
846
|
+
const { prev } = cur
|
|
847
|
+
if (!prev) { continue }
|
|
848
|
+
|
|
849
|
+
if (!newType) { // double-click, so just toggle
|
|
850
|
+
text = 'Toggle Path Segment Type'
|
|
851
|
+
|
|
852
|
+
// Toggle segment to curve/straight line
|
|
853
|
+
const oldType = cur.type
|
|
854
|
+
|
|
855
|
+
newType = (oldType === 6) ? 4 : 6
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
newType = Number(newType)
|
|
859
|
+
|
|
860
|
+
const curX = cur.item.x
|
|
861
|
+
const curY = cur.item.y
|
|
862
|
+
const prevX = prev.item.x
|
|
863
|
+
const prevY = prev.item.y
|
|
864
|
+
let points
|
|
865
|
+
switch (newType) {
|
|
866
|
+
case 6: {
|
|
867
|
+
if (cur.olditem) {
|
|
868
|
+
const old = cur.olditem
|
|
869
|
+
points = [curX, curY, old.x1, old.y1, old.x2, old.y2]
|
|
870
|
+
} else {
|
|
871
|
+
const diffX = curX - prevX
|
|
872
|
+
const diffY = curY - prevY
|
|
873
|
+
// get control points from straight line segment
|
|
874
|
+
/*
|
|
875
|
+
const ct1x = (prevX + (diffY/2));
|
|
876
|
+
const ct1y = (prevY - (diffX/2));
|
|
877
|
+
const ct2x = (curX + (diffY/2));
|
|
878
|
+
const ct2y = (curY - (diffX/2));
|
|
879
|
+
*/
|
|
880
|
+
// create control points on the line to preserve the shape (WRS)
|
|
881
|
+
const ct1x = (prevX + (diffX / 3))
|
|
882
|
+
const ct1y = (prevY + (diffY / 3))
|
|
883
|
+
const ct2x = (curX - (diffX / 3))
|
|
884
|
+
const ct2y = (curY - (diffY / 3))
|
|
885
|
+
points = [curX, curY, ct1x, ct1y, ct2x, ct2y]
|
|
886
|
+
}
|
|
887
|
+
break
|
|
888
|
+
} case 4: {
|
|
889
|
+
points = [curX, curY]
|
|
890
|
+
|
|
891
|
+
// Store original prevve segment nums
|
|
892
|
+
cur.olditem = cur.item
|
|
893
|
+
break
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
cur.setType(newType, points)
|
|
898
|
+
}
|
|
899
|
+
const path = svgCanvas.getPathObj()
|
|
900
|
+
path.endChanges(text)
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
/**
|
|
904
|
+
* @param {Integer} pt
|
|
905
|
+
* @param {Integer} ctrlNum
|
|
906
|
+
* @returns {void}
|
|
907
|
+
*/
|
|
908
|
+
selectPt (pt, ctrlNum) {
|
|
909
|
+
this.clearSelection()
|
|
910
|
+
if (!pt) {
|
|
911
|
+
this.eachSeg(function (i) {
|
|
912
|
+
// 'this' is the segment here.
|
|
913
|
+
if (this.prev) {
|
|
914
|
+
pt = i
|
|
915
|
+
}
|
|
916
|
+
})
|
|
917
|
+
}
|
|
918
|
+
this.addPtsToSelection(pt)
|
|
919
|
+
if (ctrlNum) {
|
|
920
|
+
this.dragctrl = ctrlNum
|
|
921
|
+
|
|
922
|
+
if (svgCanvas.getLinkControlPts()) {
|
|
923
|
+
this.segs[pt].setLinked(ctrlNum)
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
/**
|
|
929
|
+
* Update position of all points.
|
|
930
|
+
* @returns {Path}
|
|
931
|
+
*/
|
|
932
|
+
update () {
|
|
933
|
+
const { elem } = this
|
|
934
|
+
if (getRotationAngle(elem)) {
|
|
935
|
+
this.matrix = getMatrix(elem)
|
|
936
|
+
this.imatrix = this.matrix.inverse()
|
|
937
|
+
} else {
|
|
938
|
+
this.matrix = null
|
|
939
|
+
this.imatrix = null
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
this.eachSeg(function (i) {
|
|
943
|
+
this.item = elem.pathSegList.getItem(i)
|
|
944
|
+
this.update()
|
|
945
|
+
})
|
|
946
|
+
|
|
947
|
+
return this
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
/**
|
|
951
|
+
* @param {string} text
|
|
952
|
+
* @returns {void}
|
|
953
|
+
*/
|
|
954
|
+
endChanges (text) {
|
|
955
|
+
const cmd = new ChangeElementCommand(this.elem, { d: this.last_d }, text)
|
|
956
|
+
svgCanvas.endChanges({ cmd, elem: this.elem })
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
/**
|
|
960
|
+
* @param {Integer|Integer[]} indexes
|
|
961
|
+
* @returns {void}
|
|
962
|
+
*/
|
|
963
|
+
addPtsToSelection (indexes) {
|
|
964
|
+
if (!Array.isArray(indexes)) { indexes = [indexes] }
|
|
965
|
+
indexes.forEach((index) => {
|
|
966
|
+
const seg = this.segs[index]
|
|
967
|
+
if (seg.ptgrip && !this.selected_pts.includes(index) && index >= 0) {
|
|
968
|
+
this.selected_pts.push(index)
|
|
969
|
+
}
|
|
970
|
+
})
|
|
971
|
+
this.selected_pts.sort()
|
|
972
|
+
let i = this.selected_pts.length
|
|
973
|
+
const grips = []
|
|
974
|
+
grips.length = i
|
|
975
|
+
// Loop through points to be selected and highlight each
|
|
976
|
+
while (i--) {
|
|
977
|
+
const pt = this.selected_pts[i]
|
|
978
|
+
const seg = this.segs[pt]
|
|
979
|
+
seg.select(true)
|
|
980
|
+
grips[i] = seg.ptgrip
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
const closedSubpath = Path.subpathIsClosed(this.selected_pts[0])
|
|
984
|
+
svgCanvas.addPtsToSelection({ grips, closedSubpath })
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// STATIC
|
|
988
|
+
/**
|
|
989
|
+
* @param {Integer} index
|
|
990
|
+
* @returns {boolean}
|
|
991
|
+
*/
|
|
992
|
+
static subpathIsClosed (index) {
|
|
993
|
+
let clsd = false
|
|
994
|
+
// Check if subpath is already open
|
|
995
|
+
const path = svgCanvas.getPathObj()
|
|
996
|
+
path.eachSeg(function (i) {
|
|
997
|
+
if (i <= index) { return true }
|
|
998
|
+
if (this.type === 2) {
|
|
999
|
+
// Found M first, so open
|
|
1000
|
+
return false
|
|
1001
|
+
}
|
|
1002
|
+
if (this.type === 1) {
|
|
1003
|
+
// Found Z first, so closed
|
|
1004
|
+
clsd = true
|
|
1005
|
+
return false
|
|
1006
|
+
}
|
|
1007
|
+
return true
|
|
1008
|
+
})
|
|
1009
|
+
|
|
1010
|
+
return clsd
|
|
1011
|
+
}
|
|
1012
|
+
}
|