[![npm version](https://img.shields.io/npm/v/svg-parse-path-normalized.svg)](https://www.npmjs.com/package/svg-parse-path-normalized)


# 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](https://svgwg.org/specs/paths/#InterfaceSVGPathData) 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.  

1. **Minified `A` arcto commands** quite often lightweight parsers crash since they can't unravel concatenated `largeArc`, `sweep` and final on path x values.  
2. You may not need to load a full fledged SVG library but just a robust **foundation for your specific SVG path manipulations**.  
3. **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.
4. **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.
5. **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](#1-basic-functionality-and-helpers)
  + [1.1 Parse, normalize and stringify](#11-parse-normalize-and-stringify)
  + [1.2 Advanced conversions](#12-advanced-conversions)
* [2. Usage parser](#2-usage-parser)
  + [2.1 Browser](#21-browser)
  + [2.2 Node](#22-node)
* [3. Pathdata format](#3-pathdata-format)
* [4. All normalization options](#4-all-normalization-options)
  + [4.1 Original path data: normalization disabled](#41-original-path-data-normalization-disabled)
  + [4.2 Recommendations](#42-recommendations)
* [5. Stringify to back to d attribute string](#5-stringify-to-back-to-d-attribute-string)
* [6. More conversions via pathDataConvert.js](#6-more-conversions-via-pathdataconvertjs)
  + [6.1 Usage](#61-usage)
  + [6.2 Usage as an addon/plugin for getPathData()](#62-usage-as-an-addon-plugin-for-getpathdata)
  + [6.3 Convert pathdata structure](#63-convert-pathdata-structure)
    - [6.3.1 Array notation to pathdata](#631-array-notation-to-pathdata)
    - [6.3.2 pathDataToVerbose(pathData)](#632-pathdatatoverbosepathdata)
* [7. Demos](#7-demos)
* [8. Limitations](#8-limitations)
* [Credits](#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.g `m 0 0 .5.5.5.5` to `M 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` to `L`, `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](https://svgwg.org/specs/paths/#InterfaceSVGPathData) 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

``` lang-html
<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
```

``` lang-js

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](https://svgwg.org/specs/paths/#InterfaceSVGPathData).

The returned path data parsed from a stringified pathdata `d` attribute string is an array representing each command as an object like so:  

``` lang-js
const d ="m 0 0 .5.5.5.5a 5 1045 1040 20" 
parsePathDataNormalized(d)
```

``` lang-js
[
    {"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](https://codepen.io/herrstrietzel/pen/NWJpOYR))


## 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](https://github.com/jarek-foksa/path-data-polyfill) 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
  }
];
```

## Changelog
* 1.1.0 fixes a rare parsing issue where 'M' commands were omitted (e.g `z` followed by another drawing command than `M` – unfortunately valid). See updated demo with "path-from-hell3".


## 7. Demos 
* [parse pathdata with different normalization options](https://codepen.io/herrstrietzel/pen/NWJpOYR) (demos/index.html)  
* `pathDataConvert.js` as a [addon/plugin for `path.getPathData()`](https://codepen.io/herrstrietzel/pen/dyreNep) (demos/getPathDataAddon.html)
* [convert commands to pretty much anything](https://codepen.io/herrstrietzel/pen/JjzvRjb) | (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:
* [Catmull-Rom curve commands](https://www.w3.org/TR/2015/WD-SVG2-20150409/paths.html#PathDataCatmullRomCommand)  
* [Bearing commands](https://www.w3.org/TR/2015/WD-SVG2-20150409/paths.html#PathDataBearingCommands)

... 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](https://github.com/jarek-foksa/path-data-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](https://github.com/DmitryBaranovskiy/raphael/blob/master/raphael.js#L1848) 
* Vitaly Puzrin (fontello) for the arc to cubic conversion method  [a2c.js](https://github.com/fontello/svgpath/blob/master/lib/a2c.js) and [cubic to quadratic approximation](https://github.com/fontello/cubic2quad/blob/master/test/cubic2quad.js)
* Mike "POMAX" Kammermans for his great [A Primer on Bézier Curves](https://pomax.github.io/bezierinfo)

## 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](https://github.com/herrstrietzel/svg-getpointatlength) – Calculates a path's length or points at length based on raw pathdata
* [fix-path-directions](https://github.com/herrstrietzel/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](https://github.com/herrstrietzel/svg-pathdata-getbbox) – Calculates a path bounding box based on its raw pathdata
* [svg-transform](https://github.com/herrstrietzel/svg-transform) – A library to transform or de-transform/flatten svg paths








