├── .gitignore ├── .npmignore ├── .travis.yml ├── bower.json ├── package.json ├── LICENSE ├── parser.min.js ├── parser.min.js.map ├── readme.md ├── parser.js └── test └── tests.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | yarn.lock 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | yarn.lock 3 | bower.json 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6.9" 4 | - "4.6" 5 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "d-path-parser", 3 | "description": "JavaScript parser for SVG path's d attribute", 4 | "main": "parser.min.js", 5 | "authors": [ 6 | "Massimo Artizzu" 7 | ], 8 | "license": "MIT", 9 | "keywords": [ 10 | "svg", 11 | "parser", 12 | "path", 13 | "drawing" 14 | ], 15 | "homepage": "https://github.com/MaxArt2501/d-path-parser", 16 | "ignore": [ 17 | "**/.*", 18 | "node_modules", 19 | "test", 20 | "yarn.lock" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "d-path-parser", 3 | "version": "1.0.0", 4 | "description": "JavaScript parser for SVG path's d attribute", 5 | "main": "parser.js", 6 | "license": "MIT", 7 | "directories": { 8 | "test": "test" 9 | }, 10 | "scripts": { 11 | "test": "node_modules/.bin/mocha" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/MaxArt2501/d-path-parser.git" 16 | }, 17 | "bugs": { 18 | "url": "https://github.com/MaxArt2501/d-path-parser/issues" 19 | }, 20 | "homepage": "https://github.com/MaxArt2501/d-path-parser", 21 | "keywords": [ 22 | "svg", 23 | "parser", 24 | "path", 25 | "drawing" 26 | ], 27 | "author": { 28 | "name": "Massimo Artizzu", 29 | "email": "maxart.x@gmail.com" 30 | }, 31 | "devDependencies": { 32 | "chai": "^3.5.0", 33 | "mocha": "^3.1.2" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Massimo Artizzu (MaxArt2501) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /parser.min.js: -------------------------------------------------------------------------------- 1 | (function(root,factory){if(typeof define==="function"&&define.amd){define([],factory)}else if(typeof exports==="object"){module.exports=factory()}else{root.dPathParse=factory()}})(this,function(){"use strict";return function parse(d){var re={command:/\s*([achlmqstvz])/gi,number:/\s*([+-]?\d*\.?\d+(?:e[+-]?\d+)?)/gi,comma:/\s*(?:(,)|\s)/g,flag:/\s*([01])/g};var matchers={number:function(must){return+get("number",must)},"coordinate pair":function(must){var x=get("number",must);if(x===null&&!must)return null;get("comma");var y=get("number",true);return{x:+x,y:+y}},"arc definition":function(must){var radii=matchers["coordinate pair"](must);if(!radii&&!must)return null;get("comma");var rotation=+get("number",true);get("comma",true);var large=!!+get("flag",true);get("comma");var clockwise=!!+get("flag",true);get("comma");var end=matchers["coordinate pair"](true);return{radii:radii,rotation:rotation,large:large,clockwise:clockwise,end:end}}};var index=0;var commands=[];while(index`'s `d` attribute. 5 | 6 | [![Version](http://img.shields.io/npm/v/d-path-parser.svg)](https://www.npmjs.org/package/d-path-parser) 7 | [![Build Status](https://travis-ci.org/MaxArt2501/d-path-parser.svg?branch=master)](https://travis-ci.org/MaxArt2501/d-path-parser) 8 | 9 | ## Reason 10 | 11 | The syntax for the `d` attribute is [compact and expressive](https://www.w3.org/TR/SVG/paths.html), but unfortunately it's also 12 | dramatic to debug/maintain/read. This parser gives you a breakdown of the commands in the string, ready for you to read and 13 | manipulate. 14 | 15 | There's [another library](https://github.com/hughsk/svg-path-parser) with the same purpose around, maintained by Hugh Kennedy. 16 | Its main feature is that's generated from a formal grammar, so that's actually pretty neat. On the down size, there's its... 17 | size, and the fact that it's quite slow compared to this one. We're talking about 11x the size minified, 3.5 the size minified 18 | and gzipped (`d-path-parser` is less than 1K), and 6x-10x slower depending on the input (the longer, the slower) and the environment. 19 | 20 | One may argue that size and speed aren't usually important features, and they might be right. `d-path-parser` also has 100% 21 | test coverage, by the way. 22 | 23 | ## Installation 24 | 25 | Via `npm`/`yarn`: 26 | 27 | ```bash 28 | npm install d-path-parser 29 | yarn add d-path-parser 30 | ``` 31 | 32 | Via `bower`; 33 | 34 | ```bash 35 | bower install d-path-parser 36 | ``` 37 | 38 | ## Usage 39 | 40 | As a CommonJS package: 41 | ```javascript 42 | const parse = require("d-path-parser"); 43 | const path = "M0,0 l10,10 A14.142 14.142 0 1 1 10,-10 Z"; 44 | const commands = parse(path); 45 | ``` 46 | 47 | This will yield the following result: 48 | 49 | ```javascript 50 | [{ 51 | code: "M", 52 | relative: false, 53 | end: { x: 0, y: 0 } 54 | }, { 55 | code: "l", 56 | relative: true, 57 | end: { x: 10, y: 10 } 58 | }, { 59 | code: "A", 60 | relative: false, 61 | radii: { x: 14.142, y: 14.142 }, 62 | rotation: 0, 63 | large: true, 64 | clockwise: true, 65 | end: { x: 10, y: -10 } 66 | }, { 67 | code: "Z" 68 | }] 69 | ``` 70 | 71 | The parser can also be loaded via AMD (`define([ "d-path-parser", ... ], function(parse) { ... })`) and, if no module loader is 72 | detected, a global function `dPathParse` will be defined. 73 | 74 | ## Gotchas 75 | 76 | `d-path-parser` does *not* assume the given string is the full path string of a `d` attribute. In short, it won't throw if the 77 | first command is not a `moveTo`. 78 | 79 | ## Tests 80 | 81 | [Mocha](https://mochajs.org/) and [Chai](http://chaijs.com/) are used for tests. Just run `npm test` (or `mocha` if it's globally 82 | installed). 83 | 84 | ## License 85 | 86 | MIT @ Massimo Artizzu 2016. See [LICENSE](LICENSE). 87 | -------------------------------------------------------------------------------- /parser.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * d-path-parser - v1.0.0 3 | * by Massimo Artizzu (MaxArt2501) 4 | * 5 | * https://github.com/MaxArt2501/d-path-parser 6 | * 7 | * Licensed under the MIT License 8 | * See LICENSE for details 9 | */ 10 | 11 | (function (root, factory) { 12 | if (typeof define === "function" && define.amd) { 13 | // AMD. Register as an anonymous module. 14 | define([], factory); 15 | } else if (typeof exports === "object") { 16 | // Node. Does not work with strict CommonJS, but 17 | // only CommonJS-like environments that support module.exports, 18 | // like Node. 19 | module.exports = factory(); 20 | } else { 21 | // Browser globals (root is window) 22 | root.dPathParse = factory(); 23 | } 24 | })(this, function() { 25 | "use strict"; 26 | 27 | return function parse(d) { 28 | var re = { 29 | command: /\s*([achlmqstvz])/gi, 30 | number: /\s*([+-]?\d*\.?\d+(?:e[+-]?\d+)?)/gi, 31 | comma: /\s*(?:(,)|\s)/g, 32 | flag: /\s*([01])/g 33 | }; 34 | var matchers = { 35 | "number": function(must) { 36 | var x = get("number", must); 37 | if(x === null) return null; 38 | return +x; 39 | }, 40 | "coordinate pair": function(must) { 41 | var x = get("number", must); 42 | if (x === null && !must) return null; 43 | get("comma"); 44 | var y = get("number", true); 45 | return { x: +x, y: +y }; 46 | }, 47 | "arc definition": function(must) { 48 | var radii = matchers["coordinate pair"](must); 49 | if (!radii && !must) return null; 50 | get("comma"); 51 | var rotation = +get("number", true); 52 | get("comma", true); 53 | var large = !!+get("flag", true); 54 | get("comma"); 55 | var clockwise = !!+get("flag", true); 56 | get("comma"); 57 | var end = matchers["coordinate pair"](true); 58 | return { 59 | radii: radii, 60 | rotation: rotation, 61 | large: large, 62 | clockwise: clockwise, 63 | end: end 64 | }; 65 | } 66 | } 67 | var index = 0; 68 | var commands = []; 69 | 70 | while (index < d.length) { 71 | var cmd = get("command"); 72 | var upcmd = cmd.toUpperCase(); 73 | var relative = cmd !== upcmd; 74 | var sequence; 75 | switch (upcmd) { 76 | case "M": 77 | sequence = getSequence("coordinate pair").map(function(coords, i) { 78 | if (i === 1) cmd = relative ? "l" : "L"; 79 | return makeCommand({ end: coords }); 80 | }); 81 | break; 82 | case "L": 83 | case "T": 84 | sequence = getSequence("coordinate pair").map(function(coords) { 85 | return makeCommand({ end: coords }); 86 | }); 87 | break; 88 | case "C": 89 | sequence = getSequence("coordinate pair"); 90 | if (sequence.length % 3) 91 | throw Error("Expected coordinate pair triplet at position " + index); 92 | 93 | sequence = sequence.reduce(function(seq, coords, i) { 94 | var rest = i % 3; 95 | if (!rest) { 96 | seq.push(makeCommand({ cp1: coords })); 97 | } else { 98 | var last = seq[seq.length - 1]; 99 | last[rest === 1 ? "cp2" : "end"] = coords; 100 | } 101 | return seq; 102 | }, []); 103 | 104 | break; 105 | case "Q": 106 | case "S": 107 | sequence = getSequence("coordinate pair"); 108 | if (sequence.length & 1) 109 | throw Error("Expected coordinate pair couple at position " + index); 110 | 111 | sequence = sequence.reduce(function(seq, coords, i) { 112 | var odd = i & 1; 113 | if (!odd) { 114 | seq.push(makeCommand({ cp: coords })); 115 | } else { 116 | var last = seq[seq.length - 1]; 117 | last.end = coords; 118 | } 119 | return seq; 120 | }, []); 121 | 122 | break; 123 | case "H": 124 | case "V": 125 | sequence = getSequence("number").map(function(value) { 126 | return makeCommand({ value: value }); 127 | }); 128 | break; 129 | case "A": 130 | sequence = getSequence("arc definition").map(makeCommand); 131 | break; 132 | case "Z": 133 | sequence = [ { code: "Z" } ]; 134 | break; 135 | } 136 | commands.push.apply(commands, sequence); 137 | } 138 | 139 | return commands; 140 | 141 | function makeCommand(obj) { 142 | obj.code = cmd; 143 | obj.relative = relative; 144 | 145 | return obj; 146 | } 147 | function get(what, must) { 148 | re[what].lastIndex = index; 149 | var res = re[what].exec(d); 150 | if (!res || res.index !== index) { 151 | if (!must) return null; 152 | throw Error("Expected " + what + " at position " + index); 153 | } 154 | 155 | index = re[what].lastIndex; 156 | 157 | return res[1]; 158 | } 159 | function getSequence(what) { 160 | var sequence = []; 161 | var matched; 162 | var must = true; 163 | while ((matched = matchers[what](must)) !== null) { 164 | sequence.push(matched); 165 | must = !!get("comma"); 166 | } 167 | 168 | return sequence; 169 | } 170 | }; 171 | }); 172 | -------------------------------------------------------------------------------- /test/tests.js: -------------------------------------------------------------------------------- 1 | (function(root, tests) { 2 | if (typeof define === "function" && define.amd) 3 | define(["chai", "parser"], tests); 4 | else if (typeof exports === "object") 5 | tests(require("chai"), require("../parser.js")); 6 | else tests(root.dPathParse); 7 | })(this, function(chai, parse) { 8 | "use strict"; 9 | 10 | var expect = chai.expect; 11 | 12 | describe("d-path-parser", function() { 13 | it("should handle moveTo commands", function() { 14 | expect(parse("M10,20")).to.eql([{ code: "M", relative: false, end: { x: 10, y: 20 }}]); 15 | }); 16 | it("should handle lineTo commands", function() { 17 | expect(parse("L10,20")).to.eql([{ code: "L", relative: false, end: { x: 10, y: 20 }}]); 18 | }); 19 | it("should handle horizontal lineTo commands", function() { 20 | expect(parse("H10")).to.eql([{ code: "H", relative: false, value: 10 }]); 21 | }); 22 | it("should handle vertical lineTo commands", function() { 23 | expect(parse("V10")).to.eql([{ code: "V", relative: false, value: 10 }]); 24 | }); 25 | it("should handle curveTo commands", function() { 26 | expect(parse("C1,2 3,4 5,6")).to.eql([{ 27 | code: "C", 28 | relative: false, 29 | cp1: { x: 1, y: 2 }, 30 | cp2: { x: 3, y: 4 }, 31 | end: { x: 5, y: 6 } 32 | }]); 33 | }); 34 | it("should handle quadratic curveTo commands", function() { 35 | expect(parse("Q1,2 3,4")).to.eql([{ 36 | code: "Q", 37 | relative: false, 38 | cp: { x: 1, y: 2 }, 39 | end: { x: 3, y: 4 } 40 | }]); 41 | }); 42 | it("should handle smooth curveTo commands", function() { 43 | expect(parse("S1,2 3,4")).to.eql([{ 44 | code: "S", 45 | relative: false, 46 | cp: { x: 1, y: 2 }, 47 | end: { x: 3, y: 4 } 48 | }]); 49 | }); 50 | it("should handle quadratic smooth curveTo commands", function() { 51 | expect(parse("T1,2")).to.eql([{ code: "T", relative: false, end: { x: 1, y: 2 } }]); 52 | }); 53 | it("should handle arcTo commands", function() { 54 | expect(parse("A1,2 3 0 0 4,5")).to.eql([{ 55 | code: "A", 56 | relative: false, 57 | radii: { x: 1, y: 2 }, 58 | rotation: 3, 59 | large: false, 60 | clockwise: false, 61 | end: { x: 4, y: 5 } 62 | }]); 63 | }); 64 | it("should handle closePath commands", function() { 65 | expect(parse("Z")).to.eql([{ code: "Z" }]); 66 | }); 67 | }); 68 | describe("With relation to relative commands", function() { 69 | it("should handle relative moveTo commands", function() { 70 | expect(parse("m10,20")).to.eql([{ code: "m", relative: true, end: { x: 10, y: 20 }}]); 71 | }); 72 | it("should handle relative lineTo commands", function() { 73 | expect(parse("l10,20")).to.eql([{ code: "l", relative: true, end: { x: 10, y: 20 }}]); 74 | }); 75 | it("should handle relative horizontal lineTo commands", function() { 76 | expect(parse("h10")).to.eql([{ code: "h", relative: true, value: 10 }]); 77 | }); 78 | it("should handle relative vertical lineTo commands", function() { 79 | expect(parse("v10")).to.eql([{ code: "v", relative: true, value: 10 }]); 80 | }); 81 | it("should handle relative curveTo commands", function() { 82 | expect(parse("c1,2 3,4 5,6")).to.eql([{ 83 | code: "c", 84 | relative: true, 85 | cp1: { x: 1, y: 2 }, 86 | cp2: { x: 3, y: 4 }, 87 | end: { x: 5, y: 6 } 88 | }]); 89 | }); 90 | it("should handle relative quadratic curveTo commands", function() { 91 | expect(parse("q1,2 3,4")).to.eql([{ 92 | code: "q", 93 | relative: true, 94 | cp: { x: 1, y: 2 }, 95 | end: { x: 3, y: 4 } 96 | }]); 97 | }); 98 | it("should handle relative smooth curveTo commands", function() { 99 | expect(parse("s1,2 3,4")).to.eql([{ 100 | code: "s", 101 | relative: true, 102 | cp: { x: 1, y: 2 }, 103 | end: { x: 3, y: 4 } 104 | }]); 105 | }); 106 | it("should handle relative quadratic smooth curveTo commands", function() { 107 | expect(parse("t1,2")).to.eql([{ code: "t", relative: true, end: { x: 1, y: 2 } }]); 108 | }); 109 | it("should handle relative arcTo commands", function() { 110 | expect(parse("a1,2 3 0 0 4,5")).to.eql([{ 111 | code: "a", 112 | relative: true, 113 | radii: { x: 1, y: 2 }, 114 | rotation: 3, 115 | large: false, 116 | clockwise: false, 117 | end: { x: 4, y: 5 } 118 | }]); 119 | }); 120 | it("should make no difference between 'z' and 'Z'", function() { 121 | expect(parse("z")).to.eql([{ code: "Z" }]); 122 | }); 123 | }); 124 | 125 | describe("With relation to multiple subpaths", function() { 126 | it("should parse only the first 'm/M' command as moveTo, the following as lineTo", function() { 127 | expect(parse("M1,2 3,4")).to.eql([ 128 | { code: "M", relative: false, end: { x: 1, y: 2} }, 129 | { code: "L", relative: false, end: { x: 3, y: 4} } 130 | ]) 131 | }); 132 | it("should handle multiple lineTo subpaths", function() { 133 | expect(parse("L1,2 3,4")).to.eql([ 134 | { code: "L", relative: false, end: { x: 1, y: 2} }, 135 | { code: "L", relative: false, end: { x: 3, y: 4} } 136 | ]) 137 | }); 138 | it("should handle multiple horizontal lineTo variations", function() { 139 | expect(parse("H1 2")).to.eql([ 140 | { code: "H", relative: false, value: 1 }, 141 | { code: "H", relative: false, value: 2 } 142 | ]) 143 | }); 144 | it("should handle multiple vertical lineTo variations", function() { 145 | expect(parse("V1 2")).to.eql([ 146 | { code: "V", relative: false, value: 1 }, 147 | { code: "V", relative: false, value: 2 } 148 | ]) 149 | }); 150 | it("should handle multiple curveTo subpaths", function() { 151 | expect(parse("C1,2 3,4 5,6 7,8 9,10 11,12")).to.eql([{ 152 | code: "C", 153 | relative: false, 154 | cp1: { x: 1, y: 2 }, 155 | cp2: { x: 3, y: 4 }, 156 | end: { x: 5, y: 6 } 157 | }, { 158 | code: "C", 159 | relative: false, 160 | cp1: { x: 7, y: 8 }, 161 | cp2: { x: 9, y: 10 }, 162 | end: { x: 11, y: 12 } 163 | }]); 164 | }); 165 | it("should handle multiple quadratic curveTo subpaths", function() { 166 | expect(parse("Q1,2 3,4 5,6 7,8")).to.eql([{ 167 | code: "Q", 168 | relative: false, 169 | cp: { x: 1, y: 2 }, 170 | end: { x: 3, y: 4 } 171 | }, { 172 | code: "Q", 173 | relative: false, 174 | cp: { x: 5, y: 6 }, 175 | end: { x: 7, y: 8 } 176 | }]); 177 | }); 178 | it("should handle multiple smooth curveTo subpaths", function() { 179 | expect(parse("S1,2 3,4 5,6 7,8")).to.eql([{ 180 | code: "S", 181 | relative: false, 182 | cp: { x: 1, y: 2 }, 183 | end: { x: 3, y: 4 } 184 | }, { 185 | code: "S", 186 | relative: false, 187 | cp: { x: 5, y: 6 }, 188 | end: { x: 7, y: 8 } 189 | }]); 190 | }); 191 | it("should handle multiple quadratic smooth curveTo subpaths", function() { 192 | expect(parse("T1,2 3,4")).to.eql([ 193 | { code: "T", relative: false, end: { x: 1, y: 2} }, 194 | { code: "T", relative: false, end: { x: 3, y: 4} } 195 | ]) 196 | }); 197 | }); 198 | 199 | describe("Complex paths", function() { 200 | it("Triangle", function() { 201 | expect(parse("M1,1 L2,2 3,1 Z")).to.eql([ 202 | { code: "M", relative: false, end: { x: 1, y: 1 }}, 203 | { code: "L", relative: false, end: { x: 2, y: 2 }}, 204 | { code: "L", relative: false, end: { x: 3, y: 1 }}, 205 | { code: "Z" } 206 | ]); 207 | }); 208 | it("Pac-man", function() { 209 | expect(parse("M0,0 l10,10 A14.142 14.142 0 1 1 10,-10 Z")).to.eql([ 210 | { code: "M", relative: false, end: { x: 0, y: 0 }}, 211 | { code: "l", relative: true, end: { x: 10, y: 10 }}, 212 | { 213 | code: "A", 214 | relative: false, 215 | radii: { x: 14.142, y: 14.142 }, 216 | rotation: 0, 217 | large: true, 218 | clockwise: true, 219 | end: { x: 10, y: -10 } 220 | }, 221 | { code: "Z" } 222 | ]); 223 | }); 224 | it("Clubs", function() { 225 | expect(parse("M50,90 h10 Q53,80 53,60 C80,75 90,65 90,57 90,49 80,39 53,54 68,27 58,17 50,17 42,17 32,27 47,54 c-27,-15 -37,-5 -37,3 0,8 10,18 37,3 q0,20 -7,30Z")).to.eql([ 226 | { code: "M", relative: false, end: { x: 50, y: 90} }, 227 | { code: "h", relative: true, value: 10 }, 228 | { 229 | code: "Q", 230 | relative: false, 231 | cp: { x: 53, y: 80 }, 232 | end: { x: 53, y: 60 } 233 | }, 234 | { 235 | code: "C", 236 | relative: false, 237 | cp1: { x: 80, y: 75 }, 238 | cp2: { x: 90, y: 65 }, 239 | end: { x: 90, y: 57 } 240 | }, 241 | { 242 | code: "C", 243 | relative: false, 244 | cp1: { x: 90, y: 49 }, 245 | cp2: { x: 80, y: 39 }, 246 | end: { x: 53, y: 54 } 247 | }, 248 | { 249 | code: "C", 250 | relative: false, 251 | cp1: { x: 68, y: 27 }, 252 | cp2: { x: 58, y: 17 }, 253 | end: { x: 50, y: 17 } 254 | }, 255 | { 256 | code: "C", 257 | relative: false, 258 | cp1: { x: 42, y: 17 }, 259 | cp2: { x: 32, y: 27 }, 260 | end: { x: 47, y: 54 } 261 | }, 262 | { 263 | code: "c", 264 | relative: true, 265 | cp1: { x: -27, y: -15 }, 266 | cp2: { x: -37, y: -5 }, 267 | end: { x: -37, y: 3 } 268 | }, 269 | { 270 | code: "c", 271 | relative: true, 272 | cp1: { x: 0, y: 8 }, 273 | cp2: { x: 10, y: 18 }, 274 | end: { x: 37, y: 3 } 275 | }, 276 | { 277 | code: "q", 278 | relative: true, 279 | cp: { x: 0, y: 20 }, 280 | end: { x: -7, y: 30 } 281 | }, 282 | { code: "Z" } 283 | ]); 284 | }); 285 | }); 286 | 287 | describe("Troublesome parsing", function() { 288 | it("Decimal numbers", function() { 289 | expect(parse("M-10.5 .3")).to.eql([ 290 | { code: "M", relative: false, end: { x: -10.5, y: 0.3 }} 291 | ]); 292 | expect(parse("M1.5.3")).to.eql([ 293 | { code: "M", relative: false, end: { x: 1.5, y: 0.3 }} 294 | ]); 295 | }); 296 | it("Negative numbers", function() { 297 | expect(parse("M5-3-1,0")).to.eql([ 298 | { code: "M", relative: false, end: { x: 5, y: -3 }}, 299 | { code: "L", relative: false, end: { x: -1, y: 0 }} 300 | ]); 301 | }); 302 | it("Exponential numbers", function() { 303 | expect(parse("M 500e-1 1.23e+2")).to.eql([ 304 | { code: "M", relative: false, end: { x: 50, y: 123 }} 305 | ]); 306 | }); 307 | it("Insert ALL of the commas!", function() { 308 | expect(parse("A4,3,2,1,0,-1,-2,-3,-2,-1,0,1,2,3")).to.eql([ 309 | { 310 | code: "A", 311 | relative: false, 312 | radii: { x: 4, y: 3 }, 313 | rotation: 2, 314 | large: true, 315 | clockwise: false, 316 | end: { x: -1, y: -2 } 317 | }, 318 | { 319 | code: "A", 320 | relative: false, 321 | radii: { x: -3, y: -2 }, 322 | rotation: -1, 323 | large: false, 324 | clockwise: true, 325 | end: { x: 2, y: 3 } 326 | } 327 | ]); 328 | }); 329 | it("Collapsed whitespaces", function() { 330 | expect(parse("M1,2 3,4 5,6Zh10")).to.eql([ 331 | { code: "M", relative: false, end: { x: 1, y: 2 }}, 332 | { code: "L", relative: false, end: { x: 3, y: 4 }}, 333 | { code: "L", relative: false, end: { x: 5, y: 6 }}, 334 | { code: "Z" }, 335 | { code: "h", relative: true, value: 10 } 336 | ]); 337 | }); 338 | it("Spaaaaace", function() { 339 | expect(parse(" M 1\t2\n3\r4 ")).to.eql([ 340 | { code: "M", relative: false, end: { x: 1, y: 2 }}, 341 | { code: "L", relative: false, end: { x: 3, y: 4 }} 342 | ]); 343 | }); 344 | it("H with 0 should not be ignored", function() { 345 | expect(parse("H0")).to.eql([ 346 | { code: "H", relative: false, value: 0 }, 347 | ]); 348 | }); 349 | it("V with 0 should not be ignored", function() { 350 | expect(parse("V0")).to.eql([ 351 | { code: "V", relative: false, value: 0 }, 352 | ]); 353 | }); 354 | it("h with 0 should not be ignored", function() { 355 | expect(parse("h0")).to.eql([ 356 | { code: "h", relative: true, value: 0 }, 357 | ]); 358 | }); 359 | it("v with 0 should not be ignored", function() { 360 | expect(parse("v0")).to.eql([ 361 | { code: "v", relative: true, value: 0 }, 362 | ]); 363 | }); 364 | it("RodionNikolaev's test to not ignore H0", function() { 365 | expect(parse("M0,0 H124.659 V71.565 H0 Z")).to.eql([ 366 | { "code": "M", "end": { "x": 0,"y": 0 }, "relative": false }, 367 | { "code": "H", "relative": false, "value": 124.659 }, 368 | { "code": "V", "relative": false, "value": 71.565 }, 369 | { "code": "H", "relative": false, "value": 0 }, 370 | { "code": "Z" } 371 | ]); 372 | }); 373 | }); 374 | 375 | }); 376 | --------------------------------------------------------------------------------