@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/svgroot.js ADDED
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Tools for SVG Root Element.
3
+ * @module svgcanvas
4
+ * @license MIT
5
+ *
6
+ * @copyright 2010 Alexis Deveria, 2010 Jeff Schiller
7
+ */
8
+ import { NS } from './namespaces.js'
9
+ import { text2xml } from './utilities.js'
10
+
11
+ /**
12
+ * @function module:svgcanvas.svgRootElement svgRootElement the svg node and its children.
13
+ * @param {Element} svgdoc - window.document
14
+ * @param {ArgumentsArray} dimensions - dimensions of width and height
15
+ * @returns {svgRootElement}
16
+ */
17
+ export const svgRootElement = function (svgdoc, dimensions) {
18
+ return svgdoc.importNode(
19
+ text2xml(
20
+ `<svg id="svgroot" xmlns="${NS.SVG}" xlinkns="${NS.XLINK}" width="${dimensions[0]}"
21
+ height="${dimensions[1]}" x="${dimensions[0]}" y="${dimensions[1]}" overflow="visible">
22
+ <defs>
23
+ <filter id="canvashadow" filterUnits="objectBoundingBox">
24
+ <feGaussianBlur in="SourceAlpha" stdDeviation="4" result="blur"/>
25
+ <feOffset in="blur" dx="5" dy="5" result="offsetBlur"/>
26
+ <feMerge>
27
+ <feMergeNode in="offsetBlur"/>
28
+ <feMergeNode in="SourceGraphic"/>
29
+ </feMerge>
30
+ </filter>
31
+ </defs>
32
+ </svg>`
33
+ ).documentElement,
34
+ true
35
+ )
36
+ }
@@ -0,0 +1,530 @@
1
+ /**
2
+ * @module text-actions Tools for Text edit functions
3
+ * @license MIT
4
+ *
5
+ * @copyright 2010 Alexis Deveria, 2010 Jeff Schiller
6
+ */
7
+
8
+ import { NS } from './namespaces.js'
9
+ import {
10
+ transformPoint, getMatrix
11
+ } from './math.js'
12
+ import {
13
+ assignAttributes, getElement, getBBox as utilsGetBBox
14
+ } from './utilities.js'
15
+ import {
16
+ supportsGoodTextCharPos
17
+ } from '../../src/common/browser.js'
18
+
19
+ let svgCanvas = null
20
+
21
+ /**
22
+ * @function module:text-actions.init
23
+ * @param {module:text-actions.svgCanvas} textActionsContext
24
+ * @returns {void}
25
+ */
26
+ export const init = (canvas) => {
27
+ svgCanvas = canvas
28
+ }
29
+
30
+ /**
31
+ * Group: Text edit functions
32
+ * Functions relating to editing text elements.
33
+ * @namespace {PlainObject} textActions
34
+ * @memberof module:svgcanvas.SvgCanvas#
35
+ */
36
+ export const textActionsMethod = (function () {
37
+ let curtext
38
+ let textinput
39
+ let cursor
40
+ let selblock
41
+ let blinker
42
+ let chardata = []
43
+ let textbb // , transbb;
44
+ let matrix
45
+ let lastX; let lastY
46
+ let allowDbl
47
+
48
+ /**
49
+ *
50
+ * @param {Integer} index
51
+ * @returns {void}
52
+ */
53
+ function setCursor (index) {
54
+ const empty = (textinput.value === '')
55
+ textinput.focus()
56
+
57
+ if (!arguments.length) {
58
+ if (empty) {
59
+ index = 0
60
+ } else {
61
+ if (textinput.selectionEnd !== textinput.selectionStart) { return }
62
+ index = textinput.selectionEnd
63
+ }
64
+ }
65
+
66
+ const charbb = chardata[index]
67
+ if (!empty) {
68
+ textinput.setSelectionRange(index, index)
69
+ }
70
+ cursor = getElement('text_cursor')
71
+ if (!cursor) {
72
+ cursor = document.createElementNS(NS.SVG, 'line')
73
+ assignAttributes(cursor, {
74
+ id: 'text_cursor',
75
+ stroke: '#333',
76
+ 'stroke-width': 1
77
+ })
78
+ getElement('selectorParentGroup').append(cursor)
79
+ }
80
+
81
+ if (!blinker) {
82
+ blinker = setInterval(function () {
83
+ const show = (cursor.getAttribute('display') === 'none')
84
+ cursor.setAttribute('display', show ? 'inline' : 'none')
85
+ }, 600)
86
+ }
87
+
88
+ const startPt = ptToScreen(charbb.x, textbb.y)
89
+ const endPt = ptToScreen(charbb.x, (textbb.y + textbb.height))
90
+
91
+ assignAttributes(cursor, {
92
+ x1: startPt.x,
93
+ y1: startPt.y,
94
+ x2: endPt.x,
95
+ y2: endPt.y,
96
+ visibility: 'visible',
97
+ display: 'inline'
98
+ })
99
+
100
+ if (selblock) { selblock.setAttribute('d', '') }
101
+ }
102
+
103
+ /**
104
+ *
105
+ * @param {Integer} start
106
+ * @param {Integer} end
107
+ * @param {boolean} skipInput
108
+ * @returns {void}
109
+ */
110
+ function setSelection (start, end, skipInput) {
111
+ if (start === end) {
112
+ setCursor(end)
113
+ return
114
+ }
115
+
116
+ if (!skipInput) {
117
+ textinput.setSelectionRange(start, end)
118
+ }
119
+
120
+ selblock = getElement('text_selectblock')
121
+ if (!selblock) {
122
+ selblock = document.createElementNS(NS.SVG, 'path')
123
+ assignAttributes(selblock, {
124
+ id: 'text_selectblock',
125
+ fill: 'green',
126
+ opacity: 0.5,
127
+ style: 'pointer-events:none'
128
+ })
129
+ getElement('selectorParentGroup').append(selblock)
130
+ }
131
+
132
+ const startbb = chardata[start]
133
+ const endbb = chardata[end]
134
+
135
+ cursor.setAttribute('visibility', 'hidden')
136
+
137
+ const tl = ptToScreen(startbb.x, textbb.y)
138
+ const tr = ptToScreen(startbb.x + (endbb.x - startbb.x), textbb.y)
139
+ const bl = ptToScreen(startbb.x, textbb.y + textbb.height)
140
+ const br = ptToScreen(startbb.x + (endbb.x - startbb.x), textbb.y + textbb.height)
141
+
142
+ const dstr = 'M' + tl.x + ',' + tl.y +
143
+ ' L' + tr.x + ',' + tr.y +
144
+ ' ' + br.x + ',' + br.y +
145
+ ' ' + bl.x + ',' + bl.y + 'z'
146
+
147
+ assignAttributes(selblock, {
148
+ d: dstr,
149
+ display: 'inline'
150
+ })
151
+ }
152
+
153
+ /**
154
+ *
155
+ * @param {Float} mouseX
156
+ * @param {Float} mouseY
157
+ * @returns {Integer}
158
+ */
159
+ function getIndexFromPoint (mouseX, mouseY) {
160
+ // Position cursor here
161
+ const pt = svgCanvas.getSvgRoot().createSVGPoint()
162
+ pt.x = mouseX
163
+ pt.y = mouseY
164
+
165
+ // No content, so return 0
166
+ if (chardata.length === 1) { return 0 }
167
+ // Determine if cursor should be on left or right of character
168
+ let charpos = curtext.getCharNumAtPosition(pt)
169
+ if (charpos < 0) {
170
+ // Out of text range, look at mouse coords
171
+ charpos = chardata.length - 2
172
+ if (mouseX <= chardata[0].x) {
173
+ charpos = 0
174
+ }
175
+ } else if (charpos >= chardata.length - 2) {
176
+ charpos = chardata.length - 2
177
+ }
178
+ const charbb = chardata[charpos]
179
+ const mid = charbb.x + (charbb.width / 2)
180
+ if (mouseX > mid) {
181
+ charpos++
182
+ }
183
+ return charpos
184
+ }
185
+
186
+ /**
187
+ *
188
+ * @param {Float} mouseX
189
+ * @param {Float} mouseY
190
+ * @returns {void}
191
+ */
192
+ function setCursorFromPoint (mouseX, mouseY) {
193
+ setCursor(getIndexFromPoint(mouseX, mouseY))
194
+ }
195
+
196
+ /**
197
+ *
198
+ * @param {Float} x
199
+ * @param {Float} y
200
+ * @param {boolean} apply
201
+ * @returns {void}
202
+ */
203
+ function setEndSelectionFromPoint (x, y, apply) {
204
+ const i1 = textinput.selectionStart
205
+ const i2 = getIndexFromPoint(x, y)
206
+
207
+ const start = Math.min(i1, i2)
208
+ const end = Math.max(i1, i2)
209
+ setSelection(start, end, !apply)
210
+ }
211
+
212
+ /**
213
+ *
214
+ * @param {Float} xIn
215
+ * @param {Float} yIn
216
+ * @returns {module:math.XYObject}
217
+ */
218
+ function screenToPt (xIn, yIn) {
219
+ const out = {
220
+ x: xIn,
221
+ y: yIn
222
+ }
223
+ const zoom = svgCanvas.getZoom()
224
+ out.x /= zoom
225
+ out.y /= zoom
226
+
227
+ if (matrix) {
228
+ const pt = transformPoint(out.x, out.y, matrix.inverse())
229
+ out.x = pt.x
230
+ out.y = pt.y
231
+ }
232
+
233
+ return out
234
+ }
235
+
236
+ /**
237
+ *
238
+ * @param {Float} xIn
239
+ * @param {Float} yIn
240
+ * @returns {module:math.XYObject}
241
+ */
242
+ function ptToScreen (xIn, yIn) {
243
+ const out = {
244
+ x: xIn,
245
+ y: yIn
246
+ }
247
+
248
+ if (matrix) {
249
+ const pt = transformPoint(out.x, out.y, matrix)
250
+ out.x = pt.x
251
+ out.y = pt.y
252
+ }
253
+ const zoom = svgCanvas.getZoom()
254
+ out.x *= zoom
255
+ out.y *= zoom
256
+
257
+ return out
258
+ }
259
+
260
+ /**
261
+ *
262
+ * @param {Event} evt
263
+ * @returns {void}
264
+ */
265
+ function selectAll (evt) {
266
+ setSelection(0, curtext.textContent.length)
267
+ evt.target.removeEventListener('click', selectAll)
268
+ }
269
+
270
+ /**
271
+ *
272
+ * @param {Event} evt
273
+ * @returns {void}
274
+ */
275
+ function selectWord (evt) {
276
+ if (!allowDbl || !curtext) { return }
277
+ const zoom = svgCanvas.getZoom()
278
+ const ept = transformPoint(evt.pageX, evt.pageY, svgCanvas.getrootSctm())
279
+ const mouseX = ept.x * zoom
280
+ const mouseY = ept.y * zoom
281
+ const pt = screenToPt(mouseX, mouseY)
282
+
283
+ const index = getIndexFromPoint(pt.x, pt.y)
284
+ const str = curtext.textContent
285
+ const first = str.substr(0, index).replace(/[a-z\d]+$/i, '').length
286
+ const m = str.substr(index).match(/^[a-z\d]+/i)
287
+ const last = (m ? m[0].length : 0) + index
288
+ setSelection(first, last)
289
+
290
+ // Set tripleclick
291
+ svgCanvas.$click(evt.target, selectAll)
292
+
293
+ setTimeout(function () {
294
+ evt.target.removeEventListener('click', selectAll)
295
+ }, 300)
296
+ }
297
+
298
+ return /** @lends module:svgcanvas.SvgCanvas#textActions */ {
299
+ /**
300
+ * @param {Element} target
301
+ * @param {Float} x
302
+ * @param {Float} y
303
+ * @returns {void}
304
+ */
305
+ select (target, x, y) {
306
+ curtext = target
307
+ svgCanvas.textActions.toEditMode(x, y)
308
+ },
309
+ /**
310
+ * @param {Element} elem
311
+ * @returns {void}
312
+ */
313
+ start (elem) {
314
+ curtext = elem
315
+ svgCanvas.textActions.toEditMode()
316
+ },
317
+ /**
318
+ * @param {external:MouseEvent} evt
319
+ * @param {Element} mouseTarget
320
+ * @param {Float} startX
321
+ * @param {Float} startY
322
+ * @returns {void}
323
+ */
324
+ mouseDown (evt, mouseTarget, startX, startY) {
325
+ const pt = screenToPt(startX, startY)
326
+
327
+ textinput.focus()
328
+ setCursorFromPoint(pt.x, pt.y)
329
+ lastX = startX
330
+ lastY = startY
331
+
332
+ // TODO: Find way to block native selection
333
+ },
334
+ /**
335
+ * @param {Float} mouseX
336
+ * @param {Float} mouseY
337
+ * @returns {void}
338
+ */
339
+ mouseMove (mouseX, mouseY) {
340
+ const pt = screenToPt(mouseX, mouseY)
341
+ setEndSelectionFromPoint(pt.x, pt.y)
342
+ },
343
+ /**
344
+ * @param {external:MouseEvent} evt
345
+ * @param {Float} mouseX
346
+ * @param {Float} mouseY
347
+ * @returns {void}
348
+ */
349
+ mouseUp (evt, mouseX, mouseY) {
350
+ const pt = screenToPt(mouseX, mouseY)
351
+
352
+ setEndSelectionFromPoint(pt.x, pt.y, true)
353
+
354
+ // TODO: Find a way to make this work: Use transformed BBox instead of evt.target
355
+ // if (lastX === mouseX && lastY === mouseY
356
+ // && !rectsIntersect(transbb, {x: pt.x, y: pt.y, width: 0, height: 0})) {
357
+ // svgCanvas.textActions.toSelectMode(true);
358
+ // }
359
+
360
+ if (
361
+ evt.target !== curtext &&
362
+ mouseX < lastX + 2 &&
363
+ mouseX > lastX - 2 &&
364
+ mouseY < lastY + 2 &&
365
+ mouseY > lastY - 2
366
+ ) {
367
+ svgCanvas.textActions.toSelectMode(true)
368
+ }
369
+ },
370
+ /**
371
+ * @function
372
+ * @param {Integer} index
373
+ * @returns {void}
374
+ */
375
+ setCursor,
376
+ /**
377
+ * @param {Float} x
378
+ * @param {Float} y
379
+ * @returns {void}
380
+ */
381
+ toEditMode (x, y) {
382
+ allowDbl = false
383
+ svgCanvas.setCurrentMode('textedit')
384
+ svgCanvas.selectorManager.requestSelector(curtext).showGrips(false)
385
+ // Make selector group accept clicks
386
+ /* const selector = */ svgCanvas.selectorManager.requestSelector(curtext) // Do we need this? Has side effect of setting lock, so keeping for now, but next line wasn't being used
387
+ // const sel = selector.selectorRect;
388
+
389
+ svgCanvas.textActions.init()
390
+
391
+ curtext.style.cursor = 'text'
392
+
393
+ // if (supportsEditableText()) {
394
+ // curtext.setAttribute('editable', 'simple');
395
+ // return;
396
+ // }
397
+
398
+ if (!arguments.length) {
399
+ setCursor()
400
+ } else {
401
+ const pt = screenToPt(x, y)
402
+ setCursorFromPoint(pt.x, pt.y)
403
+ }
404
+
405
+ setTimeout(function () {
406
+ allowDbl = true
407
+ }, 300)
408
+ },
409
+ /**
410
+ * @param {boolean|Element} selectElem
411
+ * @fires module:svgcanvas.SvgCanvas#event:selected
412
+ * @returns {void}
413
+ */
414
+ toSelectMode (selectElem) {
415
+ svgCanvas.setCurrentMode('select')
416
+ clearInterval(blinker)
417
+ blinker = null
418
+ if (selblock) { selblock.setAttribute('display', 'none') }
419
+ if (cursor) { cursor.setAttribute('visibility', 'hidden') }
420
+ curtext.style.cursor = 'move'
421
+
422
+ if (selectElem) {
423
+ svgCanvas.clearSelection()
424
+ curtext.style.cursor = 'move'
425
+
426
+ svgCanvas.call('selected', [curtext])
427
+ svgCanvas.addToSelection([curtext], true)
428
+ }
429
+ if (!curtext?.textContent.length) {
430
+ // No content, so delete
431
+ svgCanvas.deleteSelectedElements()
432
+ }
433
+
434
+ textinput.blur()
435
+
436
+ curtext = false
437
+
438
+ // if (supportsEditableText()) {
439
+ // curtext.removeAttribute('editable');
440
+ // }
441
+ },
442
+ /**
443
+ * @param {Element} elem
444
+ * @returns {void}
445
+ */
446
+ setInputElem (elem) {
447
+ textinput = elem
448
+ },
449
+ /**
450
+ * @returns {void}
451
+ */
452
+ clear () {
453
+ if (svgCanvas.getCurrentMode() === 'textedit') {
454
+ svgCanvas.textActions.toSelectMode()
455
+ }
456
+ },
457
+ /**
458
+ * @param {Element} _inputElem Not in use
459
+ * @returns {void}
460
+ */
461
+ init (_inputElem) {
462
+ if (!curtext) { return }
463
+ let i; let end
464
+ // if (supportsEditableText()) {
465
+ // curtext.select();
466
+ // return;
467
+ // }
468
+
469
+ if (!curtext.parentNode) {
470
+ // Result of the ffClone, need to get correct element
471
+ const selectedElements = svgCanvas.getSelectedElements()
472
+ curtext = selectedElements[0]
473
+ svgCanvas.selectorManager.requestSelector(curtext).showGrips(false)
474
+ }
475
+
476
+ const str = curtext.textContent
477
+ const len = str.length
478
+
479
+ const xform = curtext.getAttribute('transform')
480
+
481
+ textbb = utilsGetBBox(curtext)
482
+
483
+ matrix = xform ? getMatrix(curtext) : null
484
+
485
+ chardata = []
486
+ chardata.length = len
487
+ textinput.focus()
488
+
489
+ curtext.removeEventListener('dblclick', selectWord)
490
+ curtext.addEventListener('dblclick', selectWord)
491
+
492
+ if (!len) {
493
+ end = { x: textbb.x + (textbb.width / 2), width: 0 }
494
+ }
495
+
496
+ for (i = 0; i < len; i++) {
497
+ const start = curtext.getStartPositionOfChar(i)
498
+ end = curtext.getEndPositionOfChar(i)
499
+
500
+ if (!supportsGoodTextCharPos()) {
501
+ const zoom = svgCanvas.getZoom()
502
+ const offset = svgCanvas.contentW * zoom
503
+ start.x -= offset
504
+ end.x -= offset
505
+
506
+ start.x /= zoom
507
+ end.x /= zoom
508
+ }
509
+
510
+ // Get a "bbox" equivalent for each character. Uses the
511
+ // bbox data of the actual text for y, height purposes
512
+
513
+ // TODO: Decide if y, width and height are actually necessary
514
+ chardata[i] = {
515
+ x: start.x,
516
+ y: textbb.y, // start.y?
517
+ width: end.x - start.x,
518
+ height: textbb.height
519
+ }
520
+ }
521
+
522
+ // Add a last bbox for cursor at end of text
523
+ chardata.push({
524
+ x: end.x,
525
+ width: 0
526
+ })
527
+ setSelection(textinput.selectionStart, textinput.selectionEnd, true)
528
+ }
529
+ }
530
+ }())
package/touch.js ADDED
@@ -0,0 +1,51 @@
1
+ // http://ross.posterous.com/2008/08/19/iphone-touch-events-in-javascript/
2
+ /**
3
+ *
4
+ * @param {Event} ev
5
+ * @returns {void}
6
+ */
7
+ const touchHandler = (ev) => {
8
+ ev.preventDefault()
9
+ const { changedTouches } = ev
10
+ const first = changedTouches[0]
11
+
12
+ let type = ''
13
+ switch (ev.type) {
14
+ case 'touchstart': type = 'mousedown'; break
15
+ case 'touchmove': type = 'mousemove'; break
16
+ case 'touchend': type = 'mouseup'; break
17
+ default: return
18
+ }
19
+
20
+ const { screenX, screenY, clientX, clientY } = first
21
+ const simulatedEvent = new MouseEvent(type, {
22
+ // Event interface
23
+ bubbles: true,
24
+ cancelable: true,
25
+ // UIEvent interface
26
+ view: window,
27
+ detail: 1, // click count
28
+ // MouseEvent interface (customized)
29
+ screenX,
30
+ screenY,
31
+ clientX,
32
+ clientY,
33
+ // MouseEvent interface (defaults) - these could be removed
34
+ ctrlKey: false,
35
+ altKey: false,
36
+ shiftKey: false,
37
+ metaKey: false,
38
+ button: 0, // main button (usually left)
39
+ relatedTarget: null
40
+ })
41
+ if (changedTouches.length < 2) {
42
+ first.target.dispatchEvent(simulatedEvent)
43
+ }
44
+ }
45
+
46
+ export const init = (svgCanvas) => {
47
+ svgCanvas.svgroot.addEventListener('touchstart', touchHandler)
48
+ svgCanvas.svgroot.addEventListener('touchmove', touchHandler)
49
+ svgCanvas.svgroot.addEventListener('touchend', touchHandler)
50
+ svgCanvas.svgroot.addEventListener('touchcancel', touchHandler)
51
+ }