svg-path-simplify 0.4.2 → 0.4.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/CHANGELOG.md +21 -0
- package/README.md +7 -4
- package/dist/svg-path-simplify.esm.js +3593 -1279
- package/dist/svg-path-simplify.esm.min.js +2 -2
- package/dist/svg-path-simplify.js +3594 -1278
- package/dist/svg-path-simplify.min.js +2 -2
- package/dist/svg-path-simplify.pathdata.esm.js +1017 -538
- package/dist/svg-path-simplify.pathdata.esm.min.js +2 -2
- package/dist/svg-path-simplify.poly.cjs +9 -8
- package/docs/privacy-webapp.md +24 -0
- package/index.html +331 -152
- package/package.json +1 -1
- package/src/constants.js +4 -0
- package/src/css_parse.js +317 -0
- package/src/detect_input.js +76 -28
- package/src/index.js +8 -0
- package/src/pathData_simplify_cubic.js +26 -16
- package/src/pathData_simplify_harmonize_cpts.js +77 -1
- package/src/pathData_simplify_revertToquadratics.js +0 -1
- package/src/pathSimplify-main.js +304 -276
- package/src/pathSimplify-only-pathdata.js +7 -2
- package/src/pathSimplify-presets.js +254 -0
- package/src/poly-fit-curve-schneider.js +14 -7
- package/src/simplify_poly_RC.js +102 -0
- package/src/simplify_poly_RDP.js +109 -1
- package/src/simplify_poly_radial_distance.js +3 -3
- package/src/string_helpers.js +130 -4
- package/src/svg-getAttributes.js +4 -2
- package/src/svgii/convert_units.js +1 -1
- package/src/svgii/geometry.js +322 -5
- package/src/svgii/geometry_bbox_element.js +1 -1
- package/src/svgii/geometry_deduceRadius.js +116 -27
- package/src/svgii/geometry_length.js +253 -0
- package/src/svgii/pathData_analyze.js +18 -0
- package/src/svgii/pathData_convert.js +193 -89
- package/src/svgii/pathData_fix_directions.js +12 -14
- package/src/svgii/pathData_fromPoly.js +3 -3
- package/src/svgii/pathData_getLength.js +86 -0
- package/src/svgii/pathData_parse.js +2 -0
- package/src/svgii/pathData_parse_els.js +66 -68
- package/src/svgii/pathData_reorder.js +122 -16
- package/src/svgii/pathData_simplify_refineCorners.js +130 -35
- package/src/svgii/pathData_simplify_refine_round.js +420 -0
- package/src/svgii/pathData_split_to_groups.js +168 -0
- package/src/svgii/pathData_stringify.js +26 -64
- package/src/svgii/pathData_toPolygon.js +3 -4
- package/src/svgii/poly_analyze.js +61 -0
- package/src/svgii/poly_normalize.js +11 -2
- package/src/svgii/poly_to_pathdata.js +85 -24
- package/src/svgii/rounding.js +80 -78
- package/src/svgii/svg_cleanup.js +421 -619
- package/src/svgii/svg_cleanup_convertPathLength.js +39 -0
- package/src/svgii/svg_cleanup_general_svg_atts.js +97 -0
- package/src/svgii/svg_cleanup_normalize_transforms.js +83 -0
- package/src/svgii/svg_cleanup_remove_els_and_atts.js +77 -0
- package/src/svgii/svg_cleanup_ungroup.js +36 -0
- package/src/svgii/svg_el_parse_style_props.js +72 -47
- package/src/svgii/svg_getElementLength.js +67 -0
- package/src/svgii/svg_validate.js +220 -0
- package/tests/testSVG.js +14 -1
- package/src/svgii/pathData_refine_round.js +0 -222
package/package.json
CHANGED
package/src/constants.js
CHANGED
|
@@ -7,6 +7,8 @@ export const rad2Deg = 180/Math.PI
|
|
|
7
7
|
export const deg2rad = Math.PI/180
|
|
8
8
|
export const root2 = 1.4142135623730951
|
|
9
9
|
export const svgNs = 'http://www.w3.org/2000/svg'
|
|
10
|
+
export const dummySVG = `<svg id="svgInvalid" xmlns="${svgNs}" viewBox="0 0 1 1"><path d="M0 0 h0" /></svg>`
|
|
11
|
+
|
|
10
12
|
|
|
11
13
|
// 1/2.54
|
|
12
14
|
export const inch2cm = 0.39370078;
|
|
@@ -14,3 +16,5 @@ export const inch2cm = 0.39370078;
|
|
|
14
16
|
// 1/72
|
|
15
17
|
export const inch2pt = 0.01388889;
|
|
16
18
|
|
|
19
|
+
|
|
20
|
+
|
package/src/css_parse.js
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { parseValue } from "./svgii/svg_el_parse_style_props";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Parse nested CSS text into a flat object structure
|
|
5
|
+
* Supports arbitrary nesting depth and & parent selector reference
|
|
6
|
+
* Respects !important modifiers and handles data URLs
|
|
7
|
+
*/
|
|
8
|
+
export function parseSvgCss(css, {
|
|
9
|
+
parent=null,
|
|
10
|
+
removeUnused=true,
|
|
11
|
+
flatten = true
|
|
12
|
+
}={}) {
|
|
13
|
+
|
|
14
|
+
let type = typeof css
|
|
15
|
+
if(type==='string') removeUnused = false;
|
|
16
|
+
|
|
17
|
+
// get style element text content
|
|
18
|
+
if(type!=='string' ){
|
|
19
|
+
if(css.nodeName==='style'){
|
|
20
|
+
css = css.innerHTML;
|
|
21
|
+
}
|
|
22
|
+
else if(css.nodeName==='svg'){
|
|
23
|
+
let styleEl = css.querySelector('style')
|
|
24
|
+
if(!styleEl) return {}
|
|
25
|
+
parent = css;
|
|
26
|
+
css = styleEl.innerHTML;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
//invalid input
|
|
30
|
+
else{
|
|
31
|
+
console.warn('invalid CSS input')
|
|
32
|
+
return {}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
css = css.trim();
|
|
37
|
+
if (!css) return {};
|
|
38
|
+
|
|
39
|
+
// Remove comments
|
|
40
|
+
css = css.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
function parseBlock(text, parentSelector = "") {
|
|
44
|
+
let i = 0;
|
|
45
|
+
let rules = {};
|
|
46
|
+
let l = text.length
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
while (i < l) {
|
|
50
|
+
// Skip whitespace
|
|
51
|
+
while (/\s/.test(text[i])) i++;
|
|
52
|
+
if (i >= l) break;
|
|
53
|
+
|
|
54
|
+
// Peek ahead to check if this is a selector or a declaration
|
|
55
|
+
let peekIdx = i;
|
|
56
|
+
let isSelector = false;
|
|
57
|
+
|
|
58
|
+
// Look for '{' before ';' to determine if it's a selector
|
|
59
|
+
while (peekIdx < l && text[peekIdx] !== ";") {
|
|
60
|
+
if (text[peekIdx] === "{") {
|
|
61
|
+
isSelector = true;
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
peekIdx++;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!isSelector) {
|
|
68
|
+
// It's a declaration, skip it (will be handled below)
|
|
69
|
+
i = peekIdx + 1;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Read selector (up to '{')
|
|
74
|
+
let selector = "";
|
|
75
|
+
while (i < l && text[i] !== "{") {
|
|
76
|
+
selector += text[i];
|
|
77
|
+
i++;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
selector = selector.trim();
|
|
81
|
+
if (!selector || text[i] !== "{") continue;
|
|
82
|
+
|
|
83
|
+
i++; // skip '{'
|
|
84
|
+
|
|
85
|
+
// Find matching closing brace
|
|
86
|
+
let blockContent = "";
|
|
87
|
+
let depth = 1;
|
|
88
|
+
|
|
89
|
+
while (i < l && depth > 0) {
|
|
90
|
+
if (text[i] === "{") depth++;
|
|
91
|
+
else if (text[i] === "}") depth--;
|
|
92
|
+
|
|
93
|
+
if (depth > 0) blockContent += text[i];
|
|
94
|
+
i++;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Compose full selector
|
|
98
|
+
let fullSelector = selector;
|
|
99
|
+
if (parentSelector) {
|
|
100
|
+
if (selector.includes("&")) {
|
|
101
|
+
fullSelector = selector.replace(/&/g, parentSelector);
|
|
102
|
+
} else {
|
|
103
|
+
fullSelector = parentSelector + " " + selector;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
fullSelector = fullSelector.replace(/\s+/g, " ").trim();
|
|
107
|
+
|
|
108
|
+
// Separate declarations from nested rules
|
|
109
|
+
let { declarations, hasNested } = extractDeclarations(blockContent, fullSelector);
|
|
110
|
+
|
|
111
|
+
// Add declarations for this selector (respect !important)
|
|
112
|
+
if (Object.keys(declarations).length) {
|
|
113
|
+
if (!rules[fullSelector]) {
|
|
114
|
+
rules[fullSelector] = declarations;
|
|
115
|
+
} else {
|
|
116
|
+
// Merge declarations, preserving !important
|
|
117
|
+
for (let prop in declarations) {
|
|
118
|
+
let existingValue = rules[fullSelector][prop];
|
|
119
|
+
let newValue = declarations[prop];
|
|
120
|
+
|
|
121
|
+
// Only override if existing doesn't have !important, or new has !important
|
|
122
|
+
let existingHasImportant =
|
|
123
|
+
existingValue && existingValue.includes("!important");
|
|
124
|
+
let newHasImportant = newValue.includes("!important");
|
|
125
|
+
|
|
126
|
+
if (!existingHasImportant || newHasImportant) {
|
|
127
|
+
rules[fullSelector][prop] = newValue;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// If block contains nested rules, parse them recursively
|
|
134
|
+
if (hasNested) {
|
|
135
|
+
parseBlock(blockContent, fullSelector);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return rules
|
|
140
|
+
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function extractDeclarations(content) {
|
|
144
|
+
let declarations = {};
|
|
145
|
+
let i = 0;
|
|
146
|
+
let l= content.length;
|
|
147
|
+
let hasNested = false;
|
|
148
|
+
|
|
149
|
+
while (i < l) {
|
|
150
|
+
// Skip whitespace
|
|
151
|
+
while (i < l && /\s/.test(content[i])) i++;
|
|
152
|
+
if (i >= l) break;
|
|
153
|
+
|
|
154
|
+
// Check if next thing is a nested selector or a declaration
|
|
155
|
+
let checkIdx = i;
|
|
156
|
+
let isNested = false;
|
|
157
|
+
|
|
158
|
+
// Scan until we hit ':' or '{' or ';'
|
|
159
|
+
while (checkIdx < l) {
|
|
160
|
+
if (content[checkIdx] === "{") {
|
|
161
|
+
isNested = true;
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
if (content[checkIdx] === ":") {
|
|
165
|
+
// It's a declaration
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
if (content[checkIdx] === ";") {
|
|
169
|
+
// Empty or malformed
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
checkIdx++;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (isNested) {
|
|
176
|
+
// Skip nested rule (will be handled by recursive call)
|
|
177
|
+
hasNested = true;
|
|
178
|
+
// Skip to closing brace of this nested rule
|
|
179
|
+
let depth = 0;
|
|
180
|
+
while (i < l) {
|
|
181
|
+
if (content[i] === "{") depth++;
|
|
182
|
+
if (content[i] === "}") depth--;
|
|
183
|
+
i++;
|
|
184
|
+
if (depth === 0) break;
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
// It's a declaration, read until ';' (but respect url() and quotes)
|
|
188
|
+
let decl = "";
|
|
189
|
+
let inUrl = false;
|
|
190
|
+
let inQuotes = false;
|
|
191
|
+
let quoteChar = "";
|
|
192
|
+
|
|
193
|
+
while (i < l) {
|
|
194
|
+
let char = content[i];
|
|
195
|
+
let nextChar = content[i + 1];
|
|
196
|
+
|
|
197
|
+
// Track if we're inside url()
|
|
198
|
+
if (
|
|
199
|
+
char === "u" &&
|
|
200
|
+
nextChar === "r" &&
|
|
201
|
+
content.slice(i, i + 4) === "url("
|
|
202
|
+
) {
|
|
203
|
+
inUrl = true;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Track quotes
|
|
207
|
+
if (
|
|
208
|
+
(char === '"' || char === "'") &&
|
|
209
|
+
(i === 0 || content[i - 1] !== "\\")
|
|
210
|
+
) {
|
|
211
|
+
if (!inQuotes) {
|
|
212
|
+
inQuotes = true;
|
|
213
|
+
quoteChar = char;
|
|
214
|
+
} else if (char === quoteChar) {
|
|
215
|
+
inQuotes = false;
|
|
216
|
+
quoteChar = "";
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Check for end of url()
|
|
221
|
+
if (inUrl && char === ")" && !inQuotes) {
|
|
222
|
+
inUrl = false;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Only break on semicolon if we're not inside url() or quotes
|
|
226
|
+
if (char === ";" && !inUrl && !inQuotes) {
|
|
227
|
+
i++; // skip ';'
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
decl += char;
|
|
232
|
+
i++;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
decl = decl.trim();
|
|
236
|
+
if (decl) {
|
|
237
|
+
let colonIdx = decl.indexOf(":");
|
|
238
|
+
if (colonIdx > -1) {
|
|
239
|
+
let prop = decl.substring(0, colonIdx).trim();
|
|
240
|
+
let value = decl.substring(colonIdx + 1).trim();
|
|
241
|
+
if (prop && value) {
|
|
242
|
+
//console.log('selector', selector, isId);
|
|
243
|
+
//declarations[prop] = isId && !value.includes('!important') ? value+'!important' : value;
|
|
244
|
+
declarations[prop] = value;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return { declarations, hasNested };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
let rules = parseBlock(css);
|
|
255
|
+
if(parent && removeUnused) rules = removeUnusedSelectors(parent, rules)
|
|
256
|
+
if(flatten) rules = flattenCssProps(rules)
|
|
257
|
+
|
|
258
|
+
// emulate specificity: prioritize ids and important
|
|
259
|
+
let rulesID = {};
|
|
260
|
+
let rulesImportant = {};
|
|
261
|
+
for(let rule in rules){
|
|
262
|
+
if(rule.startsWith('#')){
|
|
263
|
+
rulesID[rule] = rules[rule]
|
|
264
|
+
delete rules[rule];
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
for(let prop in rules[rule]){
|
|
268
|
+
let val = rules[rule][prop]
|
|
269
|
+
if(val.includes('!important')){
|
|
270
|
+
if(!rulesImportant[rule]) rulesImportant[rule]={}
|
|
271
|
+
rulesImportant[rule][prop] = val
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
rules= {
|
|
277
|
+
...rules,
|
|
278
|
+
...rulesID,
|
|
279
|
+
...rulesImportant
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return rules;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function flattenCssProps(rules) {
|
|
286
|
+
for (let selector in rules) {
|
|
287
|
+
let targets = selector.split(/,/).map((sel) => sel.trim());
|
|
288
|
+
let values = rules[selector];
|
|
289
|
+
if (targets.length > 1) {
|
|
290
|
+
targets.forEach((target) => {
|
|
291
|
+
let props = rules[target];
|
|
292
|
+
for (let prop in props) {
|
|
293
|
+
let value = props[prop];
|
|
294
|
+
if (!value.includes("!important")) {
|
|
295
|
+
rules[target][prop] = value;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
delete rules[selector];
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return rules;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
function removeUnusedSelectors(parent=null, props={}){
|
|
307
|
+
let selectors = Object.keys(props);
|
|
308
|
+
selectors.forEach(selector=>{
|
|
309
|
+
let el = parent.querySelector(selector)
|
|
310
|
+
// remove
|
|
311
|
+
if(!el && selector!==':root') {
|
|
312
|
+
//console.log( selector, 'doesnt exist')
|
|
313
|
+
delete props[selector]
|
|
314
|
+
}
|
|
315
|
+
})
|
|
316
|
+
return props
|
|
317
|
+
}
|
package/src/detect_input.js
CHANGED
|
@@ -1,78 +1,126 @@
|
|
|
1
|
+
import { dummySVG, svgNs } from "./constants";
|
|
2
|
+
import { validateSVG } from "./svgii/svg_validate";
|
|
3
|
+
|
|
1
4
|
export function detectInputType(input) {
|
|
2
5
|
let type = 'string';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
let log = '';
|
|
7
|
+
let isValid = true;
|
|
8
|
+
|
|
9
|
+
let result = {
|
|
10
|
+
inputType:'',
|
|
11
|
+
isValid:true,
|
|
12
|
+
fileReport:{},
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
11
16
|
if (Array.isArray(input)) {
|
|
12
17
|
|
|
18
|
+
result.inputType = "array";
|
|
19
|
+
|
|
20
|
+
|
|
13
21
|
// nested array
|
|
14
22
|
if (Array.isArray(input[0])) {
|
|
15
23
|
//console.log('is array', input[0], input[0][0])
|
|
16
24
|
|
|
17
|
-
if(input[0].length===2){
|
|
25
|
+
if (input[0].length === 2) {
|
|
18
26
|
//console.log('is single poly value array')
|
|
19
|
-
|
|
27
|
+
result.inputType = 'polyArray'
|
|
20
28
|
}
|
|
21
29
|
|
|
22
|
-
else if (Array.isArray(input[0][0]) && input[0][0].length === 2
|
|
30
|
+
else if (Array.isArray(input[0][0]) && input[0][0].length === 2) {
|
|
23
31
|
//console.log('is complex poly point value array', input[0][0])
|
|
24
|
-
|
|
32
|
+
result.inputType = 'polyComplexArray'
|
|
25
33
|
}
|
|
26
34
|
else if (input[0][0].x !== undefined && input[0][0].y !== undefined) {
|
|
27
35
|
//console.log('is nested point object array')
|
|
28
|
-
|
|
36
|
+
result.inputType = 'polyComplexObjectArray'
|
|
29
37
|
}
|
|
38
|
+
//return result
|
|
30
39
|
}
|
|
31
40
|
|
|
32
41
|
// is point array
|
|
33
|
-
else if (input[0].x!==undefined && input[0].y!==undefined) {
|
|
42
|
+
else if (input[0].x !== undefined && input[0].y !== undefined) {
|
|
34
43
|
//console.log('is nested point object array')
|
|
35
|
-
|
|
44
|
+
result.inputType = 'polyObjectArray'
|
|
36
45
|
}
|
|
37
46
|
|
|
38
47
|
// path data array
|
|
39
48
|
else if (input[0]?.type && input[0]?.values
|
|
40
49
|
) {
|
|
41
|
-
|
|
42
|
-
|
|
50
|
+
result.inputType = "pathData"
|
|
43
51
|
}
|
|
44
|
-
|
|
45
|
-
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
return result;
|
|
46
55
|
}
|
|
47
56
|
|
|
48
57
|
if (typeof input === "string") {
|
|
49
58
|
input = input.trim();
|
|
50
59
|
let isSVG = input.includes('<svg') && input.includes('</svg');
|
|
60
|
+
let isSymbol = input.startsWith('<symbol') && input.includes('</symbol');
|
|
51
61
|
let isPathData = input.startsWith('M') || input.startsWith('m');
|
|
52
62
|
let isPolyString = !isNaN(input.substring(0, 1)) && !isNaN(input.substring(input.length - 1, input.length))
|
|
53
|
-
|
|
63
|
+
let isJson = isNumberJson(input)
|
|
64
|
+
//console.log('isNumberJson', isJson);
|
|
54
65
|
|
|
55
66
|
if (isSVG) {
|
|
56
|
-
|
|
67
|
+
let validate = validateSVG(input);
|
|
68
|
+
({isValid, log} = validate) ;
|
|
69
|
+
if(!isValid){
|
|
70
|
+
//input = dummySVG
|
|
71
|
+
result.inputType = 'invalid'
|
|
72
|
+
result.isValid=false,
|
|
73
|
+
//result.log = JSON.stringify(log, null, ' ')
|
|
74
|
+
result.log = log
|
|
75
|
+
}else{
|
|
76
|
+
result.inputType = 'svgMarkup'
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
result.fileReport = validate.fileReport
|
|
80
|
+
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
else if (isJson) {
|
|
84
|
+
result.inputType = 'json'
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
else if (isSymbol) {
|
|
88
|
+
result.inputType = 'symbol'
|
|
57
89
|
}
|
|
58
90
|
else if (isPathData) {
|
|
59
|
-
|
|
91
|
+
result.inputType = 'pathDataString'
|
|
60
92
|
}
|
|
61
93
|
else if (isPolyString) {
|
|
62
|
-
|
|
94
|
+
result.inputType = 'polyString'
|
|
63
95
|
}
|
|
64
96
|
|
|
65
97
|
else {
|
|
66
98
|
let url = /^(file:|https?:\/\/|\/|\.\/|\.\.\/)/.test(input);
|
|
67
99
|
let dataUrl = input.startsWith('data:image');
|
|
68
|
-
|
|
100
|
+
result.inputType = url || dataUrl ? "url" : "string";
|
|
69
101
|
}
|
|
70
102
|
|
|
71
|
-
return
|
|
103
|
+
return result
|
|
72
104
|
}
|
|
73
105
|
|
|
74
|
-
|
|
75
|
-
let constructor = input.constructor.name
|
|
106
|
+
result.inputType = (input.constructor.name || typeof input ).toLowerCase()
|
|
76
107
|
|
|
77
|
-
return
|
|
108
|
+
return result;
|
|
78
109
|
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
function isNumberJson(str) {
|
|
113
|
+
|
|
114
|
+
str = str.trim();
|
|
115
|
+
|
|
116
|
+
let hasNumber = /\d/.test(str)
|
|
117
|
+
let hasInvalid = /[abcdfghijklmnopqrstuvwz]/gi.test(str)
|
|
118
|
+
if (!hasNumber || hasInvalid) return false
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
// is JSON like
|
|
122
|
+
let isJson = str.startsWith('[') && str.endsWith(']');
|
|
123
|
+
|
|
124
|
+
return isJson
|
|
125
|
+
|
|
126
|
+
}
|
package/src/index.js
CHANGED
|
@@ -44,11 +44,19 @@ export{getElementTransform as getElementTransform};
|
|
|
44
44
|
import {getViewBox} from './svg_getViewbox.js';
|
|
45
45
|
export{getViewBox as getViewBox};
|
|
46
46
|
|
|
47
|
+
import { validateSVG } from './svgii/svg_validate.js';
|
|
48
|
+
export { validateSVG as validateSVG};
|
|
49
|
+
import { detectInputType } from './detect_input.js';
|
|
50
|
+
export { detectInputType as detectInputType};
|
|
51
|
+
|
|
52
|
+
|
|
47
53
|
|
|
48
54
|
// IIFE
|
|
49
55
|
if (typeof window !== 'undefined') {
|
|
50
56
|
window.svgPathSimplify = svgPathSimplify;
|
|
51
57
|
window.getElementTransform = getElementTransform;
|
|
58
|
+
window.validateSVG = validateSVG;
|
|
59
|
+
window.detectInputType = detectInputType;
|
|
52
60
|
//window.simplifyPolySchneider = simplifyPolySchneider;
|
|
53
61
|
//window.getPathDataFromEl = getPathDataFromEl;
|
|
54
62
|
//window.parsePathDataString = parsePathDataString;
|
|
@@ -143,10 +143,10 @@ export function combineCubicPairs(com1, com2, {
|
|
|
143
143
|
let comS = getExtrapolatedCommand(com1, com2, t)
|
|
144
144
|
|
|
145
145
|
// test new point-at-t against original mid segment starting point
|
|
146
|
-
let
|
|
146
|
+
let ptI = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t)
|
|
147
147
|
|
|
148
148
|
|
|
149
|
-
let dist0 = getDistManhattan(com1.p,
|
|
149
|
+
let dist0 = getDistManhattan(com1.p, ptI)
|
|
150
150
|
let dist1 = 0, dist2 = 0;
|
|
151
151
|
let close = dist0 < maxDist;
|
|
152
152
|
let success = false;
|
|
@@ -162,32 +162,42 @@ export function combineCubicPairs(com1, com2, {
|
|
|
162
162
|
* to prevent distortions
|
|
163
163
|
*/
|
|
164
164
|
|
|
165
|
-
//
|
|
166
|
-
let
|
|
165
|
+
// 1st segment mid
|
|
166
|
+
let ptM_seg1 = pointAtT([com1.p0, com1.cp1, com1.cp2, com1.p], 0.5)
|
|
167
|
+
|
|
168
|
+
let t2 = t * 0.5;
|
|
169
|
+
// combined interpolated mid point
|
|
170
|
+
let ptI_seg1 = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t2)
|
|
171
|
+
dist1 = getDistManhattan(ptM_seg1, ptI_seg1)
|
|
167
172
|
|
|
168
|
-
// simplified path
|
|
169
|
-
let t3 = (1 + t) * 0.5;
|
|
170
|
-
let ptS_2 = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t3)
|
|
171
|
-
dist1 = getDistManhattan(pt_2, ptS_2)
|
|
172
173
|
|
|
173
174
|
error += dist1;
|
|
174
175
|
|
|
175
176
|
if (dist1 < maxDist) {
|
|
176
177
|
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
// 1st segment mid
|
|
181
|
-
let pt_1 = pointAtT([com1.p0, com1.cp1, com1.cp2, com1.p], 0.5)
|
|
178
|
+
// 2nd segment mid
|
|
179
|
+
let ptM_seg2 = pointAtT([com2.p0, com2.cp1, com2.cp2, com2.p], 0.5)
|
|
182
180
|
|
|
183
|
-
|
|
184
|
-
let
|
|
185
|
-
|
|
181
|
+
// simplified path
|
|
182
|
+
let t3 = (1 + t) * 0.5;
|
|
183
|
+
let ptI_seg2 = pointAtT([comS.p0, comS.cp1, comS.cp2, comS.p], t3)
|
|
184
|
+
dist2 = getDistManhattan(ptM_seg2, ptI_seg2)
|
|
186
185
|
|
|
187
186
|
error += dist2;
|
|
188
187
|
|
|
189
188
|
if (error < maxDist) success = true;
|
|
190
189
|
|
|
190
|
+
|
|
191
|
+
/*
|
|
192
|
+
renderPoint(markers, ptM_seg1, 'cyan')
|
|
193
|
+
renderPoint(markers, pt, 'orange', '1.5%', '1')
|
|
194
|
+
renderPoint(markers, ptM_seg2, 'orange')
|
|
195
|
+
|
|
196
|
+
renderPoint(markers, com1.p, 'green')
|
|
197
|
+
//renderPoint(markers, com2.p, 'green')
|
|
198
|
+
renderPoint(markers, ptI_seg1, 'purple')
|
|
199
|
+
*/
|
|
200
|
+
|
|
191
201
|
}
|
|
192
202
|
|
|
193
203
|
} // end 1st try
|
|
@@ -1,5 +1,81 @@
|
|
|
1
1
|
import { checkLineIntersection, getDistManhattan, interpolate, pointAtT } from "./svgii/geometry";
|
|
2
|
-
import { renderPoint } from "./svgii/visualize";
|
|
2
|
+
import { renderPoint, renderPoly } from "./svgii/visualize";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export function fixIntersectingCpts(pathData = []) {
|
|
6
|
+
|
|
7
|
+
let l = pathData.length;
|
|
8
|
+
let p0 = { x: pathData[0].values[0], y: pathData[0].values[1] }
|
|
9
|
+
let p = p0
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
for (let i = 1; i < l; i++) {
|
|
13
|
+
let com = pathData[i]
|
|
14
|
+
let comPrev = pathData[i - 1] || null
|
|
15
|
+
let { type, values } = com
|
|
16
|
+
let comN = pathData[i + 1] ? pathData[i + 1] : null;
|
|
17
|
+
let adjust = false;
|
|
18
|
+
|
|
19
|
+
//comN && comN.type==='C' &&
|
|
20
|
+
if (type === 'C') {
|
|
21
|
+
let tx = 1.2;
|
|
22
|
+
let cp1 = { x: values[0], y: values[1] }
|
|
23
|
+
p = { x: values[4], y: values[5] }
|
|
24
|
+
let cp2 = { x: values[2], y: values[3] }
|
|
25
|
+
|
|
26
|
+
let cp1_ex = interpolate(p0, cp1, tx )
|
|
27
|
+
let cp2_ex = interpolate( p, cp2, tx)
|
|
28
|
+
|
|
29
|
+
let valuesL = comPrev.values.slice(-2)
|
|
30
|
+
//let p0 = { x: valuesL[0], y: valuesL[1] }
|
|
31
|
+
|
|
32
|
+
// extend tangents
|
|
33
|
+
//let ptI = checkLineIntersection(p0, cp1_ex, p, cp2_ex, false, true)
|
|
34
|
+
let ptI = checkLineIntersection(p0, cp1, p, cp2_ex, true, true)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
/*
|
|
38
|
+
renderPoly(markers, [p0, cp1], 'orange', '0.75')
|
|
39
|
+
renderPoly(markers, [p, cp2], 'blue', '0.75')
|
|
40
|
+
renderPoly(markers, [p, cp2_ex], 'magenta', '0.75')
|
|
41
|
+
|
|
42
|
+
renderPoint(markers, p0, 'orange', '0.75')
|
|
43
|
+
renderPoint(markers, cp1, 'magenta', '0.75')
|
|
44
|
+
renderPoint(markers, cp1_ex, 'blue', '0.5')
|
|
45
|
+
|
|
46
|
+
renderPoint(markers, cp2, 'cyan', '0.75')
|
|
47
|
+
*/
|
|
48
|
+
//renderPoint(markers, cp2_ex, 'purple', '0.75')
|
|
49
|
+
|
|
50
|
+
//renderPoint(markers, cp1, 'purple', '0.75')
|
|
51
|
+
//renderPoint(markers, cp1_ex, 'blue', '0.75')
|
|
52
|
+
|
|
53
|
+
//renderPoint(markers, cp2, 'cyan')
|
|
54
|
+
/*
|
|
55
|
+
renderPoint(markers, p0, 'orange')
|
|
56
|
+
renderPoint(markers, p, 'magenta', '0.5')
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
if (ptI) {
|
|
61
|
+
let t = 0.666
|
|
62
|
+
cp1 = interpolate(p0, ptI, t)
|
|
63
|
+
cp2 = interpolate(p, ptI, t)
|
|
64
|
+
com.values = [cp1.x, cp1.y, cp2.x, cp2.y, p.x, p.y]
|
|
65
|
+
//renderPoint(markers, ptI)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (values.length) {
|
|
71
|
+
p0 = p
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
return pathData;
|
|
77
|
+
}
|
|
78
|
+
|
|
3
79
|
|
|
4
80
|
|
|
5
81
|
|
|
@@ -6,7 +6,6 @@ export function pathDataRevertCubicToQuadratic(pathData, tolerance=1) {
|
|
|
6
6
|
let com = pathData[c]
|
|
7
7
|
let { type, values, p0, cp1 = null, cp2 = null, p = null } = com;
|
|
8
8
|
if (type === 'C') {
|
|
9
|
-
//console.log(com);
|
|
10
9
|
let comQ = revertCubicQuadratic(p0, cp1, cp2, p, tolerance)
|
|
11
10
|
if (comQ.type === 'Q') {
|
|
12
11
|
comQ.extreme = com.extreme
|