@svgedit/svgcanvas 7.2.7 → 7.4.1
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 +161 -77
- package/core/event.js +143 -28
- package/core/history.js +50 -30
- 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 +46 -14
- package/core/select.js +44 -20
- package/core/selected-elem.js +146 -31
- package/core/selection.js +16 -6
- package/core/svg-exec.js +99 -27
- package/core/svgroot.js +1 -1
- package/core/text-actions.js +327 -306
- package/core/undo.js +20 -5
- package/core/units.js +8 -6
- package/core/utilities.js +282 -170
- package/dist/svgcanvas.js +31590 -53383
- 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 +9 -9
- package/vite.config.mjs +20 -0
- package/rollup.config.mjs +0 -38
package/core/blur-event.js
CHANGED
|
@@ -16,44 +16,92 @@ export const init = (canvas) => {
|
|
|
16
16
|
svgCanvas = canvas
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* @param {Element} filterElem
|
|
21
|
+
* @returns {?Element}
|
|
22
|
+
*/
|
|
23
|
+
const getFeGaussianBlurElem = (filterElem) => {
|
|
24
|
+
if (!filterElem || filterElem.nodeType !== 1) return null
|
|
25
|
+
return filterElem.querySelector('feGaussianBlur') || filterElem.firstElementChild
|
|
26
|
+
}
|
|
27
|
+
|
|
19
28
|
/**
|
|
20
29
|
* Sets the `stdDeviation` blur value on the selected element without being undoable.
|
|
21
30
|
* @function module:svgcanvas.SvgCanvas#setBlurNoUndo
|
|
22
31
|
* @param {Float} val - The new `stdDeviation` value
|
|
23
32
|
* @returns {void}
|
|
24
33
|
*/
|
|
25
|
-
export const setBlurNoUndo =
|
|
34
|
+
export const setBlurNoUndo = (val) => {
|
|
26
35
|
const selectedElements = svgCanvas.getSelectedElements()
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
36
|
+
const elem = selectedElements[0]
|
|
37
|
+
if (!elem) return
|
|
38
|
+
|
|
39
|
+
let filter = svgCanvas.getFilter()
|
|
40
|
+
if (!filter) {
|
|
41
|
+
filter = svgCanvas.getElement(`${elem.id}_blur`)
|
|
30
42
|
}
|
|
43
|
+
|
|
31
44
|
if (val === 0) {
|
|
32
45
|
// Don't change the StdDev, as that will hide the element.
|
|
33
46
|
// Instead, just remove the value for "filter"
|
|
34
47
|
svgCanvas.changeSelectedAttributeNoUndo('filter', '')
|
|
35
48
|
svgCanvas.setFilterHidden(true)
|
|
36
49
|
} else {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
50
|
+
if (!filter) {
|
|
51
|
+
// Create the filter if missing, but don't add history.
|
|
52
|
+
const blurElem = svgCanvas.addSVGElementsFromJson({
|
|
53
|
+
element: 'feGaussianBlur',
|
|
54
|
+
attr: {
|
|
55
|
+
in: 'SourceGraphic',
|
|
56
|
+
stdDeviation: val
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
filter = svgCanvas.addSVGElementsFromJson({
|
|
60
|
+
element: 'filter',
|
|
61
|
+
attr: {
|
|
62
|
+
id: `${elem.id}_blur`
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
filter.append(blurElem)
|
|
66
|
+
svgCanvas.findDefs().append(filter)
|
|
40
67
|
}
|
|
41
|
-
|
|
42
|
-
svgCanvas.
|
|
68
|
+
|
|
69
|
+
if (svgCanvas.getFilterHidden() || !elem.getAttribute('filter')) {
|
|
70
|
+
svgCanvas.changeSelectedAttributeNoUndo('filter', `url(#${filter.id})`)
|
|
71
|
+
svgCanvas.setFilterHidden(false)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const blurElem = getFeGaussianBlurElem(filter)
|
|
75
|
+
if (!blurElem) {
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
svgCanvas.changeSelectedAttributeNoUndo('stdDeviation', val, [blurElem])
|
|
43
79
|
svgCanvas.setBlurOffsets(filter, val)
|
|
44
80
|
}
|
|
45
81
|
}
|
|
46
82
|
|
|
47
83
|
/**
|
|
48
|
-
*
|
|
84
|
+
* Finishes the blur change command and adds it to history if not empty.
|
|
49
85
|
* @returns {void}
|
|
50
86
|
*/
|
|
51
|
-
|
|
87
|
+
const finishChange = () => {
|
|
88
|
+
const curCommand = svgCanvas.getCurCommand()
|
|
89
|
+
if (!curCommand) {
|
|
90
|
+
svgCanvas.setCurCommand(null)
|
|
91
|
+
svgCanvas.setFilter(null)
|
|
92
|
+
svgCanvas.setFilterHidden(false)
|
|
93
|
+
return
|
|
94
|
+
}
|
|
52
95
|
const bCmd = svgCanvas.undoMgr.finishUndoableChange()
|
|
53
|
-
|
|
54
|
-
|
|
96
|
+
if (!bCmd.isEmpty()) {
|
|
97
|
+
curCommand.addSubCommand(bCmd)
|
|
98
|
+
}
|
|
99
|
+
if (!curCommand.isEmpty()) {
|
|
100
|
+
svgCanvas.addCommandToHistory(curCommand)
|
|
101
|
+
}
|
|
55
102
|
svgCanvas.setCurCommand(null)
|
|
56
103
|
svgCanvas.setFilter(null)
|
|
104
|
+
svgCanvas.setFilterHidden(false)
|
|
57
105
|
}
|
|
58
106
|
|
|
59
107
|
/**
|
|
@@ -64,7 +112,13 @@ function finishChange () {
|
|
|
64
112
|
* @param {Float} stdDev - The standard deviation value on which to base the offset size
|
|
65
113
|
* @returns {void}
|
|
66
114
|
*/
|
|
67
|
-
export const setBlurOffsets =
|
|
115
|
+
export const setBlurOffsets = (filterElem, stdDev) => {
|
|
116
|
+
if (!filterElem || filterElem.nodeType !== 1) {
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
stdDev = Number(stdDev) || 0
|
|
121
|
+
|
|
68
122
|
if (stdDev > 3) {
|
|
69
123
|
// TODO: Create algorithm here where size is based on expected blur
|
|
70
124
|
svgCanvas.assignAttributes(filterElem, {
|
|
@@ -88,7 +142,7 @@ export const setBlurOffsets = function (filterElem, stdDev) {
|
|
|
88
142
|
* @param {boolean} complete - Whether or not the action should be completed (to add to the undo manager)
|
|
89
143
|
* @returns {void}
|
|
90
144
|
*/
|
|
91
|
-
export const setBlur =
|
|
145
|
+
export const setBlur = (val, complete) => {
|
|
92
146
|
const {
|
|
93
147
|
InsertElementCommand, ChangeElementCommand, BatchCommand
|
|
94
148
|
} = svgCanvas.history
|
|
@@ -101,20 +155,33 @@ export const setBlur = function (val, complete) {
|
|
|
101
155
|
|
|
102
156
|
// Looks for associated blur, creates one if not found
|
|
103
157
|
const elem = selectedElements[0]
|
|
158
|
+
if (!elem) {
|
|
159
|
+
return
|
|
160
|
+
}
|
|
104
161
|
const elemId = elem.id
|
|
105
|
-
svgCanvas.
|
|
162
|
+
let filter = svgCanvas.getElement(`${elemId}_blur`)
|
|
163
|
+
svgCanvas.setFilter(filter)
|
|
106
164
|
|
|
107
|
-
val
|
|
165
|
+
val = Number(val) || 0
|
|
108
166
|
|
|
109
|
-
const batchCmd = new BatchCommand()
|
|
167
|
+
const batchCmd = new BatchCommand('Change blur')
|
|
110
168
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (
|
|
114
|
-
|
|
169
|
+
if (val === 0) {
|
|
170
|
+
const oldFilter = elem.getAttribute('filter')
|
|
171
|
+
if (!oldFilter) {
|
|
172
|
+
return
|
|
115
173
|
}
|
|
116
|
-
|
|
117
|
-
|
|
174
|
+
const changes = { filter: oldFilter }
|
|
175
|
+
elem.removeAttribute('filter')
|
|
176
|
+
batchCmd.addSubCommand(new ChangeElementCommand(elem, changes))
|
|
177
|
+
svgCanvas.addCommandToHistory(batchCmd)
|
|
178
|
+
svgCanvas.setFilter(null)
|
|
179
|
+
svgCanvas.setFilterHidden(true)
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Ensure blur filter exists.
|
|
184
|
+
if (!filter) {
|
|
118
185
|
const newblur = svgCanvas.addSVGElementsFromJson({
|
|
119
186
|
element: 'feGaussianBlur',
|
|
120
187
|
attr: {
|
|
@@ -123,32 +190,29 @@ export const setBlur = function (val, complete) {
|
|
|
123
190
|
}
|
|
124
191
|
})
|
|
125
192
|
|
|
126
|
-
svgCanvas.
|
|
193
|
+
filter = svgCanvas.addSVGElementsFromJson({
|
|
127
194
|
element: 'filter',
|
|
128
195
|
attr: {
|
|
129
|
-
id: elemId
|
|
196
|
+
id: `${elemId}_blur`
|
|
130
197
|
}
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
svgCanvas.findDefs()
|
|
134
|
-
|
|
135
|
-
|
|
198
|
+
})
|
|
199
|
+
filter.append(newblur)
|
|
200
|
+
const defs = svgCanvas.findDefs()
|
|
201
|
+
if (defs && defs.ownerDocument === filter.ownerDocument) {
|
|
202
|
+
defs.append(filter)
|
|
203
|
+
}
|
|
204
|
+
svgCanvas.setFilter(filter)
|
|
205
|
+
batchCmd.addSubCommand(new InsertElementCommand(filter))
|
|
136
206
|
}
|
|
137
207
|
|
|
138
208
|
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)')
|
|
209
|
+
svgCanvas.changeSelectedAttributeNoUndo('filter', `url(#${filter.id})`)
|
|
147
210
|
batchCmd.addSubCommand(new ChangeElementCommand(elem, changes))
|
|
148
|
-
svgCanvas.setBlurOffsets(
|
|
149
|
-
const filter = svgCanvas.getFilter()
|
|
211
|
+
svgCanvas.setBlurOffsets(filter, val)
|
|
150
212
|
svgCanvas.setCurCommand(batchCmd)
|
|
151
|
-
|
|
213
|
+
|
|
214
|
+
const blurElem = getFeGaussianBlurElem(filter)
|
|
215
|
+
svgCanvas.undoMgr.beginUndoableChange('stdDeviation', [blurElem])
|
|
152
216
|
if (complete) {
|
|
153
217
|
svgCanvas.setBlurNoUndo(val)
|
|
154
218
|
finishChange()
|
package/core/clear.js
CHANGED
|
@@ -24,7 +24,15 @@ export const clearSvgContentElementInit = () => {
|
|
|
24
24
|
// empty
|
|
25
25
|
while (el.firstChild) { el.removeChild(el.firstChild) }
|
|
26
26
|
|
|
27
|
-
//
|
|
27
|
+
// Reset any stale attributes from the previous document.
|
|
28
|
+
for (const attr of Array.from(el.attributes)) {
|
|
29
|
+
if (attr.namespaceURI) {
|
|
30
|
+
el.removeAttributeNS(attr.namespaceURI, attr.localName)
|
|
31
|
+
} else {
|
|
32
|
+
el.removeAttribute(attr.name)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
28
36
|
const pel = svgCanvas.getSvgRoot()
|
|
29
37
|
el.setAttribute('id', 'svgcontent')
|
|
30
38
|
el.setAttribute('width', dimensions[0])
|
|
@@ -35,9 +43,11 @@ export const clearSvgContentElementInit = () => {
|
|
|
35
43
|
el.setAttribute('xmlns', NS.SVG)
|
|
36
44
|
el.setAttribute('xmlns:se', NS.SE)
|
|
37
45
|
el.setAttribute('xmlns:xlink', NS.XLINK)
|
|
38
|
-
|
|
46
|
+
if (el.parentNode !== pel) {
|
|
47
|
+
pel.appendChild(el)
|
|
48
|
+
}
|
|
39
49
|
|
|
40
50
|
// TODO: make this string optional and set by the client
|
|
41
51
|
const comment = svgCanvas.getDOMDocument().createComment(' Created with SVG-edit - https://github.com/SVG-Edit/svgedit')
|
|
42
|
-
|
|
52
|
+
el.append(comment)
|
|
43
53
|
}
|
package/core/coords.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* @license MIT
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { warn } from '../common/logger.js'
|
|
8
|
+
|
|
7
9
|
import {
|
|
8
10
|
snapToGrid,
|
|
9
11
|
assignAttributes,
|
|
@@ -22,6 +24,30 @@ import { convertToNum } from './units.js'
|
|
|
22
24
|
|
|
23
25
|
let svgCanvas = null
|
|
24
26
|
|
|
27
|
+
const flipBoxCoordinate = (value) => {
|
|
28
|
+
if (value === null || value === undefined) return null
|
|
29
|
+
const str = String(value).trim()
|
|
30
|
+
if (!str) return null
|
|
31
|
+
|
|
32
|
+
if (str.endsWith('%')) {
|
|
33
|
+
const num = Number.parseFloat(str.slice(0, -1))
|
|
34
|
+
return Number.isNaN(num) ? str : `${100 - num}%`
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const num = Number.parseFloat(str)
|
|
38
|
+
return Number.isNaN(num) ? str : String(1 - num)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const flipAttributeInBoxUnits = (elem, attr) => {
|
|
42
|
+
const value = elem.getAttribute(attr)
|
|
43
|
+
if (value === null || value === undefined) return
|
|
44
|
+
|
|
45
|
+
const flipped = flipBoxCoordinate(value)
|
|
46
|
+
if (flipped !== null && flipped !== undefined) {
|
|
47
|
+
elem.setAttribute(attr, flipped)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
25
51
|
/**
|
|
26
52
|
* Initialize the coords module with the SVG canvas.
|
|
27
53
|
* @function module:coords.init
|
|
@@ -32,28 +58,9 @@ export const init = canvas => {
|
|
|
32
58
|
svgCanvas = canvas
|
|
33
59
|
}
|
|
34
60
|
|
|
35
|
-
//
|
|
61
|
+
// Map path segment types to their corresponding commands
|
|
36
62
|
const pathMap = [
|
|
37
|
-
0,
|
|
38
|
-
'z',
|
|
39
|
-
'M',
|
|
40
|
-
'm',
|
|
41
|
-
'L',
|
|
42
|
-
'l',
|
|
43
|
-
'C',
|
|
44
|
-
'c',
|
|
45
|
-
'Q',
|
|
46
|
-
'q',
|
|
47
|
-
'A',
|
|
48
|
-
'a',
|
|
49
|
-
'H',
|
|
50
|
-
'h',
|
|
51
|
-
'V',
|
|
52
|
-
'v',
|
|
53
|
-
'S',
|
|
54
|
-
's',
|
|
55
|
-
'T',
|
|
56
|
-
't'
|
|
63
|
+
0, 'z', 'M', 'm', 'L', 'l', 'C', 'c', 'Q', 'q', 'A', 'a', 'H', 'h', 'V', 'v', 'S', 's', 'T', 't'
|
|
57
64
|
]
|
|
58
65
|
|
|
59
66
|
/**
|
|
@@ -66,19 +73,21 @@ const pathMap = [
|
|
|
66
73
|
*/
|
|
67
74
|
export const remapElement = (selected, changes, m) => {
|
|
68
75
|
const remap = (x, y) => transformPoint(x, y, m)
|
|
69
|
-
const scalew = w => m.a * w
|
|
70
|
-
const scaleh = h => m.d * h
|
|
76
|
+
const scalew = (w) => m.a * w
|
|
77
|
+
const scaleh = (h) => m.d * h
|
|
71
78
|
const doSnapping =
|
|
72
|
-
svgCanvas.getGridSnapping() &&
|
|
73
|
-
selected
|
|
79
|
+
svgCanvas.getGridSnapping?.() &&
|
|
80
|
+
selected?.parentNode?.parentNode?.localName === 'svg'
|
|
81
|
+
|
|
74
82
|
const finishUp = () => {
|
|
75
83
|
if (doSnapping) {
|
|
76
|
-
|
|
84
|
+
for (const [attr, value] of Object.entries(changes)) {
|
|
77
85
|
changes[attr] = snapToGrid(value)
|
|
78
|
-
}
|
|
86
|
+
}
|
|
79
87
|
}
|
|
80
88
|
assignAttributes(selected, changes, 1000, true)
|
|
81
89
|
}
|
|
90
|
+
|
|
82
91
|
const box = getBBox(selected)
|
|
83
92
|
|
|
84
93
|
// Handle gradients and patterns
|
|
@@ -86,25 +95,47 @@ export const remapElement = (selected, changes, m) => {
|
|
|
86
95
|
const attrVal = selected.getAttribute(type)
|
|
87
96
|
if (attrVal?.startsWith('url(') && (m.a < 0 || m.d < 0)) {
|
|
88
97
|
const grad = getRefElem(attrVal)
|
|
98
|
+
if (!grad) return
|
|
99
|
+
|
|
100
|
+
const tagName = (grad.tagName || '').toLowerCase()
|
|
101
|
+
if (!['lineargradient', 'radialgradient'].includes(tagName)) return
|
|
102
|
+
|
|
103
|
+
// userSpaceOnUse gradients do not need object-bounding-box correction.
|
|
104
|
+
if (grad.getAttribute('gradientUnits') === 'userSpaceOnUse') return
|
|
105
|
+
|
|
89
106
|
const newgrad = grad.cloneNode(true)
|
|
90
107
|
if (m.a < 0) {
|
|
91
108
|
// Flip x
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
109
|
+
if (tagName === 'lineargradient') {
|
|
110
|
+
flipAttributeInBoxUnits(newgrad, 'x1')
|
|
111
|
+
flipAttributeInBoxUnits(newgrad, 'x2')
|
|
112
|
+
} else {
|
|
113
|
+
flipAttributeInBoxUnits(newgrad, 'cx')
|
|
114
|
+
flipAttributeInBoxUnits(newgrad, 'fx')
|
|
115
|
+
}
|
|
96
116
|
}
|
|
97
117
|
|
|
98
118
|
if (m.d < 0) {
|
|
99
119
|
// Flip y
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
120
|
+
if (tagName === 'lineargradient') {
|
|
121
|
+
flipAttributeInBoxUnits(newgrad, 'y1')
|
|
122
|
+
flipAttributeInBoxUnits(newgrad, 'y2')
|
|
123
|
+
} else {
|
|
124
|
+
flipAttributeInBoxUnits(newgrad, 'cy')
|
|
125
|
+
flipAttributeInBoxUnits(newgrad, 'fy')
|
|
126
|
+
}
|
|
104
127
|
}
|
|
105
|
-
|
|
128
|
+
|
|
129
|
+
const drawing = svgCanvas.getCurrentDrawing?.() || svgCanvas.getDrawing?.()
|
|
130
|
+
const generatedId = drawing?.getNextId?.() ??
|
|
131
|
+
(grad.id ? `${grad.id}-mirrored` : `mirrored-grad-${Date.now()}`)
|
|
132
|
+
if (!generatedId) {
|
|
133
|
+
warn('Unable to mirror gradient: no drawing context available', null, 'coords')
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
newgrad.id = generatedId
|
|
106
137
|
findDefs().append(newgrad)
|
|
107
|
-
selected.setAttribute(type,
|
|
138
|
+
selected.setAttribute(type, `url(#${newgrad.id})`)
|
|
108
139
|
}
|
|
109
140
|
})
|
|
110
141
|
|
|
@@ -265,25 +296,79 @@ export const remapElement = (selected, changes, m) => {
|
|
|
265
296
|
break
|
|
266
297
|
}
|
|
267
298
|
case 'path': {
|
|
299
|
+
const supportsPathData =
|
|
300
|
+
typeof selected.getPathData === 'function' &&
|
|
301
|
+
typeof selected.setPathData === 'function'
|
|
302
|
+
|
|
268
303
|
// Handle path segments
|
|
269
|
-
const segList = selected.pathSegList
|
|
270
|
-
const len = segList.numberOfItems
|
|
304
|
+
const segList = supportsPathData ? null : selected.pathSegList
|
|
305
|
+
const len = supportsPathData ? selected.getPathData().length : segList.numberOfItems
|
|
306
|
+
const det = m.a * m.d - m.b * m.c
|
|
307
|
+
const shouldToggleArcSweep = det < 0
|
|
271
308
|
changes.d = []
|
|
272
|
-
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
309
|
+
if (supportsPathData) {
|
|
310
|
+
const pathDataSegments = selected.getPathData()
|
|
311
|
+
for (let i = 0; i < len; ++i) {
|
|
312
|
+
const seg = pathDataSegments[i]
|
|
313
|
+
const t = seg.type
|
|
314
|
+
const type = pathMap.indexOf(t)
|
|
315
|
+
if (type === -1) continue
|
|
316
|
+
const values = seg.values || []
|
|
317
|
+
const entry = { type }
|
|
318
|
+
switch (t.toUpperCase()) {
|
|
319
|
+
case 'M':
|
|
320
|
+
case 'L':
|
|
321
|
+
case 'T':
|
|
322
|
+
[entry.x, entry.y] = values
|
|
323
|
+
break
|
|
324
|
+
case 'H':
|
|
325
|
+
[entry.x] = values
|
|
326
|
+
break
|
|
327
|
+
case 'V':
|
|
328
|
+
[entry.y] = values
|
|
329
|
+
break
|
|
330
|
+
case 'C':
|
|
331
|
+
[entry.x1, entry.y1, entry.x2, entry.y2, entry.x, entry.y] = values
|
|
332
|
+
break
|
|
333
|
+
case 'S':
|
|
334
|
+
[entry.x2, entry.y2, entry.x, entry.y] = values
|
|
335
|
+
break
|
|
336
|
+
case 'Q':
|
|
337
|
+
[entry.x1, entry.y1, entry.x, entry.y] = values
|
|
338
|
+
break
|
|
339
|
+
case 'A':
|
|
340
|
+
[
|
|
341
|
+
entry.r1,
|
|
342
|
+
entry.r2,
|
|
343
|
+
entry.angle,
|
|
344
|
+
entry.largeArcFlag,
|
|
345
|
+
entry.sweepFlag,
|
|
346
|
+
entry.x,
|
|
347
|
+
entry.y
|
|
348
|
+
] = values
|
|
349
|
+
break
|
|
350
|
+
default:
|
|
351
|
+
break
|
|
352
|
+
}
|
|
353
|
+
changes.d[i] = entry
|
|
354
|
+
}
|
|
355
|
+
} else {
|
|
356
|
+
for (let i = 0; i < len; ++i) {
|
|
357
|
+
const seg = segList.getItem(i)
|
|
358
|
+
changes.d[i] = {
|
|
359
|
+
type: seg.pathSegType,
|
|
360
|
+
x: seg.x,
|
|
361
|
+
y: seg.y,
|
|
362
|
+
x1: seg.x1,
|
|
363
|
+
y1: seg.y1,
|
|
364
|
+
x2: seg.x2,
|
|
365
|
+
y2: seg.y2,
|
|
366
|
+
r1: seg.r1,
|
|
367
|
+
r2: seg.r2,
|
|
368
|
+
angle: seg.angle,
|
|
369
|
+
largeArcFlag: seg.largeArcFlag,
|
|
370
|
+
sweepFlag: seg.sweepFlag
|
|
371
|
+
}
|
|
287
372
|
}
|
|
288
373
|
}
|
|
289
374
|
|
|
@@ -302,41 +387,65 @@ export const remapElement = (selected, changes, m) => {
|
|
|
302
387
|
const thisx = seg.x !== undefined ? seg.x : currentpt.x // For V commands
|
|
303
388
|
const thisy = seg.y !== undefined ? seg.y : currentpt.y // For H commands
|
|
304
389
|
const pt = remap(thisx, thisy)
|
|
305
|
-
const pt1 = remap(seg.x1, seg.y1)
|
|
306
|
-
const pt2 = remap(seg.x2, seg.y2)
|
|
307
390
|
seg.x = pt.x
|
|
308
391
|
seg.y = pt.y
|
|
309
|
-
seg.x1
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
seg.
|
|
392
|
+
if (seg.x1 !== undefined && seg.y1 !== undefined) {
|
|
393
|
+
const pt1 = remap(seg.x1, seg.y1)
|
|
394
|
+
seg.x1 = pt1.x
|
|
395
|
+
seg.y1 = pt1.y
|
|
396
|
+
}
|
|
397
|
+
if (seg.x2 !== undefined && seg.y2 !== undefined) {
|
|
398
|
+
const pt2 = remap(seg.x2, seg.y2)
|
|
399
|
+
seg.x2 = pt2.x
|
|
400
|
+
seg.y2 = pt2.y
|
|
401
|
+
}
|
|
402
|
+
if (type === 10) {
|
|
403
|
+
seg.r1 = Math.abs(scalew(seg.r1))
|
|
404
|
+
seg.r2 = Math.abs(scaleh(seg.r2))
|
|
405
|
+
if (shouldToggleArcSweep) {
|
|
406
|
+
seg.sweepFlag = Number(seg.sweepFlag) ? 0 : 1
|
|
407
|
+
if (typeof seg.angle === 'number') {
|
|
408
|
+
seg.angle = -seg.angle
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
315
412
|
} else {
|
|
316
413
|
// For relative segments, scale x, y, x1, y1, x2, y2
|
|
317
|
-
seg.x = scalew(seg.x)
|
|
318
|
-
seg.y = scaleh(seg.y)
|
|
319
|
-
seg.x1 = scalew(seg.x1)
|
|
320
|
-
seg.y1 = scaleh(seg.y1)
|
|
321
|
-
seg.x2 = scalew(seg.x2)
|
|
322
|
-
seg.y2 = scaleh(seg.y2)
|
|
323
|
-
|
|
324
|
-
|
|
414
|
+
if (seg.x !== undefined) seg.x = scalew(seg.x)
|
|
415
|
+
if (seg.y !== undefined) seg.y = scaleh(seg.y)
|
|
416
|
+
if (seg.x1 !== undefined) seg.x1 = scalew(seg.x1)
|
|
417
|
+
if (seg.y1 !== undefined) seg.y1 = scaleh(seg.y1)
|
|
418
|
+
if (seg.x2 !== undefined) seg.x2 = scalew(seg.x2)
|
|
419
|
+
if (seg.y2 !== undefined) seg.y2 = scaleh(seg.y2)
|
|
420
|
+
if (type === 11) {
|
|
421
|
+
seg.r1 = Math.abs(scalew(seg.r1))
|
|
422
|
+
seg.r2 = Math.abs(scaleh(seg.r2))
|
|
423
|
+
if (shouldToggleArcSweep) {
|
|
424
|
+
seg.sweepFlag = Number(seg.sweepFlag) ? 0 : 1
|
|
425
|
+
if (typeof seg.angle === 'number') {
|
|
426
|
+
seg.angle = -seg.angle
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
325
430
|
}
|
|
326
431
|
}
|
|
327
432
|
|
|
328
433
|
let dstr = ''
|
|
434
|
+
const newPathData = []
|
|
329
435
|
changes.d.forEach(seg => {
|
|
330
436
|
const { type } = seg
|
|
331
|
-
|
|
437
|
+
const letter = pathMap[type]
|
|
438
|
+
dstr += letter
|
|
332
439
|
switch (type) {
|
|
333
440
|
case 13: // relative horizontal line (h)
|
|
334
441
|
case 12: // absolute horizontal line (H)
|
|
335
|
-
dstr += seg.x
|
|
442
|
+
dstr += `${seg.x} `
|
|
443
|
+
newPathData.push({ type: letter, values: [seg.x] })
|
|
336
444
|
break
|
|
337
445
|
case 15: // relative vertical line (v)
|
|
338
446
|
case 14: // absolute vertical line (V)
|
|
339
|
-
dstr += seg.y
|
|
447
|
+
dstr += `${seg.y} `
|
|
448
|
+
newPathData.push({ type: letter, values: [seg.y] })
|
|
340
449
|
break
|
|
341
450
|
case 3: // relative move (m)
|
|
342
451
|
case 5: // relative line (l)
|
|
@@ -344,27 +453,21 @@ export const remapElement = (selected, changes, m) => {
|
|
|
344
453
|
case 2: // absolute move (M)
|
|
345
454
|
case 4: // absolute line (L)
|
|
346
455
|
case 18: // absolute smooth quad (T)
|
|
347
|
-
dstr += seg.x
|
|
456
|
+
dstr += `${seg.x},${seg.y} `
|
|
457
|
+
newPathData.push({ type: letter, values: [seg.x, seg.y] })
|
|
348
458
|
break
|
|
349
459
|
case 7: // relative cubic (c)
|
|
350
460
|
case 6: // absolute cubic (C)
|
|
351
|
-
dstr +=
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
seg.y1
|
|
355
|
-
|
|
356
|
-
seg.x2 +
|
|
357
|
-
',' +
|
|
358
|
-
seg.y2 +
|
|
359
|
-
' ' +
|
|
360
|
-
seg.x +
|
|
361
|
-
',' +
|
|
362
|
-
seg.y +
|
|
363
|
-
' '
|
|
461
|
+
dstr += `${seg.x1},${seg.y1} ${seg.x2},${seg.y2} ${seg.x},${seg.y} `
|
|
462
|
+
newPathData.push({
|
|
463
|
+
type: letter,
|
|
464
|
+
values: [seg.x1, seg.y1, seg.x2, seg.y2, seg.x, seg.y]
|
|
465
|
+
})
|
|
364
466
|
break
|
|
365
467
|
case 9: // relative quad (q)
|
|
366
468
|
case 8: // absolute quad (Q)
|
|
367
|
-
dstr += seg.x1
|
|
469
|
+
dstr += `${seg.x1},${seg.y1} ${seg.x},${seg.y} `
|
|
470
|
+
newPathData.push({ type: letter, values: [seg.x1, seg.y1, seg.x, seg.y] })
|
|
368
471
|
break
|
|
369
472
|
case 11: // relative elliptical arc (a)
|
|
370
473
|
case 10: // absolute elliptical arc (A)
|
|
@@ -383,17 +486,38 @@ export const remapElement = (selected, changes, m) => {
|
|
|
383
486
|
',' +
|
|
384
487
|
seg.y +
|
|
385
488
|
' '
|
|
489
|
+
newPathData.push({
|
|
490
|
+
type: letter,
|
|
491
|
+
values: [
|
|
492
|
+
seg.r1,
|
|
493
|
+
seg.r2,
|
|
494
|
+
seg.angle,
|
|
495
|
+
Number(seg.largeArcFlag),
|
|
496
|
+
Number(seg.sweepFlag),
|
|
497
|
+
seg.x,
|
|
498
|
+
seg.y
|
|
499
|
+
]
|
|
500
|
+
})
|
|
386
501
|
break
|
|
387
502
|
case 17: // relative smooth cubic (s)
|
|
388
503
|
case 16: // absolute smooth cubic (S)
|
|
389
|
-
dstr += seg.x2
|
|
504
|
+
dstr += `${seg.x2},${seg.y2} ${seg.x},${seg.y} `
|
|
505
|
+
newPathData.push({ type: letter, values: [seg.x2, seg.y2, seg.x, seg.y] })
|
|
390
506
|
break
|
|
391
507
|
default:
|
|
392
508
|
break
|
|
393
509
|
}
|
|
394
510
|
})
|
|
395
511
|
|
|
396
|
-
|
|
512
|
+
const d = dstr.trim()
|
|
513
|
+
selected.setAttribute('d', d)
|
|
514
|
+
if (supportsPathData) {
|
|
515
|
+
try {
|
|
516
|
+
selected.setPathData(newPathData)
|
|
517
|
+
} catch (e) {
|
|
518
|
+
// Fallback to 'd' attribute if setPathData is unavailable or throws.
|
|
519
|
+
}
|
|
520
|
+
}
|
|
397
521
|
break
|
|
398
522
|
}
|
|
399
523
|
default:
|