@svgedit/svgcanvas 7.2.7 → 7.4.2
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/common/browser.js +104 -37
- package/common/logger.js +151 -0
- package/common/util.js +96 -155
- package/core/blur-event.js +106 -42
- package/core/clear.js +13 -3
- package/core/coords.js +214 -90
- package/core/copy-elem.js +27 -13
- package/core/dataStorage.js +84 -21
- package/core/draw.js +80 -40
- package/core/elem-get-set.js +247 -115
- package/core/event.js +171 -29
- package/core/history.js +105 -50
- package/core/historyrecording.js +4 -2
- package/core/json.js +54 -12
- package/core/layer.js +11 -17
- package/core/math.js +102 -23
- package/core/namespaces.js +5 -5
- package/core/paint.js +95 -24
- package/core/paste-elem.js +58 -19
- package/core/path-actions.js +812 -791
- package/core/path-method.js +236 -37
- package/core/path.js +45 -10
- package/core/recalculate.js +410 -15
- package/core/sanitize.js +51 -18
- package/core/select.js +44 -20
- package/core/selected-elem.js +159 -38
- package/core/selection.js +16 -6
- package/core/svg-exec.js +99 -27
- package/core/svgroot.js +1 -1
- package/core/text-actions.js +363 -307
- package/core/undo.js +47 -15
- package/core/units.js +8 -6
- package/core/utilities.js +282 -170
- package/dist/svgcanvas.js +40021 -55172
- package/dist/svgcanvas.js.map +1 -1
- package/package.json +55 -54
- package/publish.md +1 -6
- package/svgcanvas.d.ts +225 -0
- package/svgcanvas.js +11 -9
- package/vite.config.mjs +20 -0
- package/rollup.config.mjs +0 -38
package/core/text-actions.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { NS } from './namespaces.js'
|
|
9
|
-
import { transformPoint,
|
|
9
|
+
import { transformPoint, matrixMultiply, getTransformList, transformListToTransform } from './math.js'
|
|
10
10
|
import {
|
|
11
11
|
assignAttributes,
|
|
12
12
|
getElement,
|
|
@@ -28,68 +28,103 @@ export const init = canvas => {
|
|
|
28
28
|
/**
|
|
29
29
|
* Group: Text edit functions
|
|
30
30
|
* Functions relating to editing text elements.
|
|
31
|
-
* @
|
|
31
|
+
* @class TextActions
|
|
32
32
|
* @memberof module:svgcanvas.SvgCanvas#
|
|
33
33
|
*/
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
34
|
+
class TextActions {
|
|
35
|
+
#curtext = null
|
|
36
|
+
#textinput = null
|
|
37
|
+
#cursor = null
|
|
38
|
+
#selblock = null
|
|
39
|
+
#blinker = null
|
|
40
|
+
#chardata = []
|
|
41
|
+
#textbb = null // , transbb;
|
|
42
|
+
#matrix = null
|
|
43
|
+
#lastX = null
|
|
44
|
+
#lastY = null
|
|
45
|
+
#allowDbl = false
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get the accumulated transformation matrix from the element up to the SVG content element.
|
|
49
|
+
* This includes transforms from all parent groups, fixing the issue where text cursor
|
|
50
|
+
* appears in the wrong position when editing text inside a transformed group.
|
|
51
|
+
* @param {Element} elem - The element to get the accumulated matrix for
|
|
52
|
+
* @returns {SVGMatrix|null} The accumulated transformation matrix, or null if none
|
|
53
|
+
* @private
|
|
54
|
+
*/
|
|
55
|
+
#getAccumulatedMatrix = (elem) => {
|
|
56
|
+
const svgContent = svgCanvas.getSvgContent()
|
|
57
|
+
const matrices = []
|
|
58
|
+
|
|
59
|
+
let current = elem
|
|
60
|
+
while (current && current !== svgContent && current.nodeType === 1) {
|
|
61
|
+
const tlist = getTransformList(current)
|
|
62
|
+
if (tlist && tlist.numberOfItems > 0) {
|
|
63
|
+
const matrix = transformListToTransform(tlist).matrix
|
|
64
|
+
matrices.unshift(matrix) // Add to beginning to maintain correct order
|
|
65
|
+
}
|
|
66
|
+
current = current.parentNode
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (matrices.length === 0) {
|
|
70
|
+
return null
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (matrices.length === 1) {
|
|
74
|
+
return matrices[0]
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Multiply all matrices together
|
|
78
|
+
return matrixMultiply(...matrices)
|
|
79
|
+
}
|
|
46
80
|
|
|
47
81
|
/**
|
|
48
82
|
*
|
|
49
83
|
* @param {Integer} index
|
|
50
84
|
* @returns {void}
|
|
85
|
+
* @private
|
|
51
86
|
*/
|
|
52
|
-
|
|
53
|
-
const empty = textinput.value === ''
|
|
54
|
-
textinput.focus()
|
|
87
|
+
#setCursor = (index = undefined) => {
|
|
88
|
+
const empty = this.#textinput.value === ''
|
|
89
|
+
this.#textinput.focus()
|
|
55
90
|
|
|
56
|
-
if (
|
|
91
|
+
if (index === undefined) {
|
|
57
92
|
if (empty) {
|
|
58
93
|
index = 0
|
|
59
94
|
} else {
|
|
60
|
-
if (textinput.selectionEnd !== textinput.selectionStart) {
|
|
95
|
+
if (this.#textinput.selectionEnd !== this.#textinput.selectionStart) {
|
|
61
96
|
return
|
|
62
97
|
}
|
|
63
|
-
index = textinput.selectionEnd
|
|
98
|
+
index = this.#textinput.selectionEnd
|
|
64
99
|
}
|
|
65
100
|
}
|
|
66
101
|
|
|
67
|
-
const charbb = chardata[index]
|
|
102
|
+
const charbb = this.#chardata[index]
|
|
68
103
|
if (!empty) {
|
|
69
|
-
textinput.setSelectionRange(index, index)
|
|
104
|
+
this.#textinput.setSelectionRange(index, index)
|
|
70
105
|
}
|
|
71
|
-
cursor = getElement('text_cursor')
|
|
72
|
-
if (!cursor) {
|
|
73
|
-
cursor = document.createElementNS(NS.SVG, 'line')
|
|
74
|
-
assignAttributes(cursor, {
|
|
106
|
+
this.#cursor = getElement('text_cursor')
|
|
107
|
+
if (!this.#cursor) {
|
|
108
|
+
this.#cursor = document.createElementNS(NS.SVG, 'line')
|
|
109
|
+
assignAttributes(this.#cursor, {
|
|
75
110
|
id: 'text_cursor',
|
|
76
111
|
stroke: '#333',
|
|
77
112
|
'stroke-width': 1
|
|
78
113
|
})
|
|
79
|
-
getElement('selectorParentGroup').append(cursor)
|
|
114
|
+
getElement('selectorParentGroup').append(this.#cursor)
|
|
80
115
|
}
|
|
81
116
|
|
|
82
|
-
if (!blinker) {
|
|
83
|
-
blinker = setInterval(
|
|
84
|
-
const show = cursor.getAttribute('display') === 'none'
|
|
85
|
-
cursor.setAttribute('display', show ? 'inline' : 'none')
|
|
117
|
+
if (!this.#blinker) {
|
|
118
|
+
this.#blinker = setInterval(() => {
|
|
119
|
+
const show = this.#cursor.getAttribute('display') === 'none'
|
|
120
|
+
this.#cursor.setAttribute('display', show ? 'inline' : 'none')
|
|
86
121
|
}, 600)
|
|
87
122
|
}
|
|
88
123
|
|
|
89
|
-
const startPt = ptToScreen(charbb.x, textbb.y)
|
|
90
|
-
const endPt = ptToScreen(charbb.x, textbb.y + textbb.height)
|
|
124
|
+
const startPt = this.#ptToScreen(charbb.x, this.#textbb.y)
|
|
125
|
+
const endPt = this.#ptToScreen(charbb.x, this.#textbb.y + this.#textbb.height)
|
|
91
126
|
|
|
92
|
-
assignAttributes(cursor, {
|
|
127
|
+
assignAttributes(this.#cursor, {
|
|
93
128
|
x1: startPt.x,
|
|
94
129
|
y1: startPt.y,
|
|
95
130
|
x2: endPt.x,
|
|
@@ -98,8 +133,8 @@ export const textActionsMethod = (function () {
|
|
|
98
133
|
display: 'inline'
|
|
99
134
|
})
|
|
100
135
|
|
|
101
|
-
if (selblock) {
|
|
102
|
-
selblock.setAttribute('d', '')
|
|
136
|
+
if (this.#selblock) {
|
|
137
|
+
this.#selblock.setAttribute('d', '')
|
|
103
138
|
}
|
|
104
139
|
}
|
|
105
140
|
|
|
@@ -109,40 +144,41 @@ export const textActionsMethod = (function () {
|
|
|
109
144
|
* @param {Integer} end
|
|
110
145
|
* @param {boolean} skipInput
|
|
111
146
|
* @returns {void}
|
|
147
|
+
* @private
|
|
112
148
|
*/
|
|
113
|
-
|
|
149
|
+
#setSelection = (start, end, skipInput) => {
|
|
114
150
|
if (start === end) {
|
|
115
|
-
setCursor(end)
|
|
151
|
+
this.#setCursor(end)
|
|
116
152
|
return
|
|
117
153
|
}
|
|
118
154
|
|
|
119
155
|
if (!skipInput) {
|
|
120
|
-
textinput.setSelectionRange(start, end)
|
|
156
|
+
this.#textinput.setSelectionRange(start, end)
|
|
121
157
|
}
|
|
122
158
|
|
|
123
|
-
selblock = getElement('text_selectblock')
|
|
124
|
-
if (!selblock) {
|
|
125
|
-
selblock = document.createElementNS(NS.SVG, 'path')
|
|
126
|
-
assignAttributes(selblock, {
|
|
159
|
+
this.#selblock = getElement('text_selectblock')
|
|
160
|
+
if (!this.#selblock) {
|
|
161
|
+
this.#selblock = document.createElementNS(NS.SVG, 'path')
|
|
162
|
+
assignAttributes(this.#selblock, {
|
|
127
163
|
id: 'text_selectblock',
|
|
128
164
|
fill: 'green',
|
|
129
165
|
opacity: 0.5,
|
|
130
166
|
style: 'pointer-events:none'
|
|
131
167
|
})
|
|
132
|
-
getElement('selectorParentGroup').append(selblock)
|
|
168
|
+
getElement('selectorParentGroup').append(this.#selblock)
|
|
133
169
|
}
|
|
134
170
|
|
|
135
|
-
const startbb = chardata[start]
|
|
136
|
-
const endbb = chardata[end]
|
|
171
|
+
const startbb = this.#chardata[start]
|
|
172
|
+
const endbb = this.#chardata[end]
|
|
137
173
|
|
|
138
|
-
cursor.setAttribute('visibility', 'hidden')
|
|
174
|
+
this.#cursor.setAttribute('visibility', 'hidden')
|
|
139
175
|
|
|
140
|
-
const tl = ptToScreen(startbb.x, textbb.y)
|
|
141
|
-
const tr = ptToScreen(startbb.x + (endbb.x - startbb.x), textbb.y)
|
|
142
|
-
const bl = ptToScreen(startbb.x, textbb.y + textbb.height)
|
|
143
|
-
const br = ptToScreen(
|
|
176
|
+
const tl = this.#ptToScreen(startbb.x, this.#textbb.y)
|
|
177
|
+
const tr = this.#ptToScreen(startbb.x + (endbb.x - startbb.x), this.#textbb.y)
|
|
178
|
+
const bl = this.#ptToScreen(startbb.x, this.#textbb.y + this.#textbb.height)
|
|
179
|
+
const br = this.#ptToScreen(
|
|
144
180
|
startbb.x + (endbb.x - startbb.x),
|
|
145
|
-
textbb.y + textbb.height
|
|
181
|
+
this.#textbb.y + this.#textbb.height
|
|
146
182
|
)
|
|
147
183
|
|
|
148
184
|
const dstr =
|
|
@@ -164,7 +200,7 @@ export const textActionsMethod = (function () {
|
|
|
164
200
|
bl.y +
|
|
165
201
|
'z'
|
|
166
202
|
|
|
167
|
-
assignAttributes(selblock, {
|
|
203
|
+
assignAttributes(this.#selblock, {
|
|
168
204
|
d: dstr,
|
|
169
205
|
display: 'inline'
|
|
170
206
|
})
|
|
@@ -175,29 +211,30 @@ export const textActionsMethod = (function () {
|
|
|
175
211
|
* @param {Float} mouseX
|
|
176
212
|
* @param {Float} mouseY
|
|
177
213
|
* @returns {Integer}
|
|
214
|
+
* @private
|
|
178
215
|
*/
|
|
179
|
-
|
|
216
|
+
#getIndexFromPoint = (mouseX, mouseY) => {
|
|
180
217
|
// Position cursor here
|
|
181
218
|
const pt = svgCanvas.getSvgRoot().createSVGPoint()
|
|
182
219
|
pt.x = mouseX
|
|
183
220
|
pt.y = mouseY
|
|
184
221
|
|
|
185
222
|
// No content, so return 0
|
|
186
|
-
if (chardata.length === 1) {
|
|
223
|
+
if (this.#chardata.length === 1) {
|
|
187
224
|
return 0
|
|
188
225
|
}
|
|
189
226
|
// Determine if cursor should be on left or right of character
|
|
190
|
-
let charpos = curtext.getCharNumAtPosition(pt)
|
|
227
|
+
let charpos = this.#curtext.getCharNumAtPosition(pt)
|
|
191
228
|
if (charpos < 0) {
|
|
192
229
|
// Out of text range, look at mouse coords
|
|
193
|
-
charpos = chardata.length - 2
|
|
194
|
-
if (mouseX <= chardata[0].x) {
|
|
230
|
+
charpos = this.#chardata.length - 2
|
|
231
|
+
if (mouseX <= this.#chardata[0].x) {
|
|
195
232
|
charpos = 0
|
|
196
233
|
}
|
|
197
|
-
} else if (charpos >= chardata.length - 2) {
|
|
198
|
-
charpos = chardata.length - 2
|
|
234
|
+
} else if (charpos >= this.#chardata.length - 2) {
|
|
235
|
+
charpos = this.#chardata.length - 2
|
|
199
236
|
}
|
|
200
|
-
const charbb = chardata[charpos]
|
|
237
|
+
const charbb = this.#chardata[charpos]
|
|
201
238
|
const mid = charbb.x + charbb.width / 2
|
|
202
239
|
if (mouseX > mid) {
|
|
203
240
|
charpos++
|
|
@@ -210,9 +247,10 @@ export const textActionsMethod = (function () {
|
|
|
210
247
|
* @param {Float} mouseX
|
|
211
248
|
* @param {Float} mouseY
|
|
212
249
|
* @returns {void}
|
|
250
|
+
* @private
|
|
213
251
|
*/
|
|
214
|
-
|
|
215
|
-
setCursor(getIndexFromPoint(mouseX, mouseY))
|
|
252
|
+
#setCursorFromPoint = (mouseX, mouseY) => {
|
|
253
|
+
this.#setCursor(this.#getIndexFromPoint(mouseX, mouseY))
|
|
216
254
|
}
|
|
217
255
|
|
|
218
256
|
/**
|
|
@@ -221,14 +259,15 @@ export const textActionsMethod = (function () {
|
|
|
221
259
|
* @param {Float} y
|
|
222
260
|
* @param {boolean} apply
|
|
223
261
|
* @returns {void}
|
|
262
|
+
* @private
|
|
224
263
|
*/
|
|
225
|
-
|
|
226
|
-
const i1 = textinput.selectionStart
|
|
227
|
-
const i2 = getIndexFromPoint(x, y)
|
|
264
|
+
#setEndSelectionFromPoint = (x, y, apply) => {
|
|
265
|
+
const i1 = this.#textinput.selectionStart
|
|
266
|
+
const i2 = this.#getIndexFromPoint(x, y)
|
|
228
267
|
|
|
229
268
|
const start = Math.min(i1, i2)
|
|
230
269
|
const end = Math.max(i1, i2)
|
|
231
|
-
setSelection(start, end, !apply)
|
|
270
|
+
this.#setSelection(start, end, !apply)
|
|
232
271
|
}
|
|
233
272
|
|
|
234
273
|
/**
|
|
@@ -236,8 +275,9 @@ export const textActionsMethod = (function () {
|
|
|
236
275
|
* @param {Float} xIn
|
|
237
276
|
* @param {Float} yIn
|
|
238
277
|
* @returns {module:math.XYObject}
|
|
278
|
+
* @private
|
|
239
279
|
*/
|
|
240
|
-
|
|
280
|
+
#screenToPt = (xIn, yIn) => {
|
|
241
281
|
const out = {
|
|
242
282
|
x: xIn,
|
|
243
283
|
y: yIn
|
|
@@ -246,8 +286,8 @@ export const textActionsMethod = (function () {
|
|
|
246
286
|
out.x /= zoom
|
|
247
287
|
out.y /= zoom
|
|
248
288
|
|
|
249
|
-
if (matrix) {
|
|
250
|
-
const pt = transformPoint(out.x, out.y, matrix.inverse())
|
|
289
|
+
if (this.#matrix) {
|
|
290
|
+
const pt = transformPoint(out.x, out.y, this.#matrix.inverse())
|
|
251
291
|
out.x = pt.x
|
|
252
292
|
out.y = pt.y
|
|
253
293
|
}
|
|
@@ -260,15 +300,16 @@ export const textActionsMethod = (function () {
|
|
|
260
300
|
* @param {Float} xIn
|
|
261
301
|
* @param {Float} yIn
|
|
262
302
|
* @returns {module:math.XYObject}
|
|
303
|
+
* @private
|
|
263
304
|
*/
|
|
264
|
-
|
|
305
|
+
#ptToScreen = (xIn, yIn) => {
|
|
265
306
|
const out = {
|
|
266
307
|
x: xIn,
|
|
267
308
|
y: yIn
|
|
268
309
|
}
|
|
269
310
|
|
|
270
|
-
if (matrix) {
|
|
271
|
-
const pt = transformPoint(out.x, out.y, matrix)
|
|
311
|
+
if (this.#matrix) {
|
|
312
|
+
const pt = transformPoint(out.x, out.y, this.#matrix)
|
|
272
313
|
out.x = pt.x
|
|
273
314
|
out.y = pt.y
|
|
274
315
|
}
|
|
@@ -283,279 +324,294 @@ export const textActionsMethod = (function () {
|
|
|
283
324
|
*
|
|
284
325
|
* @param {Event} evt
|
|
285
326
|
* @returns {void}
|
|
327
|
+
* @private
|
|
286
328
|
*/
|
|
287
|
-
|
|
288
|
-
setSelection(0, curtext.textContent.length)
|
|
289
|
-
evt.target.removeEventListener('click', selectAll)
|
|
329
|
+
#selectAll = (evt) => {
|
|
330
|
+
this.#setSelection(0, this.#curtext.textContent.length)
|
|
331
|
+
evt.target.removeEventListener('click', this.#selectAll)
|
|
290
332
|
}
|
|
291
333
|
|
|
292
334
|
/**
|
|
293
335
|
*
|
|
294
336
|
* @param {Event} evt
|
|
295
337
|
* @returns {void}
|
|
338
|
+
* @private
|
|
296
339
|
*/
|
|
297
|
-
|
|
298
|
-
if (!allowDbl || !curtext) {
|
|
340
|
+
#selectWord = (evt) => {
|
|
341
|
+
if (!this.#allowDbl || !this.#curtext) {
|
|
299
342
|
return
|
|
300
343
|
}
|
|
301
344
|
const zoom = svgCanvas.getZoom()
|
|
302
345
|
const ept = transformPoint(evt.pageX, evt.pageY, svgCanvas.getrootSctm())
|
|
303
346
|
const mouseX = ept.x * zoom
|
|
304
347
|
const mouseY = ept.y * zoom
|
|
305
|
-
const pt = screenToPt(mouseX, mouseY)
|
|
348
|
+
const pt = this.#screenToPt(mouseX, mouseY)
|
|
306
349
|
|
|
307
|
-
const index = getIndexFromPoint(pt.x, pt.y)
|
|
308
|
-
const str = curtext.textContent
|
|
309
|
-
const first = str.
|
|
310
|
-
const m = str.
|
|
350
|
+
const index = this.#getIndexFromPoint(pt.x, pt.y)
|
|
351
|
+
const str = this.#curtext.textContent
|
|
352
|
+
const first = str.slice(0, index).replace(/[a-z\d]+$/i, '').length
|
|
353
|
+
const m = str.slice(index).match(/^[a-z\d]+/i)
|
|
311
354
|
const last = (m ? m[0].length : 0) + index
|
|
312
|
-
setSelection(first, last)
|
|
355
|
+
this.#setSelection(first, last)
|
|
313
356
|
|
|
314
357
|
// Set tripleclick
|
|
315
|
-
svgCanvas.$click(evt.target, selectAll)
|
|
358
|
+
svgCanvas.$click(evt.target, this.#selectAll)
|
|
316
359
|
|
|
317
|
-
setTimeout(
|
|
318
|
-
evt.target.removeEventListener('click', selectAll)
|
|
360
|
+
setTimeout(() => {
|
|
361
|
+
evt.target.removeEventListener('click', this.#selectAll)
|
|
319
362
|
}, 300)
|
|
320
363
|
}
|
|
321
364
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
},
|
|
333
|
-
/**
|
|
334
|
-
* @param {Element} elem
|
|
335
|
-
* @returns {void}
|
|
336
|
-
*/
|
|
337
|
-
start (elem) {
|
|
338
|
-
curtext = elem
|
|
339
|
-
svgCanvas.textActions.toEditMode()
|
|
340
|
-
},
|
|
341
|
-
/**
|
|
342
|
-
* @param {external:MouseEvent} evt
|
|
343
|
-
* @param {Element} mouseTarget
|
|
344
|
-
* @param {Float} startX
|
|
345
|
-
* @param {Float} startY
|
|
346
|
-
* @returns {void}
|
|
347
|
-
*/
|
|
348
|
-
mouseDown (evt, mouseTarget, startX, startY) {
|
|
349
|
-
const pt = screenToPt(startX, startY)
|
|
350
|
-
|
|
351
|
-
textinput.focus()
|
|
352
|
-
setCursorFromPoint(pt.x, pt.y)
|
|
353
|
-
lastX = startX
|
|
354
|
-
lastY = startY
|
|
355
|
-
|
|
356
|
-
// TODO: Find way to block native selection
|
|
357
|
-
},
|
|
358
|
-
/**
|
|
359
|
-
* @param {Float} mouseX
|
|
360
|
-
* @param {Float} mouseY
|
|
361
|
-
* @returns {void}
|
|
362
|
-
*/
|
|
363
|
-
mouseMove (mouseX, mouseY) {
|
|
364
|
-
const pt = screenToPt(mouseX, mouseY)
|
|
365
|
-
setEndSelectionFromPoint(pt.x, pt.y)
|
|
366
|
-
},
|
|
367
|
-
/**
|
|
368
|
-
* @param {external:MouseEvent} evt
|
|
369
|
-
* @param {Float} mouseX
|
|
370
|
-
* @param {Float} mouseY
|
|
371
|
-
* @returns {void}
|
|
372
|
-
*/
|
|
373
|
-
mouseUp (evt, mouseX, mouseY) {
|
|
374
|
-
const pt = screenToPt(mouseX, mouseY)
|
|
375
|
-
|
|
376
|
-
setEndSelectionFromPoint(pt.x, pt.y, true)
|
|
377
|
-
|
|
378
|
-
// TODO: Find a way to make this work: Use transformed BBox instead of evt.target
|
|
379
|
-
// if (lastX === mouseX && lastY === mouseY
|
|
380
|
-
// && !rectsIntersect(transbb, {x: pt.x, y: pt.y, width: 0, height: 0})) {
|
|
381
|
-
// svgCanvas.textActions.toSelectMode(true);
|
|
382
|
-
// }
|
|
383
|
-
|
|
384
|
-
if (
|
|
385
|
-
evt.target !== curtext &&
|
|
386
|
-
mouseX < lastX + 2 &&
|
|
387
|
-
mouseX > lastX - 2 &&
|
|
388
|
-
mouseY < lastY + 2 &&
|
|
389
|
-
mouseY > lastY - 2
|
|
390
|
-
) {
|
|
391
|
-
svgCanvas.textActions.toSelectMode(true)
|
|
392
|
-
}
|
|
393
|
-
},
|
|
394
|
-
/**
|
|
395
|
-
* @function
|
|
396
|
-
* @param {Integer} index
|
|
397
|
-
* @returns {void}
|
|
398
|
-
*/
|
|
399
|
-
setCursor,
|
|
400
|
-
/**
|
|
401
|
-
* @param {Float} x
|
|
402
|
-
* @param {Float} y
|
|
403
|
-
* @returns {void}
|
|
404
|
-
*/
|
|
405
|
-
toEditMode (x, y) {
|
|
406
|
-
allowDbl = false
|
|
407
|
-
svgCanvas.setCurrentMode('textedit')
|
|
408
|
-
svgCanvas.selectorManager.requestSelector(curtext).showGrips(false)
|
|
409
|
-
// Make selector group accept clicks
|
|
410
|
-
/* 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
|
|
411
|
-
// const sel = selector.selectorRect;
|
|
412
|
-
|
|
413
|
-
svgCanvas.textActions.init()
|
|
414
|
-
|
|
415
|
-
curtext.style.cursor = 'text'
|
|
416
|
-
|
|
417
|
-
// if (supportsEditableText()) {
|
|
418
|
-
// curtext.setAttribute('editable', 'simple');
|
|
419
|
-
// return;
|
|
420
|
-
// }
|
|
421
|
-
|
|
422
|
-
if (!arguments.length) {
|
|
423
|
-
setCursor()
|
|
424
|
-
} else {
|
|
425
|
-
const pt = screenToPt(x, y)
|
|
426
|
-
setCursorFromPoint(pt.x, pt.y)
|
|
427
|
-
}
|
|
365
|
+
/**
|
|
366
|
+
* @param {Element} target
|
|
367
|
+
* @param {Float} x
|
|
368
|
+
* @param {Float} y
|
|
369
|
+
* @returns {void}
|
|
370
|
+
*/
|
|
371
|
+
select (target, x, y) {
|
|
372
|
+
this.#curtext = target
|
|
373
|
+
svgCanvas.textActions.toEditMode(x, y)
|
|
374
|
+
}
|
|
428
375
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
*/
|
|
438
|
-
toSelectMode (selectElem) {
|
|
439
|
-
svgCanvas.setCurrentMode('select')
|
|
440
|
-
clearInterval(blinker)
|
|
441
|
-
blinker = null
|
|
442
|
-
if (selblock) {
|
|
443
|
-
selblock.setAttribute('display', 'none')
|
|
444
|
-
}
|
|
445
|
-
if (cursor) {
|
|
446
|
-
cursor.setAttribute('visibility', 'hidden')
|
|
447
|
-
}
|
|
448
|
-
curtext.style.cursor = 'move'
|
|
376
|
+
/**
|
|
377
|
+
* @param {Element} elem
|
|
378
|
+
* @returns {void}
|
|
379
|
+
*/
|
|
380
|
+
start (elem) {
|
|
381
|
+
this.#curtext = elem
|
|
382
|
+
svgCanvas.textActions.toEditMode()
|
|
383
|
+
}
|
|
449
384
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
385
|
+
/**
|
|
386
|
+
* @param {external:MouseEvent} evt
|
|
387
|
+
* @param {Element} mouseTarget
|
|
388
|
+
* @param {Float} startX
|
|
389
|
+
* @param {Float} startY
|
|
390
|
+
* @returns {void}
|
|
391
|
+
*/
|
|
392
|
+
mouseDown (evt, mouseTarget, startX, startY) {
|
|
393
|
+
const pt = this.#screenToPt(startX, startY)
|
|
453
394
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
// No content, so delete
|
|
459
|
-
svgCanvas.deleteSelectedElements()
|
|
460
|
-
}
|
|
395
|
+
this.#textinput.focus()
|
|
396
|
+
this.#setCursorFromPoint(pt.x, pt.y)
|
|
397
|
+
this.#lastX = startX
|
|
398
|
+
this.#lastY = startY
|
|
461
399
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
400
|
+
// TODO: Find way to block native selection
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* @param {Float} mouseX
|
|
405
|
+
* @param {Float} mouseY
|
|
406
|
+
* @returns {void}
|
|
407
|
+
*/
|
|
408
|
+
mouseMove (mouseX, mouseY) {
|
|
409
|
+
const pt = this.#screenToPt(mouseX, mouseY)
|
|
410
|
+
this.#setEndSelectionFromPoint(pt.x, pt.y)
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* @param {external:MouseEvent} evt
|
|
415
|
+
* @param {Float} mouseX
|
|
416
|
+
* @param {Float} mouseY
|
|
417
|
+
* @returns {void}
|
|
418
|
+
*/
|
|
419
|
+
mouseUp (evt, mouseX, mouseY) {
|
|
420
|
+
const pt = this.#screenToPt(mouseX, mouseY)
|
|
421
|
+
|
|
422
|
+
this.#setEndSelectionFromPoint(pt.x, pt.y, true)
|
|
423
|
+
|
|
424
|
+
// TODO: Find a way to make this work: Use transformed BBox instead of evt.target
|
|
425
|
+
// if (lastX === mouseX && lastY === mouseY
|
|
426
|
+
// && !rectsIntersect(transbb, {x: pt.x, y: pt.y, width: 0, height: 0})) {
|
|
427
|
+
// svgCanvas.textActions.toSelectMode(true);
|
|
428
|
+
// }
|
|
429
|
+
|
|
430
|
+
if (
|
|
431
|
+
evt.target !== this.#curtext &&
|
|
432
|
+
mouseX < this.#lastX + 2 &&
|
|
433
|
+
mouseX > this.#lastX - 2 &&
|
|
434
|
+
mouseY < this.#lastY + 2 &&
|
|
435
|
+
mouseY > this.#lastY - 2
|
|
436
|
+
) {
|
|
437
|
+
svgCanvas.textActions.toSelectMode(true)
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* @param {Integer} index
|
|
443
|
+
* @returns {void}
|
|
444
|
+
*/
|
|
445
|
+
setCursor (index) {
|
|
446
|
+
this.#setCursor(index)
|
|
447
|
+
}
|
|
506
448
|
|
|
507
|
-
|
|
508
|
-
|
|
449
|
+
/**
|
|
450
|
+
* @param {Float} x
|
|
451
|
+
* @param {Float} y
|
|
452
|
+
* @returns {void}
|
|
453
|
+
*/
|
|
454
|
+
toEditMode (x, y) {
|
|
455
|
+
this.#allowDbl = false
|
|
456
|
+
svgCanvas.setCurrentMode('textedit')
|
|
457
|
+
svgCanvas.selectorManager.requestSelector(this.#curtext).showGrips(false)
|
|
458
|
+
// Make selector group accept clicks
|
|
459
|
+
/* const selector = */ svgCanvas.selectorManager.requestSelector(this.#curtext) // Do we need this? Has side effect of setting lock, so keeping for now, but next line wasn't being used
|
|
460
|
+
// const sel = selector.selectorRect;
|
|
461
|
+
|
|
462
|
+
svgCanvas.textActions.init()
|
|
463
|
+
|
|
464
|
+
this.#curtext.style.cursor = 'text'
|
|
465
|
+
|
|
466
|
+
// if (supportsEditableText()) {
|
|
467
|
+
// curtext.setAttribute('editable', 'simple');
|
|
468
|
+
// return;
|
|
469
|
+
// }
|
|
470
|
+
|
|
471
|
+
if (arguments.length === 0) {
|
|
472
|
+
this.#setCursor()
|
|
473
|
+
} else {
|
|
474
|
+
const pt = this.#screenToPt(x, y)
|
|
475
|
+
this.#setCursorFromPoint(pt.x, pt.y)
|
|
476
|
+
}
|
|
509
477
|
|
|
510
|
-
|
|
478
|
+
setTimeout(() => {
|
|
479
|
+
this.#allowDbl = true
|
|
480
|
+
}, 300)
|
|
481
|
+
}
|
|
511
482
|
|
|
512
|
-
|
|
483
|
+
/**
|
|
484
|
+
* @param {boolean|Element} selectElem
|
|
485
|
+
* @fires module:svgcanvas.SvgCanvas#event:selected
|
|
486
|
+
* @returns {void}
|
|
487
|
+
*/
|
|
488
|
+
toSelectMode (selectElem) {
|
|
489
|
+
svgCanvas.setCurrentMode('select')
|
|
490
|
+
clearInterval(this.#blinker)
|
|
491
|
+
this.#blinker = null
|
|
492
|
+
if (this.#selblock) {
|
|
493
|
+
this.#selblock.setAttribute('display', 'none')
|
|
494
|
+
}
|
|
495
|
+
if (this.#cursor) {
|
|
496
|
+
this.#cursor.setAttribute('visibility', 'hidden')
|
|
497
|
+
}
|
|
498
|
+
this.#curtext.style.cursor = 'move'
|
|
513
499
|
|
|
514
|
-
|
|
500
|
+
if (selectElem) {
|
|
501
|
+
svgCanvas.clearSelection()
|
|
502
|
+
this.#curtext.style.cursor = 'move'
|
|
515
503
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
504
|
+
svgCanvas.call('selected', [this.#curtext])
|
|
505
|
+
svgCanvas.addToSelection([this.#curtext], true)
|
|
506
|
+
}
|
|
507
|
+
if (!this.#curtext?.textContent.length) {
|
|
508
|
+
// No content, so delete
|
|
509
|
+
svgCanvas.deleteSelectedElements()
|
|
510
|
+
}
|
|
519
511
|
|
|
520
|
-
|
|
521
|
-
curtext.addEventListener('dblclick', selectWord)
|
|
512
|
+
this.#textinput.blur()
|
|
522
513
|
|
|
523
|
-
|
|
524
|
-
end = { x: textbb.x + textbb.width / 2, width: 0 }
|
|
525
|
-
}
|
|
514
|
+
this.#curtext = false
|
|
526
515
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
516
|
+
// if (supportsEditableText()) {
|
|
517
|
+
// curtext.removeAttribute('editable');
|
|
518
|
+
// }
|
|
519
|
+
}
|
|
530
520
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
521
|
+
/**
|
|
522
|
+
* @param {Element} elem
|
|
523
|
+
* @returns {void}
|
|
524
|
+
*/
|
|
525
|
+
setInputElem (elem) {
|
|
526
|
+
this.#textinput = elem
|
|
527
|
+
}
|
|
536
528
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
529
|
+
/**
|
|
530
|
+
* @returns {void}
|
|
531
|
+
*/
|
|
532
|
+
clear () {
|
|
533
|
+
if (svgCanvas.getCurrentMode() === 'textedit') {
|
|
534
|
+
svgCanvas.textActions.toSelectMode()
|
|
535
|
+
}
|
|
536
|
+
}
|
|
540
537
|
|
|
541
|
-
|
|
542
|
-
|
|
538
|
+
/**
|
|
539
|
+
* @param {Element} _inputElem Not in use
|
|
540
|
+
* @returns {void}
|
|
541
|
+
*/
|
|
542
|
+
init (_inputElem) {
|
|
543
|
+
if (!this.#curtext) {
|
|
544
|
+
return
|
|
545
|
+
}
|
|
546
|
+
let i
|
|
547
|
+
let end
|
|
548
|
+
// if (supportsEditableText()) {
|
|
549
|
+
// curtext.select();
|
|
550
|
+
// return;
|
|
551
|
+
// }
|
|
552
|
+
|
|
553
|
+
if (!this.#curtext.parentNode) {
|
|
554
|
+
// Result of the ffClone, need to get correct element
|
|
555
|
+
const selectedElements = svgCanvas.getSelectedElements()
|
|
556
|
+
this.#curtext = selectedElements[0]
|
|
557
|
+
svgCanvas.selectorManager.requestSelector(this.#curtext).showGrips(false)
|
|
558
|
+
}
|
|
543
559
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
560
|
+
const str = this.#curtext.textContent
|
|
561
|
+
const len = str.length
|
|
562
|
+
|
|
563
|
+
this.#textbb = utilsGetBBox(this.#curtext)
|
|
564
|
+
|
|
565
|
+
// Calculate accumulated transform matrix including all parent groups
|
|
566
|
+
// This fixes the issue where text cursor appears in wrong position
|
|
567
|
+
// when editing text inside a group with transforms
|
|
568
|
+
this.#matrix = this.#getAccumulatedMatrix(this.#curtext)
|
|
569
|
+
|
|
570
|
+
this.#chardata = []
|
|
571
|
+
this.#chardata.length = len
|
|
572
|
+
this.#textinput.focus()
|
|
573
|
+
|
|
574
|
+
this.#curtext.removeEventListener('dblclick', this.#selectWord)
|
|
575
|
+
this.#curtext.addEventListener('dblclick', this.#selectWord)
|
|
576
|
+
|
|
577
|
+
if (!len) {
|
|
578
|
+
end = { x: this.#textbb.x + this.#textbb.width / 2, width: 0 }
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
for (i = 0; i < len; i++) {
|
|
582
|
+
const start = this.#curtext.getStartPositionOfChar(i)
|
|
583
|
+
end = this.#curtext.getEndPositionOfChar(i)
|
|
584
|
+
|
|
585
|
+
if (!supportsGoodTextCharPos()) {
|
|
586
|
+
const zoom = svgCanvas.getZoom()
|
|
587
|
+
const offset = svgCanvas.contentW * zoom
|
|
588
|
+
start.x -= offset
|
|
589
|
+
end.x -= offset
|
|
590
|
+
|
|
591
|
+
start.x /= zoom
|
|
592
|
+
end.x /= zoom
|
|
551
593
|
}
|
|
552
594
|
|
|
553
|
-
//
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
595
|
+
// Get a "bbox" equivalent for each character. Uses the
|
|
596
|
+
// bbox data of the actual text for y, height purposes
|
|
597
|
+
|
|
598
|
+
// TODO: Decide if y, width and height are actually necessary
|
|
599
|
+
this.#chardata[i] = {
|
|
600
|
+
x: start.x,
|
|
601
|
+
y: this.#textbb.y, // start.y?
|
|
602
|
+
width: end.x - start.x,
|
|
603
|
+
height: this.#textbb.height
|
|
604
|
+
}
|
|
559
605
|
}
|
|
606
|
+
|
|
607
|
+
// Add a last bbox for cursor at end of text
|
|
608
|
+
this.#chardata.push({
|
|
609
|
+
x: end.x,
|
|
610
|
+
width: 0
|
|
611
|
+
})
|
|
612
|
+
this.#setSelection(this.#textinput.selectionStart, this.#textinput.selectionEnd, true)
|
|
560
613
|
}
|
|
561
|
-
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// Export singleton instance for backward compatibility
|
|
617
|
+
export const textActionsMethod = new TextActions()
|