@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/event.js
ADDED
|
@@ -0,0 +1,1388 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tools for event.
|
|
3
|
+
* @module event
|
|
4
|
+
* @license MIT
|
|
5
|
+
* @copyright 2011 Jeff Schiller
|
|
6
|
+
*/
|
|
7
|
+
import {
|
|
8
|
+
assignAttributes, cleanupElement, getElement, getRotationAngle, snapToGrid, walkTree,
|
|
9
|
+
preventClickDefault, setHref, getBBox
|
|
10
|
+
} from './utilities.js'
|
|
11
|
+
import {
|
|
12
|
+
convertAttrs
|
|
13
|
+
} from '../../src/common/units.js'
|
|
14
|
+
import {
|
|
15
|
+
transformPoint, hasMatrixTransform, getMatrix, snapToAngle
|
|
16
|
+
} from './math.js'
|
|
17
|
+
import * as draw from './draw.js'
|
|
18
|
+
import * as pathModule from './path.js'
|
|
19
|
+
import * as hstry from './history.js'
|
|
20
|
+
import { findPos } from '../../src/common/util.js'
|
|
21
|
+
|
|
22
|
+
const {
|
|
23
|
+
InsertElementCommand
|
|
24
|
+
} = hstry
|
|
25
|
+
|
|
26
|
+
let svgCanvas = null
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @function module:undo.init
|
|
30
|
+
* @param {module:undo.eventContext} eventContext
|
|
31
|
+
* @returns {void}
|
|
32
|
+
*/
|
|
33
|
+
export const init = (canvas) => {
|
|
34
|
+
svgCanvas = canvas
|
|
35
|
+
svgCanvas.mouseDownEvent = mouseDownEvent
|
|
36
|
+
svgCanvas.mouseMoveEvent = mouseMoveEvent
|
|
37
|
+
svgCanvas.dblClickEvent = dblClickEvent
|
|
38
|
+
svgCanvas.mouseUpEvent = mouseUpEvent
|
|
39
|
+
svgCanvas.mouseOutEvent = mouseOutEvent
|
|
40
|
+
svgCanvas.DOMMouseScrollEvent = DOMMouseScrollEvent
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const getBsplinePoint = (t) => {
|
|
44
|
+
const spline = { x: 0, y: 0 }
|
|
45
|
+
const p0 = { x: svgCanvas.getControllPoint2('x'), y: svgCanvas.getControllPoint2('y') }
|
|
46
|
+
const p1 = { x: svgCanvas.getControllPoint1('x'), y: svgCanvas.getControllPoint1('y') }
|
|
47
|
+
const p2 = { x: svgCanvas.getStart('x'), y: svgCanvas.getStart('y') }
|
|
48
|
+
const p3 = { x: svgCanvas.getEnd('x'), y: svgCanvas.getEnd('y') }
|
|
49
|
+
const S = 1.0 / 6.0
|
|
50
|
+
const t2 = t * t
|
|
51
|
+
const t3 = t2 * t
|
|
52
|
+
|
|
53
|
+
const m = [
|
|
54
|
+
[-1, 3, -3, 1],
|
|
55
|
+
[3, -6, 3, 0],
|
|
56
|
+
[-3, 0, 3, 0],
|
|
57
|
+
[1, 4, 1, 0]
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
spline.x = S * (
|
|
61
|
+
(p0.x * m[0][0] + p1.x * m[0][1] + p2.x * m[0][2] + p3.x * m[0][3]) * t3 +
|
|
62
|
+
(p0.x * m[1][0] + p1.x * m[1][1] + p2.x * m[1][2] + p3.x * m[1][3]) * t2 +
|
|
63
|
+
(p0.x * m[2][0] + p1.x * m[2][1] + p2.x * m[2][2] + p3.x * m[2][3]) * t +
|
|
64
|
+
(p0.x * m[3][0] + p1.x * m[3][1] + p2.x * m[3][2] + p3.x * m[3][3])
|
|
65
|
+
)
|
|
66
|
+
spline.y = S * (
|
|
67
|
+
(p0.y * m[0][0] + p1.y * m[0][1] + p2.y * m[0][2] + p3.y * m[0][3]) * t3 +
|
|
68
|
+
(p0.y * m[1][0] + p1.y * m[1][1] + p2.y * m[1][2] + p3.y * m[1][3]) * t2 +
|
|
69
|
+
(p0.y * m[2][0] + p1.y * m[2][1] + p2.y * m[2][2] + p3.y * m[2][3]) * t +
|
|
70
|
+
(p0.y * m[3][0] + p1.y * m[3][1] + p2.y * m[3][2] + p3.y * m[3][3])
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
x: spline.x,
|
|
75
|
+
y: spline.y
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// update the dummy transform in our transform list
|
|
80
|
+
// to be a translate. We need to check if there was a transformation
|
|
81
|
+
// to avoid loosing it
|
|
82
|
+
const updateTransformList = (svgRoot, element, dx, dy) => {
|
|
83
|
+
const xform = svgRoot.createSVGTransform()
|
|
84
|
+
xform.setTranslate(dx, dy)
|
|
85
|
+
const tlist = element.transform?.baseVal
|
|
86
|
+
if (tlist.numberOfItems) {
|
|
87
|
+
const firstItem = tlist.getItem(0)
|
|
88
|
+
if (firstItem.type === 2) { // SVG_TRANSFORM_TRANSLATE = 2
|
|
89
|
+
tlist.replaceItem(xform, 0)
|
|
90
|
+
} else {
|
|
91
|
+
tlist.insertItemBefore(xform, 0)
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
tlist.appendItem(xform)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
*
|
|
100
|
+
* @param {MouseEvent} evt
|
|
101
|
+
* @fires module:svgcanvas.SvgCanvas#event:transition
|
|
102
|
+
* @fires module:svgcanvas.SvgCanvas#event:ext_mouseMove
|
|
103
|
+
* @returns {void}
|
|
104
|
+
*/
|
|
105
|
+
const mouseMoveEvent = (evt) => {
|
|
106
|
+
// if the mouse is move without dragging an element, just return.
|
|
107
|
+
if (!svgCanvas.getStarted()) { return }
|
|
108
|
+
if (evt.button === 1 || svgCanvas.spaceKey) { return }
|
|
109
|
+
|
|
110
|
+
svgCanvas.textActions.init()
|
|
111
|
+
|
|
112
|
+
evt.preventDefault()
|
|
113
|
+
|
|
114
|
+
const selectedElements = svgCanvas.getSelectedElements()
|
|
115
|
+
const zoom = svgCanvas.getZoom()
|
|
116
|
+
const svgRoot = svgCanvas.getSvgRoot()
|
|
117
|
+
const selected = selectedElements[0]
|
|
118
|
+
|
|
119
|
+
let i
|
|
120
|
+
let xya
|
|
121
|
+
let cx
|
|
122
|
+
let cy
|
|
123
|
+
let dx
|
|
124
|
+
let dy
|
|
125
|
+
let len
|
|
126
|
+
let angle
|
|
127
|
+
let box
|
|
128
|
+
|
|
129
|
+
const pt = transformPoint(evt.clientX, evt.clientY, svgCanvas.getrootSctm())
|
|
130
|
+
const mouseX = pt.x * zoom
|
|
131
|
+
const mouseY = pt.y * zoom
|
|
132
|
+
const shape = getElement(svgCanvas.getId())
|
|
133
|
+
|
|
134
|
+
let realX = mouseX / zoom
|
|
135
|
+
let x = realX
|
|
136
|
+
let realY = mouseY / zoom
|
|
137
|
+
let y = realY
|
|
138
|
+
|
|
139
|
+
if (svgCanvas.getCurConfig().gridSnapping) {
|
|
140
|
+
x = snapToGrid(x)
|
|
141
|
+
y = snapToGrid(y)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let tlist
|
|
145
|
+
switch (svgCanvas.getCurrentMode()) {
|
|
146
|
+
case 'select': {
|
|
147
|
+
// we temporarily use a translate on the element(s) being dragged
|
|
148
|
+
// this transform is removed upon mousing up and the element is
|
|
149
|
+
// relocated to the new location
|
|
150
|
+
if (selected) {
|
|
151
|
+
dx = x - svgCanvas.getStartX()
|
|
152
|
+
dy = y - svgCanvas.getStartY()
|
|
153
|
+
if (svgCanvas.getCurConfig().gridSnapping) {
|
|
154
|
+
dx = snapToGrid(dx)
|
|
155
|
+
dy = snapToGrid(dy)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (dx || dy) {
|
|
159
|
+
selectedElements.forEach((el) => {
|
|
160
|
+
if (el) {
|
|
161
|
+
updateTransformList(svgRoot, el, dx, dy)
|
|
162
|
+
// update our internal bbox that we're tracking while dragging
|
|
163
|
+
svgCanvas.selectorManager.requestSelector(el).resize()
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
svgCanvas.call('transition', selectedElements)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
break
|
|
170
|
+
}
|
|
171
|
+
case 'multiselect': {
|
|
172
|
+
realX *= zoom
|
|
173
|
+
realY *= zoom
|
|
174
|
+
assignAttributes(svgCanvas.getRubberBox(), {
|
|
175
|
+
x: Math.min(svgCanvas.getRStartX(), realX),
|
|
176
|
+
y: Math.min(svgCanvas.getRStartY(), realY),
|
|
177
|
+
width: Math.abs(realX - svgCanvas.getRStartX()),
|
|
178
|
+
height: Math.abs(realY - svgCanvas.getRStartY())
|
|
179
|
+
}, 100)
|
|
180
|
+
|
|
181
|
+
// for each selected:
|
|
182
|
+
// - if newList contains selected, do nothing
|
|
183
|
+
// - if newList doesn't contain selected, remove it from selected
|
|
184
|
+
// - for any newList that was not in selectedElements, add it to selected
|
|
185
|
+
const elemsToRemove = selectedElements.slice(); const elemsToAdd = []
|
|
186
|
+
const newList = svgCanvas.getIntersectionList()
|
|
187
|
+
|
|
188
|
+
// For every element in the intersection, add if not present in selectedElements.
|
|
189
|
+
len = newList.length
|
|
190
|
+
for (i = 0; i < len; ++i) {
|
|
191
|
+
const intElem = newList[i]
|
|
192
|
+
// Found an element that was not selected before, so we should add it.
|
|
193
|
+
if (!selectedElements.includes(intElem)) {
|
|
194
|
+
elemsToAdd.push(intElem)
|
|
195
|
+
}
|
|
196
|
+
// Found an element that was already selected, so we shouldn't remove it.
|
|
197
|
+
const foundInd = elemsToRemove.indexOf(intElem)
|
|
198
|
+
if (foundInd !== -1) {
|
|
199
|
+
elemsToRemove.splice(foundInd, 1)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (elemsToRemove.length > 0) {
|
|
204
|
+
svgCanvas.removeFromSelection(elemsToRemove)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (elemsToAdd.length > 0) {
|
|
208
|
+
svgCanvas.addToSelection(elemsToAdd)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
break
|
|
212
|
+
}
|
|
213
|
+
case 'resize': {
|
|
214
|
+
// we track the resize bounding box and translate/scale the selected element
|
|
215
|
+
// while the mouse is down, when mouse goes up, we use this to recalculate
|
|
216
|
+
// the shape's coordinates
|
|
217
|
+
tlist = selected.transform.baseVal
|
|
218
|
+
const hasMatrix = hasMatrixTransform(tlist)
|
|
219
|
+
box = hasMatrix ? svgCanvas.getInitBbox() : getBBox(selected)
|
|
220
|
+
let left = box.x
|
|
221
|
+
let top = box.y
|
|
222
|
+
let { width, height } = box
|
|
223
|
+
dx = (x - svgCanvas.getStartX())
|
|
224
|
+
dy = (y - svgCanvas.getStartY())
|
|
225
|
+
|
|
226
|
+
if (svgCanvas.getCurConfig().gridSnapping) {
|
|
227
|
+
dx = snapToGrid(dx)
|
|
228
|
+
dy = snapToGrid(dy)
|
|
229
|
+
height = snapToGrid(height)
|
|
230
|
+
width = snapToGrid(width)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// if rotated, adjust the dx,dy values
|
|
234
|
+
angle = getRotationAngle(selected)
|
|
235
|
+
if (angle) {
|
|
236
|
+
const r = Math.sqrt(dx * dx + dy * dy)
|
|
237
|
+
const theta = Math.atan2(dy, dx) - angle * Math.PI / 180.0
|
|
238
|
+
dx = r * Math.cos(theta)
|
|
239
|
+
dy = r * Math.sin(theta)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// if not stretching in y direction, set dy to 0
|
|
243
|
+
// if not stretching in x direction, set dx to 0
|
|
244
|
+
if (!svgCanvas.getCurrentResizeMode().includes('n') && !svgCanvas.getCurrentResizeMode().includes('s')) {
|
|
245
|
+
dy = 0
|
|
246
|
+
}
|
|
247
|
+
if (!svgCanvas.getCurrentResizeMode().includes('e') && !svgCanvas.getCurrentResizeMode().includes('w')) {
|
|
248
|
+
dx = 0
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
let // ts = null,
|
|
252
|
+
tx = 0; let ty = 0
|
|
253
|
+
let sy = height ? (height + dy) / height : 1
|
|
254
|
+
let sx = width ? (width + dx) / width : 1
|
|
255
|
+
// if we are dragging on the north side, then adjust the scale factor and ty
|
|
256
|
+
if (svgCanvas.getCurrentResizeMode().includes('n')) {
|
|
257
|
+
sy = height ? (height - dy) / height : 1
|
|
258
|
+
ty = height
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// if we dragging on the east side, then adjust the scale factor and tx
|
|
262
|
+
if (svgCanvas.getCurrentResizeMode().includes('w')) {
|
|
263
|
+
sx = width ? (width - dx) / width : 1
|
|
264
|
+
tx = width
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// update the transform list with translate,scale,translate
|
|
268
|
+
const translateOrigin = svgRoot.createSVGTransform()
|
|
269
|
+
const scale = svgRoot.createSVGTransform()
|
|
270
|
+
const translateBack = svgRoot.createSVGTransform()
|
|
271
|
+
|
|
272
|
+
if (svgCanvas.getCurConfig().gridSnapping) {
|
|
273
|
+
left = snapToGrid(left)
|
|
274
|
+
tx = snapToGrid(tx)
|
|
275
|
+
top = snapToGrid(top)
|
|
276
|
+
ty = snapToGrid(ty)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
translateOrigin.setTranslate(-(left + tx), -(top + ty))
|
|
280
|
+
if (evt.shiftKey) {
|
|
281
|
+
if (sx === 1) {
|
|
282
|
+
sx = sy
|
|
283
|
+
} else { sy = sx }
|
|
284
|
+
}
|
|
285
|
+
scale.setScale(sx, sy)
|
|
286
|
+
|
|
287
|
+
translateBack.setTranslate(left + tx, top + ty)
|
|
288
|
+
if (hasMatrix) {
|
|
289
|
+
const diff = angle ? 1 : 0
|
|
290
|
+
tlist.replaceItem(translateOrigin, 2 + diff)
|
|
291
|
+
tlist.replaceItem(scale, 1 + diff)
|
|
292
|
+
tlist.replaceItem(translateBack, Number(diff))
|
|
293
|
+
} else {
|
|
294
|
+
const N = tlist.numberOfItems
|
|
295
|
+
tlist.replaceItem(translateBack, N - 3)
|
|
296
|
+
tlist.replaceItem(scale, N - 2)
|
|
297
|
+
tlist.replaceItem(translateOrigin, N - 1)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
svgCanvas.selectorManager.requestSelector(selected).resize()
|
|
301
|
+
svgCanvas.call('transition', selectedElements)
|
|
302
|
+
|
|
303
|
+
break
|
|
304
|
+
}
|
|
305
|
+
case 'zoom': {
|
|
306
|
+
realX *= zoom
|
|
307
|
+
realY *= zoom
|
|
308
|
+
assignAttributes(svgCanvas.getRubberBox(), {
|
|
309
|
+
x: Math.min(svgCanvas.getRStartX() * zoom, realX),
|
|
310
|
+
y: Math.min(svgCanvas.getRStartY() * zoom, realY),
|
|
311
|
+
width: Math.abs(realX - svgCanvas.getRStartX() * zoom),
|
|
312
|
+
height: Math.abs(realY - svgCanvas.getRStartY() * zoom)
|
|
313
|
+
}, 100)
|
|
314
|
+
break
|
|
315
|
+
}
|
|
316
|
+
case 'text': {
|
|
317
|
+
assignAttributes(shape, {
|
|
318
|
+
x,
|
|
319
|
+
y
|
|
320
|
+
}, 1000)
|
|
321
|
+
break
|
|
322
|
+
}
|
|
323
|
+
case 'line': {
|
|
324
|
+
if (svgCanvas.getCurConfig().gridSnapping) {
|
|
325
|
+
x = snapToGrid(x)
|
|
326
|
+
y = snapToGrid(y)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
let x2 = x
|
|
330
|
+
let y2 = y
|
|
331
|
+
|
|
332
|
+
if (evt.shiftKey) {
|
|
333
|
+
xya = snapToAngle(svgCanvas.getStartX(), svgCanvas.getStartY(), x2, y2)
|
|
334
|
+
x2 = xya.x
|
|
335
|
+
y2 = xya.y
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
shape.setAttribute('x2', x2)
|
|
339
|
+
shape.setAttribute('y2', y2)
|
|
340
|
+
break
|
|
341
|
+
}
|
|
342
|
+
case 'foreignObject': // fall through
|
|
343
|
+
case 'square':
|
|
344
|
+
case 'rect':
|
|
345
|
+
case 'image': {
|
|
346
|
+
const square = (svgCanvas.getCurrentMode() === 'square') || evt.shiftKey
|
|
347
|
+
let
|
|
348
|
+
w = Math.abs(x - svgCanvas.getStartX())
|
|
349
|
+
let h = Math.abs(y - svgCanvas.getStartY())
|
|
350
|
+
let newX; let newY
|
|
351
|
+
if (square) {
|
|
352
|
+
w = h = Math.max(w, h)
|
|
353
|
+
newX = svgCanvas.getStartX() < x ? svgCanvas.getStartX() : svgCanvas.getStartX() - w
|
|
354
|
+
newY = svgCanvas.getStartY() < y ? svgCanvas.getStartY() : svgCanvas.getStartY() - h
|
|
355
|
+
} else {
|
|
356
|
+
newX = Math.min(svgCanvas.getStartX(), x)
|
|
357
|
+
newY = Math.min(svgCanvas.getStartY(), y)
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (svgCanvas.getCurConfig().gridSnapping) {
|
|
361
|
+
w = snapToGrid(w)
|
|
362
|
+
h = snapToGrid(h)
|
|
363
|
+
newX = snapToGrid(newX)
|
|
364
|
+
newY = snapToGrid(newY)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
assignAttributes(shape, {
|
|
368
|
+
width: w,
|
|
369
|
+
height: h,
|
|
370
|
+
x: newX,
|
|
371
|
+
y: newY
|
|
372
|
+
}, 1000)
|
|
373
|
+
|
|
374
|
+
break
|
|
375
|
+
}
|
|
376
|
+
case 'circle': {
|
|
377
|
+
cx = Number(shape.getAttribute('cx'))
|
|
378
|
+
cy = Number(shape.getAttribute('cy'))
|
|
379
|
+
let rad = Math.sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy))
|
|
380
|
+
if (svgCanvas.getCurConfig().gridSnapping) {
|
|
381
|
+
rad = snapToGrid(rad)
|
|
382
|
+
}
|
|
383
|
+
shape.setAttribute('r', rad)
|
|
384
|
+
break
|
|
385
|
+
}
|
|
386
|
+
case 'ellipse': {
|
|
387
|
+
cx = Number(shape.getAttribute('cx'))
|
|
388
|
+
cy = Number(shape.getAttribute('cy'))
|
|
389
|
+
if (svgCanvas.getCurConfig().gridSnapping) {
|
|
390
|
+
x = snapToGrid(x)
|
|
391
|
+
cx = snapToGrid(cx)
|
|
392
|
+
y = snapToGrid(y)
|
|
393
|
+
cy = snapToGrid(cy)
|
|
394
|
+
}
|
|
395
|
+
shape.setAttribute('rx', Math.abs(x - cx))
|
|
396
|
+
const ry = Math.abs(evt.shiftKey ? (x - cx) : (y - cy))
|
|
397
|
+
shape.setAttribute('ry', ry)
|
|
398
|
+
break
|
|
399
|
+
}
|
|
400
|
+
case 'fhellipse':
|
|
401
|
+
case 'fhrect': {
|
|
402
|
+
svgCanvas.setFreehand('minx', Math.min(realX, svgCanvas.getFreehand('minx')))
|
|
403
|
+
svgCanvas.setFreehand('maxx', Math.max(realX, svgCanvas.getFreehand('maxx')))
|
|
404
|
+
svgCanvas.setFreehand('miny', Math.min(realY, svgCanvas.getFreehand('miny')))
|
|
405
|
+
svgCanvas.setFreehand('maxy', Math.max(realY, svgCanvas.getFreehand('maxy')))
|
|
406
|
+
}
|
|
407
|
+
// Fallthrough
|
|
408
|
+
case 'fhpath': {
|
|
409
|
+
// dAttr += + realX + ',' + realY + ' ';
|
|
410
|
+
// shape.setAttribute('points', dAttr);
|
|
411
|
+
svgCanvas.setEnd('x', realX)
|
|
412
|
+
svgCanvas.setEnd('y', realY)
|
|
413
|
+
if (svgCanvas.getControllPoint2('x') && svgCanvas.getControllPoint2('y')) {
|
|
414
|
+
for (i = 0; i < svgCanvas.getStepCount() - 1; i++) {
|
|
415
|
+
svgCanvas.setParameter(i / svgCanvas.getStepCount())
|
|
416
|
+
svgCanvas.setNextParameter((i + 1) / svgCanvas.getStepCount())
|
|
417
|
+
svgCanvas.setbSpline(getBsplinePoint(svgCanvas.getNextParameter()))
|
|
418
|
+
svgCanvas.setNextPos({ x: svgCanvas.getbSpline('x'), y: svgCanvas.getbSpline('y') })
|
|
419
|
+
svgCanvas.setbSpline(getBsplinePoint(svgCanvas.getParameter()))
|
|
420
|
+
svgCanvas.setSumDistance(
|
|
421
|
+
svgCanvas.getSumDistance() + Math.sqrt((svgCanvas.getNextPos('x') -
|
|
422
|
+
svgCanvas.getbSpline('x')) * (svgCanvas.getNextPos('x') -
|
|
423
|
+
svgCanvas.getbSpline('x')) + (svgCanvas.getNextPos('y') -
|
|
424
|
+
svgCanvas.getbSpline('y')) * (svgCanvas.getNextPos('y') - svgCanvas.getbSpline('y')))
|
|
425
|
+
)
|
|
426
|
+
if (svgCanvas.getSumDistance() > svgCanvas.getThreSholdDist()) {
|
|
427
|
+
svgCanvas.setSumDistance(svgCanvas.getSumDistance() - svgCanvas.getThreSholdDist())
|
|
428
|
+
|
|
429
|
+
// Faster than completely re-writing the points attribute.
|
|
430
|
+
const point = svgCanvas.getSvgContent().createSVGPoint()
|
|
431
|
+
point.x = svgCanvas.getbSpline('x')
|
|
432
|
+
point.y = svgCanvas.getbSpline('y')
|
|
433
|
+
shape.points.appendItem(point)
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
svgCanvas.setControllPoint2('x', svgCanvas.getControllPoint1('x'))
|
|
438
|
+
svgCanvas.setControllPoint2('y', svgCanvas.getControllPoint1('y'))
|
|
439
|
+
svgCanvas.setControllPoint1('x', svgCanvas.getStart('x'))
|
|
440
|
+
svgCanvas.setControllPoint1('y', svgCanvas.getStart('y'))
|
|
441
|
+
svgCanvas.setStart({ x: svgCanvas.getEnd('x'), y: svgCanvas.getEnd('y') })
|
|
442
|
+
break
|
|
443
|
+
// update path stretch line coordinates
|
|
444
|
+
}
|
|
445
|
+
case 'path': // fall through
|
|
446
|
+
case 'pathedit': {
|
|
447
|
+
x *= zoom
|
|
448
|
+
y *= zoom
|
|
449
|
+
|
|
450
|
+
if (svgCanvas.getCurConfig().gridSnapping) {
|
|
451
|
+
x = snapToGrid(x)
|
|
452
|
+
y = snapToGrid(y)
|
|
453
|
+
svgCanvas.setStartX(snapToGrid(svgCanvas.getStartX()))
|
|
454
|
+
svgCanvas.setStartY(snapToGrid(svgCanvas.getStartY()))
|
|
455
|
+
}
|
|
456
|
+
if (evt.shiftKey) {
|
|
457
|
+
const { path } = pathModule
|
|
458
|
+
let x1; let y1
|
|
459
|
+
if (path) {
|
|
460
|
+
x1 = path.dragging ? path.dragging[0] : svgCanvas.getStartX()
|
|
461
|
+
y1 = path.dragging ? path.dragging[1] : svgCanvas.getStartY()
|
|
462
|
+
} else {
|
|
463
|
+
x1 = svgCanvas.getStartX()
|
|
464
|
+
y1 = svgCanvas.getStartY()
|
|
465
|
+
}
|
|
466
|
+
xya = snapToAngle(x1, y1, x, y);
|
|
467
|
+
({ x, y } = xya)
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (svgCanvas.getRubberBox()?.getAttribute('display') !== 'none') {
|
|
471
|
+
realX *= zoom
|
|
472
|
+
realY *= zoom
|
|
473
|
+
assignAttributes(svgCanvas.getRubberBox(), {
|
|
474
|
+
x: Math.min(svgCanvas.getRStartX() * zoom, realX),
|
|
475
|
+
y: Math.min(svgCanvas.getRStartY() * zoom, realY),
|
|
476
|
+
width: Math.abs(realX - svgCanvas.getRStartX() * zoom),
|
|
477
|
+
height: Math.abs(realY - svgCanvas.getRStartY() * zoom)
|
|
478
|
+
}, 100)
|
|
479
|
+
}
|
|
480
|
+
svgCanvas.pathActions.mouseMove(x, y)
|
|
481
|
+
|
|
482
|
+
break
|
|
483
|
+
}
|
|
484
|
+
case 'textedit': {
|
|
485
|
+
x *= zoom
|
|
486
|
+
y *= zoom
|
|
487
|
+
svgCanvas.textActions.mouseMove(mouseX, mouseY)
|
|
488
|
+
|
|
489
|
+
break
|
|
490
|
+
}
|
|
491
|
+
case 'rotate': {
|
|
492
|
+
box = getBBox(selected)
|
|
493
|
+
cx = box.x + box.width / 2
|
|
494
|
+
cy = box.y + box.height / 2
|
|
495
|
+
const m = getMatrix(selected)
|
|
496
|
+
const center = transformPoint(cx, cy, m)
|
|
497
|
+
cx = center.x
|
|
498
|
+
cy = center.y
|
|
499
|
+
angle = ((Math.atan2(cy - y, cx - x) * (180 / Math.PI)) - 90) % 360
|
|
500
|
+
if (svgCanvas.getCurConfig().gridSnapping) {
|
|
501
|
+
angle = snapToGrid(angle)
|
|
502
|
+
}
|
|
503
|
+
if (evt.shiftKey) { // restrict rotations to nice angles (WRS)
|
|
504
|
+
const snap = 45
|
|
505
|
+
angle = Math.round(angle / snap) * snap
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
svgCanvas.setRotationAngle(angle < -180 ? (360 + angle) : angle, true)
|
|
509
|
+
svgCanvas.call('transition', selectedElements)
|
|
510
|
+
break
|
|
511
|
+
}
|
|
512
|
+
default:
|
|
513
|
+
// A mode can be defined by an extenstion
|
|
514
|
+
break
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* The mouse has moved on the canvas area.
|
|
519
|
+
* @event module:svgcanvas.SvgCanvas#event:ext_mouseMove
|
|
520
|
+
* @type {PlainObject}
|
|
521
|
+
* @property {MouseEvent} event The event object
|
|
522
|
+
* @property {Float} mouse_x x coordinate on canvas
|
|
523
|
+
* @property {Float} mouse_y y coordinate on canvas
|
|
524
|
+
* @property {Element} selected Refers to the first selected element
|
|
525
|
+
*/
|
|
526
|
+
svgCanvas.runExtensions('mouseMove', /** @type {module:svgcanvas.SvgCanvas#event:ext_mouseMove} */ {
|
|
527
|
+
event: evt,
|
|
528
|
+
mouse_x: mouseX,
|
|
529
|
+
mouse_y: mouseY,
|
|
530
|
+
selected
|
|
531
|
+
})
|
|
532
|
+
} // mouseMove()
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
*
|
|
536
|
+
* @returns {void}
|
|
537
|
+
*/
|
|
538
|
+
const mouseOutEvent = () => {
|
|
539
|
+
const { $id } = svgCanvas
|
|
540
|
+
if (svgCanvas.getCurrentMode() !== 'select' && svgCanvas.getStarted()) {
|
|
541
|
+
const event = new Event('mouseup')
|
|
542
|
+
$id('svgcanvas').dispatchEvent(event)
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// - in create mode, the element's opacity is set properly, we create an InsertElementCommand
|
|
547
|
+
// and store it on the Undo stack
|
|
548
|
+
// - in move/resize mode, the element's attributes which were affected by the move/resize are
|
|
549
|
+
// identified, a ChangeElementCommand is created and stored on the stack for those attrs
|
|
550
|
+
// this is done in when we recalculate the selected dimensions()
|
|
551
|
+
/**
|
|
552
|
+
*
|
|
553
|
+
* @param {MouseEvent} evt
|
|
554
|
+
* @fires module:svgcanvas.SvgCanvas#event:zoomed
|
|
555
|
+
* @fires module:svgcanvas.SvgCanvas#event:changed
|
|
556
|
+
* @fires module:svgcanvas.SvgCanvas#event:ext_mouseUp
|
|
557
|
+
* @returns {void}
|
|
558
|
+
*/
|
|
559
|
+
const mouseUpEvent = (evt) => {
|
|
560
|
+
if (evt.button === 2) { return }
|
|
561
|
+
if (!svgCanvas.getStarted()) { return }
|
|
562
|
+
|
|
563
|
+
svgCanvas.textActions.init()
|
|
564
|
+
|
|
565
|
+
const selectedElements = svgCanvas.getSelectedElements()
|
|
566
|
+
const zoom = svgCanvas.getZoom()
|
|
567
|
+
|
|
568
|
+
const tempJustSelected = svgCanvas.getJustSelected()
|
|
569
|
+
svgCanvas.setJustSelected(null)
|
|
570
|
+
|
|
571
|
+
const pt = transformPoint(evt.clientX, evt.clientY, svgCanvas.getrootSctm())
|
|
572
|
+
const mouseX = pt.x * zoom
|
|
573
|
+
const mouseY = pt.y * zoom
|
|
574
|
+
const x = mouseX / zoom
|
|
575
|
+
const y = mouseY / zoom
|
|
576
|
+
|
|
577
|
+
let element = getElement(svgCanvas.getId())
|
|
578
|
+
let keep = false
|
|
579
|
+
|
|
580
|
+
const realX = x
|
|
581
|
+
const realY = y
|
|
582
|
+
|
|
583
|
+
// TODO: Make true when in multi-unit mode
|
|
584
|
+
const useUnit = false // (svgCanvas.getCurConfig().baseUnit !== 'px');
|
|
585
|
+
svgCanvas.setStarted(false)
|
|
586
|
+
let t
|
|
587
|
+
switch (svgCanvas.getCurrentMode()) {
|
|
588
|
+
// intentionally fall-through to select here
|
|
589
|
+
case 'resize':
|
|
590
|
+
case 'multiselect':
|
|
591
|
+
if (svgCanvas.getRubberBox()) {
|
|
592
|
+
svgCanvas.getRubberBox().setAttribute('display', 'none')
|
|
593
|
+
svgCanvas.setCurBBoxes([])
|
|
594
|
+
}
|
|
595
|
+
svgCanvas.setCurrentMode('select')
|
|
596
|
+
// Fallthrough
|
|
597
|
+
case 'select':
|
|
598
|
+
if (selectedElements[0]) {
|
|
599
|
+
// if we only have one selected element
|
|
600
|
+
if (!selectedElements[1]) {
|
|
601
|
+
// set our current stroke/fill properties to the element's
|
|
602
|
+
const selected = selectedElements[0]
|
|
603
|
+
switch (selected.tagName) {
|
|
604
|
+
case 'g':
|
|
605
|
+
case 'use':
|
|
606
|
+
case 'image':
|
|
607
|
+
case 'foreignObject':
|
|
608
|
+
break
|
|
609
|
+
case 'text':
|
|
610
|
+
svgCanvas.setCurText('font_size', selected.getAttribute('font-size'))
|
|
611
|
+
svgCanvas.setCurText('font_family', selected.getAttribute('font-family'))
|
|
612
|
+
// fallthrough
|
|
613
|
+
default:
|
|
614
|
+
svgCanvas.setCurProperties('fill', selected.getAttribute('fill'))
|
|
615
|
+
svgCanvas.setCurProperties('fill_opacity', selected.getAttribute('fill-opacity'))
|
|
616
|
+
svgCanvas.setCurProperties('stroke', selected.getAttribute('stroke'))
|
|
617
|
+
svgCanvas.setCurProperties('stroke_opacity', selected.getAttribute('stroke-opacity'))
|
|
618
|
+
svgCanvas.setCurProperties('stroke_width', selected.getAttribute('stroke-width'))
|
|
619
|
+
svgCanvas.setCurProperties('stroke_dasharray', selected.getAttribute('stroke-dasharray'))
|
|
620
|
+
svgCanvas.setCurProperties('stroke_linejoin', selected.getAttribute('stroke-linejoin'))
|
|
621
|
+
svgCanvas.setCurProperties('stroke_linecap', selected.getAttribute('stroke-linecap'))
|
|
622
|
+
}
|
|
623
|
+
svgCanvas.selectorManager.requestSelector(selected).showGrips(true)
|
|
624
|
+
}
|
|
625
|
+
// always recalculate dimensions to strip off stray identity transforms
|
|
626
|
+
svgCanvas.recalculateAllSelectedDimensions()
|
|
627
|
+
// if it was being dragged/resized
|
|
628
|
+
if (realX !== svgCanvas.getRStartX() || realY !== svgCanvas.getRStartY()) {
|
|
629
|
+
const len = selectedElements.length
|
|
630
|
+
for (let i = 0; i < len; ++i) {
|
|
631
|
+
if (!selectedElements[i]) { break }
|
|
632
|
+
svgCanvas.selectorManager.requestSelector(selectedElements[i]).resize()
|
|
633
|
+
}
|
|
634
|
+
// no change in position/size, so maybe we should move to pathedit
|
|
635
|
+
} else {
|
|
636
|
+
t = evt.target
|
|
637
|
+
if (selectedElements[0].nodeName === 'path' && !selectedElements[1]) {
|
|
638
|
+
svgCanvas.pathActions.select(selectedElements[0])
|
|
639
|
+
// if it was a path
|
|
640
|
+
// else, if it was selected and this is a shift-click, remove it from selection
|
|
641
|
+
} else if (evt.shiftKey && tempJustSelected !== t) {
|
|
642
|
+
svgCanvas.removeFromSelection([t])
|
|
643
|
+
}
|
|
644
|
+
} // no change in mouse position
|
|
645
|
+
|
|
646
|
+
// Remove non-scaling stroke
|
|
647
|
+
const elem = selectedElements[0]
|
|
648
|
+
if (elem) {
|
|
649
|
+
elem.removeAttribute('style')
|
|
650
|
+
walkTree(elem, (el) => {
|
|
651
|
+
el.removeAttribute('style')
|
|
652
|
+
})
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
return
|
|
656
|
+
case 'zoom': {
|
|
657
|
+
svgCanvas.getRubberBox()?.setAttribute('display', 'none')
|
|
658
|
+
const factor = evt.shiftKey ? 0.5 : 2
|
|
659
|
+
svgCanvas.call('zoomed', {
|
|
660
|
+
x: Math.min(svgCanvas.getRStartX(), realX),
|
|
661
|
+
y: Math.min(svgCanvas.getRStartY(), realY),
|
|
662
|
+
width: Math.abs(realX - svgCanvas.getRStartX()),
|
|
663
|
+
height: Math.abs(realY - svgCanvas.getRStartY()),
|
|
664
|
+
factor
|
|
665
|
+
})
|
|
666
|
+
return
|
|
667
|
+
} case 'fhpath': {
|
|
668
|
+
// Check that the path contains at least 2 points; a degenerate one-point path
|
|
669
|
+
// causes problems.
|
|
670
|
+
// Webkit ignores how we set the points attribute with commas and uses space
|
|
671
|
+
// to separate all coordinates, see https://bugs.webkit.org/show_bug.cgi?id=29870
|
|
672
|
+
svgCanvas.setSumDistance(0)
|
|
673
|
+
svgCanvas.setControllPoint2('x', 0)
|
|
674
|
+
svgCanvas.setControllPoint2('y', 0)
|
|
675
|
+
svgCanvas.setControllPoint1('x', 0)
|
|
676
|
+
svgCanvas.setControllPoint1('y', 0)
|
|
677
|
+
svgCanvas.setStart({ x: 0, y: 0 })
|
|
678
|
+
svgCanvas.setEnd('x', 0)
|
|
679
|
+
svgCanvas.setEnd('y', 0)
|
|
680
|
+
const coords = element.getAttribute('points')
|
|
681
|
+
const commaIndex = coords.indexOf(',')
|
|
682
|
+
keep = commaIndex >= 0 ? coords.includes(',', commaIndex + 1) : coords.includes(' ', coords.indexOf(' ') + 1)
|
|
683
|
+
if (keep) {
|
|
684
|
+
element = svgCanvas.pathActions.smoothPolylineIntoPath(element)
|
|
685
|
+
}
|
|
686
|
+
break
|
|
687
|
+
} case 'line': {
|
|
688
|
+
const x1 = element.getAttribute('x1')
|
|
689
|
+
const y1 = element.getAttribute('y1')
|
|
690
|
+
const x2 = element.getAttribute('x2')
|
|
691
|
+
const y2 = element.getAttribute('y2')
|
|
692
|
+
keep = (x1 !== x2 || y1 !== y2)
|
|
693
|
+
}
|
|
694
|
+
break
|
|
695
|
+
case 'foreignObject':
|
|
696
|
+
case 'square':
|
|
697
|
+
case 'rect':
|
|
698
|
+
case 'image': {
|
|
699
|
+
const width = element.getAttribute('width')
|
|
700
|
+
const height = element.getAttribute('height')
|
|
701
|
+
// Image should be kept regardless of size (use inherit dimensions later)
|
|
702
|
+
keep = (width || height) || svgCanvas.getCurrentMode() === 'image'
|
|
703
|
+
}
|
|
704
|
+
break
|
|
705
|
+
case 'circle':
|
|
706
|
+
keep = (element.getAttribute('r') !== '0')
|
|
707
|
+
break
|
|
708
|
+
case 'ellipse': {
|
|
709
|
+
const rx = Number(element.getAttribute('rx'))
|
|
710
|
+
const ry = Number(element.getAttribute('ry'))
|
|
711
|
+
keep = (rx || ry)
|
|
712
|
+
}
|
|
713
|
+
break
|
|
714
|
+
case 'fhellipse':
|
|
715
|
+
if ((svgCanvas.getFreehand('maxx') - svgCanvas.getFreehand('minx')) > 0 &&
|
|
716
|
+
(svgCanvas.getFreehand('maxy') - svgCanvas.getFreehand('miny')) > 0) {
|
|
717
|
+
element = svgCanvas.addSVGElementsFromJson({
|
|
718
|
+
element: 'ellipse',
|
|
719
|
+
curStyles: true,
|
|
720
|
+
attr: {
|
|
721
|
+
cx: (svgCanvas.getFreehand('minx') + svgCanvas.getFreehand('maxx')) / 2,
|
|
722
|
+
cy: (svgCanvas.getFreehand('miny') + svgCanvas.getFreehand('maxy')) / 2,
|
|
723
|
+
rx: (svgCanvas.getFreehand('maxx') - svgCanvas.getFreehand('minx')) / 2,
|
|
724
|
+
ry: (svgCanvas.getFreehand('maxy') - svgCanvas.getFreehand('miny')) / 2,
|
|
725
|
+
id: svgCanvas.getId()
|
|
726
|
+
}
|
|
727
|
+
})
|
|
728
|
+
svgCanvas.call('changed', [element])
|
|
729
|
+
keep = true
|
|
730
|
+
}
|
|
731
|
+
break
|
|
732
|
+
case 'fhrect':
|
|
733
|
+
if ((svgCanvas.getFreehand('maxx') - svgCanvas.getFreehand('minx')) > 0 &&
|
|
734
|
+
(svgCanvas.getFreehand('maxy') - svgCanvas.getFreehand('miny')) > 0) {
|
|
735
|
+
element = svgCanvas.addSVGElementsFromJson({
|
|
736
|
+
element: 'rect',
|
|
737
|
+
curStyles: true,
|
|
738
|
+
attr: {
|
|
739
|
+
x: svgCanvas.getFreehand('minx'),
|
|
740
|
+
y: svgCanvas.getFreehand('miny'),
|
|
741
|
+
width: (svgCanvas.getFreehand('maxx') - svgCanvas.getFreehand('minx')),
|
|
742
|
+
height: (svgCanvas.getFreehand('maxy') - svgCanvas.getFreehand('miny')),
|
|
743
|
+
id: svgCanvas.getId()
|
|
744
|
+
}
|
|
745
|
+
})
|
|
746
|
+
svgCanvas.call('changed', [element])
|
|
747
|
+
keep = true
|
|
748
|
+
}
|
|
749
|
+
break
|
|
750
|
+
case 'text':
|
|
751
|
+
keep = true
|
|
752
|
+
svgCanvas.selectOnly([element])
|
|
753
|
+
svgCanvas.textActions.start(element)
|
|
754
|
+
break
|
|
755
|
+
case 'path': {
|
|
756
|
+
// set element to null here so that it is not removed nor finalized
|
|
757
|
+
element = null
|
|
758
|
+
// continue to be set to true so that mouseMove happens
|
|
759
|
+
svgCanvas.setStarted(true)
|
|
760
|
+
|
|
761
|
+
const res = svgCanvas.pathActions.mouseUp(evt, element, mouseX, mouseY);
|
|
762
|
+
({ element } = res);
|
|
763
|
+
({ keep } = res)
|
|
764
|
+
break
|
|
765
|
+
} case 'pathedit':
|
|
766
|
+
keep = true
|
|
767
|
+
element = null
|
|
768
|
+
svgCanvas.pathActions.mouseUp(evt)
|
|
769
|
+
break
|
|
770
|
+
case 'textedit':
|
|
771
|
+
keep = false
|
|
772
|
+
element = null
|
|
773
|
+
svgCanvas.textActions.mouseUp(evt, mouseX, mouseY)
|
|
774
|
+
break
|
|
775
|
+
case 'rotate': {
|
|
776
|
+
keep = true
|
|
777
|
+
element = null
|
|
778
|
+
svgCanvas.setCurrentMode('select')
|
|
779
|
+
const batchCmd = svgCanvas.undoMgr.finishUndoableChange()
|
|
780
|
+
if (!batchCmd.isEmpty()) {
|
|
781
|
+
svgCanvas.addCommandToHistory(batchCmd)
|
|
782
|
+
}
|
|
783
|
+
// perform recalculation to weed out any stray identity transforms that might get stuck
|
|
784
|
+
svgCanvas.recalculateAllSelectedDimensions()
|
|
785
|
+
svgCanvas.call('changed', selectedElements)
|
|
786
|
+
break
|
|
787
|
+
} default:
|
|
788
|
+
// This could occur in an extension
|
|
789
|
+
break
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* The main (left) mouse button is released (anywhere).
|
|
794
|
+
* @event module:svgcanvas.SvgCanvas#event:ext_mouseUp
|
|
795
|
+
* @type {PlainObject}
|
|
796
|
+
* @property {MouseEvent} event The event object
|
|
797
|
+
* @property {Float} mouse_x x coordinate on canvas
|
|
798
|
+
* @property {Float} mouse_y y coordinate on canvas
|
|
799
|
+
*/
|
|
800
|
+
const extResult = svgCanvas.runExtensions('mouseUp', {
|
|
801
|
+
event: evt,
|
|
802
|
+
mouse_x: mouseX,
|
|
803
|
+
mouse_y: mouseY
|
|
804
|
+
}, true)
|
|
805
|
+
|
|
806
|
+
extResult.forEach((r) => {
|
|
807
|
+
if (r) {
|
|
808
|
+
keep = r.keep || keep;
|
|
809
|
+
({ element } = r)
|
|
810
|
+
svgCanvas.setStarted(r.started || svgCanvas.getStarted())
|
|
811
|
+
}
|
|
812
|
+
})
|
|
813
|
+
|
|
814
|
+
if (!keep && element) {
|
|
815
|
+
svgCanvas.getCurrentDrawing().releaseId(svgCanvas.getId())
|
|
816
|
+
element.remove()
|
|
817
|
+
element = null
|
|
818
|
+
|
|
819
|
+
t = evt.target
|
|
820
|
+
|
|
821
|
+
// if this element is in a group, go up until we reach the top-level group
|
|
822
|
+
// just below the layer groups
|
|
823
|
+
// TODO: once we implement links, we also would have to check for <a> elements
|
|
824
|
+
while (t?.parentNode?.parentNode?.tagName === 'g') {
|
|
825
|
+
t = t.parentNode
|
|
826
|
+
}
|
|
827
|
+
// if we are not in the middle of creating a path, and we've clicked on some shape,
|
|
828
|
+
// then go to Select mode.
|
|
829
|
+
// WebKit returns <div> when the canvas is clicked, Firefox/Opera return <svg>
|
|
830
|
+
if ((svgCanvas.getCurrentMode() !== 'path' || !svgCanvas.getDrawnPath()) &&
|
|
831
|
+
t &&
|
|
832
|
+
t.parentNode?.id !== 'selectorParentGroup' &&
|
|
833
|
+
t.id !== 'svgcanvas' && t.id !== 'svgroot'
|
|
834
|
+
) {
|
|
835
|
+
// switch into "select" mode if we've clicked on an element
|
|
836
|
+
svgCanvas.setMode('select')
|
|
837
|
+
svgCanvas.selectOnly([t], true)
|
|
838
|
+
}
|
|
839
|
+
} else if (element) {
|
|
840
|
+
/**
|
|
841
|
+
* @name module:svgcanvas.SvgCanvas#addedNew
|
|
842
|
+
* @type {boolean}
|
|
843
|
+
*/
|
|
844
|
+
svgCanvas.addedNew = true
|
|
845
|
+
|
|
846
|
+
if (useUnit) { convertAttrs(element) }
|
|
847
|
+
|
|
848
|
+
let aniDur = 0.2
|
|
849
|
+
let cAni
|
|
850
|
+
const curShape = svgCanvas.getStyle()
|
|
851
|
+
const opacAni = svgCanvas.getOpacAni()
|
|
852
|
+
if (opacAni.beginElement && Number.parseFloat(element.getAttribute('opacity')) !== curShape.opacity) {
|
|
853
|
+
cAni = opacAni.cloneNode(true)
|
|
854
|
+
cAni.setAttribute('to', curShape.opacity)
|
|
855
|
+
cAni.setAttribute('dur', aniDur)
|
|
856
|
+
element.appendChild(cAni)
|
|
857
|
+
try {
|
|
858
|
+
// Fails in FF4 on foreignObject
|
|
859
|
+
cAni.beginElement()
|
|
860
|
+
} catch (e) { /* empty fn */ }
|
|
861
|
+
} else {
|
|
862
|
+
aniDur = 0
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// Ideally this would be done on the endEvent of the animation,
|
|
866
|
+
// but that doesn't seem to be supported in Webkit
|
|
867
|
+
setTimeout(() => {
|
|
868
|
+
if (cAni) { cAni.remove() }
|
|
869
|
+
element.setAttribute('opacity', curShape.opacity)
|
|
870
|
+
element.setAttribute('style', 'pointer-events:inherit')
|
|
871
|
+
cleanupElement(element)
|
|
872
|
+
if (svgCanvas.getCurrentMode() === 'path') {
|
|
873
|
+
svgCanvas.pathActions.toEditMode(element)
|
|
874
|
+
} else if (svgCanvas.getCurConfig().selectNew) {
|
|
875
|
+
const modes = ['circle', 'ellipse', 'square', 'rect', 'fhpath', 'line', 'fhellipse', 'fhrect', 'star', 'polygon']
|
|
876
|
+
if (modes.indexOf(svgCanvas.getCurrentMode()) !== -1) {
|
|
877
|
+
svgCanvas.setMode('select')
|
|
878
|
+
}
|
|
879
|
+
svgCanvas.selectOnly([element], true)
|
|
880
|
+
}
|
|
881
|
+
// we create the insert command that is stored on the stack
|
|
882
|
+
// undo means to call cmd.unapply(), redo means to call cmd.apply()
|
|
883
|
+
svgCanvas.addCommandToHistory(new InsertElementCommand(element))
|
|
884
|
+
svgCanvas.call('changed', [element])
|
|
885
|
+
}, aniDur * 1000)
|
|
886
|
+
}
|
|
887
|
+
svgCanvas.setStartTransform(null)
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
const dblClickEvent = (evt) => {
|
|
891
|
+
const selectedElements = svgCanvas.getSelectedElements()
|
|
892
|
+
const evtTarget = evt.target
|
|
893
|
+
const parent = evtTarget.parentNode
|
|
894
|
+
|
|
895
|
+
let mouseTarget = svgCanvas.getMouseTarget(evt)
|
|
896
|
+
const { tagName } = mouseTarget
|
|
897
|
+
|
|
898
|
+
if (tagName === 'text' && svgCanvas.getCurrentMode() !== 'textedit') {
|
|
899
|
+
const pt = transformPoint(evt.clientX, evt.clientY, svgCanvas.getrootSctm())
|
|
900
|
+
svgCanvas.textActions.select(mouseTarget, pt.x, pt.y)
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// Do nothing if already in current group
|
|
904
|
+
if (parent === svgCanvas.getCurrentGroup()) { return }
|
|
905
|
+
|
|
906
|
+
if ((tagName === 'g' || tagName === 'a') && getRotationAngle(mouseTarget)) {
|
|
907
|
+
// TODO: Allow method of in-group editing without having to do
|
|
908
|
+
// this (similar to editing rotated paths)
|
|
909
|
+
|
|
910
|
+
// Ungroup and regroup
|
|
911
|
+
svgCanvas.pushGroupProperties(mouseTarget)
|
|
912
|
+
mouseTarget = selectedElements[0]
|
|
913
|
+
svgCanvas.clearSelection(true)
|
|
914
|
+
}
|
|
915
|
+
// Reset context
|
|
916
|
+
if (svgCanvas.getCurrentGroup()) {
|
|
917
|
+
draw.leaveContext()
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
if ((parent.tagName !== 'g' && parent.tagName !== 'a') ||
|
|
921
|
+
parent === svgCanvas.getCurrentDrawing().getCurrentLayer() ||
|
|
922
|
+
mouseTarget === svgCanvas.selectorManager.selectorParentGroup
|
|
923
|
+
) {
|
|
924
|
+
// Escape from in-group edit
|
|
925
|
+
return
|
|
926
|
+
}
|
|
927
|
+
draw.setContext(mouseTarget)
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
/**
|
|
931
|
+
* Follows these conditions:
|
|
932
|
+
* - When we are in a create mode, the element is added to the canvas but the
|
|
933
|
+
* action is not recorded until mousing up.
|
|
934
|
+
* - When we are in select mode, select the element, remember the position
|
|
935
|
+
* and do nothing else.
|
|
936
|
+
* @param {MouseEvent} evt
|
|
937
|
+
* @fires module:svgcanvas.SvgCanvas#event:ext_mouseDown
|
|
938
|
+
* @returns {void}
|
|
939
|
+
*/
|
|
940
|
+
const mouseDownEvent = (evt) => {
|
|
941
|
+
const dataStorage = svgCanvas.getDataStorage()
|
|
942
|
+
const selectedElements = svgCanvas.getSelectedElements()
|
|
943
|
+
const zoom = svgCanvas.getZoom()
|
|
944
|
+
const curShape = svgCanvas.getStyle()
|
|
945
|
+
const svgRoot = svgCanvas.getSvgRoot()
|
|
946
|
+
const { $id } = svgCanvas
|
|
947
|
+
|
|
948
|
+
if (svgCanvas.spaceKey || evt.button === 1) { return }
|
|
949
|
+
|
|
950
|
+
const rightClick = (evt.button === 2)
|
|
951
|
+
|
|
952
|
+
if (evt.altKey) { // duplicate when dragging
|
|
953
|
+
svgCanvas.cloneSelectedElements(0, 0)
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
svgCanvas.setRootSctm($id('svgcontent').querySelector('g').getScreenCTM().inverse())
|
|
957
|
+
|
|
958
|
+
const pt = transformPoint(evt.clientX, evt.clientY, svgCanvas.getrootSctm())
|
|
959
|
+
const mouseX = pt.x * zoom
|
|
960
|
+
const mouseY = pt.y * zoom
|
|
961
|
+
|
|
962
|
+
evt.preventDefault()
|
|
963
|
+
|
|
964
|
+
if (rightClick) {
|
|
965
|
+
if (svgCanvas.getCurrentMode() === 'path') {
|
|
966
|
+
return
|
|
967
|
+
}
|
|
968
|
+
svgCanvas.setCurrentMode('select')
|
|
969
|
+
svgCanvas.setLastClickPoint(pt)
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
let x = mouseX / zoom
|
|
973
|
+
let y = mouseY / zoom
|
|
974
|
+
let mouseTarget = svgCanvas.getMouseTarget(evt)
|
|
975
|
+
|
|
976
|
+
if (mouseTarget.tagName === 'a' && mouseTarget.childNodes.length === 1) {
|
|
977
|
+
mouseTarget = mouseTarget.firstChild
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// realX/y ignores grid-snap value
|
|
981
|
+
const realX = x
|
|
982
|
+
svgCanvas.setStartX(x)
|
|
983
|
+
svgCanvas.setRStartX(x)
|
|
984
|
+
const realY = y
|
|
985
|
+
svgCanvas.setStartY(y)
|
|
986
|
+
svgCanvas.setRStartY(y)
|
|
987
|
+
|
|
988
|
+
if (svgCanvas.getCurConfig().gridSnapping) {
|
|
989
|
+
x = snapToGrid(x)
|
|
990
|
+
y = snapToGrid(y)
|
|
991
|
+
svgCanvas.setStartX(snapToGrid(svgCanvas.getStartX()))
|
|
992
|
+
svgCanvas.setStartY(snapToGrid(svgCanvas.getStartY()))
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
// if it is a selector grip, then it must be a single element selected,
|
|
996
|
+
// set the mouseTarget to that and update the mode to rotate/resize
|
|
997
|
+
|
|
998
|
+
if (mouseTarget === svgCanvas.selectorManager.selectorParentGroup && selectedElements[0]) {
|
|
999
|
+
const grip = evt.target
|
|
1000
|
+
const griptype = dataStorage.get(grip, 'type')
|
|
1001
|
+
// rotating
|
|
1002
|
+
if (griptype === 'rotate') {
|
|
1003
|
+
svgCanvas.setCurrentMode('rotate')
|
|
1004
|
+
// svgCanvas.setCurrentRotateMode(dataStorage.get(grip, 'dir'));
|
|
1005
|
+
// resizing
|
|
1006
|
+
} else if (griptype === 'resize') {
|
|
1007
|
+
svgCanvas.setCurrentMode('resize')
|
|
1008
|
+
svgCanvas.setCurrentResizeMode(dataStorage.get(grip, 'dir'))
|
|
1009
|
+
}
|
|
1010
|
+
mouseTarget = selectedElements[0]
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
svgCanvas.setStartTransform(mouseTarget.getAttribute('transform'))
|
|
1014
|
+
|
|
1015
|
+
const tlist = mouseTarget.transform.baseVal
|
|
1016
|
+
// consolidate transforms using standard SVG but keep the transformation used for the move/scale
|
|
1017
|
+
if (tlist.numberOfItems > 1) {
|
|
1018
|
+
const firstTransform = tlist.getItem(0)
|
|
1019
|
+
tlist.removeItem(0)
|
|
1020
|
+
tlist.consolidate()
|
|
1021
|
+
tlist.insertItemBefore(firstTransform, 0)
|
|
1022
|
+
}
|
|
1023
|
+
switch (svgCanvas.getCurrentMode()) {
|
|
1024
|
+
case 'select':
|
|
1025
|
+
svgCanvas.setStarted(true)
|
|
1026
|
+
svgCanvas.setCurrentResizeMode('none')
|
|
1027
|
+
if (rightClick) { svgCanvas.setStarted(false) }
|
|
1028
|
+
|
|
1029
|
+
if (mouseTarget !== svgRoot) {
|
|
1030
|
+
// if this element is not yet selected, clear selection and select it
|
|
1031
|
+
if (!selectedElements.includes(mouseTarget)) {
|
|
1032
|
+
// only clear selection if shift is not pressed (otherwise, add
|
|
1033
|
+
// element to selection)
|
|
1034
|
+
if (!evt.shiftKey) {
|
|
1035
|
+
// No need to do the call here as it will be done on addToSelection
|
|
1036
|
+
svgCanvas.clearSelection(true)
|
|
1037
|
+
}
|
|
1038
|
+
svgCanvas.addToSelection([mouseTarget])
|
|
1039
|
+
svgCanvas.setJustSelected(mouseTarget)
|
|
1040
|
+
svgCanvas.pathActions.clear()
|
|
1041
|
+
}
|
|
1042
|
+
// else if it's a path, go into pathedit mode in mouseup
|
|
1043
|
+
|
|
1044
|
+
if (!rightClick) {
|
|
1045
|
+
// insert a dummy transform so if the element(s) are moved it will have
|
|
1046
|
+
// a transform to use for its translate
|
|
1047
|
+
for (const selectedElement of selectedElements) {
|
|
1048
|
+
if (!selectedElement) { continue }
|
|
1049
|
+
const slist = selectedElement.transform?.baseVal
|
|
1050
|
+
if (slist.numberOfItems) {
|
|
1051
|
+
slist.insertItemBefore(svgRoot.createSVGTransform(), 0)
|
|
1052
|
+
} else {
|
|
1053
|
+
slist.appendItem(svgRoot.createSVGTransform())
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
} else if (!rightClick) {
|
|
1058
|
+
svgCanvas.clearSelection()
|
|
1059
|
+
svgCanvas.setCurrentMode('multiselect')
|
|
1060
|
+
if (!svgCanvas.getRubberBox()) {
|
|
1061
|
+
svgCanvas.setRubberBox(svgCanvas.selectorManager.getRubberBandBox())
|
|
1062
|
+
}
|
|
1063
|
+
svgCanvas.setRStartX(svgCanvas.getRStartX() * zoom)
|
|
1064
|
+
svgCanvas.setRStartY(svgCanvas.getRStartY() * zoom)
|
|
1065
|
+
|
|
1066
|
+
assignAttributes(svgCanvas.getRubberBox(), {
|
|
1067
|
+
x: svgCanvas.getRStartX(),
|
|
1068
|
+
y: svgCanvas.getRStartY(),
|
|
1069
|
+
width: 0,
|
|
1070
|
+
height: 0,
|
|
1071
|
+
display: 'inline'
|
|
1072
|
+
}, 100)
|
|
1073
|
+
}
|
|
1074
|
+
break
|
|
1075
|
+
case 'zoom':
|
|
1076
|
+
svgCanvas.setStarted(true)
|
|
1077
|
+
if (!svgCanvas.getRubberBox()) {
|
|
1078
|
+
svgCanvas.setRubberBox(svgCanvas.selectorManager.getRubberBandBox())
|
|
1079
|
+
}
|
|
1080
|
+
assignAttributes(svgCanvas.getRubberBox(), {
|
|
1081
|
+
x: realX * zoom,
|
|
1082
|
+
y: realX * zoom,
|
|
1083
|
+
width: 0,
|
|
1084
|
+
height: 0,
|
|
1085
|
+
display: 'inline'
|
|
1086
|
+
}, 100)
|
|
1087
|
+
break
|
|
1088
|
+
case 'resize': {
|
|
1089
|
+
svgCanvas.setStarted(true)
|
|
1090
|
+
svgCanvas.setStartX(x)
|
|
1091
|
+
svgCanvas.setStartY(y)
|
|
1092
|
+
|
|
1093
|
+
// Getting the BBox from the selection box, since we know we
|
|
1094
|
+
// want to orient around it
|
|
1095
|
+
svgCanvas.setInitBbox(getBBox($id('selectedBox0')))
|
|
1096
|
+
const bb = {}
|
|
1097
|
+
for (const [key, val] of Object.entries(svgCanvas.getInitBbox())) {
|
|
1098
|
+
bb[key] = val / zoom
|
|
1099
|
+
}
|
|
1100
|
+
svgCanvas.setInitBbox(bb)
|
|
1101
|
+
|
|
1102
|
+
// append three dummy transforms to the tlist so that
|
|
1103
|
+
// we can translate,scale,translate in mousemove
|
|
1104
|
+
const pos = getRotationAngle(mouseTarget) ? 1 : 0
|
|
1105
|
+
|
|
1106
|
+
if (hasMatrixTransform(tlist)) {
|
|
1107
|
+
tlist.insertItemBefore(svgRoot.createSVGTransform(), pos)
|
|
1108
|
+
tlist.insertItemBefore(svgRoot.createSVGTransform(), pos)
|
|
1109
|
+
tlist.insertItemBefore(svgRoot.createSVGTransform(), pos)
|
|
1110
|
+
} else {
|
|
1111
|
+
tlist.appendItem(svgRoot.createSVGTransform())
|
|
1112
|
+
tlist.appendItem(svgRoot.createSVGTransform())
|
|
1113
|
+
tlist.appendItem(svgRoot.createSVGTransform())
|
|
1114
|
+
}
|
|
1115
|
+
break
|
|
1116
|
+
}
|
|
1117
|
+
case 'fhellipse':
|
|
1118
|
+
case 'fhrect':
|
|
1119
|
+
case 'fhpath':
|
|
1120
|
+
svgCanvas.setStart({ x: realX, y: realY })
|
|
1121
|
+
svgCanvas.setControllPoint1('x', 0)
|
|
1122
|
+
svgCanvas.setControllPoint1('y', 0)
|
|
1123
|
+
svgCanvas.setControllPoint2('x', 0)
|
|
1124
|
+
svgCanvas.setControllPoint2('y', 0)
|
|
1125
|
+
svgCanvas.setStarted(true)
|
|
1126
|
+
svgCanvas.setDAttr(realX + ',' + realY + ' ')
|
|
1127
|
+
// Commented out as doing nothing now:
|
|
1128
|
+
// strokeW = parseFloat(curShape.stroke_width) === 0 ? 1 : curShape.stroke_width;
|
|
1129
|
+
svgCanvas.addSVGElementsFromJson({
|
|
1130
|
+
element: 'polyline',
|
|
1131
|
+
curStyles: true,
|
|
1132
|
+
attr: {
|
|
1133
|
+
points: svgCanvas.getDAttr(),
|
|
1134
|
+
id: svgCanvas.getNextId(),
|
|
1135
|
+
fill: 'none',
|
|
1136
|
+
opacity: curShape.opacity / 2,
|
|
1137
|
+
'stroke-linecap': 'round',
|
|
1138
|
+
style: 'pointer-events:none'
|
|
1139
|
+
}
|
|
1140
|
+
})
|
|
1141
|
+
svgCanvas.setFreehand('minx', realX)
|
|
1142
|
+
svgCanvas.setFreehand('maxx', realX)
|
|
1143
|
+
svgCanvas.setFreehand('miny', realY)
|
|
1144
|
+
svgCanvas.setFreehand('maxy', realY)
|
|
1145
|
+
break
|
|
1146
|
+
case 'image': {
|
|
1147
|
+
svgCanvas.setStarted(true)
|
|
1148
|
+
const newImage = svgCanvas.addSVGElementsFromJson({
|
|
1149
|
+
element: 'image',
|
|
1150
|
+
attr: {
|
|
1151
|
+
x,
|
|
1152
|
+
y,
|
|
1153
|
+
width: 0,
|
|
1154
|
+
height: 0,
|
|
1155
|
+
id: svgCanvas.getNextId(),
|
|
1156
|
+
opacity: curShape.opacity / 2,
|
|
1157
|
+
style: 'pointer-events:inherit'
|
|
1158
|
+
}
|
|
1159
|
+
})
|
|
1160
|
+
setHref(newImage, svgCanvas.getLastGoodImgUrl())
|
|
1161
|
+
preventClickDefault(newImage)
|
|
1162
|
+
break
|
|
1163
|
+
} case 'square':
|
|
1164
|
+
// TODO: once we create the rect, we lose information that this was a square
|
|
1165
|
+
// (for resizing purposes this could be important)
|
|
1166
|
+
// Fallthrough
|
|
1167
|
+
case 'rect':
|
|
1168
|
+
svgCanvas.setStarted(true)
|
|
1169
|
+
svgCanvas.setStartX(x)
|
|
1170
|
+
svgCanvas.setStartY(y)
|
|
1171
|
+
svgCanvas.addSVGElementsFromJson({
|
|
1172
|
+
element: 'rect',
|
|
1173
|
+
curStyles: true,
|
|
1174
|
+
attr: {
|
|
1175
|
+
x,
|
|
1176
|
+
y,
|
|
1177
|
+
width: 0,
|
|
1178
|
+
height: 0,
|
|
1179
|
+
id: svgCanvas.getNextId(),
|
|
1180
|
+
opacity: curShape.opacity / 2
|
|
1181
|
+
}
|
|
1182
|
+
})
|
|
1183
|
+
break
|
|
1184
|
+
case 'line': {
|
|
1185
|
+
svgCanvas.setStarted(true)
|
|
1186
|
+
const strokeW = Number(curShape.stroke_width) === 0 ? 1 : curShape.stroke_width
|
|
1187
|
+
svgCanvas.addSVGElementsFromJson({
|
|
1188
|
+
element: 'line',
|
|
1189
|
+
curStyles: true,
|
|
1190
|
+
attr: {
|
|
1191
|
+
x1: x,
|
|
1192
|
+
y1: y,
|
|
1193
|
+
x2: x,
|
|
1194
|
+
y2: y,
|
|
1195
|
+
id: svgCanvas.getNextId(),
|
|
1196
|
+
stroke: curShape.stroke,
|
|
1197
|
+
'stroke-width': strokeW,
|
|
1198
|
+
'stroke-dasharray': curShape.stroke_dasharray,
|
|
1199
|
+
'stroke-linejoin': curShape.stroke_linejoin,
|
|
1200
|
+
'stroke-linecap': curShape.stroke_linecap,
|
|
1201
|
+
'stroke-opacity': curShape.stroke_opacity,
|
|
1202
|
+
fill: 'none',
|
|
1203
|
+
opacity: curShape.opacity / 2,
|
|
1204
|
+
style: 'pointer-events:none'
|
|
1205
|
+
}
|
|
1206
|
+
})
|
|
1207
|
+
break
|
|
1208
|
+
} case 'circle':
|
|
1209
|
+
svgCanvas.setStarted(true)
|
|
1210
|
+
svgCanvas.addSVGElementsFromJson({
|
|
1211
|
+
element: 'circle',
|
|
1212
|
+
curStyles: true,
|
|
1213
|
+
attr: {
|
|
1214
|
+
cx: x,
|
|
1215
|
+
cy: y,
|
|
1216
|
+
r: 0,
|
|
1217
|
+
id: svgCanvas.getNextId(),
|
|
1218
|
+
opacity: curShape.opacity / 2
|
|
1219
|
+
}
|
|
1220
|
+
})
|
|
1221
|
+
break
|
|
1222
|
+
case 'ellipse':
|
|
1223
|
+
svgCanvas.setStarted(true)
|
|
1224
|
+
svgCanvas.addSVGElementsFromJson({
|
|
1225
|
+
element: 'ellipse',
|
|
1226
|
+
curStyles: true,
|
|
1227
|
+
attr: {
|
|
1228
|
+
cx: x,
|
|
1229
|
+
cy: y,
|
|
1230
|
+
rx: 0,
|
|
1231
|
+
ry: 0,
|
|
1232
|
+
id: svgCanvas.getNextId(),
|
|
1233
|
+
opacity: curShape.opacity / 2
|
|
1234
|
+
}
|
|
1235
|
+
})
|
|
1236
|
+
break
|
|
1237
|
+
case 'text':
|
|
1238
|
+
svgCanvas.setStarted(true)
|
|
1239
|
+
/* const newText = */ svgCanvas.addSVGElementsFromJson({
|
|
1240
|
+
element: 'text',
|
|
1241
|
+
curStyles: true,
|
|
1242
|
+
attr: {
|
|
1243
|
+
x,
|
|
1244
|
+
y,
|
|
1245
|
+
id: svgCanvas.getNextId(),
|
|
1246
|
+
fill: svgCanvas.getCurText('fill'),
|
|
1247
|
+
'stroke-width': svgCanvas.getCurText('stroke_width'),
|
|
1248
|
+
'font-size': svgCanvas.getCurText('font_size'),
|
|
1249
|
+
'font-family': svgCanvas.getCurText('font_family'),
|
|
1250
|
+
'text-anchor': 'middle',
|
|
1251
|
+
'xml:space': 'preserve',
|
|
1252
|
+
opacity: curShape.opacity
|
|
1253
|
+
}
|
|
1254
|
+
})
|
|
1255
|
+
// newText.textContent = 'text';
|
|
1256
|
+
break
|
|
1257
|
+
case 'path':
|
|
1258
|
+
// Fall through
|
|
1259
|
+
case 'pathedit':
|
|
1260
|
+
svgCanvas.setStartX(svgCanvas.getStartX() * zoom)
|
|
1261
|
+
svgCanvas.setStartY(svgCanvas.getStartY() * zoom)
|
|
1262
|
+
svgCanvas.pathActions.mouseDown(evt, mouseTarget, svgCanvas.getStartX(), svgCanvas.getStartY())
|
|
1263
|
+
svgCanvas.setStarted(true)
|
|
1264
|
+
break
|
|
1265
|
+
case 'textedit':
|
|
1266
|
+
svgCanvas.setStartX(svgCanvas.getStartX() * zoom)
|
|
1267
|
+
svgCanvas.setStartY(svgCanvas.getStartY() * zoom)
|
|
1268
|
+
svgCanvas.textActions.mouseDown(evt, mouseTarget, svgCanvas.getStartX(), svgCanvas.getStartY())
|
|
1269
|
+
svgCanvas.setStarted(true)
|
|
1270
|
+
break
|
|
1271
|
+
case 'rotate':
|
|
1272
|
+
svgCanvas.setStarted(true)
|
|
1273
|
+
// we are starting an undoable change (a drag-rotation)
|
|
1274
|
+
svgCanvas.undoMgr.beginUndoableChange('transform', selectedElements)
|
|
1275
|
+
break
|
|
1276
|
+
default:
|
|
1277
|
+
// This could occur in an extension
|
|
1278
|
+
break
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
/**
|
|
1282
|
+
* The main (left) mouse button is held down on the canvas area.
|
|
1283
|
+
* @event module:svgcanvas.SvgCanvas#event:ext_mouseDown
|
|
1284
|
+
* @type {PlainObject}
|
|
1285
|
+
* @property {MouseEvent} event The event object
|
|
1286
|
+
* @property {Float} start_x x coordinate on canvas
|
|
1287
|
+
* @property {Float} start_y y coordinate on canvas
|
|
1288
|
+
* @property {Element[]} selectedElements An array of the selected Elements
|
|
1289
|
+
*/
|
|
1290
|
+
const extResult = svgCanvas.runExtensions('mouseDown', {
|
|
1291
|
+
event: evt,
|
|
1292
|
+
start_x: svgCanvas.getStartX(),
|
|
1293
|
+
start_y: svgCanvas.getStartY(),
|
|
1294
|
+
selectedElements
|
|
1295
|
+
}, true)
|
|
1296
|
+
|
|
1297
|
+
extResult.forEach((r) => {
|
|
1298
|
+
if (r?.started) {
|
|
1299
|
+
svgCanvas.setStarted(true)
|
|
1300
|
+
}
|
|
1301
|
+
})
|
|
1302
|
+
}
|
|
1303
|
+
/**
|
|
1304
|
+
* @param {Event} e
|
|
1305
|
+
* @fires module:event.SvgCanvas#event:updateCanvas
|
|
1306
|
+
* @fires module:event.SvgCanvas#event:zoomDone
|
|
1307
|
+
* @returns {void}
|
|
1308
|
+
*/
|
|
1309
|
+
const DOMMouseScrollEvent = (e) => {
|
|
1310
|
+
const zoom = svgCanvas.getZoom()
|
|
1311
|
+
const { $id } = svgCanvas
|
|
1312
|
+
if (!e.shiftKey) { return }
|
|
1313
|
+
|
|
1314
|
+
e.preventDefault()
|
|
1315
|
+
|
|
1316
|
+
svgCanvas.setRootSctm($id('svgcontent').querySelector('g').getScreenCTM().inverse())
|
|
1317
|
+
|
|
1318
|
+
const workarea = document.getElementById('workarea')
|
|
1319
|
+
const scrbar = 15
|
|
1320
|
+
const rulerwidth = svgCanvas.getCurConfig().showRulers ? 16 : 0
|
|
1321
|
+
|
|
1322
|
+
// mouse relative to content area in content pixels
|
|
1323
|
+
const pt = transformPoint(e.clientX, e.clientY, svgCanvas.getrootSctm())
|
|
1324
|
+
|
|
1325
|
+
// full work area width in screen pixels
|
|
1326
|
+
const editorFullW = parseFloat(getComputedStyle(workarea, null).width.replace('px', ''))
|
|
1327
|
+
const editorFullH = parseFloat(getComputedStyle(workarea, null).height.replace('px', ''))
|
|
1328
|
+
|
|
1329
|
+
// work area width minus scroll and ruler in screen pixels
|
|
1330
|
+
const editorW = editorFullW - scrbar - rulerwidth
|
|
1331
|
+
const editorH = editorFullH - scrbar - rulerwidth
|
|
1332
|
+
|
|
1333
|
+
// work area width in content pixels
|
|
1334
|
+
const workareaViewW = editorW * svgCanvas.getrootSctm().a
|
|
1335
|
+
const workareaViewH = editorH * svgCanvas.getrootSctm().d
|
|
1336
|
+
|
|
1337
|
+
// content offset from canvas in screen pixels
|
|
1338
|
+
const wOffset = findPos(workarea)
|
|
1339
|
+
const wOffsetLeft = wOffset.left + rulerwidth
|
|
1340
|
+
const wOffsetTop = wOffset.top + rulerwidth
|
|
1341
|
+
|
|
1342
|
+
const delta = (e.wheelDelta) ? e.wheelDelta : (e.detail) ? -e.detail : 0
|
|
1343
|
+
if (!delta) { return }
|
|
1344
|
+
|
|
1345
|
+
let factor = Math.max(3 / 4, Math.min(4 / 3, (delta)))
|
|
1346
|
+
|
|
1347
|
+
let wZoom; let hZoom
|
|
1348
|
+
if (factor > 1) {
|
|
1349
|
+
wZoom = Math.ceil(editorW / workareaViewW * factor * 100) / 100
|
|
1350
|
+
hZoom = Math.ceil(editorH / workareaViewH * factor * 100) / 100
|
|
1351
|
+
} else {
|
|
1352
|
+
wZoom = Math.floor(editorW / workareaViewW * factor * 100) / 100
|
|
1353
|
+
hZoom = Math.floor(editorH / workareaViewH * factor * 100) / 100
|
|
1354
|
+
}
|
|
1355
|
+
let zoomlevel = Math.min(wZoom, hZoom)
|
|
1356
|
+
zoomlevel = Math.min(10, Math.max(0.01, zoomlevel))
|
|
1357
|
+
if (zoomlevel === zoom) {
|
|
1358
|
+
return
|
|
1359
|
+
}
|
|
1360
|
+
factor = zoomlevel / zoom
|
|
1361
|
+
|
|
1362
|
+
// top left of workarea in content pixels before zoom
|
|
1363
|
+
const topLeftOld = transformPoint(wOffsetLeft, wOffsetTop, svgCanvas.getrootSctm())
|
|
1364
|
+
|
|
1365
|
+
// top left of workarea in content pixels after zoom
|
|
1366
|
+
const topLeftNew = {
|
|
1367
|
+
x: pt.x - (pt.x - topLeftOld.x) / factor,
|
|
1368
|
+
y: pt.y - (pt.y - topLeftOld.y) / factor
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
// top left of workarea in canvas pixels relative to content after zoom
|
|
1372
|
+
const topLeftNewCanvas = {
|
|
1373
|
+
x: topLeftNew.x * zoomlevel,
|
|
1374
|
+
y: topLeftNew.y * zoomlevel
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
// new center in canvas pixels
|
|
1378
|
+
const newCtr = {
|
|
1379
|
+
x: topLeftNewCanvas.x - rulerwidth + editorFullW / 2,
|
|
1380
|
+
y: topLeftNewCanvas.y - rulerwidth + editorFullH / 2
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
svgCanvas.setZoom(zoomlevel)
|
|
1384
|
+
document.getElementById('zoom').value = ((zoomlevel * 100).toFixed(1))
|
|
1385
|
+
|
|
1386
|
+
svgCanvas.call('updateCanvas', { center: false, newCtr })
|
|
1387
|
+
svgCanvas.call('zoomDone')
|
|
1388
|
+
}
|