@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/blur-event.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tools for blur event.
|
|
3
|
+
* @module blur
|
|
4
|
+
* @license MIT
|
|
5
|
+
* @copyright 2011 Jeff Schiller
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
let svgCanvas = null
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @function module:blur.init
|
|
12
|
+
* @param {module:blur.blurContext} blurContext
|
|
13
|
+
* @returns {void}
|
|
14
|
+
*/
|
|
15
|
+
export const init = (canvas) => {
|
|
16
|
+
svgCanvas = canvas
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Sets the `stdDeviation` blur value on the selected element without being undoable.
|
|
21
|
+
* @function module:svgcanvas.SvgCanvas#setBlurNoUndo
|
|
22
|
+
* @param {Float} val - The new `stdDeviation` value
|
|
23
|
+
* @returns {void}
|
|
24
|
+
*/
|
|
25
|
+
export const setBlurNoUndo = function (val) {
|
|
26
|
+
const selectedElements = svgCanvas.getSelectedElements()
|
|
27
|
+
if (!svgCanvas.getFilter()) {
|
|
28
|
+
svgCanvas.setBlur(val)
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
if (val === 0) {
|
|
32
|
+
// Don't change the StdDev, as that will hide the element.
|
|
33
|
+
// Instead, just remove the value for "filter"
|
|
34
|
+
svgCanvas.changeSelectedAttributeNoUndo('filter', '')
|
|
35
|
+
svgCanvas.setFilterHidden(true)
|
|
36
|
+
} else {
|
|
37
|
+
const elem = selectedElements[0]
|
|
38
|
+
if (svgCanvas.getFilterHidden()) {
|
|
39
|
+
svgCanvas.changeSelectedAttributeNoUndo('filter', 'url(#' + elem.id + '_blur)')
|
|
40
|
+
}
|
|
41
|
+
const filter = svgCanvas.getFilter()
|
|
42
|
+
svgCanvas.changeSelectedAttributeNoUndo('stdDeviation', val, [filter.firstChild])
|
|
43
|
+
svgCanvas.setBlurOffsets(filter, val)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
*
|
|
49
|
+
* @returns {void}
|
|
50
|
+
*/
|
|
51
|
+
function finishChange () {
|
|
52
|
+
const bCmd = svgCanvas.undoMgr.finishUndoableChange()
|
|
53
|
+
svgCanvas.getCurCommand().addSubCommand(bCmd)
|
|
54
|
+
svgCanvas.addCommandToHistory(svgCanvas.getCurCommand())
|
|
55
|
+
svgCanvas.setCurCommand(null)
|
|
56
|
+
svgCanvas.setFilter(null)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Sets the `x`, `y`, `width`, `height` values of the filter element in order to
|
|
61
|
+
* make the blur not be clipped. Removes them if not neeeded.
|
|
62
|
+
* @function module:svgcanvas.SvgCanvas#setBlurOffsets
|
|
63
|
+
* @param {Element} filterElem - The filter DOM element to update
|
|
64
|
+
* @param {Float} stdDev - The standard deviation value on which to base the offset size
|
|
65
|
+
* @returns {void}
|
|
66
|
+
*/
|
|
67
|
+
export const setBlurOffsets = function (filterElem, stdDev) {
|
|
68
|
+
if (stdDev > 3) {
|
|
69
|
+
// TODO: Create algorithm here where size is based on expected blur
|
|
70
|
+
svgCanvas.assignAttributes(filterElem, {
|
|
71
|
+
x: '-50%',
|
|
72
|
+
y: '-50%',
|
|
73
|
+
width: '200%',
|
|
74
|
+
height: '200%'
|
|
75
|
+
}, 100)
|
|
76
|
+
} else {
|
|
77
|
+
filterElem.removeAttribute('x')
|
|
78
|
+
filterElem.removeAttribute('y')
|
|
79
|
+
filterElem.removeAttribute('width')
|
|
80
|
+
filterElem.removeAttribute('height')
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Adds/updates the blur filter to the selected element.
|
|
86
|
+
* @function module:svgcanvas.SvgCanvas#setBlur
|
|
87
|
+
* @param {Float} val - Float with the new `stdDeviation` blur value
|
|
88
|
+
* @param {boolean} complete - Whether or not the action should be completed (to add to the undo manager)
|
|
89
|
+
* @returns {void}
|
|
90
|
+
*/
|
|
91
|
+
export const setBlur = function (val, complete) {
|
|
92
|
+
const {
|
|
93
|
+
InsertElementCommand, ChangeElementCommand, BatchCommand
|
|
94
|
+
} = svgCanvas.history
|
|
95
|
+
|
|
96
|
+
const selectedElements = svgCanvas.getSelectedElements()
|
|
97
|
+
if (svgCanvas.getCurCommand()) {
|
|
98
|
+
finishChange()
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Looks for associated blur, creates one if not found
|
|
103
|
+
const elem = selectedElements[0]
|
|
104
|
+
const elemId = elem.id
|
|
105
|
+
svgCanvas.setFilter(svgCanvas.getElement(elemId + '_blur'))
|
|
106
|
+
|
|
107
|
+
val -= 0
|
|
108
|
+
|
|
109
|
+
const batchCmd = new BatchCommand()
|
|
110
|
+
|
|
111
|
+
// Blur found!
|
|
112
|
+
if (svgCanvas.getFilter()) {
|
|
113
|
+
if (val === 0) {
|
|
114
|
+
svgCanvas.setFilter(null)
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
// Not found, so create
|
|
118
|
+
const newblur = svgCanvas.addSVGElementsFromJson({
|
|
119
|
+
element: 'feGaussianBlur',
|
|
120
|
+
attr: {
|
|
121
|
+
in: 'SourceGraphic',
|
|
122
|
+
stdDeviation: val
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
svgCanvas.setFilter(svgCanvas.addSVGElementsFromJson({
|
|
127
|
+
element: 'filter',
|
|
128
|
+
attr: {
|
|
129
|
+
id: elemId + '_blur'
|
|
130
|
+
}
|
|
131
|
+
}))
|
|
132
|
+
svgCanvas.getFilter().append(newblur)
|
|
133
|
+
svgCanvas.findDefs().append(svgCanvas.getFilter())
|
|
134
|
+
|
|
135
|
+
batchCmd.addSubCommand(new InsertElementCommand(svgCanvas.getFilter()))
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const changes = { filter: elem.getAttribute('filter') }
|
|
139
|
+
|
|
140
|
+
if (val === 0) {
|
|
141
|
+
elem.removeAttribute('filter')
|
|
142
|
+
batchCmd.addSubCommand(new ChangeElementCommand(elem, changes))
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
svgCanvas.changeSelectedAttribute('filter', 'url(#' + elemId + '_blur)')
|
|
147
|
+
batchCmd.addSubCommand(new ChangeElementCommand(elem, changes))
|
|
148
|
+
svgCanvas.setBlurOffsets(svgCanvas.getFilter(), val)
|
|
149
|
+
const filter = svgCanvas.getFilter()
|
|
150
|
+
svgCanvas.setCurCommand(batchCmd)
|
|
151
|
+
svgCanvas.undoMgr.beginUndoableChange('stdDeviation', [filter ? filter.firstChild : null])
|
|
152
|
+
if (complete) {
|
|
153
|
+
svgCanvas.setBlurNoUndo(val)
|
|
154
|
+
finishChange()
|
|
155
|
+
}
|
|
156
|
+
}
|
package/clear.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tools for clear.
|
|
3
|
+
* @module clear
|
|
4
|
+
* @license MIT
|
|
5
|
+
* @copyright 2011 Jeff Schiller
|
|
6
|
+
*/
|
|
7
|
+
import { NS } from './namespaces.js'
|
|
8
|
+
|
|
9
|
+
let svgCanvas = null
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @function module:clear.init
|
|
13
|
+
* @param {module:clear.SvgCanvas#init} clearContext
|
|
14
|
+
* @returns {void}
|
|
15
|
+
*/
|
|
16
|
+
export const init = (canvas) => {
|
|
17
|
+
svgCanvas = canvas
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const clearSvgContentElementInit = () => {
|
|
21
|
+
const curConfig = svgCanvas.getCurConfig()
|
|
22
|
+
const { dimensions } = curConfig
|
|
23
|
+
const el = svgCanvas.getSvgContent()
|
|
24
|
+
// empty
|
|
25
|
+
while (el.firstChild) { el.removeChild(el.firstChild) }
|
|
26
|
+
|
|
27
|
+
// TODO: Clear out all other attributes first?
|
|
28
|
+
const pel = svgCanvas.getSvgRoot()
|
|
29
|
+
el.setAttribute('id', 'svgcontent')
|
|
30
|
+
el.setAttribute('width', dimensions[0])
|
|
31
|
+
el.setAttribute('height', dimensions[1])
|
|
32
|
+
el.setAttribute('x', dimensions[0])
|
|
33
|
+
el.setAttribute('y', dimensions[1])
|
|
34
|
+
el.setAttribute('overflow', curConfig.show_outside_canvas ? 'visible' : 'hidden')
|
|
35
|
+
el.setAttribute('xmlns', NS.SVG)
|
|
36
|
+
el.setAttribute('xmlns:se', NS.SE)
|
|
37
|
+
el.setAttribute('xmlns:xlink', NS.XLINK)
|
|
38
|
+
pel.appendChild(el)
|
|
39
|
+
|
|
40
|
+
// TODO: make this string optional and set by the client
|
|
41
|
+
const comment = svgCanvas.getDOMDocument().createComment(' Created with SVG-edit - https://github.com/SVG-Edit/svgedit')
|
|
42
|
+
svgCanvas.getSvgContent().append(comment)
|
|
43
|
+
}
|
package/coords.js
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manipulating coordinates.
|
|
3
|
+
* @module coords
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
snapToGrid, assignAttributes, getBBox, getRefElem, findDefs
|
|
9
|
+
} from './utilities.js'
|
|
10
|
+
import {
|
|
11
|
+
transformPoint, transformListToTransform, matrixMultiply, transformBox
|
|
12
|
+
} from './math.js'
|
|
13
|
+
|
|
14
|
+
// this is how we map paths to our preferred relative segment types
|
|
15
|
+
const pathMap = [
|
|
16
|
+
0, 'z', 'M', 'm', 'L', 'l', 'C', 'c', 'Q', 'q', 'A', 'a',
|
|
17
|
+
'H', 'h', 'V', 'v', 'S', 's', 'T', 't'
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @interface module:coords.EditorContext
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* @function module:coords.EditorContext#getGridSnapping
|
|
25
|
+
* @returns {boolean}
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* @function module:coords.EditorContext#getSvgRoot
|
|
29
|
+
* @returns {SVGSVGElement}
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
let svgCanvas = null
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @function module:coords.init
|
|
36
|
+
* @param {module:svgcanvas.SvgCanvas#event:pointsAdded} editorContext
|
|
37
|
+
* @returns {void}
|
|
38
|
+
*/
|
|
39
|
+
export const init = (canvas) => {
|
|
40
|
+
svgCanvas = canvas
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Applies coordinate changes to an element based on the given matrix.
|
|
45
|
+
* @name module:coords.remapElement
|
|
46
|
+
* @type {module:path.EditorContext#remapElement}
|
|
47
|
+
*/
|
|
48
|
+
export const remapElement = (selected, changes, m) => {
|
|
49
|
+
const remap = (x, y) => transformPoint(x, y, m)
|
|
50
|
+
const scalew = (w) => m.a * w
|
|
51
|
+
const scaleh = (h) => m.d * h
|
|
52
|
+
const doSnapping = svgCanvas.getGridSnapping() && selected.parentNode.parentNode.localName === 'svg'
|
|
53
|
+
const finishUp = () => {
|
|
54
|
+
if (doSnapping) {
|
|
55
|
+
Object.entries(changes).forEach(([o, value]) => {
|
|
56
|
+
changes[o] = snapToGrid(value)
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
assignAttributes(selected, changes, 1000, true)
|
|
60
|
+
}
|
|
61
|
+
const box = getBBox(selected);
|
|
62
|
+
|
|
63
|
+
['fill', 'stroke'].forEach((type) => {
|
|
64
|
+
const attrVal = selected.getAttribute(type)
|
|
65
|
+
if (attrVal?.startsWith('url(') && (m.a < 0 || m.d < 0)) {
|
|
66
|
+
const grad = getRefElem(attrVal)
|
|
67
|
+
const newgrad = grad.cloneNode(true)
|
|
68
|
+
if (m.a < 0) {
|
|
69
|
+
// flip x
|
|
70
|
+
const x1 = newgrad.getAttribute('x1')
|
|
71
|
+
const x2 = newgrad.getAttribute('x2')
|
|
72
|
+
newgrad.setAttribute('x1', -(x1 - 1))
|
|
73
|
+
newgrad.setAttribute('x2', -(x2 - 1))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (m.d < 0) {
|
|
77
|
+
// flip y
|
|
78
|
+
const y1 = newgrad.getAttribute('y1')
|
|
79
|
+
const y2 = newgrad.getAttribute('y2')
|
|
80
|
+
newgrad.setAttribute('y1', -(y1 - 1))
|
|
81
|
+
newgrad.setAttribute('y2', -(y2 - 1))
|
|
82
|
+
}
|
|
83
|
+
newgrad.id = svgCanvas.getCurrentDrawing().getNextId()
|
|
84
|
+
findDefs().append(newgrad)
|
|
85
|
+
selected.setAttribute(type, 'url(#' + newgrad.id + ')')
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
const elName = selected.tagName
|
|
90
|
+
if (elName === 'g' || elName === 'text' || elName === 'tspan' || elName === 'use') {
|
|
91
|
+
// if it was a translate, then just update x,y
|
|
92
|
+
if (m.a === 1 && m.b === 0 && m.c === 0 && m.d === 1 && (m.e !== 0 || m.f !== 0)) {
|
|
93
|
+
// [T][M] = [M][T']
|
|
94
|
+
// therefore [T'] = [M_inv][T][M]
|
|
95
|
+
const existing = transformListToTransform(selected).matrix
|
|
96
|
+
const tNew = matrixMultiply(existing.inverse(), m, existing)
|
|
97
|
+
changes.x = Number.parseFloat(changes.x) + tNew.e
|
|
98
|
+
changes.y = Number.parseFloat(changes.y) + tNew.f
|
|
99
|
+
} else {
|
|
100
|
+
// we just absorb all matrices into the element and don't do any remapping
|
|
101
|
+
const chlist = selected.transform.baseVal
|
|
102
|
+
const mt = svgCanvas.getSvgRoot().createSVGTransform()
|
|
103
|
+
mt.setMatrix(matrixMultiply(transformListToTransform(chlist).matrix, m))
|
|
104
|
+
chlist.clear()
|
|
105
|
+
chlist.appendItem(mt)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// now we have a set of changes and an applied reduced transform list
|
|
110
|
+
// we apply the changes directly to the DOM
|
|
111
|
+
switch (elName) {
|
|
112
|
+
case 'foreignObject':
|
|
113
|
+
case 'rect':
|
|
114
|
+
case 'image': {
|
|
115
|
+
// Allow images to be inverted (give them matrix when flipped)
|
|
116
|
+
if (elName === 'image' && (m.a < 0 || m.d < 0)) {
|
|
117
|
+
// Convert to matrix
|
|
118
|
+
const chlist = selected.transform.baseVal
|
|
119
|
+
const mt = svgCanvas.getSvgRoot().createSVGTransform()
|
|
120
|
+
mt.setMatrix(matrixMultiply(transformListToTransform(chlist).matrix, m))
|
|
121
|
+
chlist.clear()
|
|
122
|
+
chlist.appendItem(mt)
|
|
123
|
+
} else {
|
|
124
|
+
const pt1 = remap(changes.x, changes.y)
|
|
125
|
+
changes.width = scalew(changes.width)
|
|
126
|
+
changes.height = scaleh(changes.height)
|
|
127
|
+
changes.x = pt1.x + Math.min(0, changes.width)
|
|
128
|
+
changes.y = pt1.y + Math.min(0, changes.height)
|
|
129
|
+
changes.width = Math.abs(changes.width)
|
|
130
|
+
changes.height = Math.abs(changes.height)
|
|
131
|
+
}
|
|
132
|
+
finishUp()
|
|
133
|
+
break
|
|
134
|
+
} case 'ellipse': {
|
|
135
|
+
const c = remap(changes.cx, changes.cy)
|
|
136
|
+
changes.cx = c.x
|
|
137
|
+
changes.cy = c.y
|
|
138
|
+
changes.rx = scalew(changes.rx)
|
|
139
|
+
changes.ry = scaleh(changes.ry)
|
|
140
|
+
changes.rx = Math.abs(changes.rx)
|
|
141
|
+
changes.ry = Math.abs(changes.ry)
|
|
142
|
+
finishUp()
|
|
143
|
+
break
|
|
144
|
+
} case 'circle': {
|
|
145
|
+
const c = remap(changes.cx, changes.cy)
|
|
146
|
+
changes.cx = c.x
|
|
147
|
+
changes.cy = c.y
|
|
148
|
+
// take the minimum of the new selected box's dimensions for the new circle radius
|
|
149
|
+
const tbox = transformBox(box.x, box.y, box.width, box.height, m)
|
|
150
|
+
const w = tbox.tr.x - tbox.tl.x; const h = tbox.bl.y - tbox.tl.y
|
|
151
|
+
changes.r = Math.min(w / 2, h / 2)
|
|
152
|
+
|
|
153
|
+
if (changes.r) { changes.r = Math.abs(changes.r) }
|
|
154
|
+
finishUp()
|
|
155
|
+
break
|
|
156
|
+
} case 'line': {
|
|
157
|
+
const pt1 = remap(changes.x1, changes.y1)
|
|
158
|
+
const pt2 = remap(changes.x2, changes.y2)
|
|
159
|
+
changes.x1 = pt1.x
|
|
160
|
+
changes.y1 = pt1.y
|
|
161
|
+
changes.x2 = pt2.x
|
|
162
|
+
changes.y2 = pt2.y
|
|
163
|
+
} // Fallthrough
|
|
164
|
+
case 'text':
|
|
165
|
+
case 'tspan':
|
|
166
|
+
case 'use': {
|
|
167
|
+
finishUp()
|
|
168
|
+
break
|
|
169
|
+
} case 'g': {
|
|
170
|
+
const dataStorage = svgCanvas.getDataStorage()
|
|
171
|
+
const gsvg = dataStorage.get(selected, 'gsvg')
|
|
172
|
+
if (gsvg) {
|
|
173
|
+
assignAttributes(gsvg, changes, 1000, true)
|
|
174
|
+
}
|
|
175
|
+
break
|
|
176
|
+
} case 'polyline':
|
|
177
|
+
case 'polygon': {
|
|
178
|
+
changes.points.forEach((pt) => {
|
|
179
|
+
const { x, y } = remap(pt.x, pt.y)
|
|
180
|
+
pt.x = x
|
|
181
|
+
pt.y = y
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
// const len = changes.points.length;
|
|
185
|
+
let pstr = ''
|
|
186
|
+
changes.points.forEach((pt) => {
|
|
187
|
+
pstr += pt.x + ',' + pt.y + ' '
|
|
188
|
+
})
|
|
189
|
+
selected.setAttribute('points', pstr)
|
|
190
|
+
break
|
|
191
|
+
} case 'path': {
|
|
192
|
+
const segList = selected.pathSegList
|
|
193
|
+
let len = segList.numberOfItems
|
|
194
|
+
changes.d = []
|
|
195
|
+
for (let i = 0; i < len; ++i) {
|
|
196
|
+
const seg = segList.getItem(i)
|
|
197
|
+
changes.d[i] = {
|
|
198
|
+
type: seg.pathSegType,
|
|
199
|
+
x: seg.x,
|
|
200
|
+
y: seg.y,
|
|
201
|
+
x1: seg.x1,
|
|
202
|
+
y1: seg.y1,
|
|
203
|
+
x2: seg.x2,
|
|
204
|
+
y2: seg.y2,
|
|
205
|
+
r1: seg.r1,
|
|
206
|
+
r2: seg.r2,
|
|
207
|
+
angle: seg.angle,
|
|
208
|
+
largeArcFlag: seg.largeArcFlag,
|
|
209
|
+
sweepFlag: seg.sweepFlag
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
len = changes.d.length
|
|
214
|
+
const firstseg = changes.d[0]
|
|
215
|
+
let currentpt
|
|
216
|
+
if (len > 0) {
|
|
217
|
+
currentpt = remap(firstseg.x, firstseg.y)
|
|
218
|
+
changes.d[0].x = currentpt.x
|
|
219
|
+
changes.d[0].y = currentpt.y
|
|
220
|
+
}
|
|
221
|
+
for (let i = 1; i < len; ++i) {
|
|
222
|
+
const seg = changes.d[i]
|
|
223
|
+
const { type } = seg
|
|
224
|
+
// if absolute or first segment, we want to remap x, y, x1, y1, x2, y2
|
|
225
|
+
// if relative, we want to scalew, scaleh
|
|
226
|
+
if (type % 2 === 0) { // absolute
|
|
227
|
+
const thisx = (seg.x !== undefined) ? seg.x : currentpt.x // for V commands
|
|
228
|
+
const thisy = (seg.y !== undefined) ? seg.y : currentpt.y // for H commands
|
|
229
|
+
const pt = remap(thisx, thisy)
|
|
230
|
+
const pt1 = remap(seg.x1, seg.y1)
|
|
231
|
+
const pt2 = remap(seg.x2, seg.y2)
|
|
232
|
+
seg.x = pt.x
|
|
233
|
+
seg.y = pt.y
|
|
234
|
+
seg.x1 = pt1.x
|
|
235
|
+
seg.y1 = pt1.y
|
|
236
|
+
seg.x2 = pt2.x
|
|
237
|
+
seg.y2 = pt2.y
|
|
238
|
+
seg.r1 = scalew(seg.r1)
|
|
239
|
+
seg.r2 = scaleh(seg.r2)
|
|
240
|
+
} else { // relative
|
|
241
|
+
seg.x = scalew(seg.x)
|
|
242
|
+
seg.y = scaleh(seg.y)
|
|
243
|
+
seg.x1 = scalew(seg.x1)
|
|
244
|
+
seg.y1 = scaleh(seg.y1)
|
|
245
|
+
seg.x2 = scalew(seg.x2)
|
|
246
|
+
seg.y2 = scaleh(seg.y2)
|
|
247
|
+
seg.r1 = scalew(seg.r1)
|
|
248
|
+
seg.r2 = scaleh(seg.r2)
|
|
249
|
+
}
|
|
250
|
+
} // for each segment
|
|
251
|
+
|
|
252
|
+
let dstr = ''
|
|
253
|
+
changes.d.forEach((seg) => {
|
|
254
|
+
const { type } = seg
|
|
255
|
+
dstr += pathMap[type]
|
|
256
|
+
switch (type) {
|
|
257
|
+
case 13: // relative horizontal line (h)
|
|
258
|
+
case 12: // absolute horizontal line (H)
|
|
259
|
+
dstr += seg.x + ' '
|
|
260
|
+
break
|
|
261
|
+
case 15: // relative vertical line (v)
|
|
262
|
+
case 14: // absolute vertical line (V)
|
|
263
|
+
dstr += seg.y + ' '
|
|
264
|
+
break
|
|
265
|
+
case 3: // relative move (m)
|
|
266
|
+
case 5: // relative line (l)
|
|
267
|
+
case 19: // relative smooth quad (t)
|
|
268
|
+
case 2: // absolute move (M)
|
|
269
|
+
case 4: // absolute line (L)
|
|
270
|
+
case 18: // absolute smooth quad (T)
|
|
271
|
+
dstr += seg.x + ',' + seg.y + ' '
|
|
272
|
+
break
|
|
273
|
+
case 7: // relative cubic (c)
|
|
274
|
+
case 6: // absolute cubic (C)
|
|
275
|
+
dstr += seg.x1 + ',' + seg.y1 + ' ' + seg.x2 + ',' + seg.y2 + ' ' +
|
|
276
|
+
seg.x + ',' + seg.y + ' '
|
|
277
|
+
break
|
|
278
|
+
case 9: // relative quad (q)
|
|
279
|
+
case 8: // absolute quad (Q)
|
|
280
|
+
dstr += seg.x1 + ',' + seg.y1 + ' ' + seg.x + ',' + seg.y + ' '
|
|
281
|
+
break
|
|
282
|
+
case 11: // relative elliptical arc (a)
|
|
283
|
+
case 10: // absolute elliptical arc (A)
|
|
284
|
+
dstr += seg.r1 + ',' + seg.r2 + ' ' + seg.angle + ' ' + Number(seg.largeArcFlag) +
|
|
285
|
+
' ' + Number(seg.sweepFlag) + ' ' + seg.x + ',' + seg.y + ' '
|
|
286
|
+
break
|
|
287
|
+
case 17: // relative smooth cubic (s)
|
|
288
|
+
case 16: // absolute smooth cubic (S)
|
|
289
|
+
dstr += seg.x2 + ',' + seg.y2 + ' ' + seg.x + ',' + seg.y + ' '
|
|
290
|
+
break
|
|
291
|
+
}
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
selected.setAttribute('d', dstr)
|
|
295
|
+
break
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
package/copy-elem.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { preventClickDefault } from './utilities.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Create a clone of an element, updating its ID and its children's IDs when needed.
|
|
5
|
+
* @function module:utilities.copyElem
|
|
6
|
+
* @param {Element} el - DOM element to clone
|
|
7
|
+
* @param {module:utilities.GetNextID} getNextId - The getter of the next unique ID.
|
|
8
|
+
* @returns {Element} The cloned element
|
|
9
|
+
*/
|
|
10
|
+
export const copyElem = function (el, getNextId) {
|
|
11
|
+
// manually create a copy of the element
|
|
12
|
+
const newEl = document.createElementNS(el.namespaceURI, el.nodeName)
|
|
13
|
+
Object.values(el.attributes).forEach((attr) => {
|
|
14
|
+
newEl.setAttributeNS(attr.namespaceURI, attr.nodeName, attr.value)
|
|
15
|
+
})
|
|
16
|
+
// set the copied element's new id
|
|
17
|
+
newEl.removeAttribute('id')
|
|
18
|
+
newEl.id = getNextId()
|
|
19
|
+
|
|
20
|
+
// now create copies of all children
|
|
21
|
+
el.childNodes.forEach(function (child) {
|
|
22
|
+
switch (child.nodeType) {
|
|
23
|
+
case 1: // element node
|
|
24
|
+
newEl.append(copyElem(child, getNextId))
|
|
25
|
+
break
|
|
26
|
+
case 3: // text node
|
|
27
|
+
newEl.textContent = child.nodeValue
|
|
28
|
+
break
|
|
29
|
+
default:
|
|
30
|
+
break
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
if (el.dataset.gsvg) {
|
|
35
|
+
newEl.dataset.gsvg = newEl.firstChild
|
|
36
|
+
} else if (el.dataset.symbol) {
|
|
37
|
+
const ref = el.dataset.symbol
|
|
38
|
+
newEl.dataset.ref = ref
|
|
39
|
+
newEl.dataset.symbol = ref
|
|
40
|
+
} else if (newEl.tagName === 'image') {
|
|
41
|
+
preventClickDefault(newEl)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return newEl
|
|
45
|
+
}
|
package/dataStorage.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** A storage solution aimed at replacing jQuerys data function.
|
|
2
|
+
* Implementation Note: Elements are stored in a (WeakMap)[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap].
|
|
3
|
+
* This makes sure the data is garbage collected when the node is removed.
|
|
4
|
+
*/
|
|
5
|
+
const dataStorage = {
|
|
6
|
+
_storage: new WeakMap(),
|
|
7
|
+
put: function (element, key, obj) {
|
|
8
|
+
if (!this._storage.has(element)) {
|
|
9
|
+
this._storage.set(element, new Map())
|
|
10
|
+
}
|
|
11
|
+
this._storage.get(element).set(key, obj)
|
|
12
|
+
},
|
|
13
|
+
get: function (element, key) {
|
|
14
|
+
return this._storage.get(element)?.get(key)
|
|
15
|
+
},
|
|
16
|
+
has: function (element, key) {
|
|
17
|
+
return this._storage.has(element) && this._storage.get(element).has(key)
|
|
18
|
+
},
|
|
19
|
+
remove: function (element, key) {
|
|
20
|
+
const ret = this._storage.get(element).delete(key)
|
|
21
|
+
if (this._storage.get(element).size === 0) {
|
|
22
|
+
this._storage.delete(element)
|
|
23
|
+
}
|
|
24
|
+
return ret
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default dataStorage
|