svg-parse-path-normalized
v1.0.9
Published
Parse svg path data from string and normalize it to calculable value arrays
Downloads
979
Maintainers
Readme
svg-parse-path-normalized
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.
Yet another SVG path parser?
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.
Table of content
- 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
1. Basic functionality and helpers
1.1 Parse, normalize and stringify
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
1.2 Advanced conversions
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.
2. Usage parser
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.
2.1 Browser
<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>
2.2 Node
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);
3. Pathdata format
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)
4. All normalization options
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 |
4.1 Original path data: normalization disabled
Set normalize to false to get the original (not normalized) pathdata – including relative or shorthand commands.
parsePathDataNormalized(d, {normalize:false})
4.2 Recommendations
- 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
5. Stringify to back to d
attribute string
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);
6. More conversions via pathDataConvert.js
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 |
6.1 Usage
<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)
6.2 Usage as an addon/plugin for getPathData()
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)
6.3 Convert pathdata structure
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.
6.3.1 Array notation to pathdata
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)
6.3.2 pathDataToVerbose(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
}
];
7. Demos
- 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)
8. Limitations
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.
Credits
- 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
Related Repositories/projects
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