Parses path data from string including fine-grained normalisation and conversion options.
This library aims to provide a robust and versatile yet quite compact (~6KB/3KB minified; gzipped) parser – respecting all minified/shorthand notations as a basis for all kinds of custom path data manipulations. Compatible with the w3C SVGPathData interface draft format recommendations.
While there is no shortage of excellent parsers – unfortunately, the same applies to rather incomplete ones often deployed in libraries due to their appealing lightweight codebase.
-
Minified
A
arcto commands quite often lightweight parsers crash since they can't unravel concatenatedlargeArc
,sweep
and final on path x values. - You may not need to load a full fledged SVG library but just a robust foundation for your specific SVG path manipulations.
-
Finegrained normalization: Normalize as little as possible: Usually, you need at least absolute coordinates as well as "unshortened" ones. You may in some case need to convert quadratic béziers to cubics or
A
arcs to cubics – your choice – by default the parser will return the least destructive normalization. - Just parsing without normalization? Fair enough, works as well e.g if you need a to scale the path data proportinally/keeping the aspect ratio.
- Debugging Sometimes you may also need some hints to what's wrong with your current path data – e.g if paths were sliced or concatenated incorrectly. The debugging option will return info about the problematic commands.
- 1. Basic functionality and helpers
- 2. Usage parser
- 3. Pathdata format
- 4. All normalization options
- 5. Stringify to back to d attribute string
- 6. More conversions via pathDataConvert.js
- 7. Demos
- 8. Limitations
- Credits
Usually parsing alone is not enough to get computable path data values – due to relative or shorthand commands or a
arcto commands that may rather complicate further manipulations such as length or area calculations – especially when dealing with elliptical and/or rotated arcs.
Normalization (admittedly a slightly ambigious term) via parsePathDataNormalized(d)
applies by default these conversions:
- (default) all commands to absolute
- (default) decompose implicit or repeated commands
e.gm 0 0 .5.5.5.5
toM 0 0 l 0.5 0.5 l 0.5 0.5
- commands to shorthand/reflected commands to longhand equivalents like e.g
h
,v
,s
,t
toL
,C
,T
- (optional) convert/approximate arcs to cubics
- (optional) convert quadratic béziers to cubics
- (optional) debug: detect malformed path data inputs
- (optional) round coordinates
-
stringify to
d
attribute – including minification options
Provided by pathDataConvert.js
: Useful to convert your manipulated/processed path data to all kind of command types/structures
(E.g to get a more compact or special formats like lineto-to-bezier conversions for morphing animations by converting)
- all commands to relative (usually more concise in file size)
- apply shorthands – if possible (also decreases filesize)
- linetos to cubic or quadratic béziers
- cubic béziers to quadratic
- different path data formats e.g array based path data notations as used in snap.svg and other libraries or APIs
- this scripts also includes all normalizations options such as relative-absolute, shorthand-to-longhands, rounding etc.
- can be used as an addon complementing
getPathData()
or other parsers compliant with the w3C SVGPathData interface draft format recommendations.
Update: You can now use a more convenient function name for the parsing - the more verbose/clunky function still works. Since this parser aims to parse path data into a processable data array, "parsePathDataNormalized" is semantically more accurate – I hope you understand my naming delemma =)
/* shorthand notation */
let pathData = parseD(d, options)
/* original notation */
let pathData = parsePathDataNormalized(d, options)
Feel free to post an issue or write a post in the discussion if the recent update messed up anything.
<script src="https://www.unpkg.com/svg-parse-path-normalized@latest/js/pathDataParseNormalized.js"></script>
Optional: Load minified script via jsDelivr (~6KB/3KB minified; gzipped)
<!--basic parser --->
<script src="https://cdn.jsdelivr.net/npm/svg-parse-path-normalized@latest/js/pathDataParseNormalized.min.js"></script>
<script>
//parse
const d ="m 0 0 .5.5.5.5a 5 10 45 1040 20" ;
/* shorthand notation */
let pathData = parseD(d)
/* verbose notation */
let pathData = parsePathDataNormalized(d)
//stringify to pathdata d string
let minify = false;
/* chainable notation */
let dNew = pathData.toD(decimals, minify);
/* classic function syntax */
let dNew = pathDataToD(pathData, decimals, minify);
console.log(pathData);
console.log(dNew);
</script>
npm install svg-parse-path-normalized
const parsepathData = require('svg-parse-path-normalized');
const {parsePathDataNormalized, pathDataToD} = parsepathData;
//parse
const d ="m 0 0 .5.5.5.5a 5 10 45 1040 20" ;
let pathData = parsePathDataNormalized(d)
//stringify to pathdata d string
let minify = false;
let dNew = pathDataToD(pathData, 1, minify);
console.log(pathData);
console.log(dNew);
This library uses the pathdata format as suggested in the w3C SVGPathData interface draft.
The returned path data parsed from a stringified pathdata d
attribute string is an array representing each command as an object like so:
const d ="m 0 0 .5.5.5.5a 5 1045 1040 20"
parsePathDataNormalized(d)
[
{"type":"M","values":[0,0]},
{"type":"L","values":[0.5, 0.5]},
{"type":"L","values":[1, 1]},
{"type":"A","values":[5, 10, 45, 1, 0, 41, 21]}
]
The above example illustrates a problem with overly "lightweight" path parsers:
We need an extra check to "unravel" the A
arcto's largeArc
and sweep
flags, which can be concatenated with the subsequent on-path x coordinate value. (See basic example)
parsePathDataNormalized(d, options)
accepts these parameters
let options= {
normalize: null, //shorthand for aggressive normalisation
toAbsolute: true, //necessary for most calculations
unshort: true, //dito
arcToCubic: false, //sometimes necessary
quadraticToCubic: false, //frankly, not necessary most of the time
lineToCubic: false, //handy for morphing animations
debug: false, //handy to detect malformed pathdata retrieved from user inputs
decimals: -1 //-1=no rounding
}
parameter | default | effect |
---|---|---|
toAbsolute | true | convert all to absolute |
unshort | true | convert all shorthands to longhands |
arcToCubic | false | convert arcs A commands to cubic béziers |
quadraticToCubic | false | convert quadratic to cubic béziers |
lineToCubic | false | convert all L linetos to cubic béziers (handy for morphing animations) |
decimals | -1 | round values to floating point decimals. -1=no rounding |
debug | false | reports malformed path data structures via console.log
|
normalize | null | shorthand to also convert arcs and quadratic béziers to cubic – similar to the W3C draft's suggested getPathData({normalize:true}) parameter |
Set normalize to false to get the original (not normalized) pathdata – including relative or shorthand commands.
parsePathDataNormalized(d, {normalize:false})
- Quadratic béziers usually provide much faster calculations/algorithms – think twice before converting to cubic.
-
debug:true
can be handy if you need to find errors in malformed pathdata – maybe caused by manual path splitting - Arc to cubic conversion/approximation is quite complex and thus quite expensive – you may not need this conversion
Options:
- decimals: rounds pathdata
- minify: omits command letters for implicit or repeated commands and leading zeros
You can stringify the path data to a d
attribute (or CSS property) by a chained prototype method or the basic function like so:
let d = pathData.toD(decimals, minify)
which is just a wrapper for the actual stringifying function.
let d = pathDataToD(pathData, decimals, minify)
and eventually apply it like so:
path.setAttribute('d', d);
Load pathDataConvert.js
to get more conversion methods. This script is intended to provide various conversions to optimize the path data after processing e.g for a minified path output.
parameter | default | effect |
---|---|---|
toRelative | false | convert all to relative |
toAbsolute | true | convert all to absolute |
toShorthands | false | convert all to to shorthands – if applicable |
toLonghands | true | convert all shorthands to longhands |
arcToCubic | false | convert arcs A commands to cubic béziers |
lineToCubic | false | convert all L linetos to cubic béziers (handy for morphing animations) |
quadraticToCubic | false | convert quadratic to cubic béziers |
cubicToQuadratic | false | convert all cubic to quadratic |
cubicToQuadraticPrecision | 0.1 | cubic to quadratic accuracy |
decimals | -1 | round values to floating point decimals. -1=no rounding |
normalize | null , true, false | shorthand to also convert arcs and quadratic béziers to cubic – similar to the W3C draft's suggested getPathData({normalize:true}) parameter |
optimize | false | shorthand to convert to shorthands, relative and round to 3 decimals for a more compact output |
<script src="https://www.unpkg.com/svg-parse-path-normalized@latest/js/pathDataConvert.js"></script>
Load minified via jsDelivr (13KB/6KB minified)
<!-- optional conversions -->
<script src="https://cdn.jsdelivr.net/npm/svg-parse-path-normalized@latest/js/pathDataConvert.min.js"></script>
let options = {arcToCubic:true, toRelative:true, decimals:0}
let pathDataCon = pathData.convert(options)
Conversion can be applied via
- chainable prototype method
convert(options)
to apply all conversions at once - separate chainable methods like
pathData.toAbsolute()
,pathData.toRelative()
,pathData.toLonghands()
,pathData.toShorthands()
,pathData.round()
,pathData.toQuadratic()
,pathData.toVerbose()
- individual functions like
pathDataToAbsolute(pathData)
,pathDataToRelative(pathData)
,pathDataToShorthands(pathData)
,pathDataToShorthands(pathData)
,pathDataToQuadratic(pathData)
,roundPathData(pathData)
Currently, the W3C draft for the SVGPathData interface is not supported by any major browser. Fortunately Jarek Foksa wrote a this great polyfill library and also contributed to the potential spec outcome – most importantly that it should include geometry elements like circle
, rect
, polygon
, line
to retrieve path data.
This polyfill is a "battle-proof" parser! Since the W3C draft doesn't include fine-grained control over the normalisation/conversion process you can use the pathDataConvert.js
script as an addon/plugin alongside with the aforementioned polyfill script. (See Demo/getPathDataAddon.html)
You may already have a set of parsed/abstracted path data retrieved from other libraries or APIs or need a more verbose notation.
In this case you may use these conversion methods.
A lot of libraries – such as snap.svg use a nested array structure for each command like so
[
["M", 0, 0] ,
["L", 0.5, 0.5],
["L", 1, 1],
["A", 5, 10, 45, 1, 0, 41, 21]
]
In case you need to convert these you can use the helper methods (included in pathDataConvert.js) to convert format in both directions
convertArrayPathData(pathDataArray)
revertPathDataToArray(pathData)
Besides you can use pathDataToVerbose(pathData)
to get a more detailed data array including original and absolute point coordinates as well as parametrized arc data rx
and ry
, startAngle
, endAngle
, deltaAngle
(in radians)
let data = [
{
type: "M",
values: [0, 0],
valuesAbsolute: [0, 0],
pFinal: { x: 0, y: 0 },
isRelative: false
},
{
type: "l",
values: [0.5, 0.5],
valuesAbsolute: [0.5, 0.5],
pFinal: { x: 0.5, y: 0.5 },
isRelative: true,
pPrev: { x: 0, y: 0 }
},
{
type: "l",
values: [0.5, 0.5],
valuesAbsolute: [1, 1],
pFinal: { x: 1, y: 1 },
isRelative: true,
pPrev: { x: 0.5, y: 0.5 }
},
{
type: "a",
values: [5, 10, 45, 1, 0, 40, 20],
valuesAbsolute: [5, 10, 45, 1, 0, 41, 21],
pFinal: { x: 41, y: 21 },
isRelative: true,
pPrev: { x: 1, y: 1 },
rx: 21.505813167606572,
ry: 43.011626335213144,
xAxisRotation: 45,
largeArcFlag: 1,
sweepFlag: 0,
startAngle: 2.976443999504017,
endAngle: 6.118036608390327,
deltaAngle: -3.1415926982932767
}
];
- parse pathdata with different normalization options (demos/index.html)
-
pathDataConvert.js
as a addon/plugin forpath.getPathData()
(demos/getPathDataAddon.html) - convert commands to pretty much anything | (demos/converter.html)
This library does only support SVG path data that's actually supported by browsers.
In other words: these "newer" commands are not included:
... and this contextual closepath thing ...
Seriously, there's no point in implementing these features until they're implemented (see the sad story of SVG multi-line text... we'll probably never get it).
Especially the Catmull-ROM syntax extension is spooking around for more than 10 years without any results. Frankly, these features may never be included as they would bomb quite a few renderers and there are visualisation libraries to draw paths in a abstracted way.
- Jarek Foksa for his great polyfill heavily inspring to adopt the new pathData interface methodology and for contributing to the specification
- Dmitry Baranovskiy for (raphael.j/snap.svg) pathToAbsolute/Relative functions
- Vitaly Puzrin (fontello) for the arc to cubic conversion method a2c.js and cubic to quadratic approximation
- Mike "POMAX" Kammermans for his great A Primer on Bézier Curves
All of these helpers are based on the path parser described above (or by-products - although they include stripped down versions for the sake of providing a standalone lib).
- svg-getpointatlength – Calculates a path's length or points at length based on raw pathdata
- fix-path-directions – Correct sub path directions in compound path for apps that don't support fill-rules or just reverse path directions (e.g for path animations)
- svg-pathdata-getbbox – Calculates a path bounding box based on its raw pathdata
- svg-transform – A library to transform or de-transform/flatten svg paths