svg-path-parser vs svgpath
Parsing and Manipulating SVG Path Data in Web Applications
svg-path-parsersvgpath

Parsing and Manipulating SVG Path Data in Web Applications

svg-path-parser and svgpath are both JavaScript libraries designed to parse, normalize, and transform SVG path data strings into structured formats that can be processed programmatically. These tools are essential when building vector graphics editors, animation systems, or any application that needs to interpret or modify SVG <path> elements at runtime. While both address the same core problem — converting raw SVG path syntax like 'M10 10 L20 20' into actionable data — they differ significantly in architecture, mutability approach, feature scope, and extensibility.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
svg-path-parser170,043227-79 years agoMIT
svgpath056943.2 kB1-MIT

svg-path-parser vs svgpath: Parsing and Transforming SVG Paths Compared

Both svg-path-parser and svgpath help developers work with SVG path data — those cryptic strings like "M10,10 L20,30 Q40,50 60,70" inside <path d="..."> elements. But they solve the problem in very different ways. Let’s break down how they handle real-world tasks.

🔍 Core Philosophy: Immutable Parser vs Mutable Transformer

svg-path-parser treats path data as read-only input. It parses the string once and returns a plain array of normalized command objects. No modifications allowed after parsing.

// svg-path-parser: immutable output
import parse from 'svg-path-parser';

const commands = parse('M10 10L20 20');
// Returns:
// [
//   { code: 'M', command: 'moveto', x: 10, y: 10 },
//   { code: 'L', command: 'lineto', x: 20, y: 20 }
// ]

svgpath gives you a mutable object you can chain methods on. Parsing is just the first step — you then transform, round, or serialize the path in place.

// svgpath: chainable transformations
import SVGPath from 'svgpath';

const result = new SVGPath('M10 10L20 20')
  .scale(2)
  .round(1)
  .toString();
// Returns: "M20 20L40 40"

🧩 Output Structure: Plain Objects vs Encapsulated State

svg-path-parser returns a flat array of plain JavaScript objects. Each object includes both the short code ('M') and full command name ('moveto'), plus parsed coordinates. This makes it easy to iterate, filter, or feed into other systems without hidden state.

const parsed = parse('M0 0 C10 10 20 20 30 30');
console.log(parsed[1]);
// {
//   code: 'C',
//   command: 'curveto',
//   x1: 10, y1: 10,
//   x2: 20, y2: 20,
//   x: 30, y: 30
// }

svgpath doesn’t expose internal structure directly. You interact via methods like .abs() (convert to absolute coords) or .rel() (relative), and only get the final string via .toString(). If you need access to individual segments, you must parse externally or use undocumented internals — which isn’t recommended.

🛠️ Built-in Transformations: None vs Full Suite

svg-path-parser does one thing: parse. Want to scale the path? Rotate it? You’ll need to write your own logic or pair it with a geometry library.

// With svg-path-parser, you handle transforms manually
const scaled = parsed.map(cmd => ({
  ...cmd,
  x: cmd.x * 2,
  y: cmd.y * 2,
  // ...handle control points too
}));

svgpath includes common operations out of the box:

  • .scale(sx, sy)
  • .translate(x, y)
  • .rotate(angle, cx, cy)
  • .matrix(a, b, c, d, e, f)
  • .round(precision)
  • .abs() / .rel()

This saves significant effort in apps that frequently adjust paths (e.g., drag-to-resize in a vector editor).

📏 Normalization Behavior

Both libraries normalize path syntax, but differently.

svg-path-parser converts all commands to their canonical form and ensures consistent property names. For example, horizontal/vertical lines (H, V) become generic L commands with full (x, y) coordinates. This simplifies downstream processing.

svgpath preserves original command types unless you explicitly call .unshort() or .abs(). By default, H10 stays H10 — which may be desirable if you care about minimizing output size or retaining author intent.

🔄 Round-Trip Fidelity

If you parse a path and immediately serialize it back:

  • svg-path-parser cannot serialize — you’d need a separate formatter.
  • svgpath maintains high fidelity by default, but note: calling .abs() or .round() changes the output permanently. There’s no “undo” — it’s a one-way transformation pipeline.

🧪 Error Handling

svg-path-parser throws clear errors on invalid syntax (e.g., missing coordinates). This helps catch malformed paths early during development.

svgpath is more permissive. It may silently skip malformed segments or produce unexpected results. You’ll need extra validation if input comes from untrusted sources.

🤝 Similarities: Shared Ground

Despite differences, both libraries agree on fundamentals:

1. ✅ Support Standard SVG Path Syntax

  • Handle all commands: M, L, C, Q, A, Z, etc.
  • Parse both absolute and relative coordinates correctly.
// Both parse this valid path
'M10,10 l5,0 q2,5 4,0 z'

