@svgedit/svgcanvas 7.1.6 → 7.2.0
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 +67 -0
- package/common/util.js +198 -0
- package/{blur-event.js → core/blur-event.js} +0 -0
- package/{clear.js → core/clear.js} +0 -0
- package/{coords.js → core/coords.js} +0 -0
- package/{copy-elem.js → core/copy-elem.js} +0 -0
- package/{dataStorage.js → core/dataStorage.js} +0 -0
- package/{draw.js → core/draw.js} +1 -1
- package/{elem-get-set.js → core/elem-get-set.js} +1 -1
- package/{event.js → core/event.js} +19 -5
- package/{history.js → core/history.js} +0 -0
- package/{historyrecording.js → core/historyrecording.js} +0 -0
- package/{json.js → core/json.js} +0 -0
- package/{layer.js → core/layer.js} +0 -0
- package/{math.js → core/math.js} +0 -0
- package/{namespaces.js → core/namespaces.js} +0 -0
- package/{paint.js → core/paint.js} +0 -0
- package/{paste-elem.js → core/paste-elem.js} +0 -0
- package/{path-actions.js → core/path-actions.js} +0 -0
- package/{path-method.js → core/path-method.js} +0 -0
- package/{path.js → core/path.js} +0 -0
- package/{recalculate.js → core/recalculate.js} +17 -5
- package/{sanitize.js → core/sanitize.js} +0 -0
- package/{select.js → core/select.js} +102 -85
- package/{selected-elem.js → core/selected-elem.js} +3 -3
- package/{selection.js → core/selection.js} +2 -2
- package/{svg-exec.js → core/svg-exec.js} +8 -5
- package/{svgroot.js → core/svgroot.js} +0 -0
- package/{text-actions.js → core/text-actions.js} +1 -1
- package/{touch.js → core/touch.js} +0 -0
- package/{undo.js → core/undo.js} +1 -1
- package/{units.js → core/units.js} +0 -0
- package/{utilities.js → core/utilities.js} +19 -9
- package/dist/svgcanvas.js +55562 -397
- package/dist/svgcanvas.js.map +1 -1
- package/package.json +1 -1
- package/{rollup.config.js → rollup.config.mjs} +0 -2
- package/svgcanvas.js +27 -27
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser detection.
|
|
3
|
+
* @module browser
|
|
4
|
+
* @license MIT
|
|
5
|
+
*
|
|
6
|
+
* @copyright 2010 Jeff Schiller, 2010 Alexis Deveria
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const NSSVG = 'http://www.w3.org/2000/svg'
|
|
10
|
+
|
|
11
|
+
const { userAgent } = navigator
|
|
12
|
+
|
|
13
|
+
// Note: Browser sniffing should only be used if no other detection method is possible
|
|
14
|
+
const isWebkit_ = userAgent.includes('AppleWebKit')
|
|
15
|
+
const isGecko_ = userAgent.includes('Gecko/')
|
|
16
|
+
const isChrome_ = userAgent.includes('Chrome/')
|
|
17
|
+
const isMac_ = userAgent.includes('Macintosh')
|
|
18
|
+
|
|
19
|
+
// text character positioning (for IE9 and now Chrome)
|
|
20
|
+
const supportsGoodTextCharPos_ = (function () {
|
|
21
|
+
const svgroot = document.createElementNS(NSSVG, 'svg')
|
|
22
|
+
const svgContent = document.createElementNS(NSSVG, 'svg')
|
|
23
|
+
document.documentElement.append(svgroot)
|
|
24
|
+
svgContent.setAttribute('x', 5)
|
|
25
|
+
svgroot.append(svgContent)
|
|
26
|
+
const text = document.createElementNS(NSSVG, 'text')
|
|
27
|
+
text.textContent = 'a'
|
|
28
|
+
svgContent.append(text)
|
|
29
|
+
try { // Chrome now fails here
|
|
30
|
+
const pos = text.getStartPositionOfChar(0).x
|
|
31
|
+
return (pos === 0)
|
|
32
|
+
} catch (err) {
|
|
33
|
+
return false
|
|
34
|
+
} finally {
|
|
35
|
+
svgroot.remove()
|
|
36
|
+
}
|
|
37
|
+
}())
|
|
38
|
+
|
|
39
|
+
// Public API
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @function module:browser.isWebkit
|
|
43
|
+
* @returns {boolean}
|
|
44
|
+
*/
|
|
45
|
+
export const isWebkit = () => isWebkit_
|
|
46
|
+
/**
|
|
47
|
+
* @function module:browser.isGecko
|
|
48
|
+
* @returns {boolean}
|
|
49
|
+
*/
|
|
50
|
+
export const isGecko = () => isGecko_
|
|
51
|
+
/**
|
|
52
|
+
* @function module:browser.isChrome
|
|
53
|
+
* @returns {boolean}
|
|
54
|
+
*/
|
|
55
|
+
export const isChrome = () => isChrome_
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @function module:browser.isMac
|
|
59
|
+
* @returns {boolean}
|
|
60
|
+
*/
|
|
61
|
+
export const isMac = () => isMac_
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @function module:browser.supportsGoodTextCharPos
|
|
65
|
+
* @returns {boolean}
|
|
66
|
+
*/
|
|
67
|
+
export const supportsGoodTextCharPos = () => supportsGoodTextCharPos_
|
package/common/util.js
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {any} obj
|
|
3
|
+
* @returns {any}
|
|
4
|
+
*/
|
|
5
|
+
export function findPos (obj) {
|
|
6
|
+
let curleft = 0
|
|
7
|
+
let curtop = 0
|
|
8
|
+
if (obj.offsetParent) {
|
|
9
|
+
do {
|
|
10
|
+
curleft += obj.offsetLeft
|
|
11
|
+
curtop += obj.offsetTop
|
|
12
|
+
// eslint-disable-next-line no-cond-assign
|
|
13
|
+
} while (obj = obj.offsetParent)
|
|
14
|
+
return { left: curleft, top: curtop }
|
|
15
|
+
}
|
|
16
|
+
return { left: curleft, top: curtop }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function isObject (item) {
|
|
20
|
+
return (item && typeof item === 'object' && !Array.isArray(item))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function mergeDeep (target, source) {
|
|
24
|
+
const output = Object.assign({}, target)
|
|
25
|
+
if (isObject(target) && isObject(source)) {
|
|
26
|
+
Object.keys(source).forEach((key) => {
|
|
27
|
+
if (isObject(source[key])) {
|
|
28
|
+
if (!(key in target)) { Object.assign(output, { [key]: source[key] }) } else { output[key] = mergeDeep(target[key], source[key]) }
|
|
29
|
+
} else {
|
|
30
|
+
Object.assign(output, { [key]: source[key] })
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
return output
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get the closest matching element up the DOM tree.
|
|
39
|
+
* @param {Element} elem Starting element
|
|
40
|
+
* @param {String} selector Selector to match against (class, ID, data attribute, or tag)
|
|
41
|
+
* @return {Boolean|Element} Returns null if not match found
|
|
42
|
+
*/
|
|
43
|
+
export function getClosest (elem, selector) {
|
|
44
|
+
const firstChar = selector.charAt(0)
|
|
45
|
+
const supports = 'classList' in document.documentElement
|
|
46
|
+
let attribute; let value
|
|
47
|
+
// If selector is a data attribute, split attribute from value
|
|
48
|
+
if (firstChar === '[') {
|
|
49
|
+
selector = selector.substr(1, selector.length - 2)
|
|
50
|
+
attribute = selector.split('=')
|
|
51
|
+
if (attribute.length > 1) {
|
|
52
|
+
value = true
|
|
53
|
+
attribute[1] = attribute[1].replace(/"/g, '').replace(/'/g, '')
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Get closest match
|
|
57
|
+
for (; elem && elem !== document && elem.nodeType === 1; elem = elem.parentNode) {
|
|
58
|
+
// If selector is a class
|
|
59
|
+
if (firstChar === '.') {
|
|
60
|
+
if (supports) {
|
|
61
|
+
if (elem.classList.contains(selector.substr(1))) {
|
|
62
|
+
return elem
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
if (new RegExp('(^|\\s)' + selector.substr(1) + '(\\s|$)').test(elem.className)) {
|
|
66
|
+
return elem
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// If selector is an ID
|
|
71
|
+
if (firstChar === '#') {
|
|
72
|
+
if (elem.id === selector.substr(1)) {
|
|
73
|
+
return elem
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// If selector is a data attribute
|
|
77
|
+
if (firstChar === '[') {
|
|
78
|
+
if (elem.hasAttribute(attribute[0])) {
|
|
79
|
+
if (value) {
|
|
80
|
+
if (elem.getAttribute(attribute[0]) === attribute[1]) {
|
|
81
|
+
return elem
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
return elem
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// If selector is a tag
|
|
89
|
+
if (elem.tagName.toLowerCase() === selector) {
|
|
90
|
+
return elem
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return null
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get all DOM element up the tree that contain a class, ID, or data attribute
|
|
98
|
+
* @param {Node} elem The base element
|
|
99
|
+
* @param {String} selector The class, id, data attribute, or tag to look for
|
|
100
|
+
* @return {Array} Null if no match
|
|
101
|
+
*/
|
|
102
|
+
export function getParents (elem, selector) {
|
|
103
|
+
const parents = []
|
|
104
|
+
const firstChar = selector?.charAt(0)
|
|
105
|
+
// Get matches
|
|
106
|
+
for (; elem && elem !== document; elem = elem.parentNode) {
|
|
107
|
+
if (selector) {
|
|
108
|
+
// If selector is a class
|
|
109
|
+
if (firstChar === '.') {
|
|
110
|
+
if (elem.classList.contains(selector.substr(1))) {
|
|
111
|
+
parents.push(elem)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// If selector is an ID
|
|
115
|
+
if (firstChar === '#') {
|
|
116
|
+
if (elem.id === selector.substr(1)) {
|
|
117
|
+
parents.push(elem)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// If selector is a data attribute
|
|
121
|
+
if (firstChar === '[') {
|
|
122
|
+
if (elem.hasAttribute(selector.substr(1, selector.length - 1))) {
|
|
123
|
+
parents.push(elem)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// If selector is a tag
|
|
127
|
+
if (elem.tagName.toLowerCase() === selector) {
|
|
128
|
+
parents.push(elem)
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
parents.push(elem)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Return parents if any exist
|
|
135
|
+
return parents.length ? parents : null
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function getParentsUntil (elem, parent, selector) {
|
|
139
|
+
const parents = []
|
|
140
|
+
const parentType = parent?.charAt(0)
|
|
141
|
+
const selectorType = selector?.selector.charAt(0)
|
|
142
|
+
// Get matches
|
|
143
|
+
for (; elem && elem !== document; elem = elem.parentNode) {
|
|
144
|
+
// Check if parent has been reached
|
|
145
|
+
if (parent) {
|
|
146
|
+
// If parent is a class
|
|
147
|
+
if (parentType === '.') {
|
|
148
|
+
if (elem.classList.contains(parent.substr(1))) {
|
|
149
|
+
break
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// If parent is an ID
|
|
153
|
+
if (parentType === '#') {
|
|
154
|
+
if (elem.id === parent.substr(1)) {
|
|
155
|
+
break
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// If parent is a data attribute
|
|
159
|
+
if (parentType === '[') {
|
|
160
|
+
if (elem.hasAttribute(parent.substr(1, parent.length - 1))) {
|
|
161
|
+
break
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// If parent is a tag
|
|
165
|
+
if (elem.tagName.toLowerCase() === parent) {
|
|
166
|
+
break
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (selector) {
|
|
170
|
+
// If selector is a class
|
|
171
|
+
if (selectorType === '.') {
|
|
172
|
+
if (elem.classList.contains(selector.substr(1))) {
|
|
173
|
+
parents.push(elem)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// If selector is an ID
|
|
177
|
+
if (selectorType === '#') {
|
|
178
|
+
if (elem.id === selector.substr(1)) {
|
|
179
|
+
parents.push(elem)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// If selector is a data attribute
|
|
183
|
+
if (selectorType === '[') {
|
|
184
|
+
if (elem.hasAttribute(selector.substr(1, selector.length - 1))) {
|
|
185
|
+
parents.push(elem)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// If selector is a tag
|
|
189
|
+
if (elem.tagName.toLowerCase() === selector) {
|
|
190
|
+
parents.push(elem)
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
parents.push(elem)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// Return parents if any exist
|
|
197
|
+
return parents.length ? parents : null
|
|
198
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/{draw.js → core/draw.js}
RENAMED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
import {
|
|
16
16
|
copyElem as utilCopyElem
|
|
17
17
|
} from './copy-elem.js'
|
|
18
|
-
import { getParentsUntil } from '
|
|
18
|
+
import { getParentsUntil } from '../common/util.js'
|
|
19
19
|
|
|
20
20
|
const visElems = 'a,circle,ellipse,foreignObject,g,image,line,path,polygon,polyline,rect,svg,text,tspan,use'.split(',')
|
|
21
21
|
|
|
@@ -17,13 +17,14 @@ import {
|
|
|
17
17
|
import * as draw from './draw.js'
|
|
18
18
|
import * as pathModule from './path.js'
|
|
19
19
|
import * as hstry from './history.js'
|
|
20
|
-
import { findPos } from '../../
|
|
20
|
+
import { findPos } from '../../svgcanvas/common/util.js'
|
|
21
21
|
|
|
22
22
|
const {
|
|
23
23
|
InsertElementCommand
|
|
24
24
|
} = hstry
|
|
25
25
|
|
|
26
26
|
let svgCanvas = null
|
|
27
|
+
let moveSelectionThresholdReached = false
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
30
|
* @function module:undo.init
|
|
@@ -155,7 +156,13 @@ const mouseMoveEvent = (evt) => {
|
|
|
155
156
|
dy = snapToGrid(dy)
|
|
156
157
|
}
|
|
157
158
|
|
|
158
|
-
if
|
|
159
|
+
// Enable moving selection only if mouse has been moved at least 4 px in any direction
|
|
160
|
+
// This prevents objects from being accidentally moved when (initially) selected
|
|
161
|
+
const deltaThreshold = 4
|
|
162
|
+
const deltaThresholdReached = Math.abs(dx) > deltaThreshold || Math.abs(dy) > deltaThreshold
|
|
163
|
+
moveSelectionThresholdReached = moveSelectionThresholdReached || deltaThresholdReached
|
|
164
|
+
|
|
165
|
+
if (moveSelectionThresholdReached) {
|
|
159
166
|
selectedElements.forEach((el) => {
|
|
160
167
|
if (el) {
|
|
161
168
|
updateTransformList(svgRoot, el, dx, dy)
|
|
@@ -277,7 +284,9 @@ const mouseMoveEvent = (evt) => {
|
|
|
277
284
|
}
|
|
278
285
|
|
|
279
286
|
translateOrigin.setTranslate(-(left + tx), -(top + ty))
|
|
280
|
-
|
|
287
|
+
// For images, we maintain aspect ratio by default and relax when shift pressed
|
|
288
|
+
const maintainAspectRatio = (selected.tagName !== 'image' && evt.shiftKey) || (selected.tagName === 'image' && !evt.shiftKey)
|
|
289
|
+
if (maintainAspectRatio) {
|
|
281
290
|
if (sx === 1) {
|
|
282
291
|
sx = sy
|
|
283
292
|
} else { sy = sx }
|
|
@@ -343,12 +352,16 @@ const mouseMoveEvent = (evt) => {
|
|
|
343
352
|
case 'square':
|
|
344
353
|
case 'rect':
|
|
345
354
|
case 'image': {
|
|
346
|
-
|
|
355
|
+
// For images, we maintain aspect ratio by default and relax when shift pressed
|
|
356
|
+
const maintainAspectRatio = (svgCanvas.getCurrentMode() === 'square') ||
|
|
357
|
+
(svgCanvas.getCurrentMode() === 'image' && !evt.shiftKey) ||
|
|
358
|
+
(svgCanvas.getCurrentMode() !== 'image' && evt.shiftKey)
|
|
359
|
+
|
|
347
360
|
let
|
|
348
361
|
w = Math.abs(x - svgCanvas.getStartX())
|
|
349
362
|
let h = Math.abs(y - svgCanvas.getStartY())
|
|
350
363
|
let newX; let newY
|
|
351
|
-
if (
|
|
364
|
+
if (maintainAspectRatio) {
|
|
352
365
|
w = h = Math.max(w, h)
|
|
353
366
|
newX = svgCanvas.getStartX() < x ? svgCanvas.getStartX() : svgCanvas.getStartX() - w
|
|
354
367
|
newY = svgCanvas.getStartY() < y ? svgCanvas.getStartY() : svgCanvas.getStartY() - h
|
|
@@ -557,6 +570,7 @@ const mouseOutEvent = () => {
|
|
|
557
570
|
* @returns {void}
|
|
558
571
|
*/
|
|
559
572
|
const mouseUpEvent = (evt) => {
|
|
573
|
+
moveSelectionThresholdReached = false
|
|
560
574
|
if (evt.button === 2) { return }
|
|
561
575
|
if (!svgCanvas.getStarted()) { return }
|
|
562
576
|
|
|
File without changes
|
|
File without changes
|
package/{json.js → core/json.js}
RENAMED
|
File without changes
|
|
File without changes
|
package/{math.js → core/math.js}
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/{path.js → core/path.js}
RENAMED
|
File without changes
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
} from './math.js'
|
|
16
16
|
import {
|
|
17
17
|
mergeDeep
|
|
18
|
-
} from '
|
|
18
|
+
} from '../common/util.js'
|
|
19
19
|
|
|
20
20
|
let svgCanvas
|
|
21
21
|
|
|
@@ -359,7 +359,10 @@ export const recalculateDimensions = (selected) => {
|
|
|
359
359
|
childTlist.appendItem(scale)
|
|
360
360
|
childTlist.appendItem(translateOrigin)
|
|
361
361
|
} // not rotated
|
|
362
|
-
|
|
362
|
+
const recalculatedDimensions = recalculateDimensions(child)
|
|
363
|
+
if (recalculatedDimensions) {
|
|
364
|
+
batchCmd.addSubCommand(recalculatedDimensions)
|
|
365
|
+
}
|
|
363
366
|
svgCanvas.setStartTransform(oldStartTransform)
|
|
364
367
|
} // element
|
|
365
368
|
} // for each child
|
|
@@ -420,7 +423,10 @@ export const recalculateDimensions = (selected) => {
|
|
|
420
423
|
} else {
|
|
421
424
|
childTlist.appendItem(newxlate)
|
|
422
425
|
}
|
|
423
|
-
|
|
426
|
+
const recalculatedDimensions = recalculateDimensions(child)
|
|
427
|
+
if (recalculatedDimensions) {
|
|
428
|
+
batchCmd.addSubCommand(recalculatedDimensions)
|
|
429
|
+
}
|
|
424
430
|
// If any <use> have this group as a parent and are
|
|
425
431
|
// referencing this child, then impose a reverse translate on it
|
|
426
432
|
// so that when it won't get double-translated
|
|
@@ -464,7 +470,10 @@ export const recalculateDimensions = (selected) => {
|
|
|
464
470
|
childTlist.clear()
|
|
465
471
|
childTlist.appendItem(e2m, 0)
|
|
466
472
|
|
|
467
|
-
|
|
473
|
+
const recalculatedDimensions = recalculateDimensions(child)
|
|
474
|
+
if (recalculatedDimensions) {
|
|
475
|
+
batchCmd.addSubCommand(recalculatedDimensions)
|
|
476
|
+
}
|
|
468
477
|
svgCanvas.setStartTransform(oldStartTransform)
|
|
469
478
|
|
|
470
479
|
// Convert stroke
|
|
@@ -544,7 +553,10 @@ export const recalculateDimensions = (selected) => {
|
|
|
544
553
|
childTlist.appendItem(newxlate)
|
|
545
554
|
}
|
|
546
555
|
|
|
547
|
-
|
|
556
|
+
const recalculatedDimensions = recalculateDimensions(child)
|
|
557
|
+
if (recalculatedDimensions) {
|
|
558
|
+
batchCmd.addSubCommand(recalculatedDimensions)
|
|
559
|
+
}
|
|
548
560
|
svgCanvas.setStartTransform(oldStartTransform)
|
|
549
561
|
}
|
|
550
562
|
}
|
|
File without changes
|
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
* @copyright 2010 Alexis Deveria, 2010 Jeff Schiller
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { isWebkit } from '
|
|
9
|
+
import { isWebkit } from '../common/browser.js'
|
|
10
10
|
import { getRotationAngle, getBBox, getStrokedBBox } from './utilities.js'
|
|
11
|
-
import { transformListToTransform, transformBox, transformPoint } from './math.js'
|
|
11
|
+
import { transformListToTransform, transformBox, transformPoint, matrixMultiply } from './math.js'
|
|
12
|
+
import { NS } from './namespaces'
|
|
12
13
|
|
|
13
14
|
let svgCanvas
|
|
14
15
|
let selectorManager_ // A Singleton
|
|
@@ -122,9 +123,24 @@ export class Selector {
|
|
|
122
123
|
offset += 2 / zoom
|
|
123
124
|
}
|
|
124
125
|
|
|
126
|
+
// find the transformations applied to the parent of the selected element
|
|
127
|
+
const svg = document.createElementNS(NS.SVG, 'svg')
|
|
128
|
+
let parentTransformationMatrix = svg.createSVGMatrix()
|
|
129
|
+
let currentElt = selected
|
|
130
|
+
while (currentElt.parentNode) {
|
|
131
|
+
if (currentElt.parentNode && currentElt.parentNode.tagName === 'g' && currentElt.parentNode.transform) {
|
|
132
|
+
if (currentElt.parentNode.transform.baseVal.numberOfItems) {
|
|
133
|
+
parentTransformationMatrix = matrixMultiply(transformListToTransform(selected.parentNode.transform.baseVal).matrix, parentTransformationMatrix)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
currentElt = currentElt.parentNode
|
|
137
|
+
}
|
|
138
|
+
|
|
125
139
|
// loop and transform our bounding box until we reach our first rotation
|
|
126
140
|
const tlist = selected.transform.baseVal
|
|
127
|
-
|
|
141
|
+
|
|
142
|
+
// combines the parent transformation with that of the selected element if necessary
|
|
143
|
+
const m = parentTransformationMatrix ? matrixMultiply(parentTransformationMatrix, transformListToTransform(tlist).matrix) : transformListToTransform(tlist).matrix
|
|
128
144
|
|
|
129
145
|
// This should probably be handled somewhere else, but for now
|
|
130
146
|
// it keeps the selection box correctly positioned when zoomed
|
|
@@ -145,92 +161,93 @@ export class Selector {
|
|
|
145
161
|
}
|
|
146
162
|
}
|
|
147
163
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
164
|
+
if (bbox) {
|
|
165
|
+
// apply the transforms
|
|
166
|
+
const l = bbox.x; const t = bbox.y; const w = bbox.width; const h = bbox.height
|
|
167
|
+
// bbox = {x: l, y: t, width: w, height: h}; // Not in use
|
|
168
|
+
|
|
169
|
+
// we need to handle temporary transforms too
|
|
170
|
+
// if skewed, get its transformed box, then find its axis-aligned bbox
|
|
171
|
+
|
|
172
|
+
// *
|
|
173
|
+
offset *= zoom
|
|
174
|
+
|
|
175
|
+
const nbox = transformBox(l * zoom, t * zoom, w * zoom, h * zoom, m)
|
|
176
|
+
const { aabox } = nbox
|
|
177
|
+
let nbax = aabox.x - offset
|
|
178
|
+
let nbay = aabox.y - offset
|
|
179
|
+
let nbaw = aabox.width + (offset * 2)
|
|
180
|
+
let nbah = aabox.height + (offset * 2)
|
|
181
|
+
|
|
182
|
+
// now if the shape is rotated, un-rotate it
|
|
183
|
+
const cx = nbax + nbaw / 2
|
|
184
|
+
const cy = nbay + nbah / 2
|
|
185
|
+
|
|
186
|
+
const angle = getRotationAngle(selected)
|
|
187
|
+
if (angle) {
|
|
188
|
+
const rot = svgCanvas.getSvgRoot().createSVGTransform()
|
|
189
|
+
rot.setRotate(-angle, cx, cy)
|
|
190
|
+
const rotm = rot.matrix
|
|
191
|
+
nbox.tl = transformPoint(nbox.tl.x, nbox.tl.y, rotm)
|
|
192
|
+
nbox.tr = transformPoint(nbox.tr.x, nbox.tr.y, rotm)
|
|
193
|
+
nbox.bl = transformPoint(nbox.bl.x, nbox.bl.y, rotm)
|
|
194
|
+
nbox.br = transformPoint(nbox.br.x, nbox.br.y, rotm)
|
|
195
|
+
|
|
196
|
+
// calculate the axis-aligned bbox
|
|
197
|
+
const { tl } = nbox
|
|
198
|
+
let minx = tl.x
|
|
199
|
+
let miny = tl.y
|
|
200
|
+
let maxx = tl.x
|
|
201
|
+
let maxy = tl.y
|
|
202
|
+
|
|
203
|
+
const { min, max } = Math
|
|
204
|
+
|
|
205
|
+
minx = min(minx, min(nbox.tr.x, min(nbox.bl.x, nbox.br.x))) - offset
|
|
206
|
+
miny = min(miny, min(nbox.tr.y, min(nbox.bl.y, nbox.br.y))) - offset
|
|
207
|
+
maxx = max(maxx, max(nbox.tr.x, max(nbox.bl.x, nbox.br.x))) + offset
|
|
208
|
+
maxy = max(maxy, max(nbox.tr.y, max(nbox.bl.y, nbox.br.y))) + offset
|
|
209
|
+
|
|
210
|
+
nbax = minx
|
|
211
|
+
nbay = miny
|
|
212
|
+
nbaw = (maxx - minx)
|
|
213
|
+
nbah = (maxy - miny)
|
|
214
|
+
}
|
|
198
215
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
216
|
+
const dstr = 'M' + nbax + ',' + nbay +
|
|
217
|
+
' L' + (nbax + nbaw) + ',' + nbay +
|
|
218
|
+
' ' + (nbax + nbaw) + ',' + (nbay + nbah) +
|
|
219
|
+
' ' + nbax + ',' + (nbay + nbah) + 'z'
|
|
220
|
+
|
|
221
|
+
const xform = angle ? 'rotate(' + [angle, cx, cy].join(',') + ')' : ''
|
|
222
|
+
|
|
223
|
+
// TODO(codedread): Is this needed?
|
|
224
|
+
// if (selected === selectedElements[0]) {
|
|
225
|
+
this.gripCoords = {
|
|
226
|
+
nw: [nbax, nbay],
|
|
227
|
+
ne: [nbax + nbaw, nbay],
|
|
228
|
+
sw: [nbax, nbay + nbah],
|
|
229
|
+
se: [nbax + nbaw, nbay + nbah],
|
|
230
|
+
n: [nbax + (nbaw) / 2, nbay],
|
|
231
|
+
w: [nbax, nbay + (nbah) / 2],
|
|
232
|
+
e: [nbax + nbaw, nbay + (nbah) / 2],
|
|
233
|
+
s: [nbax + (nbaw) / 2, nbay + nbah]
|
|
234
|
+
}
|
|
235
|
+
selectedBox.setAttribute('d', dstr)
|
|
236
|
+
this.selectorGroup.setAttribute('transform', xform)
|
|
237
|
+
Object.entries(this.gripCoords).forEach(([dir, coords]) => {
|
|
238
|
+
selectedGrips[dir].setAttribute('cx', coords[0])
|
|
239
|
+
selectedGrips[dir].setAttribute('cy', coords[1])
|
|
240
|
+
})
|
|
203
241
|
|
|
204
|
-
|
|
242
|
+
// we want to go 20 pixels in the negative transformed y direction, ignoring scale
|
|
243
|
+
mgr.rotateGripConnector.setAttribute('x1', nbax + (nbaw) / 2)
|
|
244
|
+
mgr.rotateGripConnector.setAttribute('y1', nbay)
|
|
245
|
+
mgr.rotateGripConnector.setAttribute('x2', nbax + (nbaw) / 2)
|
|
246
|
+
mgr.rotateGripConnector.setAttribute('y2', nbay - (gripRadius * 5))
|
|
205
247
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
this.gripCoords = {
|
|
209
|
-
nw: [nbax, nbay],
|
|
210
|
-
ne: [nbax + nbaw, nbay],
|
|
211
|
-
sw: [nbax, nbay + nbah],
|
|
212
|
-
se: [nbax + nbaw, nbay + nbah],
|
|
213
|
-
n: [nbax + (nbaw) / 2, nbay],
|
|
214
|
-
w: [nbax, nbay + (nbah) / 2],
|
|
215
|
-
e: [nbax + nbaw, nbay + (nbah) / 2],
|
|
216
|
-
s: [nbax + (nbaw) / 2, nbay + nbah]
|
|
248
|
+
mgr.rotateGrip.setAttribute('cx', nbax + (nbaw) / 2)
|
|
249
|
+
mgr.rotateGrip.setAttribute('cy', nbay - (gripRadius * 5))
|
|
217
250
|
}
|
|
218
|
-
selectedBox.setAttribute('d', dstr)
|
|
219
|
-
this.selectorGroup.setAttribute('transform', xform)
|
|
220
|
-
Object.entries(this.gripCoords).forEach(([dir, coords]) => {
|
|
221
|
-
selectedGrips[dir].setAttribute('cx', coords[0])
|
|
222
|
-
selectedGrips[dir].setAttribute('cy', coords[1])
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
// we want to go 20 pixels in the negative transformed y direction, ignoring scale
|
|
226
|
-
mgr.rotateGripConnector.setAttribute('x1', nbax + (nbaw) / 2)
|
|
227
|
-
mgr.rotateGripConnector.setAttribute('y1', nbay)
|
|
228
|
-
mgr.rotateGripConnector.setAttribute('x2', nbax + (nbaw) / 2)
|
|
229
|
-
mgr.rotateGripConnector.setAttribute('y2', nbay - (gripRadius * 5))
|
|
230
|
-
|
|
231
|
-
mgr.rotateGrip.setAttribute('cx', nbax + (nbaw) / 2)
|
|
232
|
-
mgr.rotateGrip.setAttribute('cy', nbay - (gripRadius * 5))
|
|
233
|
-
// }
|
|
234
251
|
}
|
|
235
252
|
|
|
236
253
|
// STATIC methods
|