svg-path-simplify 0.3.5 → 0.3.6
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/dist/svg-path-simplify.esm.js +4665 -4063
- package/dist/svg-path-simplify.esm.min.js +2 -8
- package/dist/svg-path-simplify.js +4666 -4062
- package/dist/svg-path-simplify.min.js +2 -8
- package/dist/svg-path-simplify.pathdata.esm.js +1090 -1039
- package/dist/svg-path-simplify.pathdata.esm.min.js +2 -8
- package/index.html +118 -33
- package/package.json +1 -1
- package/src/constants.js +3 -1
- package/src/index.js +7 -1
- package/src/pathData_simplify_cubic.js +1 -10
- package/src/pathSimplify-main.js +69 -28
- package/src/pathSimplify-only-pathdata.js +2 -2
- package/src/svg-getAttributes.js +13 -0
- package/src/svg_getViewbox.js +23 -11
- package/src/svg_rootSVG.js +9 -0
- package/src/svgii/convert_colors.js +98 -0
- package/src/svgii/convert_units.js +144 -0
- package/src/svgii/geometry.js +24 -4
- package/src/svgii/geometry_bbox.js +2 -1
- package/src/svgii/geometry_bbox_element.js +46 -0
- package/src/svgii/pathData_analyze.js +1 -1
- package/src/svgii/pathData_convert.js +143 -29
- package/src/svgii/pathData_parse.js +2 -99
- package/src/svgii/pathData_parse_els.js +198 -125
- package/src/svgii/pathData_simplify_refineCorners.js +72 -43
- package/src/svgii/pathData_stringify.js +6 -5
- package/src/svgii/poly_normalize.js +21 -1
- package/src/svgii/rounding.js +36 -5
- package/src/svgii/svg-styles-to-attributes-const.js +8 -3
- package/src/svgii/svg-styles-to-attributes.js +25 -6
- package/src/svgii/svg_cleanup.js +285 -40
- package/src/svgii/svg_el_parse_style_props.js +423 -0
- package/src/svgii/stringify.js +0 -103
package/src/svgii/rounding.js
CHANGED
|
@@ -85,32 +85,63 @@ export function detectAccuracy(pathData) {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
|
|
88
|
+
|
|
89
|
+
|
|
88
90
|
export function roundTo(num = 0, decimals = 3) {
|
|
89
91
|
if (!decimals) return Math.round(num);
|
|
90
92
|
let factor = 10 ** decimals;
|
|
91
93
|
return Math.round(num * factor) / factor;
|
|
92
94
|
}
|
|
93
95
|
|
|
96
|
+
/**
|
|
97
|
+
* round to reasonable
|
|
98
|
+
* floating point accuracy
|
|
99
|
+
* based on numeric value
|
|
100
|
+
*/
|
|
101
|
+
export function autoRound(val, integerThresh = 10){
|
|
102
|
+
let decimals=8;
|
|
103
|
+
|
|
104
|
+
//console.log('val', val);
|
|
105
|
+
|
|
106
|
+
if(val>integerThresh){
|
|
107
|
+
decimals=0
|
|
108
|
+
}
|
|
109
|
+
else if(val>5){
|
|
110
|
+
decimals=1
|
|
111
|
+
}else{
|
|
112
|
+
decimals=Math.ceil(integerThresh/val).toString().length
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let factor = 10 ** decimals;
|
|
116
|
+
return Math.round(val * factor) / factor;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
94
121
|
|
|
95
122
|
/**
|
|
96
123
|
* round path data
|
|
97
124
|
* either by explicit decimal value or
|
|
98
125
|
* based on suggested accuracy in path data
|
|
99
126
|
*/
|
|
100
|
-
export function roundPathData(pathData,
|
|
127
|
+
export function roundPathData(pathData, decimalsGlobal = -1) {
|
|
101
128
|
|
|
102
|
-
if (
|
|
129
|
+
if (decimalsGlobal < 0) return pathData;
|
|
103
130
|
|
|
104
131
|
let len = pathData.length;
|
|
132
|
+
//let decimals = pathData[0].decimals ? pathData[0].decimals+1 : decimalsGlobal
|
|
133
|
+
let decimals = decimalsGlobal
|
|
134
|
+
//decimals = decimalsGlobal;
|
|
135
|
+
//console.log('decimals subpath', decimals, pathData[0].decimals, 'decimalsGlobal', decimalsGlobal);
|
|
105
136
|
|
|
106
137
|
for (let c = 0; c < len; c++) {
|
|
107
|
-
|
|
108
|
-
let values =
|
|
138
|
+
let com = pathData[c];
|
|
139
|
+
let {values} = com
|
|
140
|
+
//let values = pathData[c].values
|
|
109
141
|
let valLen = values.length;
|
|
110
142
|
if (!valLen) continue
|
|
111
143
|
|
|
112
144
|
for (let v = 0; v < valLen; v++) {
|
|
113
|
-
//pathData[c].values[v] = +values[v].toFixed(decimals);
|
|
114
145
|
pathData[c].values[v] = roundTo(values[v], decimals);
|
|
115
146
|
}
|
|
116
147
|
};
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* used to remove unnecessary attribution
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
const shapeEls = [
|
|
7
|
+
export const shapeEls = [
|
|
8
8
|
"polygon",
|
|
9
9
|
"polyline",
|
|
10
10
|
"line",
|
|
@@ -13,17 +13,22 @@ const shapeEls = [
|
|
|
13
13
|
"ellipse",
|
|
14
14
|
]
|
|
15
15
|
|
|
16
|
-
const
|
|
16
|
+
export const horizontalProps = ['x', 'cx', 'rx', 'dx', 'width', 'translateX'];
|
|
17
|
+
export const verticalProps = ['y', 'cy', 'ry', 'dy', 'height', 'translateY'];
|
|
18
|
+
|
|
19
|
+
export const geometryEls = [
|
|
17
20
|
"path",
|
|
18
21
|
...shapeEls
|
|
19
22
|
]
|
|
20
23
|
|
|
21
|
-
const textEls = [
|
|
24
|
+
export const textEls = [
|
|
22
25
|
"textPath",
|
|
23
26
|
"text",
|
|
24
27
|
"tspan",
|
|
25
28
|
]
|
|
26
29
|
|
|
30
|
+
export const strokeAtts = ['stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin','stroke-linecap', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-miterlimit', 'stroke-opacity' ];
|
|
31
|
+
|
|
27
32
|
export const attLookup = {
|
|
28
33
|
|
|
29
34
|
atts: {
|
|
@@ -1,6 +1,26 @@
|
|
|
1
|
-
import { getMatrix, parseCSSTransform } from './svg-styles-getTransforms';
|
|
1
|
+
import { getMatrix, parseCSSTransform, parseTransform } from './svg-styles-getTransforms';
|
|
2
2
|
import { attLookup } from './svg-styles-to-attributes-const';
|
|
3
3
|
|
|
4
|
+
/*
|
|
5
|
+
export function parseStyleProperty(propName='', value=''){
|
|
6
|
+
|
|
7
|
+
let propObj = {}
|
|
8
|
+
//console.log(propName, value);
|
|
9
|
+
|
|
10
|
+
// check for numbers or units
|
|
11
|
+
if(propName==='transform'){
|
|
12
|
+
|
|
13
|
+
//let trans = parseCSSTransform()
|
|
14
|
+
|
|
15
|
+
}
|
|
16
|
+
else if(propName==='d'){
|
|
17
|
+
propObj = {name:propName, value}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return propObj
|
|
21
|
+
|
|
22
|
+
}
|
|
23
|
+
*/
|
|
4
24
|
|
|
5
25
|
|
|
6
26
|
export function getElementProps(el, {
|
|
@@ -84,9 +104,11 @@ export function svgStylesToAttributes(el, {
|
|
|
84
104
|
let cssProps = getElStyleProps(el)
|
|
85
105
|
|
|
86
106
|
// normalize transform attributes
|
|
107
|
+
/*
|
|
87
108
|
if (attProps['transform']) {
|
|
88
109
|
console.log(`attProps['transform']`, attProps['transform']);
|
|
89
110
|
}
|
|
111
|
+
*/
|
|
90
112
|
|
|
91
113
|
// merge properties
|
|
92
114
|
let props = {
|
|
@@ -198,11 +220,7 @@ export function getElAttributes(el) {
|
|
|
198
220
|
}
|
|
199
221
|
|
|
200
222
|
|
|
201
|
-
|
|
202
|
-
return val && isNaN(val) ? val.match(/[^\d|.]+/g)[0] : '';
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
|
|
223
|
+
/*
|
|
206
224
|
function roundValue(value = '', decimals = -1) {
|
|
207
225
|
if (decimals < 0) return value;
|
|
208
226
|
value = value.replace(/["]/g, '').trim()
|
|
@@ -215,3 +233,4 @@ function roundValue(value = '', decimals = -1) {
|
|
|
215
233
|
//console.log('rounded', value)
|
|
216
234
|
return value;
|
|
217
235
|
}
|
|
236
|
+
*/
|
package/src/svgii/svg_cleanup.js
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
|
+
import { getElementAtts } from "../svg-getAttributes";
|
|
1
2
|
import { flattenTransforms } from "../svg_flatten_transforms";
|
|
3
|
+
import { getViewBox } from "../svg_getViewbox";
|
|
4
|
+
import { normalizeUnits } from "./convert_units";
|
|
5
|
+
import { getPathDataVertices } from "./geometry";
|
|
6
|
+
import { checkBBoxIntersections, getPathDataBBox, getPolyBBox } from "./geometry_bbox";
|
|
7
|
+
import { getElBBox } from "./geometry_bbox_element";
|
|
2
8
|
import { parsePathDataString } from "./pathData_parse";
|
|
3
|
-
import {
|
|
9
|
+
import { parsePathDataNormalized } from "./pathData_convert";
|
|
10
|
+
import { pathElToShape, shapeElToPath } from "./pathData_parse_els";
|
|
4
11
|
import { svgStylesToAttributes } from "./svg-styles-to-attributes";
|
|
12
|
+
import { strokeAtts } from "./svg-styles-to-attributes-const";
|
|
13
|
+
import { parseStylesProperties } from "./svg_el_parse_style_props";
|
|
5
14
|
|
|
6
15
|
|
|
7
16
|
export function removeEmptySVGEls(svg) {
|
|
@@ -14,28 +23,41 @@ export function removeEmptySVGEls(svg) {
|
|
|
14
23
|
//const DOMParserPoly = globalThis.DOMParser;
|
|
15
24
|
|
|
16
25
|
export function cleanUpSVG(svgMarkup, {
|
|
17
|
-
returnDom = false,
|
|
18
26
|
removeHidden = true,
|
|
19
|
-
removeUnused = true,
|
|
27
|
+
//removeUnused = true,
|
|
20
28
|
stylesToAttributes = true,
|
|
21
29
|
removePrologue = true,
|
|
22
30
|
removeIds = false,
|
|
23
31
|
removeClassNames = false,
|
|
24
32
|
removeDimensions = false,
|
|
25
|
-
fixHref =
|
|
33
|
+
fixHref = false,
|
|
34
|
+
legacyHref = false,
|
|
35
|
+
cleanupDefs = true,
|
|
36
|
+
cleanupClip = true,
|
|
37
|
+
addViewBox = false,
|
|
38
|
+
addDimensions = false,
|
|
39
|
+
|
|
26
40
|
mergePaths = false,
|
|
41
|
+
removeOffCanvas = true,
|
|
27
42
|
cleanupSVGAtts = true,
|
|
28
43
|
removeNameSpaced = true,
|
|
29
44
|
attributesToGroup = true,
|
|
30
|
-
shapesToPaths = false,
|
|
45
|
+
//shapesToPaths = false,
|
|
46
|
+
shapeConvert = false,
|
|
47
|
+
convert_rects = false,
|
|
48
|
+
convert_ellipses = false,
|
|
49
|
+
convert_poly = false,
|
|
50
|
+
convert_lines=false,
|
|
51
|
+
|
|
31
52
|
convertTransforms = false,
|
|
32
|
-
cleanUpStrokes=true,
|
|
53
|
+
cleanUpStrokes = true,
|
|
33
54
|
decimals = -1,
|
|
34
55
|
excludedEls = [],
|
|
35
56
|
} = {}) {
|
|
36
57
|
|
|
37
58
|
attributesToGroup = cleanupSVGAtts ? true : false;
|
|
38
59
|
|
|
60
|
+
|
|
39
61
|
// replace namespaced refs
|
|
40
62
|
if (fixHref) svgMarkup = svgMarkup.replaceAll("xlink:href=", "href=");
|
|
41
63
|
|
|
@@ -43,22 +65,57 @@ export function cleanUpSVG(svgMarkup, {
|
|
|
43
65
|
.parseFromString(svgMarkup, "text/html")
|
|
44
66
|
.querySelector("svg");
|
|
45
67
|
|
|
68
|
+
let viewBox = getViewBox(svg)
|
|
69
|
+
let { x, y, width, height } = viewBox;
|
|
70
|
+
|
|
46
71
|
|
|
47
72
|
if (cleanupSVGAtts) {
|
|
48
73
|
//console.log('cleanupSVGAtts');
|
|
49
74
|
let allowed = ['viewBox', 'xmlns', 'width', 'height', 'id', 'class', 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin'];
|
|
50
75
|
removeExcludedAttribues(svg, allowed)
|
|
76
|
+
}
|
|
51
77
|
|
|
78
|
+
// add viewBox
|
|
79
|
+
if (addViewBox) addSvgViewBox(svg, { x, y, width, height })
|
|
80
|
+
if (addDimensions) {
|
|
81
|
+
svg.setAttribute('width', width);
|
|
82
|
+
svg.setAttribute('height', height);
|
|
52
83
|
}
|
|
53
84
|
|
|
85
|
+
|
|
86
|
+
// remove unused defs or optimize order
|
|
87
|
+
if (cleanupDefs) cleanupSvgDefs(svg, { x, y, width, height, cleanupClip });
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
// remove off canvas
|
|
91
|
+
if (removeOffCanvas) removeOffCanvasEls(svg, { x, y, width, height });
|
|
92
|
+
|
|
93
|
+
|
|
54
94
|
// always remove scripts
|
|
55
95
|
let removeEls = ['metadata', 'script', ...excludedEls]
|
|
56
96
|
|
|
57
97
|
let els = svg.querySelectorAll('*')
|
|
58
|
-
|
|
98
|
+
|
|
99
|
+
// an array of all elements' properties
|
|
100
|
+
let svgElProps = []
|
|
59
101
|
|
|
60
102
|
let geometryElements = ['polygon', 'polyline', 'line', 'rect', 'circle', 'ellipse']
|
|
61
103
|
|
|
104
|
+
//console.log('shapeConvert', shapeConvert);
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
/** convert paths to shapes */
|
|
108
|
+
if(shapeConvert === 'toShapes'){
|
|
109
|
+
let paths = svg.querySelectorAll('path')
|
|
110
|
+
paths.forEach(path=>{
|
|
111
|
+
let shape = pathElToShape(path, {convert_rects, convert_ellipses, convert_poly, convert_lines})
|
|
112
|
+
path.replaceWith(shape)
|
|
113
|
+
path = shape;
|
|
114
|
+
//console.log('path', path);
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
}
|
|
118
|
+
|
|
62
119
|
|
|
63
120
|
for (let i = 0; i < els.length; i++) {
|
|
64
121
|
let el = els[i];
|
|
@@ -66,8 +123,8 @@ export function cleanUpSVG(svgMarkup, {
|
|
|
66
123
|
let name = el.nodeName.toLowerCase();
|
|
67
124
|
|
|
68
125
|
// convert shapes
|
|
69
|
-
if (
|
|
70
|
-
let path = shapeElToPath(el);
|
|
126
|
+
if (shapeConvert === 'toPaths' && name !== 'path' && geometryElements.includes(name)) {
|
|
127
|
+
let path = shapeElToPath(el, { width, height, convert_rects, convert_ellipses, convert_poly, convert_lines });
|
|
71
128
|
el.replaceWith(path)
|
|
72
129
|
name = 'path'
|
|
73
130
|
el = path;
|
|
@@ -83,19 +140,61 @@ export function cleanUpSVG(svgMarkup, {
|
|
|
83
140
|
continue;
|
|
84
141
|
}
|
|
85
142
|
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* get all style properties
|
|
146
|
+
* convert relative or physical units
|
|
147
|
+
* to user units
|
|
148
|
+
*/
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
/*
|
|
152
|
+
let styleProps = parseStylesProperties(el, {
|
|
153
|
+
width:viewBox.width,
|
|
154
|
+
height:viewBox.height
|
|
155
|
+
})
|
|
156
|
+
*/
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
/*
|
|
160
|
+
let propTest = normalizeUnits('50%',{width:200, height:100, isHorizontal:true})
|
|
161
|
+
console.log('propTest', propTest);
|
|
162
|
+
*/
|
|
163
|
+
|
|
164
|
+
|
|
86
165
|
// styles to attributes
|
|
87
|
-
if (stylesToAttributes || attributesToGroup || mergePaths) {
|
|
166
|
+
if (stylesToAttributes || attributesToGroup || mergePaths || cleanUpStrokes) {
|
|
88
167
|
let propsFiltered = svgStylesToAttributes(el, { removeNameSpaced, decimals })
|
|
89
|
-
if (name === 'path') {
|
|
90
|
-
|
|
91
|
-
}
|
|
168
|
+
//if (name === 'path') {}
|
|
169
|
+
svgElProps.push({ el, name, idx: i, propsFiltered })
|
|
92
170
|
}
|
|
171
|
+
|
|
93
172
|
}
|
|
94
173
|
|
|
95
174
|
|
|
175
|
+
|
|
176
|
+
// remove stroke properties if no stroke color applied - common inkscape issue
|
|
177
|
+
if (cleanUpStrokes) {
|
|
178
|
+
|
|
179
|
+
for (let item of svgElProps) {
|
|
180
|
+
|
|
181
|
+
let { el, propsFiltered } = item;
|
|
182
|
+
let strokeProps = Object.keys(propsFiltered)
|
|
183
|
+
|
|
184
|
+
if (!strokeProps.includes('stroke')) {
|
|
185
|
+
strokeAtts.forEach(att => {
|
|
186
|
+
el.removeAttribute(att)
|
|
187
|
+
|
|
188
|
+
// delete in property object
|
|
189
|
+
if (item['propsFiltered'][att] !== undefined) delete item['propsFiltered'][att]
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
96
195
|
// group styles
|
|
97
196
|
if (attributesToGroup || mergePaths) {
|
|
98
|
-
moveAttributesToGroup(
|
|
197
|
+
moveAttributesToGroup(svgElProps, mergePaths)
|
|
99
198
|
}
|
|
100
199
|
|
|
101
200
|
if (removeDimensions) {
|
|
@@ -116,54 +215,195 @@ export function cleanUpSVG(svgMarkup, {
|
|
|
116
215
|
//console.log('!!!svgMarkup', svgMarkup);
|
|
117
216
|
|
|
118
217
|
|
|
119
|
-
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* refine properties
|
|
221
|
+
* such as transforms or properties including units
|
|
222
|
+
*/
|
|
223
|
+
|
|
224
|
+
/*
|
|
225
|
+
for(let i=0; i<svgElProps.length; i++){
|
|
226
|
+
let item = svgElProps[i];
|
|
227
|
+
let {propsFiltered} = item;
|
|
228
|
+
|
|
229
|
+
for(let prop in propsFiltered){
|
|
230
|
+
|
|
231
|
+
let propOb = parseStyleProperty(prop, propsFiltered[prop])
|
|
232
|
+
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
}
|
|
236
|
+
*/
|
|
237
|
+
|
|
238
|
+
// remove futile clip-paths
|
|
239
|
+
if (cleanupClip) removeFutileClipPaths(svg, { x, y, width, height })
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
// replace href attributes with namespace - required by many older applications
|
|
243
|
+
if (legacyHref) {
|
|
244
|
+
svg.setAttribute('xmlns:xlink', "http://www.w3.org/1999/xlink")
|
|
245
|
+
let hrefs = svg.querySelectorAll('[href]')
|
|
246
|
+
hrefs.forEach(el => {
|
|
247
|
+
let href = el.getAttribute('href')
|
|
248
|
+
el.setAttribute('xlink:href', href)
|
|
249
|
+
el.removeAttribute('href')
|
|
250
|
+
})
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
return { svg, svgElProps }
|
|
256
|
+
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
function removeOffCanvasEls(svg, { x = 0, y = 0, width = 0, height = 0 } = {}) {
|
|
261
|
+
let els = [...svg.querySelectorAll('path, polygon, polyline, line, rect, circle, ellipse, text')];
|
|
262
|
+
els = els.filter(el => !el.parentNode.closest('defs') && !el.parentNode.closest('symbol') && !el.parentNode.closest('clipPath') && !el.parentNode.closest('mask') && !el.parentNode.closest('pattern'))
|
|
263
|
+
//console.log('removeOffCanvasEls', els, width, height);
|
|
264
|
+
|
|
265
|
+
let bb0 = { x, y, width, height }
|
|
266
|
+
bb0.right = x + width
|
|
267
|
+
bb0.bottom = y + height
|
|
268
|
+
|
|
269
|
+
els.forEach(el => {
|
|
270
|
+
let bb = getElBBox(el)
|
|
271
|
+
let outside = bb.right < bb0.x || bb.bottom < bb0.y || bb.x > bb0.right || bb.y > bb.bottom
|
|
272
|
+
if (outside) el.remove();
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function addSvgViewBox(svg, { x = 0, y = 0, width = 0, height = 0 } = {}) {
|
|
278
|
+
if (svg.hasAttribute('viewBox')) return;
|
|
279
|
+
if (!width || !height) {
|
|
280
|
+
({ x, y, width, height } = getViewBox(svg));
|
|
281
|
+
}
|
|
282
|
+
svg.setAttribute('viewBox', [x, y, width, height].join(' '))
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
function cleanupSvgDefs(svg, { x = 0, y = 0, width = 0, height = 0, cleanupClip = true } = {}) {
|
|
120
287
|
let defs = svg.querySelectorAll('defs')
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
288
|
+
let defEls = svg.querySelectorAll('symbol, pattern, linearGradient, radialGradient, clipPath, mask, marker, filter')
|
|
289
|
+
|
|
290
|
+
// no defs to remove/optimize
|
|
291
|
+
if (!defs.length && !defEls.length) return;
|
|
292
|
+
|
|
293
|
+
defs.forEach(def => {
|
|
294
|
+
// remove empty defs
|
|
295
|
+
let children = [...def.children]
|
|
296
|
+
if (!children.length) {
|
|
125
297
|
def.remove()
|
|
126
298
|
}
|
|
299
|
+
// move defs to top
|
|
300
|
+
else {
|
|
301
|
+
svg.insertBefore(def, svg.children[0])
|
|
302
|
+
}
|
|
127
303
|
})
|
|
128
304
|
|
|
305
|
+
//clean up unused defs
|
|
306
|
+
let refIds = new Set([])
|
|
307
|
+
defEls.forEach(def => {
|
|
308
|
+
refIds.add(def.id)
|
|
309
|
+
})
|
|
129
310
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
311
|
+
Array.from(refIds).forEach(id => {
|
|
312
|
+
let els = svg.querySelectorAll(`[href="#${id}"], [xlink\\:href="#${id}"], [clip-path="url(#${id})"], [mask="url(#${id})"], [fill="url(#${id})"], [stroke="url(#${id})"]`);
|
|
313
|
+
|
|
314
|
+
//definition is unused – remove
|
|
315
|
+
if (!els.length) {
|
|
316
|
+
//console.log('remove', id);
|
|
317
|
+
svg.getElementById(id).remove()
|
|
318
|
+
}
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
// remove futile clip-paths
|
|
322
|
+
//if (cleanupClip) removeFutileClipPaths(svg, {x, y, width, height})
|
|
323
|
+
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
function removeFutileClipPaths(svg, { x = 0, y = 0, width = 0, height = 0 } = {}) {
|
|
328
|
+
let clipPaths = svg.querySelectorAll('clipPath');
|
|
329
|
+
|
|
330
|
+
if (!clipPaths.length) return
|
|
331
|
+
|
|
332
|
+
if (!width || !height) {
|
|
333
|
+
({ x, y, width, height } = getViewBox(svg));
|
|
133
334
|
}
|
|
134
|
-
*/
|
|
135
335
|
|
|
336
|
+
clipPaths.forEach(clip => {
|
|
337
|
+
let children = [...clip.children];
|
|
338
|
+
if (children.length > 1) return;
|
|
339
|
+
|
|
340
|
+
let clipEl = children[0]
|
|
341
|
+
let type = clipEl.nodeName.toLowerCase();
|
|
342
|
+
|
|
343
|
+
if (type === 'path' || type === 'rect') {
|
|
344
|
+
let bb = { x: 0, y: 0, width: 0, height: 0 }
|
|
136
345
|
|
|
137
|
-
|
|
138
|
-
|
|
346
|
+
if (type === 'path') {
|
|
347
|
+
let pathData = parsePathDataNormalized(clipEl.getAttribute('d'));
|
|
348
|
+
let coms = Array.from(new Set(pathData.map(com => com.type.toLowerCase()))).join('');
|
|
349
|
+
let isPolygon = !(/[acqts]/gi).test(coms);
|
|
139
350
|
|
|
140
|
-
|
|
351
|
+
// path is too complex - unlikely to be a rectangle
|
|
352
|
+
if (!isPolygon || pathData.length > 5) return
|
|
141
353
|
|
|
354
|
+
let vertices = getPathDataVertices(pathData)
|
|
355
|
+
bb = getPolyBBox(vertices)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
else if (type === 'rect') {
|
|
359
|
+
bb = { x: +clipEl.getAttribute('x'), y: +clipEl.getAttribute('y'), width: +clipEl.getAttribute('width'), height: +clipEl.getAttribute('height') }
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// is futile if clip path's bbox equals the SVG's viewBox
|
|
363
|
+
if (bb.x === x && bb.y === y && bb.width === width && bb.height === height) {
|
|
364
|
+
clip.remove();
|
|
365
|
+
let clippedEls = svg.querySelectorAll(`[clip-path="url(#${clip.id})"]`);
|
|
366
|
+
//console.log('clippedEls', clippedEls);
|
|
367
|
+
clippedEls.forEach(clipped => {
|
|
368
|
+
clipped.removeAttribute('clip-path')
|
|
369
|
+
})
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
})
|
|
142
373
|
|
|
143
|
-
return markup;
|
|
144
374
|
}
|
|
145
375
|
|
|
146
376
|
|
|
147
|
-
function moveAttributesToGroup(elProps = [], mergePaths = true) {
|
|
148
377
|
|
|
149
|
-
|
|
378
|
+
function moveAttributesToGroup(svgElProps = [], mergePaths = true) {
|
|
379
|
+
|
|
380
|
+
let combine = [[svgElProps[0]]]
|
|
150
381
|
let idx = 0;
|
|
151
382
|
let lastProps = '';
|
|
152
|
-
let l =
|
|
153
|
-
let itemsWithProps =
|
|
383
|
+
let l = svgElProps.length;
|
|
384
|
+
let itemsWithProps = svgElProps.filter(item => item.propstr)
|
|
154
385
|
let path0;
|
|
155
386
|
|
|
156
387
|
|
|
157
388
|
// merge paths without properties
|
|
389
|
+
let dCombined = ''
|
|
158
390
|
if (!itemsWithProps.length && mergePaths) {
|
|
159
|
-
let
|
|
160
|
-
|
|
161
|
-
let
|
|
391
|
+
let path0 = null;
|
|
392
|
+
|
|
393
|
+
for (let i = 0; i < l; i++) {
|
|
394
|
+
let item = svgElProps[i]
|
|
395
|
+
if (item.name !== 'path') continue;
|
|
396
|
+
let remove = true;
|
|
397
|
+
|
|
162
398
|
|
|
163
|
-
for (let i = 1; i < l; i++) {
|
|
164
|
-
let item = elProps[i]
|
|
165
399
|
let path = item.el;
|
|
166
400
|
|
|
401
|
+
// set 1st path
|
|
402
|
+
if (!path0) {
|
|
403
|
+
path0 = path;
|
|
404
|
+
remove = false;
|
|
405
|
+
}
|
|
406
|
+
|
|
167
407
|
let d = item.propsFiltered.d
|
|
168
408
|
let isAbs = d.startsWith('M')
|
|
169
409
|
let dAbs = isAbs ? d : parsePathDataString(d).pathData.map(com => `${com.type} ${com.values.join(' ')}`).join(' ')
|
|
@@ -171,17 +411,18 @@ function moveAttributesToGroup(elProps = [], mergePaths = true) {
|
|
|
171
411
|
dCombined += dAbs;
|
|
172
412
|
|
|
173
413
|
// delete path el
|
|
174
|
-
path.remove();
|
|
414
|
+
if (remove) path.remove();
|
|
175
415
|
}
|
|
176
416
|
|
|
177
|
-
|
|
417
|
+
//console.log('dCombined', dCombined);
|
|
418
|
+
if (path0) path0.setAttribute('d', dCombined)
|
|
178
419
|
return
|
|
179
420
|
}
|
|
180
421
|
|
|
181
422
|
|
|
182
423
|
// add to combine chunks
|
|
183
424
|
for (let i = 0; i < l; i++) {
|
|
184
|
-
let item =
|
|
425
|
+
let item = svgElProps[i];
|
|
185
426
|
let props = item.propsFiltered;
|
|
186
427
|
let propstr = [];
|
|
187
428
|
for (let prop in props) {
|
|
@@ -256,6 +497,8 @@ function moveAttributesToGroup(elProps = [], mergePaths = true) {
|
|
|
256
497
|
|
|
257
498
|
let dAbs = isAbs ? d : parsePathDataString(d).pathData.map(com => `${com.type} ${com.values.join(' ')}`).join(' ')
|
|
258
499
|
|
|
500
|
+
console.log('dAbs', dAbs);
|
|
501
|
+
|
|
259
502
|
//console.log(isAbs, dAbs);
|
|
260
503
|
// concat pathdata string
|
|
261
504
|
dCombined += dAbs;
|
|
@@ -333,7 +576,9 @@ export function stringifySVG(svg, omitNamespace = false) {
|
|
|
333
576
|
//.replace(/ +/g, ' ')
|
|
334
577
|
.replace(/> </g, '><')
|
|
335
578
|
.trim()
|
|
336
|
-
|
|
579
|
+
// sanitize linebreaks within pathdata
|
|
580
|
+
.replaceAll(' ', '\n');
|
|
581
|
+
|
|
337
582
|
|
|
338
583
|
return markup
|
|
339
584
|
}
|