2. 📦 Zero Dependencies

  • Pure JavaScript, no external runtime requirements.
  • Safe to use in browsers and Node.js.

3. 🔍 Whitespace and Separator Agnostic

  • Correctly interpret commas, spaces, or lack thereof between numbers.
// All equivalent:
'M10 10'
'M10,10'
'M 10 10'

🆚 Summary: Key Differences

Featuresvg-path-parsersvgpath
Primary Use CaseRead-only parsingParse + transform + serialize
MutabilityImmutable outputMutable, chainable object
TransformationsNone built-inScale, rotate, translate, round, etc.
Output AccessDirect array of command objectsOnly via .toString()
NormalizationAggressive (e.g., HL)Conservative (preserves original cmds)
SerializationNot supportedBuilt-in
Error StrictnessThrows on invalid inputTolerant, may skip bad segments

💡 When to Use Which?

  • Use svg-path-parser when you’re building a renderer, analyzer, or converter that only needs to read path data. Think: exporting SVG to canvas, calculating path length, or generating previews. Its simplicity and immutability reduce bugs in data-flow-heavy apps.

  • Use svgpath when you’re building an editor, optimizer, or transformer that needs to modify paths. Think: a design tool that scales icons, a build plugin that minifies SVGs, or an animation system that morphs paths. Its built-in utilities cut down on custom math code.

Both are mature, stable choices — pick based on whether your workflow stops at parsing or continues into transformation.

How to Choose: svg-path-parser vs svgpath

  • svg-path-parser:

    Choose svg-path-parser if you need a lightweight, immutable parser that strictly focuses on converting SVG path strings into a clean, normalized array of command objects. It’s ideal for read-only scenarios like rendering paths in canvas, analyzing path geometry, or feeding data into visualization pipelines where side effects must be avoided. Its functional design aligns well with modern reactive frameworks like React or Svelte.

  • svgpath:

    Choose svgpath if you require an all-in-one toolkit for not just parsing but also transforming, optimizing, and serializing SVG paths. It supports in-place mutations like scaling, rotating, or rounding coordinates, making it suitable for interactive editors, export utilities, or build-time optimization tools. Its chainable API and built-in normalization features reduce boilerplate when manipulating paths dynamically.

README for svg-path-parser

svg-path-parser stable

An SVG path parser, originally built from the PEG.js grammar specified here, published as an NPM module.

Grammar originally written by Gavin Kistner.

svg-path-parser

Usage

require('svg-path-parser')(d)

Takes an SVG path string. The following code…

var parseSVG = require('svg-path-parser');
var d='M3,7 5-6 L1,7 1e2-.4 m-10,10 l10,0  \
  V27 89 H23           v10 h10             \
  C33,43 38,47 43,47   c0,5 5,10 10,10     \
  S63,67 63,67         s-10,10 10,10       \
  Q50,50 73,57         q20,-5 0,-10        \
  T70,40               t0,-15              \
  A5,5 45 1,0 40,20    a5,5 20 0,1 -10-10  Z';
console.log(parseSVG(d));

…will yield an array of commands that define the path, like so:

[ { code:'M', command:'moveto', x:3, y:7 },
  { code:'L', command:'lineto', x:5, y:-6 },
  { code:'L', command:'lineto', x:1, y:7 },
  { code:'L', command:'lineto', x:100, y:-0.4 },
  { code:'m', command:'moveto', relative:true, x:-10, y:10 },
  { code:'l', command:'lineto', relative:true, x:10, y:0 },
  { code:'V', command:'vertical lineto', y:27 },
  { code:'V', command:'vertical lineto', y:89 },
  { code:'H', command:'horizontal lineto', x:23 },
  { code:'v', command:'vertical lineto', relative:true, y:10 },
  { code:'h', command:'horizontal lineto', relative:true, x:10 },
  { code:'C', command:'curveto', x1:33, y1:43, x2:38, y2:47, x:43, y:47 },
  { code:'c', command:'curveto', relative:true, x1:0, y1:5, x2:5, y2:10, x:10, y:10 },
  { code:'S', command:'smooth curveto', x2:63, y2:67, x:63, y:67 },
  { code:'s', command:'smooth curveto', relative:true, x2:-10, y2:10, x:10, y:10 },
  { code:'Q', command:'quadratic curveto', x1:50, y1:50, x:73, y:57 },
  { code:'q', command:'quadratic curveto', relative:true, x1:20, y1:-5, x:0, y:-10 },
  { code:'T', command:'smooth quadratic curveto', x:70, y:40 },
  { code:'t', command:'smooth quadratic curveto', relative:true, x:0, y:-15 },
  { code:'A', command:'elliptical arc', rx:5, ry:5, xAxisRotation:45, largeArc:true, sweep:false, x:40, y:20 },
  { code:'a', command:'elliptical arc', relative:true, rx:5, ry:5, xAxisRotation:20, largeArc:false, sweep:true, x:-10, y:-10 },
  { code:'Z', command:'closepath' } ]

