@svgedit/svgcanvas 7.2.4 → 7.2.7
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/CHANGES.md +9 -0
- package/core/event.js +12 -3
- package/core/history.js +1 -1
- package/core/paint.js +15 -9
- package/core/recalculate.js +28 -9
- package/core/sanitize.js +61 -28
- package/core/svg-exec.js +6 -4
- package/core/utilities.js +36 -35
- package/dist/svgcanvas.js +499 -333
- package/dist/svgcanvas.js.map +1 -1
- package/package.json +4 -2
package/CHANGES.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# svgcanvas CHANGES
|
|
2
2
|
|
|
3
|
+
|
|
4
|
+
## 7.2.7
|
|
5
|
+
- Prefer href to xlink href (#1059)
|
|
6
|
+
- Fix group rotation (#1058)
|
|
7
|
+
- Fixed a bug where a rotated text or image did not translate correctly. (#1055)
|
|
8
|
+
|
|
9
|
+
## 7.2.5
|
|
10
|
+
- update dependencies
|
|
11
|
+
|
|
3
12
|
## 7.2.4
|
|
4
13
|
- bug fixes
|
|
5
14
|
- update dependencies
|
package/core/event.js
CHANGED
|
@@ -662,9 +662,18 @@ const mouseUpEvent = (evt) => {
|
|
|
662
662
|
const elem = selectedElements[0]
|
|
663
663
|
if (elem) {
|
|
664
664
|
elem.removeAttribute('style')
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
665
|
+
|
|
666
|
+
// we don't remove the style elements for contents of foreignObjects
|
|
667
|
+
// because that is a valid way to style them
|
|
668
|
+
if (elem.localName === 'foreignObject') {
|
|
669
|
+
walkTree(elem, (el) => {
|
|
670
|
+
el.style.removeProperty('pointer-events')
|
|
671
|
+
})
|
|
672
|
+
} else {
|
|
673
|
+
walkTree(elem, (el) => {
|
|
674
|
+
el.removeAttribute('style')
|
|
675
|
+
})
|
|
676
|
+
}
|
|
668
677
|
}
|
|
669
678
|
}
|
|
670
679
|
return
|
package/core/history.js
CHANGED
|
@@ -425,7 +425,7 @@ export class BatchCommand extends Command {
|
|
|
425
425
|
*/
|
|
426
426
|
unapply (handler) {
|
|
427
427
|
super.unapply(handler, () => {
|
|
428
|
-
this.stack.reverse().forEach((stackItem) => {
|
|
428
|
+
[...this.stack].reverse().forEach((stackItem) => {
|
|
429
429
|
console.assert(stackItem, 'stack item should not be null')
|
|
430
430
|
stackItem && stackItem.unapply(handler)
|
|
431
431
|
})
|
package/core/paint.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
export default class Paint {
|
|
5
5
|
/**
|
|
6
6
|
* @param {module:jGraduate.jGraduatePaintOptions} [opt]
|
|
7
|
-
|
|
7
|
+
*/
|
|
8
8
|
constructor (opt) {
|
|
9
9
|
const options = opt || {}
|
|
10
10
|
this.alpha = isNaN(options.alpha) ? 100 : options.alpha
|
|
@@ -51,33 +51,39 @@ export default class Paint {
|
|
|
51
51
|
this.radialGradient = options.copy.radialGradient.cloneNode(true)
|
|
52
52
|
break
|
|
53
53
|
}
|
|
54
|
-
|
|
54
|
+
// create linear gradient paint
|
|
55
55
|
} else if (options.linearGradient) {
|
|
56
56
|
this.type = 'linearGradient'
|
|
57
57
|
this.solidColor = null
|
|
58
58
|
this.radialGradient = null
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
const hrefAttr =
|
|
60
|
+
options.linearGradient.getAttribute('href') ||
|
|
61
|
+
options.linearGradient.getAttribute('xlink:href')
|
|
62
|
+
if (hrefAttr) {
|
|
63
|
+
const xhref = document.getElementById(hrefAttr.replace(/^#/, ''))
|
|
61
64
|
this.linearGradient = xhref.cloneNode(true)
|
|
62
65
|
} else {
|
|
63
66
|
this.linearGradient = options.linearGradient.cloneNode(true)
|
|
64
67
|
}
|
|
65
|
-
|
|
68
|
+
// create linear gradient paint
|
|
66
69
|
} else if (options.radialGradient) {
|
|
67
70
|
this.type = 'radialGradient'
|
|
68
71
|
this.solidColor = null
|
|
69
72
|
this.linearGradient = null
|
|
70
|
-
|
|
71
|
-
|
|
73
|
+
const hrefAttr =
|
|
74
|
+
options.radialGradient.getAttribute('href') ||
|
|
75
|
+
options.radialGradient.getAttribute('xlink:href')
|
|
76
|
+
if (hrefAttr) {
|
|
77
|
+
const xhref = document.getElementById(hrefAttr.replace(/^#/, ''))
|
|
72
78
|
this.radialGradient = xhref.cloneNode(true)
|
|
73
79
|
} else {
|
|
74
80
|
this.radialGradient = options.radialGradient.cloneNode(true)
|
|
75
81
|
}
|
|
76
|
-
|
|
82
|
+
// create solid color paint
|
|
77
83
|
} else if (options.solidColor) {
|
|
78
84
|
this.type = 'solidColor'
|
|
79
85
|
this.solidColor = options.solidColor
|
|
80
|
-
|
|
86
|
+
// create empty paint
|
|
81
87
|
} else {
|
|
82
88
|
this.type = 'none'
|
|
83
89
|
this.solidColor = null
|
package/core/recalculate.js
CHANGED
|
@@ -236,16 +236,35 @@ export const recalculateDimensions = selected => {
|
|
|
236
236
|
// Handle rotation transformations
|
|
237
237
|
const angle = getRotationAngle(selected)
|
|
238
238
|
if (angle) {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
x
|
|
242
|
-
y
|
|
239
|
+
if (selected.localName === 'image') {
|
|
240
|
+
// Use the center of the image as the rotation center
|
|
241
|
+
const xAttr = convertToNum('x', selected.getAttribute('x') || '0')
|
|
242
|
+
const yAttr = convertToNum('y', selected.getAttribute('y') || '0')
|
|
243
|
+
const width = convertToNum('width', selected.getAttribute('width') || '0')
|
|
244
|
+
const height = convertToNum('height', selected.getAttribute('height') || '0')
|
|
245
|
+
const cx = xAttr + width / 2
|
|
246
|
+
const cy = yAttr + height / 2
|
|
247
|
+
oldcenter = { x: cx, y: cy }
|
|
248
|
+
const transform = transformListToTransform(tlist).matrix
|
|
249
|
+
newcenter = transformPoint(cx, cy, transform)
|
|
250
|
+
} else if (selected.localName === 'text') {
|
|
251
|
+
// Use the center of the bounding box as the rotation center for text
|
|
252
|
+
const cx = box.x + box.width / 2
|
|
253
|
+
const cy = box.y + box.height / 2
|
|
254
|
+
oldcenter = { x: cx, y: cy }
|
|
255
|
+
newcenter = transformPoint(cx, cy, transformListToTransform(tlist).matrix)
|
|
256
|
+
} else {
|
|
257
|
+
// Include x and y in the rotation center calculation for other elements
|
|
258
|
+
oldcenter = {
|
|
259
|
+
x: box.x + box.width / 2 + x,
|
|
260
|
+
y: box.y + box.height / 2 + y
|
|
261
|
+
}
|
|
262
|
+
newcenter = transformPoint(
|
|
263
|
+
box.x + box.width / 2 + x,
|
|
264
|
+
box.y + box.height / 2 + y,
|
|
265
|
+
transformListToTransform(tlist).matrix
|
|
266
|
+
)
|
|
243
267
|
}
|
|
244
|
-
newcenter = transformPoint(
|
|
245
|
-
box.x + box.width / 2 + x,
|
|
246
|
-
box.y + box.height / 2 + y,
|
|
247
|
-
transformListToTransform(tlist).matrix
|
|
248
|
-
)
|
|
249
268
|
|
|
250
269
|
// Remove the rotation transform from the list
|
|
251
270
|
for (let i = 0; i < tlist.numberOfItems; ++i) {
|
package/core/sanitize.js
CHANGED
|
@@ -21,7 +21,7 @@ const REVERSE_NS = getReverseNS()
|
|
|
21
21
|
const svgGenericWhiteList = ['class', 'id', 'display', 'transform', 'style']
|
|
22
22
|
const svgWhiteList_ = {
|
|
23
23
|
// SVG Elements
|
|
24
|
-
a: ['clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'mask', 'opacity', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'xlink:href', 'xlink:title'],
|
|
24
|
+
a: ['clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'href', 'mask', 'opacity', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'xlink:href', 'xlink:title'],
|
|
25
25
|
circle: ['clip-path', 'clip-rule', 'cx', 'cy', 'enable-background', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'mask', 'opacity', 'r', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage'],
|
|
26
26
|
clipPath: ['clipPathUnits'],
|
|
27
27
|
defs: [],
|
|
@@ -36,20 +36,20 @@ const svgWhiteList_ = {
|
|
|
36
36
|
feMergeNode: ['in'],
|
|
37
37
|
feMorphology: ['in', 'operator', 'radius'],
|
|
38
38
|
feOffset: ['dx', 'in', 'dy', 'result'],
|
|
39
|
-
filter: ['color-interpolation-filters', 'filterRes', 'filterUnits', 'height', 'primitiveUnits', 'requiredFeatures', 'width', 'x', 'xlink:href', 'y'],
|
|
39
|
+
filter: ['color-interpolation-filters', 'filterRes', 'filterUnits', 'height', 'href', 'primitiveUnits', 'requiredFeatures', 'width', 'x', 'xlink:href', 'y'],
|
|
40
40
|
foreignObject: ['font-size', 'height', 'opacity', 'requiredFeatures', 'width', 'x', 'y'],
|
|
41
41
|
g: ['clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'font-family', 'font-size', 'font-style', 'font-weight', 'text-anchor'],
|
|
42
|
-
image: ['clip-path', 'clip-rule', 'filter', 'height', 'mask', 'opacity', 'requiredFeatures', 'systemLanguage', 'width', 'x', 'xlink:href', 'xlink:title', 'y'],
|
|
42
|
+
image: ['clip-path', 'clip-rule', 'filter', 'height', 'mask', 'opacity', 'requiredFeatures', 'systemLanguage', 'width', 'x', 'href', 'xlink:href', 'xlink:title', 'y'],
|
|
43
43
|
line: ['clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'x1', 'x2', 'y1', 'y2'],
|
|
44
|
-
linearGradient: ['gradientTransform', 'gradientUnits', 'requiredFeatures', 'spreadMethod', 'systemLanguage', 'x1', 'x2', 'xlink:href', 'y1', 'y2'],
|
|
44
|
+
linearGradient: ['gradientTransform', 'gradientUnits', 'requiredFeatures', 'spreadMethod', 'systemLanguage', 'x1', 'x2', 'href', 'xlink:href', 'y1', 'y2'],
|
|
45
45
|
marker: ['markerHeight', 'markerUnits', 'markerWidth', 'orient', 'preserveAspectRatio', 'refX', 'refY', 'se_type', 'systemLanguage', 'viewBox'],
|
|
46
46
|
mask: ['height', 'maskContentUnits', 'maskUnits', 'width', 'x', 'y'],
|
|
47
47
|
metadata: [],
|
|
48
48
|
path: ['clip-path', 'clip-rule', 'd', 'enable-background', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage'],
|
|
49
|
-
pattern: ['height', 'patternContentUnits', 'patternTransform', 'patternUnits', 'requiredFeatures', 'systemLanguage', 'viewBox', 'width', 'x', 'xlink:href', 'y'],
|
|
49
|
+
pattern: ['height', 'patternContentUnits', 'patternTransform', 'patternUnits', 'requiredFeatures', 'systemLanguage', 'viewBox', 'width', 'x', 'href', 'xlink:href', 'y'],
|
|
50
50
|
polygon: ['clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'points', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'sides', 'shape', 'edge', 'point', 'starRadiusMultiplier', 'r', 'radialshift', 'r2', 'orient', 'cx', 'cy'],
|
|
51
51
|
polyline: ['clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'points', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'se:connector'],
|
|
52
|
-
radialGradient: ['cx', 'cy', 'fx', 'fy', 'gradientTransform', 'gradientUnits', 'r', 'requiredFeatures', 'spreadMethod', 'systemLanguage', 'xlink:href'],
|
|
52
|
+
radialGradient: ['cx', 'cy', 'fx', 'fy', 'gradientTransform', 'gradientUnits', 'r', 'requiredFeatures', 'spreadMethod', 'systemLanguage', 'href', 'xlink:href'],
|
|
53
53
|
rect: ['clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'height', 'mask', 'opacity', 'requiredFeatures', 'rx', 'ry', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'width', 'x', 'y'],
|
|
54
54
|
stop: ['offset', 'requiredFeatures', 'stop-opacity', 'systemLanguage', 'stop-color', 'gradientUnits', 'gradientTransform'],
|
|
55
55
|
style: ['type'],
|
|
@@ -57,10 +57,22 @@ const svgWhiteList_ = {
|
|
|
57
57
|
switch: ['requiredFeatures', 'systemLanguage'],
|
|
58
58
|
symbol: ['fill', 'fill-opacity', 'fill-rule', 'filter', 'font-family', 'font-size', 'font-style', 'font-weight', 'opacity', 'overflow', 'preserveAspectRatio', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'viewBox', 'width', 'height'],
|
|
59
59
|
text: ['clip-path', 'clip-rule', 'dominant-baseline', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'font-family', 'font-size', 'font-style', 'font-weight', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'text-anchor', 'letter-spacing', 'word-spacing', 'text-decoration', 'textLength', 'lengthAdjust', 'x', 'xml:space', 'y'],
|
|
60
|
-
textPath: ['dominant-baseline', 'method', 'requiredFeatures', 'spacing', 'startOffset', 'systemLanguage', 'xlink:href'],
|
|
60
|
+
textPath: ['dominant-baseline', 'href', 'method', 'requiredFeatures', 'spacing', 'startOffset', 'systemLanguage', 'xlink:href'],
|
|
61
61
|
title: [],
|
|
62
62
|
tspan: ['clip-path', 'clip-rule', 'dx', 'dy', 'dominant-baseline', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'font-family', 'font-size', 'font-style', 'font-weight', 'mask', 'opacity', 'requiredFeatures', 'rotate', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'text-anchor', 'textLength', 'x', 'xml:space', 'y'],
|
|
63
|
-
use: ['clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'height', 'mask', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'width', 'x', 'xlink:href', 'y', 'overflow'],
|
|
63
|
+
use: ['clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'height', 'href', 'mask', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'width', 'x', 'xlink:href', 'y', 'overflow'],
|
|
64
|
+
// Filter Primitives
|
|
65
|
+
feComponentTransfer: ['in', 'result'],
|
|
66
|
+
feFuncR: ['type', 'tableValues', 'slope', 'intercept', 'amplitude', 'exponent', 'offset'],
|
|
67
|
+
feFuncG: ['type', 'tableValues', 'slope', 'intercept', 'amplitude', 'exponent', 'offset'],
|
|
68
|
+
feFuncB: ['type', 'tableValues', 'slope', 'intercept', 'amplitude', 'exponent', 'offset'],
|
|
69
|
+
feFuncA: ['type', 'tableValues', 'slope', 'intercept', 'amplitude', 'exponent', 'offset'],
|
|
70
|
+
feConvolveMatrix: ['in', 'order', 'kernelMatrix', 'divisor', 'bias', 'targetX', 'targetY', 'edgeMode', 'kernelUnitLength', 'preserveAlpha'],
|
|
71
|
+
feDiffuseLighting: ['in', 'surfaceScale', 'diffuseConstant', 'kernelUnitLength', 'lighting-color'],
|
|
72
|
+
feSpecularLighting: ['in', 'surfaceScale', 'specularConstant', 'specularExponent', 'kernelUnitLength', 'lighting-color'],
|
|
73
|
+
feDisplacementMap: ['in', 'in2', 'scale', 'xChannelSelector', 'yChannelSelector'],
|
|
74
|
+
feTurbulence: ['baseFrequency', 'numOctaves', 'result', 'seed', 'stitchTiles', 'type'],
|
|
75
|
+
feTile: ['in'],
|
|
64
76
|
|
|
65
77
|
// MathML Elements
|
|
66
78
|
annotation: ['encoding'],
|
|
@@ -79,7 +91,7 @@ const svgWhiteList_ = {
|
|
|
79
91
|
mphantom: [],
|
|
80
92
|
mprescripts: [],
|
|
81
93
|
mroot: [],
|
|
82
|
-
mrow: ['xlink:href', 'xlink:type', 'xmlns:xlink'],
|
|
94
|
+
mrow: ['href', 'xlink:href', 'xlink:type', 'xmlns:xlink'],
|
|
83
95
|
mspace: ['depth', 'height', 'width'],
|
|
84
96
|
msqrt: [],
|
|
85
97
|
mstyle: ['displaystyle', 'mathbackground', 'mathcolor', 'mathvariant', 'scriptlevel'],
|
|
@@ -93,9 +105,25 @@ const svgWhiteList_ = {
|
|
|
93
105
|
munder: [],
|
|
94
106
|
munderover: [],
|
|
95
107
|
none: [],
|
|
96
|
-
semantics: []
|
|
108
|
+
semantics: [],
|
|
109
|
+
|
|
110
|
+
// HTML Elements for use in a foreignObject
|
|
111
|
+
div: [],
|
|
112
|
+
p: [],
|
|
113
|
+
li: [],
|
|
114
|
+
pre: [],
|
|
115
|
+
ol: [],
|
|
116
|
+
ul: [],
|
|
117
|
+
span: [],
|
|
118
|
+
hr: [],
|
|
119
|
+
br: [],
|
|
120
|
+
h1: [],
|
|
121
|
+
h2: [],
|
|
122
|
+
h3: [],
|
|
123
|
+
h4: [],
|
|
124
|
+
h5: [],
|
|
125
|
+
h6: []
|
|
97
126
|
}
|
|
98
|
-
/* eslint-enable max-len */
|
|
99
127
|
|
|
100
128
|
// add generic attributes to all elements of the whitelist
|
|
101
129
|
Object.keys(svgWhiteList_).forEach((element) => { svgWhiteList_[element] = [...svgWhiteList_[element], ...svgGenericWhiteList] })
|
|
@@ -162,16 +190,20 @@ export const sanitizeSvg = (node) => {
|
|
|
162
190
|
// our whitelist or is a namespace declaration for one of our allowed namespaces
|
|
163
191
|
if (attrNsURI !== allowedAttrsNS[attrLocalName] && attrNsURI !== NS.XMLNS &&
|
|
164
192
|
!(attrNsURI === NS.XMLNS && REVERSE_NS[attr.value])) {
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
// We
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
193
|
+
// Special case: allow href attribute even without namespace if it's in the whitelist
|
|
194
|
+
const isHrefAttribute = (attrLocalName === 'href' && allowedAttrs.includes('href'))
|
|
195
|
+
if (!isHrefAttribute) {
|
|
196
|
+
// Bypassing the whitelist to allow se: and oi: prefixes
|
|
197
|
+
// We can add specific namepaces on demand for now.
|
|
198
|
+
// Is there a more appropriate way to do this?
|
|
199
|
+
if (attrName.startsWith('se:') || attrName.startsWith('oi:') || attrName.startsWith('data-')) {
|
|
200
|
+
// We should bypass the namespace aswell
|
|
201
|
+
const seAttrNS = (attrName.startsWith('se:')) ? NS.SE : ((attrName.startsWith('oi:')) ? NS.OI : null)
|
|
202
|
+
seAttrs.push([attrName, attr.value, seAttrNS])
|
|
203
|
+
} else {
|
|
204
|
+
console.warn(`sanitizeSvg: attribute ${attrName} in element ${node.nodeName} not in whitelist is removed: ${node.outerHTML}`)
|
|
205
|
+
node.removeAttributeNS(attrNsURI, attrLocalName)
|
|
206
|
+
}
|
|
175
207
|
}
|
|
176
208
|
}
|
|
177
209
|
|
|
@@ -196,34 +228,35 @@ export const sanitizeSvg = (node) => {
|
|
|
196
228
|
node.setAttributeNS(ns, att, val)
|
|
197
229
|
})
|
|
198
230
|
|
|
199
|
-
// for some elements that have a xlink:href, ensure the URI refers to a local element
|
|
200
|
-
// (but not for links)
|
|
231
|
+
// for some elements that have a xlink:href or href, ensure the URI refers to a local element
|
|
232
|
+
// (but not for links and other elements where external hrefs are allowed)
|
|
201
233
|
const href = getHref(node)
|
|
202
234
|
if (href &&
|
|
203
235
|
['filter', 'linearGradient', 'pattern',
|
|
204
236
|
'radialGradient', 'textPath', 'use'].includes(node.nodeName) && href[0] !== '#') {
|
|
205
237
|
// remove the attribute (but keep the element)
|
|
206
238
|
setHref(node, '')
|
|
207
|
-
console.warn(`sanitizeSvg: attribute href in element ${node.nodeName} pointing to a non-local reference (${href}) is removed`)
|
|
239
|
+
console.warn(`sanitizeSvg: attribute href in element ${node.nodeName} pointing to a non-local reference (${href}) is removed: ${node.outerHTML}`)
|
|
208
240
|
node.removeAttributeNS(NS.XLINK, 'href')
|
|
241
|
+
node.removeAttribute('href')
|
|
209
242
|
}
|
|
210
243
|
|
|
211
244
|
// Safari crashes on a <use> without a xlink:href, so we just remove the node here
|
|
212
245
|
if (node.nodeName === 'use' && !getHref(node)) {
|
|
213
|
-
console.warn(`sanitizeSvg: element ${node.nodeName} without a xlink:href is removed`)
|
|
246
|
+
console.warn(`sanitizeSvg: element ${node.nodeName} without a xlink:href or href is removed: ${node.outerHTML}`)
|
|
214
247
|
node.remove()
|
|
215
248
|
return
|
|
216
249
|
}
|
|
217
250
|
// if the element has attributes pointing to a non-local reference,
|
|
218
251
|
// need to remove the attribute
|
|
219
|
-
|
|
252
|
+
['clip-path', 'fill', 'filter', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'stroke'].forEach((attr) => {
|
|
220
253
|
let val = node.getAttribute(attr)
|
|
221
254
|
if (val) {
|
|
222
255
|
val = getUrlFromAttr(val)
|
|
223
256
|
// simply check for first character being a '#'
|
|
224
257
|
if (val && val[0] !== '#') {
|
|
225
258
|
node.setAttribute(attr, '')
|
|
226
|
-
console.warn(`sanitizeSvg: attribute ${attr} in element ${node.nodeName} pointing to a non-local reference (${val}) is removed`)
|
|
259
|
+
console.warn(`sanitizeSvg: attribute ${attr} in element ${node.nodeName} pointing to a non-local reference (${val}) is removed: ${node.outerHTML}`)
|
|
227
260
|
node.removeAttribute(attr)
|
|
228
261
|
}
|
|
229
262
|
}
|
|
@@ -236,7 +269,7 @@ export const sanitizeSvg = (node) => {
|
|
|
236
269
|
} else {
|
|
237
270
|
// remove all children from this node and insert them before this node
|
|
238
271
|
// TODO: in the case of animation elements this will hardly ever be correct
|
|
239
|
-
console.warn(`sanitizeSvg: element ${node.nodeName} not supported is removed`)
|
|
272
|
+
console.warn(`sanitizeSvg: element ${node.nodeName} not supported is removed: ${node.outerHTML}`)
|
|
240
273
|
const children = []
|
|
241
274
|
while (node.hasChildNodes()) {
|
|
242
275
|
children.push(parent.insertBefore(node.firstChild, node))
|
package/core/svg-exec.js
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
text2xml,
|
|
13
13
|
cleanupElement,
|
|
14
14
|
findDefs,
|
|
15
|
+
setHref,
|
|
15
16
|
getHref,
|
|
16
17
|
preventClickDefault,
|
|
17
18
|
toXml,
|
|
@@ -144,7 +145,7 @@ const svgToString = (elem, indent) => {
|
|
|
144
145
|
out.push(' ')
|
|
145
146
|
}
|
|
146
147
|
out.push('<')
|
|
147
|
-
out.push(elem.
|
|
148
|
+
out.push(elem.localName)
|
|
148
149
|
if (elem.id === 'svgcontent') {
|
|
149
150
|
// Process root element separately
|
|
150
151
|
const res = svgCanvas.getResolution()
|
|
@@ -352,7 +353,7 @@ const svgToString = (elem, indent) => {
|
|
|
352
353
|
}
|
|
353
354
|
}
|
|
354
355
|
out.push('</')
|
|
355
|
-
out.push(elem.
|
|
356
|
+
out.push(elem.localName)
|
|
356
357
|
out.push('>')
|
|
357
358
|
} else {
|
|
358
359
|
out.push('/>')
|
|
@@ -443,7 +444,8 @@ const setSvgString = (xmlString, preventUndo) => {
|
|
|
443
444
|
// const url = decodeURIComponent(m.groups.url);
|
|
444
445
|
const iimg = new Image()
|
|
445
446
|
iimg.addEventListener('load', () => {
|
|
446
|
-
|
|
447
|
+
// Set the href attribute to the data URL
|
|
448
|
+
setHref(image, val)
|
|
447
449
|
})
|
|
448
450
|
iimg.src = url
|
|
449
451
|
}
|
|
@@ -858,7 +860,7 @@ const convertImagesToBase64 = async svgElement => {
|
|
|
858
860
|
const reader = new FileReader()
|
|
859
861
|
return new Promise(resolve => {
|
|
860
862
|
reader.onload = () => {
|
|
861
|
-
img
|
|
863
|
+
setHref(img, reader.result)
|
|
862
864
|
resolve()
|
|
863
865
|
}
|
|
864
866
|
reader.readAsDataURL(blob)
|
package/core/utilities.js
CHANGED
|
@@ -376,21 +376,22 @@ export const getUrlFromAttr = function (attrVal) {
|
|
|
376
376
|
/**
|
|
377
377
|
* @function module:utilities.getHref
|
|
378
378
|
* @param {Element} elem
|
|
379
|
-
* @returns {string} The given element's `
|
|
379
|
+
* @returns {string} The given element's `href` value
|
|
380
380
|
*/
|
|
381
381
|
export let getHref = function (elem) {
|
|
382
|
-
|
|
382
|
+
// Prefer 'href', fallback to 'xlink:href'
|
|
383
|
+
return elem.getAttribute('href') || elem.getAttributeNS(NS.XLINK, 'href')
|
|
383
384
|
}
|
|
384
385
|
|
|
385
386
|
/**
|
|
386
|
-
* Sets the given element's `
|
|
387
|
+
* Sets the given element's `href` value.
|
|
387
388
|
* @function module:utilities.setHref
|
|
388
389
|
* @param {Element} elem
|
|
389
390
|
* @param {string} val
|
|
390
391
|
* @returns {void}
|
|
391
392
|
*/
|
|
392
393
|
export let setHref = function (elem, val) {
|
|
393
|
-
elem.
|
|
394
|
+
elem.setAttribute('href', val)
|
|
394
395
|
}
|
|
395
396
|
|
|
396
397
|
/**
|
|
@@ -665,38 +666,38 @@ export const getPathDFromElement = function (elem) {
|
|
|
665
666
|
const h = b.height
|
|
666
667
|
num = 4 - num // Why? Because!
|
|
667
668
|
|
|
668
|
-
d =
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
[
|
|
685
|
-
'C',
|
|
669
|
+
d =
|
|
670
|
+
!rx && !ry // Regular rect
|
|
671
|
+
? getPathDFromSegments([
|
|
672
|
+
['M', [x, y]],
|
|
673
|
+
['L', [x + w, y]],
|
|
674
|
+
['L', [x + w, y + h]],
|
|
675
|
+
['L', [x, y + h]],
|
|
676
|
+
['L', [x, y]],
|
|
677
|
+
['Z', []]
|
|
678
|
+
])
|
|
679
|
+
: getPathDFromSegments([
|
|
680
|
+
['M', [x, y + ry]],
|
|
681
|
+
['C', [x, y + ry / num, x + rx / num, y, x + rx, y]],
|
|
682
|
+
['L', [x + w - rx, y]],
|
|
683
|
+
['C', [x + w - rx / num, y, x + w, y + ry / num, x + w, y + ry]],
|
|
684
|
+
['L', [x + w, y + h - ry]],
|
|
686
685
|
[
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
686
|
+
'C',
|
|
687
|
+
[
|
|
688
|
+
x + w,
|
|
689
|
+
y + h - ry / num,
|
|
690
|
+
x + w - rx / num,
|
|
691
|
+
y + h,
|
|
692
|
+
x + w - rx,
|
|
693
|
+
y + h
|
|
694
|
+
]
|
|
695
|
+
],
|
|
696
|
+
['L', [x + rx, y + h]],
|
|
697
|
+
['C', [x + rx / num, y + h, x, y + h - ry / num, x, y + h - ry]],
|
|
698
|
+
['L', [x, y + ry]],
|
|
699
|
+
['Z', []]
|
|
700
|
+
])
|
|
700
701
|
break
|
|
701
702
|
}
|
|
702
703
|
default:
|