@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 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