├── .gitignore
├── index.js
├── .travis.yml
├── LICENSE
├── package.json
├── src
└── svg-path-utils.js
├── README.md
└── test
└── svg-path-utils-test.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | node_modules
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | export {default as SVGPathUtils} from './src/svg-path-utils';
--------------------------------------------------------------------------------
/.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.
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "svg-path-utils",
3 | "version": "0.0.3",
4 | "description": "Some utils for SVG's path data like path data generation, inverse path data calculation, ...",
5 | "keywords": [
6 | "svg",
7 | "utils",
8 | "path",
9 | "inverse path"
10 | ],
11 | "homepage": "https://github.com/krispo/svg-path-utils",
12 | "license": "MIT",
13 | "main": "build/svg-path-utils.js",
14 | "jsnext:main": "index",
15 | "scripts": {
16 | "uglify": "uglifyjs build/svg-path-utils.js -c -m -o build/svg-path-utils.min.js",
17 | "build": "mkdir -p build && VERSION=`node -e 'console.log(require(\"./package.json\").version)'` && rollup -f umd --banner \"/* svg-path-utils, v${VERSION} */\" -n svg_path_utils -o build/svg-path-utils.js -- index.js && npm run uglify",
18 | "pretest": "npm run build",
19 | "test": "faucet test/*-test.js",
20 | "prepublish": "npm run build"
21 | },
22 | "author": "Konstantin Skipor",
23 | "repository": {
24 | "type": "git",
25 | "url": "https://github.com/krispo/svg-path-utils.git"
26 | },
27 | "devDependencies": {
28 | "faucet": "0.0.1",
29 | "rollup": "^0.25.4",
30 | "tape": "^4.4.0",
31 | "uglify-js": "^2.6.1"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/svg-path-utils.js:
--------------------------------------------------------------------------------
1 | export default SVGPathUtils;
2 |
3 | var SVGPathUtils = function(){
4 | var utils = {};
5 |
6 | // uppercase (M) - absolute coordinates, lowercase (m) - relative coordinates
7 | // m - moveto
8 | // l - lineto
9 | // h - horizontal lineto
10 | // v - vertical lineto
11 | // c - cubic bezier curveto
12 | // s - smooth cubic bezier curveto
13 | // q - quadratic bezier curveto
14 | // t - smooth quadratic bezier
15 | // a - elliptical arc (+ to add)
16 | // z - close path
17 | utils.M = function M(x,y){ return 'M' + x + ',' + y };
18 | utils.m = function m(x,y){ return 'm' + x + ',' + y };
19 | utils.L = function L(x,y){ return 'L' + x + ',' + y };
20 | utils.l = function l(x,y){ return 'l' + x + ',' + y };
21 | utils.H = function H(x){ return 'H' + x };
22 | utils.h = function h(x){ return 'h' + x };
23 | utils.V = function V(y){ return 'V' + y };
24 | utils.v = function v(y){ return 'v' + y };
25 | utils.C = function C(x1,y1,x2,y2,x,y){ return 'C' + x1 + ',' + y1 + ' ' + x2 + ',' + y2 + ' ' + x + ',' + y };
26 | utils.c = function c(x1,y1,x2,y2,x,y){ return 'c' + x1 + ',' + y1 + ' ' + x2 + ',' + y2 + ' ' + x + ',' + y };
27 | utils.S = function S(x2,y2,x,y){ return 'S' + x2 + ',' + y2 + ' ' + x + ',' + y };
28 | utils.s = function s(x2,y2,x,y){ return 's' + x2 + ',' + y2 + ' ' + x + ',' + y };
29 | utils.Q = function Q(x1,y1,x,y){ return 'Q' + x1 + ',' + y1 + ' ' + x + ',' + y };
30 | utils.q = function q(x1,y1,x,y){ return 'q' + x1 + ',' + y1 + ' ' + x + ',' + y };
31 | utils.T = function T(x,y){ return 'T' + x + ',' + y };
32 | utils.t = function t(x,y){ return 't' + x + ',' + y };
33 | utils.Z = function Z(){ return 'Z' };
34 | utils.z = function z(){ return 'z' };
35 |
36 | utils.angle = function angle(p1, p2){
37 | return Math.atan2(p2.y - p1.y, p2.x - p1.x)*180/Math.PI;
38 | }
39 |
40 | utils.parse = function(d){
41 | var operators = d.replace(/[\d,\-\s]+/g, '').split('');
42 | var points = [];
43 |
44 | var nums = d.replace(/[A-Za-z,]+/g, ' ').trim().replace(/\s\s+/g, ' ').split(' ');
45 |
46 | var f, i = -1;
47 | operators.forEach(function(key){
48 | if (typeof (f = utils[key]) === 'function') {
49 | if (f.length === 1) {
50 | points.push({ x: +nums[++i] })
51 | } else {
52 | for (var j = -1, l = f.length/2; ++j < l;) {
53 | points.push({ x: +nums[++i], y: +nums[++i] })
54 | }
55 | }
56 | }
57 | });
58 |
59 | return { operators: operators, points: points }
60 | }
61 |
62 | utils.generate = function(_){
63 | var p = _.points.slice();
64 | var str = [];
65 | var f;
66 | _.operators.forEach(function(key){
67 | if (typeof (f = utils[key]) === 'function') {
68 | var args = [];
69 | if (f.length === 1) {
70 | args.push(p.shift().x);
71 | } else {
72 | for (var i = -1, l = f.length/2; ++i < l;) {
73 | var point = p.shift();
74 | args.push(point.x, point.y);
75 | }
76 | }
77 | str.push(f.apply(null, args));
78 | }
79 | });
80 | return str.join(' ');
81 | }
82 |
83 | utils.inversePath = function(d){
84 | var _ = utils.parse(d);
85 | var ro = _.operators.reverse();
86 | var rp = _.points.reverse();
87 |
88 | //define first and last operators (M, Z)
89 | var first, last;
90 | first = ro.pop();
91 | if ((last = ro[0]).toLowerCase() === 'z') {
92 | ro.push(last);
93 | ro.shift();
94 | }
95 | ro.unshift(first);
96 |
97 | return utils.generate({ operators: ro, points: rp });
98 | }
99 |
100 | utils.join = function(){
101 | if (!arguments.length) return;
102 | return Array.prototype.join.call(arguments, ' ');
103 | }
104 |
105 | return utils;
106 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # svg-path-utils
2 | [](https://travis-ci.org/krispo/svg-path-utils)
3 | [](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
--------------------------------------------------------------------------------
/test/svg-path-utils-test.js:
--------------------------------------------------------------------------------
1 | var test = require('tape'),
2 | SVGPathUtils = require('../')
3 | ;
4 |
5 | test('svg-path-utils tests', function(t){
6 | var utils = new SVGPathUtils.SVGPathUtils();
7 |
8 | t.test('.M ', function(t){
9 | t.equal(utils.M(10,20), 'M10,20');
10 | t.end();
11 | });
12 | t.test('.m ', function(t){
13 | t.equal(utils.m(10,20), 'm10,20');
14 | t.end();
15 | });
16 |
17 | t.test('.L ', function(t){
18 | t.equal(utils.L(10,20), 'L10,20');
19 | t.end();
20 | });
21 | t.test('.l ', function(t){
22 | t.equal(utils.l(10,20), 'l10,20');
23 | t.end();
24 | });
25 |
26 | t.test('.H ', function(t){
27 | t.equal(utils.H(10), 'H10');
28 | t.end();
29 | });
30 | t.test('.h ', function(t){
31 | t.equal(utils.h(10), 'h10');
32 | t.end();
33 | });
34 |
35 | t.test('.V ', function(t){
36 | t.equal(utils.V(10), 'V10');
37 | t.end();
38 | });
39 | t.test('.v ', function(t){
40 | t.equal(utils.v(10), 'v10');
41 | t.end();
42 | });
43 |
44 | t.test('.C ', function(t){
45 | t.equal(utils.C(10,0,400,400,400,200), 'C10,0 400,400 400,200');
46 | t.end();
47 | });
48 | t.test('.c ', function(t){
49 | t.equal(utils.c(10,0,400,400,400,200), 'c10,0 400,400 400,200');
50 | t.end();
51 | });
52 |
53 | t.test('.S ', function(t){
54 | t.equal(utils.S(10,0,400,400), 'S10,0 400,400');
55 | t.end();
56 | });
57 | t.test('.s ', function(t){
58 | t.equal(utils.s(10,0,400,400), 's10,0 400,400');
59 | t.end();
60 | });
61 |
62 | t.test('.Q ', function(t){
63 | t.equal(utils.Q(10,0,400,400), 'Q10,0 400,400');
64 | t.end();
65 | });
66 | t.test('.q ', function(t){
67 | t.equal(utils.q(10,0,400,400), 'q10,0 400,400');
68 | t.end();
69 | });
70 |
71 | t.test('.T ', function(t){
72 | t.equal(utils.T(10,20), 'T10,20');
73 | t.end();
74 | });
75 | t.test('.t ', function(t){
76 | t.equal(utils.t(10,20), 't10,20');
77 | t.end();
78 | });
79 |
80 | t.test('.Z ', function(t){
81 | t.equal(utils.Z(), 'Z');
82 | t.end();
83 | });
84 | t.test('.z ', function(t){
85 | t.equal(utils.z(), 'z');
86 | t.end();
87 | });
88 |
89 | t.test('.parse ', function(t){
90 | var d = 'M10,200 C10,0 400,400 400,200 L20,30';
91 | var _ = utils.parse(d);
92 | t.deepEqual(_.operators, ['M', 'C', 'L']);
93 | t.deepEqual(_.points, [{x: 10, y:200}, {x: 10, y: 0}, {x: 400, y:400}, {x: 400, y: 200}, {x: 20, y: 30}]);
94 | t.end();
95 | });
96 |
97 | t.test('.parse with spaces', function(t){
98 | var d = 'M 10 200 C 10 0 400 400 400 200 L 20 30';
99 | var _ = utils.parse(d);
100 | t.deepEqual(_.operators, ['M', 'C', 'L']);
101 | t.deepEqual(_.points, [{x: 10, y:200}, {x: 10, y: 0}, {x: 400, y: 400}, {x: 400, y: 200}, {x: 20, y: 30}]);
102 | t.end();
103 | });
104 |
105 | t.test('.parse complex string', function(t){
106 | var d = ' M10, 200 C 10 0 400, 400 400 \n,200L20,30 ';
107 | var _ = utils.parse(d);
108 | t.deepEqual(_.operators, ['M', 'C', 'L']);
109 | t.deepEqual(_.points, [{x: 10, y:200}, {x: 10, y: 0}, {x: 400, y: 400}, {x: 400, y: 200}, {x: 20, y: 30}]);
110 | t.end();
111 | });
112 |
113 | t.test('.parse complex string with negatives', function(t){
114 | var d = ' M-10, -200 C -10 0 -400, -400 -400 ,-200L-20,-30 ';
115 | var _ = utils.parse(d);
116 | t.deepEqual(_.operators, ['M', 'C', 'L']);
117 | t.deepEqual(_.points, [{x: -10, y:-200}, {x: -10, y: 0}, {x: -400, y: -400}, {x: -400, y: -200}, {x: -20, y: -30}]);
118 | t.end();
119 | });
120 |
121 | t.test('.parse with H and V', function(t){
122 | var d = 'M10,200 H20 C10,0 400,400 400,200 V30 L20,30';
123 | var _ = utils.parse(d);
124 | t.deepEqual(_.operators, ['M', 'H', 'C', 'V', 'L']);
125 | t.deepEqual(_.points, [{x: 10, y:200}, {x: 20}, {x: 10, y: 0}, {x: 400, y:400}, {x: 400, y: 200}, {x: 30}, {x: 20, y: 30}]);
126 | t.end();
127 | });
128 |
129 | t.test('.parse with H and V without spaces', function(t){
130 | var d = 'M10,200H20V30h40v50v60z';
131 | var _ = utils.parse(d);
132 | t.deepEqual(_.operators, ['M', 'H', 'V', 'h', 'v', 'v', 'z']);
133 | t.deepEqual(_.points, [{x: 10, y:200}, {x: 20}, {x: 30}, {x: 40}, {x: 50}, {x: 60}]);
134 | t.end();
135 | });
136 |
137 | t.test('.parse complex string with H and V', function(t){
138 | var d = ' M10, 200 H 20 V 30 H40 C 10 0 400,400 400 200';
139 | var _ = utils.parse(d);
140 | t.deepEqual(_.operators, ['M', 'H', 'V', 'H', 'C']);
141 | t.deepEqual(_.points, [{x: 10, y:200}, {x: 20}, {x: 30}, {x: 40}, {x: 10, y: 0}, {x: 400, y: 400}, {x: 400, y: 200}]);
142 | t.end();
143 | });
144 |
145 | t.test('.parse complex string with negatives H and V', function(t){
146 | var d = ' M-10, -200 H -20 V -30 H-40 C -10 0 -400,-400 -400 -200';
147 | var _ = utils.parse(d);
148 | t.deepEqual(_.operators, ['M', 'H', 'V', 'H', 'C']);
149 | t.deepEqual(_.points, [{x: -10, y: -200}, {x: -20}, {x: -30}, {x: -40}, {x: -10, y: 0}, {x: -400, y: -400}, {x: -400, y: -200}]);
150 | t.end();
151 | });
152 |
153 | t.test('.generate ', function(t){
154 | var _ = {
155 | operators: ['M', 'C', 'L'],
156 | points: [{x: 10, y:200}, {x: 10, y: 0}, {x: 400, y:400}, {x: 400, y: 200}, {x: 20, y:30}]
157 | }
158 | var d = utils.generate(_);
159 | t.equal(d, 'M10,200 C10,0 400,400 400,200 L20,30');
160 | t.end();
161 | });
162 |
163 | t.test('.generate with negatives', function(t){
164 | var _ = {
165 | operators: ['M', 'C', 'L'],
166 | points: [{x: -10, y: -200}, {x: -10, y: 0}, {x: -400, y: -400}, {x: -400, y: -200}, {x: -20, y: -30}]
167 | }
168 | var d = utils.generate(_);
169 | t.equal(d, 'M-10,-200 C-10,0 -400,-400 -400,-200 L-20,-30');
170 | t.end();
171 | });
172 |
173 | t.test('.generate with H and V', function(t){
174 | var _ = {
175 | operators: ['M', 'H', 'C', 'V', 'L'],
176 | points: [{x: 10, y: 200}, {x: 20}, {x: 10, y: 0}, {x: 400, y: 400}, {x: 400, y: 200}, {x: 30}, {x: 20, y: 30}]
177 | }
178 | var d = utils.generate(_);
179 | t.equal(d, 'M10,200 H20 C10,0 400,400 400,200 V30 L20,30');
180 | t.end();
181 | });
182 |
183 | t.test('.generate with negatives H and V', function(t){
184 | var _ = {
185 | operators: ['M', 'H', 'C', 'V', 'L'],
186 | points: [{x: -10, y: -200}, {x: -20}, {x: -10, y: 0}, {x: -400, y: -400}, {x: -400, y: -200}, {x: -30}, {x: -20, y: -30}]
187 | }
188 | var d = utils.generate(_);
189 | t.equal(d, 'M-10,-200 H-20 C-10,0 -400,-400 -400,-200 V-30 L-20,-30');
190 | t.end();
191 | });
192 |
193 |
194 |
195 |
196 | t.test('.inversePath ', function(t){
197 | var d = 'M10,200 C10,0 400,400 400,200 L20,30';
198 | var inverse_d = utils.inversePath(d);
199 | t.equal(inverse_d, 'M20,30 L400,200 C400,400 10,0 10,200');
200 | t.end();
201 | });
202 |
203 | t.test('.join ', function(t){
204 | t.equal(utils.join('foo','bar','buz'), 'foo bar buz');
205 | t.equal(utils.join(utils.M(10,20), utils.L(200, 100), utils.C(300,100,300,0,0,0), utils.Z()), 'M10,20 L200,100 C300,100 300,0 0,0 Z')
206 | t.end();
207 | });
208 |
209 | t.end();
210 | });
--------------------------------------------------------------------------------