├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build ├── svg-path-utils.js └── svg-path-utils.min.js ├── index.js ├── package.json ├── src └── svg-path-utils.js └── test └── svg-path-utils-test.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - '4.2' 5 | 6 | branch: 7 | only: 8 | - master 9 | 10 | script: 11 | - npm test -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 Konstantin Skipor 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 5 | and associated documentation files (the "Software"), to deal in the Software without restriction, 6 | including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 13 | LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 14 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 15 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 16 | OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # svg-path-utils 2 | [![Build Status](https://travis-ci.org/krispo/svg-path-utils.svg?branch=master)](https://travis-ci.org/krispo/svg-path-utils) 3 | [![NPM Version](http://img.shields.io/npm/v/svg-path-utils.svg?style=flat)](https://www.npmjs.org/package/svg-path-utils) 4 | 5 | Some utilities for svg [path](https://www.w3.org/TR/SVG/paths.html) element to help operating with [path data](https://www.w3.org/TR/SVG/paths.html#PathData), 6 | like calculating inverse path data... 7 | 8 | ## Install 9 | 10 | npm install svg-path-utils 11 | 12 | ## Usage 13 | 14 | ```js 15 | var spu = require('svg-path-utils'); 16 | var utils = new spu.SVGPathUtils(); 17 | ``` 18 | or in es6 19 | ```js 20 | import {SVGPathUtils} from 'svg-path-utils'; 21 | const utils = new SVGPathUtils(); 22 | ``` 23 | then 24 | ```js 25 | // generate path data 26 | const d = utils.join(utils.M(50,100), utils.L(200,300), utils.Z()); // d = "M50,100 L200,300 Z" 27 | 28 | // calculate inverse path data 29 | const inverse_d = utils.inversePath(d); // inverse_d = "M200,300 L50,100 Z" 30 | ``` 31 | 32 | ## Example 33 | Inverse path data calculation 34 | 35 | | Direct Path | Inverse Path | 36 | |:-------------:|:-------------:| 37 | | | | 38 | | `d="M50,300 L50,250 C50,150 75,150 100,250 C150,450 200,450 200,250 Q200,100 400,100"` | `d="M400,100 Q200,100 200,250 C200,450 150,450 100,250 C75,150 50,150 50,250 L50,300"`| 39 | 40 | Check this [online demo](http://plnkr.co/edit/rIhZfI?p=preview). 41 | 42 | It is also used in [Yarrow](http://krispo.github.io/yarrow/). 43 | 44 | ## API Reference 45 | 46 | #### Path data commands (operators) 47 | 48 | Uppercase (M, L, H, ...) - uses absolute coordinates, lowercase (m, l, h, ...) - uses relative coordinates 49 | 50 | # utils.M(x, y) - [“moveto” commands](http://www.w3.org/TR/SVG/paths.html#PathDataMovetoCommands). 51 | 52 | # utils.m(x, y) 53 | 54 | # utils.L(x, y) - [“lineto” commands](http://www.w3.org/TR/SVG/paths.html#PathDataLinetoCommands). 55 | 56 | # utils.l(x, y) 57 | 58 | # utils.H(x) - horizontal [“lineto” commands](http://www.w3.org/TR/SVG/paths.html#PathDataLinetoCommands). 59 | 60 | # utils.h(x) 61 | 62 | # utils.V(y) - vertical [“lineto” commands](http://www.w3.org/TR/SVG/paths.html#PathDataLinetoCommands). 63 | 64 | # utils.v(y) 65 | 66 | # utils.C(x1, y1, x2, y2, x, y) - [cubic Bézier curve commands](http://www.w3.org/TR/SVG/paths.html#PathDataCubicBezierCommands). 67 | 68 | # utils.c(x1, y1, x2, y2, x, y) 69 | 70 | # utils.S(x2, y2, x, y) - smooth [cubic Bézier curve commands](http://www.w3.org/TR/SVG/paths.html#PathDataCubicBezierCommands). 71 | 72 | # utils.s(x2, y2, x, y) 73 | 74 | # utils.Q(x1, y1, x, y) - [quadratic Bézier curve commands](http://www.w3.org/TR/SVG/paths.html#PathDataQuadraticBezierCommands). 75 | 76 | # utils.q(x1, y1, x, y) 77 | 78 | # utils.T(x, y) - smooth [quadratic Bézier curve commands](http://www.w3.org/TR/SVG/paths.html#PathDataQuadraticBezierCommands). 79 | 80 | # utils.t(x, y) 81 | 82 | # utils.Z() - [“closepath” commands](http://www.w3.org/TR/SVG/paths.html#PathDataClosePathCommand). 83 | 84 | # utils.z() 85 | 86 | > Todo: add .A(...) and .a(...) - [elliptical arc curve commands](http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands). 87 | 88 | Example of usage: 89 | 90 | ```js 91 | utils.M(50,100) // return: "M50,100" 92 | utils.L(200,300) // return: "L200,300" 93 | utils.c(10,20,30,40,50,60) // return: "c10,20 30,40 50,60" 94 | utils.Z() // return: "Z" 95 | ``` 96 | 97 | #### Path data utils 98 | 99 | # utils.parse(d) 100 | 101 | Parse path data *d* into sequence of operators ['M', 'L', ...] and sequence of principal points [*(x,y)*]. 102 | 103 | ```js 104 | utils.parse("M50,100 L200,300 H100"); 105 | /* 106 | return: 107 | { 108 | operators: ["M", "L", "H"], 109 | points: [ 110 | {x: 50, y: 100}, 111 | {x: 200, y: 300}, 112 | {x: 100} 113 | ] 114 | } 115 | */ 116 | ``` 117 | 118 | # utils.generate({ operators: [operators], points: [points]}) 119 | 120 | Generate path data *d* from a sequence of operators ['M', 'L', ...] and sequence of principal points [*(x,y)*]. 121 | 122 | ```js 123 | const data = { 124 | operators: ["M", "L", "H"], 125 | points: [ 126 | {x: 50, y: 100}, 127 | {x: 200, y: 300}, 128 | {x: 100} 129 | ] 130 | } 131 | utils.generate(data); // return: "M50,100 L200,300 H100" 132 | ``` 133 | 134 | # utils.inversePath(d) 135 | 136 | Calculate and return inverse path data for path data *d*. 137 | 138 | ```js 139 | utils.inversePath("M50,100 L200,300 Z"); // return: "M200,300 L50,100 Z" 140 | ``` 141 | 142 | # utils.join(args) 143 | 144 | Join input args into a string with space (' ') separator. 145 | 146 | ```js 147 | utils.join("M50,100", "L200,300", "Z"); // return: "M50,100 L200,300 Z" 148 | utils.join(utils.M(50,100), utils.L(200,300), utils.Z()); // return: "M50,100 L200,300 Z" 149 | ``` 150 | 151 | ## Licence 152 | MIT -------------------------------------------------------------------------------- /build/svg-path-utils.js: -------------------------------------------------------------------------------- 1 | /* svg-path-utils, v0.0.3 */ 2 | (function (global, factory) { 3 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : 4 | typeof define === 'function' && define.amd ? define(['exports'], factory) : 5 | (factory((global.svg_path_utils = global.svg_path_utils || {}))); 6 | }(this, function (exports) { 'use strict'; 7 | 8 | var SVGPathUtils = function(){ 9 | var utils = {}; 10 | 11 | // uppercase (M) - absolute coordinates, lowercase (m) - relative coordinates 12 | // m - moveto 13 | // l - lineto 14 | // h - horizontal lineto 15 | // v - vertical lineto 16 | // c - cubic bezier curveto 17 | // s - smooth cubic bezier curveto 18 | // q - quadratic bezier curveto 19 | // t - smooth quadratic bezier 20 | // a - elliptical arc (+ to add) 21 | // z - close path 22 | utils.M = function M(x,y){ return 'M' + x + ',' + y }; 23 | utils.m = function m(x,y){ return 'm' + x + ',' + y }; 24 | utils.L = function L(x,y){ return 'L' + x + ',' + y }; 25 | utils.l = function l(x,y){ return 'l' + x + ',' + y }; 26 | utils.H = function H(x){ return 'H' + x }; 27 | utils.h = function h(x){ return 'h' + x }; 28 | utils.V = function V(y){ return 'V' + y }; 29 | utils.v = function v(y){ return 'v' + y }; 30 | utils.C = function C(x1,y1,x2,y2,x,y){ return 'C' + x1 + ',' + y1 + ' ' + x2 + ',' + y2 + ' ' + x + ',' + y }; 31 | utils.c = function c(x1,y1,x2,y2,x,y){ return 'c' + x1 + ',' + y1 + ' ' + x2 + ',' + y2 + ' ' + x + ',' + y }; 32 | utils.S = function S(x2,y2,x,y){ return 'S' + x2 + ',' + y2 + ' ' + x + ',' + y }; 33 | utils.s = function s(x2,y2,x,y){ return 's' + x2 + ',' + y2 + ' ' + x + ',' + y }; 34 | utils.Q = function Q(x1,y1,x,y){ return 'Q' + x1 + ',' + y1 + ' ' + x + ',' + y }; 35 | utils.q = function q(x1,y1,x,y){ return 'q' + x1 + ',' + y1 + ' ' + x + ',' + y }; 36 | utils.T = function T(x,y){ return 'T' + x + ',' + y }; 37 | utils.t = function t(x,y){ return 't' + x + ',' + y }; 38 | utils.Z = function Z(){ return 'Z' }; 39 | utils.z = function z(){ return 'z' }; 40 | 41 | utils.angle = function angle(p1, p2){ 42 | return Math.atan2(p2.y - p1.y, p2.x - p1.x)*180/Math.PI; 43 | } 44 | 45 | utils.parse = function(d){ 46 | var operators = d.replace(/[\d,\-\s]+/g, '').split(''); 47 | var points = []; 48 | 49 | var nums = d.replace(/[A-Za-z,]+/g, ' ').trim().replace(/\s\s+/g, ' ').split(' '); 50 | 51 | var f, i = -1; 52 | operators.forEach(function(key){ 53 | if (typeof (f = utils[key]) === 'function') { 54 | if (f.length === 1) { 55 | points.push({ x: +nums[++i] }) 56 | } else { 57 | for (var j = -1, l = f.length/2; ++j < l;) { 58 | points.push({ x: +nums[++i], y: +nums[++i] }) 59 | } 60 | } 61 | } 62 | }); 63 | 64 | return { operators: operators, points: points } 65 | } 66 | 67 | utils.generate = function(_){ 68 | var p = _.points.slice(); 69 | var str = []; 70 | var f; 71 | _.operators.forEach(function(key){ 72 | if (typeof (f = utils[key]) === 'function') { 73 | var args = []; 74 | if (f.length === 1) { 75 | args.push(p.shift().x); 76 | } else { 77 | for (var i = -1, l = f.length/2; ++i < l;) { 78 | var point = p.shift(); 79 | args.push(point.x, point.y); 80 | } 81 | } 82 | str.push(f.apply(null, args)); 83 | } 84 | }); 85 | return str.join(' '); 86 | } 87 | 88 | utils.inversePath = function(d){ 89 | var _ = utils.parse(d); 90 | var ro = _.operators.reverse(); 91 | var rp = _.points.reverse(); 92 | 93 | //define first and last operators (M, Z) 94 | var first, last; 95 | first = ro.pop(); 96 | if ((last = ro[0]).toLowerCase() === 'z') { 97 | ro.push(last); 98 | ro.shift(); 99 | } 100 | ro.unshift(first); 101 | 102 | return utils.generate({ operators: ro, points: rp }); 103 | } 104 | 105 | utils.join = function(){ 106 | if (!arguments.length) return; 107 | return Array.prototype.join.call(arguments, ' '); 108 | } 109 | 110 | return utils; 111 | } 112 | 113 | exports.SVGPathUtils = SVGPathUtils; 114 | 115 | })); -------------------------------------------------------------------------------- /build/svg-path-utils.min.js: -------------------------------------------------------------------------------- 1 | !function(n,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t(n.svg_path_utils=n.svg_path_utils||{})}(this,function(n){"use strict";var t=function(){var n={};return n.M=function(n,t){return"M"+n+","+t},n.m=function(n,t){return"m"+n+","+t},n.L=function(n,t){return"L"+n+","+t},n.l=function(n,t){return"l"+n+","+t},n.H=function(n){return"H"+n},n.h=function(n){return"h"+n},n.V=function(n){return"V"+n},n.v=function(n){return"v"+n},n.C=function(n,t,r,e,u,o){return"C"+n+","+t+" "+r+","+e+" "+u+","+o},n.c=function(n,t,r,e,u,o){return"c"+n+","+t+" "+r+","+e+" "+u+","+o},n.S=function(n,t,r,e){return"S"+n+","+t+" "+r+","+e},n.s=function(n,t,r,e){return"s"+n+","+t+" "+r+","+e},n.Q=function(n,t,r,e){return"Q"+n+","+t+" "+r+","+e},n.q=function(n,t,r,e){return"q"+n+","+t+" "+r+","+e},n.T=function(n,t){return"T"+n+","+t},n.t=function(n,t){return"t"+n+","+t},n.Z=function(){return"Z"},n.z=function(){return"z"},n.angle=function(n,t){return 180*Math.atan2(t.y-n.y,t.x-n.x)/Math.PI},n.parse=function(t){var r,e=t.replace(/[\d,\-\s]+/g,"").split(""),u=[],o=t.replace(/[A-Za-z,]+/g," ").trim().replace(/\s\s+/g," ").split(" "),i=-1;return e.forEach(function(t){if("function"==typeof(r=n[t]))if(1===r.length)u.push({x:+o[++i]});else for(var e=-1,f=r.length/2;++e