Alternatively, from version 1.1 on, the module exports multiple functions that you can separately use:

const {parseSVG, makeAbsolute} = require('svg-path-parser');

Absolute Path Commands

Version 1.1 adds the ability to convert an array of path commands into their absolute-coordinate equivalents. This modifies the parsed command objects in place, and also returns the array of commands. Continuing the example above:

const {parseSVG, makeAbsolute} = require('svg-path-parser');
const commands = parseSVG(d);
makeAbsolute(commands); // Note: mutates the commands in place!
console.log(commands);
[ { code:'M', command:'moveto',                   x0:0, y0:0 x:3, y:7 },
  { code:'L', command:'lineto',                   x0:3, y0:7 x:5, y:-6 },
  { code:'L', command:'lineto',                   x0:5, y0:-6 x:1, y:7 },
  { code:'L', command:'lineto',                   x0:1, y0:7 x:100, y:-0.4 },
  { code:'M', command:'moveto',                   x0:100, y0:-0.4 x:90, y:9.6 },
  { code:'L', command:'lineto',                   x0:90, y0:9.6 x:100, y:9.6 },
  { code:'V', command:'vertical lineto',          x0:100, y0:9.6, x:100, y:27  },
  { code:'V', command:'vertical lineto',          x0:100, y0:27, x:100, y:89 },
  { code:'H', command:'horizontal lineto',        x0:100, y0:89, x:23, y:89 },
  { code:'V', command:'vertical lineto',          x0:23, y0:89, y:99, x:23 },
  { code:'H', command:'horizontal lineto',        x0:23, y0:99, x:33, y:99 },
  { code:'C', command:'curveto',                  x0:33, y0:99 x1:33, y1:43, x2:38, y2:47, x:43, y:47 },
  { code:'C', command:'curveto',                  x0:43, y0:47 x1:43, y1:52, x2:48, y2:57, x:53, y:57 },
  { code:'S', command:'smooth curveto',           x0:53, y0:57 x2:63, y2:67, x:63, y:67 },
  { code:'S', command:'smooth curveto',           x0:63, y0:67 x2:53, y2:77, x:73, y:77 },
  { code:'Q', command:'quadratic curveto',        x0:73, y0:77 x1:50, y1:50, x:73, y:57 },
  { code:'Q', command:'quadratic curveto',        x0:73, y0:57 x1:93, y1:52, x:73, y:47 },
  { code:'T', command:'smooth quadratic curveto', x0:73, y0:47 x:70, y:40 },
  { code:'T', command:'smooth quadratic curveto', x0:70, y0:40 x:70, y:25 },
  { code:'A', command:'elliptical arc',           x0:70, y0:25 rx:5, ry:5, xAxisRotation:45, largeArc:true, sweep:false, x:40, y:20 },
  { code:'A', command:'elliptical arc',           x0:40, y0:20 rx:5, ry:5, xAxisRotation:20, largeArc:false, sweep:true, x:30, y:10 },
  { code:'Z', command:'closepath',                x0:30, y0:10, x:90, y:9.6 } ]

In addition to converting all commands to absolute coordinates, the makeAbsolute function ensures that:

  • Every command has x0 and y0 properties showing the start point for the command.
  • Every command has x and y properties showing the finish point for the command.
    • This makes H, V, and Z commands equivalent to an L command.

History

v1.1.0 - 2017-Jun-19

  • Add makeAbsolute(cmds).

v1.0.2 - 2017-Mar-1

  • Update package to allow latest PEGJS versions (was locked to v0.7.x).
  • Fix bug preventing parsing errors from appearing for newer PEGJS. (Issue #9)

v1.0.1 - 2014-Oct-30

  • Fix bug that prevented more than two subpaths from being returned.

v1.0.0 - 2014-Oct-12

  • Changed return values to represent each unique path command as its own object, regardless of whether the markup merged them or not. Arguments for a command (e.g. .x) are no longer in a .args array of values, but are instead part of the command object itself.

v0.0.4 - 2014-Oct-10

  • Unroll recursive grammar descriptions that could cause parsing a large path to overflow the stack.

v0.0.3 - 2014-Oct-1

  • Fix bug that prevented parsing some valid documents.

v0.0.2 - 2014-Oct-1

  • Fix parsing of numbers other than integers to work.
  • First moveto command is always absolute.
  • Additional coordinates after moveto are treated as lineto.

License

This library is released under an MIT-style license. That generally means that you are free to do almost anything you want with it as long as you give a bit of credit where credit is due. See the LICENSE file included for the actual legal limitations.