Skip to content

Instantly share code, notes, and snippets.

@shamansir
Last active September 17, 2022 18:47
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save shamansir/0ba30dc262d54d04cd7f79e03b281505 to your computer and use it in GitHub Desktop.
Save shamansir/0ba30dc262d54d04cd7f79e03b281505 to your computer and use it in GitHub Desktop.
Parse and convert any SVG path to the list of commands with JavaScript + Regular Expressions
// svgPathToCommands('M10,10 l 5,7 C-5,7.2,.3-16,24,10 z');
//
// produces:
//
// [ { marker: "M", values: [ 10, 10 ] },
// { marker: "l", values: [ 5, 7 ] },
// { marker: "C", values: [ -5, 7.2, 0.3, -16, 24, 10 ] },
// { marker: "z", values: [ ] } ]
//
// commandsToSvgPath(svgPathToCommands('M10,10 l 5,7 C-5,7.2,.3-16,24,10 z'))
//
// produces: 'M 10,10 l 5,7 C -5,7.2,0.3,-16,24,10 z'
var markerRegEx = /[MmLlSsQqLlHhVvCcSsQqTtAaZz]/g;
var digitRegEx = /-?[0-9]*\.?\d+/g;
function svgPathToCommands(str) {
var results = [];
var match; while ((match = markerRegEx.exec(str)) !== null) { results.push(match); };
return results
.map(function(match) {
return { marker: str[match.index],
index: match.index };
})
.reduceRight(function(all, cur) {
var chunk = str.substring(cur.index, all.length ? all[all.length - 1].index : str.length);
return all.concat([
{ marker: cur.marker,
index: cur.index,
chunk: (chunk.length > 0) ? chunk.substr(1, chunk.length - 1) : chunk }
]);
}, [])
.reverse()
.map(function(command) {
var values = command.chunk.match(digitRegEx);
return { marker: command.marker, values: values ? values.map(parseFloat) : []};
})
}
function commandsToSvgPath(commands) {
return commands.map(function(command) {
return command.marker + ' ' + command.values.join(',');
}).join(' ').trim();
}
/* -------- EXAMPLE: SHIFT THE PATH VALUES TO CENTER --------- */
var myCommands = svgPathToCommands('M10,10 l 5,7 C-5,7.2,.3-16,24,10 z');
var minMax = myCommands.reduce(function(prev, command) {
return command.values.reduce(function(prev, value, i) {
return { minX: (i % 2) ? prev.minX : Math.min(value, prev.minX),
maxX: (i % 2) ? prev.maxX : Math.max(value, prev.maxX),
minY: (i % 2) ? Math.min(value, prev.minY) : prev.minY,
maxY: (i % 2) ? Math.max(value, prev.maxY) : prev.maxY };
}, prev);
}, { minX: Infinity, maxX: 0, minY: Infinity, maxY: 0 });
var center = { x: minMax.minX + ((minMax.maxX - minMax.minX) / 2),
y: minMax.minY + ((minMax.maxY - minMax.minY) / 2) };
var result = commandsToSvgPath(
myCommands.map(function(command) {
return { marker: command.marker,
values: command.values.map(function(value, i) {
return (i % 2) ? (value - center.y) : (value - center.x); })
};
})
);
console.log(minMax, center, result);
// -> Object {minX: -5, maxX: 24, minY: -16, maxY: 10}
// -> Object {x: 9.5, y: -3}
// -> "M 0.5,13 l -4.5,10 C -14.5,10.2,-9.2,-13,14.5,13 z"
/* -------- TESTS --------- */
var tests = [
'',
'M-11.11,-22 L.33-44 ac55 66 h77 M88 .99 Z',
'M500,500 L500,200 L800,500 z M400,600 L400,900 L100,600 z',
'M70.491,50.826c-2.232,1.152-6.913,2.304-12.817,2.304c-13.682,0-23.906-8.641-23.906-24.626' +
'c0-15.266,10.297-25.49,25.346-25.49c5.977,0,9.865,1.296,11.521,2.16l-1.584,5.112C66.747,9.134,63.363,8.27,59.33,8.27' +
'c-11.377,0-18.938,7.272-18.938,20.018c0,11.953,6.841,19.514,18.578,19.514c3.888,0,7.777-0.792,10.297-2.016L70.491,50.826z',
'M10,10',
'Z'
];
tests.forEach(function(test) {
console.log('----');
console.log('source:', test || '<empty>');
console.log('encoded:');
svgPathToCommands(test).forEach(function(command) {
console.log(command.marker, command.values);
});
console.log('decoded:', commandsToSvgPath(svgPathToCommands(test)) || '<empty>');
});
@juanitoddd
Copy link

Thanks !, Really useful !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment