├── .eslintrc.json ├── .gitignore ├── .travis.yml ├── Readme.md ├── index.js ├── index.mjs ├── license.md ├── package-lock.json ├── package.json └── test.mjs /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true, 5 | "commonjs": true, 6 | "es6": true 7 | }, 8 | "extends": "eslint:recommended", 9 | "rules": { 10 | "strict": 2, 11 | "indent": 0, 12 | "linebreak-style": 0, 13 | "quotes": 0, 14 | "semi": 0, 15 | "no-cond-assign": 1, 16 | "no-constant-condition": 1, 17 | "no-duplicate-case": 1, 18 | "no-empty": 1, 19 | "no-ex-assign": 1, 20 | "no-extra-boolean-cast": 1, 21 | "no-extra-semi": 1, 22 | "no-fallthrough": 1, 23 | "no-func-assign": 1, 24 | "no-global-assign": 1, 25 | "no-implicit-globals": 2, 26 | "no-inner-declarations": ["error", "functions"], 27 | "no-irregular-whitespace": 2, 28 | "no-loop-func": 1, 29 | "no-multi-str": 1, 30 | "no-mixed-spaces-and-tabs": 1, 31 | "no-proto": 1, 32 | "no-sequences": 1, 33 | "no-throw-literal": 1, 34 | "no-unmodified-loop-condition": 1, 35 | "no-useless-call": 1, 36 | "no-void": 1, 37 | "no-with": 2, 38 | "wrap-iife": 1, 39 | "no-redeclare": 1, 40 | "no-unused-vars": ["error", { "vars": "all", "args": "none" }], 41 | "no-sparse-arrays": 1 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "stable" 4 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # normalize-svg-path [![build](https://travis-ci.org/jkroso/normalize-svg-path.svg?branch=master)](https://travis-ci.org/jkroso/normalize-svg-path) 2 | 3 | 4 | Convert all segments in a path to curves. Usefull if you intend to animate one shape to another. By defining all segments with curves instead of a mix of lines, arcs, and curves tweening becomes much simpler. It could also help you rewrite your SVG code according to the principles of [narcissistic design](//vimeo.com/77199361). 5 | 6 | ## Usage 7 | 8 | [![npm install normalize-svg-path](https://nodei.co/npm/normalize-svg-path.png?mini=true)](https://npmjs.org/package/normalize-svg-path/) 9 | 10 | ```js 11 | import parse from 'parse-svg-path' 12 | import abs from 'abs-svg-path' 13 | import normalize from 'normalize-svg-path' 14 | 15 | const segments = normalize(abs(parse('M0 0L10 10A10 10 0 0 0 20 20Z'))) 16 | ``` 17 | 18 | ## API 19 | 20 | ### normalize(path) 21 | 22 | Translate each segment in `path` to an equivalent cubic bézier curve. The input `path` must be [absolute](//github.com/jkroso/abs-svg-path). 23 | 24 | ```js 25 | normalize([['L',1,2]]) // => [['C',0,0,1,2,1,2]] 26 | ``` 27 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = normalize 4 | 5 | var arcToCurve = require('svg-arc-to-cubic-bezier') 6 | 7 | function normalize(path){ 8 | // init state 9 | var prev 10 | var result = [] 11 | var bezierX = 0 12 | var bezierY = 0 13 | var startX = 0 14 | var startY = 0 15 | var quadX = null 16 | var quadY = null 17 | var x = 0 18 | var y = 0 19 | 20 | for (var i = 0, len = path.length; i < len; i++) { 21 | var seg = path[i] 22 | var command = seg[0] 23 | 24 | switch (command) { 25 | case 'M': 26 | startX = seg[1] 27 | startY = seg[2] 28 | break 29 | case 'A': 30 | var curves = arcToCurve({ 31 | px: x, 32 | py: y, 33 | cx: seg[6], 34 | cy: seg[7], 35 | rx: seg[1], 36 | ry: seg[2], 37 | xAxisRotation: seg[3], 38 | largeArcFlag: seg[4], 39 | sweepFlag: seg[5] 40 | }) 41 | 42 | // null-curves 43 | if (!curves.length) continue 44 | 45 | for (var j = 0, c; j < curves.length; j++) { 46 | c = curves[j] 47 | seg = ['C', c.x1, c.y1, c.x2, c.y2, c.x, c.y] 48 | if (j < curves.length - 1) result.push(seg) 49 | } 50 | 51 | break 52 | case 'S': 53 | // default control point 54 | var cx = x 55 | var cy = y 56 | if (prev == 'C' || prev == 'S') { 57 | cx += cx - bezierX // reflect the previous command's control 58 | cy += cy - bezierY // point relative to the current point 59 | } 60 | seg = ['C', cx, cy, seg[1], seg[2], seg[3], seg[4]] 61 | break 62 | case 'T': 63 | if (prev == 'Q' || prev == 'T') { 64 | quadX = x * 2 - quadX // as with 'S' reflect previous control point 65 | quadY = y * 2 - quadY 66 | } else { 67 | quadX = x 68 | quadY = y 69 | } 70 | seg = quadratic(x, y, quadX, quadY, seg[1], seg[2]) 71 | break 72 | case 'Q': 73 | quadX = seg[1] 74 | quadY = seg[2] 75 | seg = quadratic(x, y, seg[1], seg[2], seg[3], seg[4]) 76 | break 77 | case 'L': 78 | seg = line(x, y, seg[1], seg[2]) 79 | break 80 | case 'H': 81 | seg = line(x, y, seg[1], y) 82 | break 83 | case 'V': 84 | seg = line(x, y, x, seg[1]) 85 | break 86 | case 'Z': 87 | seg = line(x, y, startX, startY) 88 | break 89 | } 90 | 91 | // update state 92 | prev = command 93 | x = seg[seg.length - 2] 94 | y = seg[seg.length - 1] 95 | if (seg.length > 4) { 96 | bezierX = seg[seg.length - 4] 97 | bezierY = seg[seg.length - 3] 98 | } else { 99 | bezierX = x 100 | bezierY = y 101 | } 102 | result.push(seg) 103 | } 104 | 105 | return result 106 | } 107 | 108 | function line(x1, y1, x2, y2){ 109 | return ['C', x1, y1, x2, y2, x2, y2] 110 | } 111 | 112 | function quadratic(x1, y1, cx, cy, x2, y2){ 113 | return [ 114 | 'C', 115 | x1/3 + (2/3) * cx, 116 | y1/3 + (2/3) * cy, 117 | x2/3 + (2/3) * cx, 118 | y2/3 + (2/3) * cy, 119 | x2, 120 | y2 121 | ] 122 | } 123 | -------------------------------------------------------------------------------- /index.mjs: -------------------------------------------------------------------------------- 1 | import arcToCurve from 'svg-arc-to-cubic-bezier' 2 | 3 | export default function normalize(path){ 4 | // init state 5 | var prev 6 | var result = [] 7 | var bezierX = 0 8 | var bezierY = 0 9 | var startX = 0 10 | var startY = 0 11 | var quadX = null 12 | var quadY = null 13 | var x = 0 14 | var y = 0 15 | 16 | for (var i = 0, len = path.length; i < len; i++) { 17 | var seg = path[i] 18 | var command = seg[0] 19 | 20 | switch (command) { 21 | case 'M': 22 | startX = seg[1] 23 | startY = seg[2] 24 | break 25 | case 'A': 26 | var curves = arcToCurve({ 27 | px: x, 28 | py: y, 29 | cx: seg[6], 30 | cy: seg[7], 31 | rx: seg[1], 32 | ry: seg[2], 33 | xAxisRotation: seg[3], 34 | largeArcFlag: seg[4], 35 | sweepFlag: seg[5] 36 | }) 37 | 38 | // null-curves 39 | if (!curves.length) continue 40 | 41 | for (var j = 0, c; j < curves.length; j++) { 42 | c = curves[j] 43 | seg = ['C', c.x1, c.y1, c.x2, c.y2, c.x, c.y] 44 | if (j < curves.length - 1) result.push(seg) 45 | } 46 | 47 | break 48 | case 'S': 49 | // default control point 50 | var cx = x 51 | var cy = y 52 | if (prev == 'C' || prev == 'S') { 53 | cx += cx - bezierX // reflect the previous command's control 54 | cy += cy - bezierY // point relative to the current point 55 | } 56 | seg = ['C', cx, cy, seg[1], seg[2], seg[3], seg[4]] 57 | break 58 | case 'T': 59 | if (prev == 'Q' || prev == 'T') { 60 | quadX = x * 2 - quadX // as with 'S' reflect previous control point 61 | quadY = y * 2 - quadY 62 | } else { 63 | quadX = x 64 | quadY = y 65 | } 66 | seg = quadratic(x, y, quadX, quadY, seg[1], seg[2]) 67 | break 68 | case 'Q': 69 | quadX = seg[1] 70 | quadY = seg[2] 71 | seg = quadratic(x, y, seg[1], seg[2], seg[3], seg[4]) 72 | break 73 | case 'L': 74 | seg = line(x, y, seg[1], seg[2]) 75 | break 76 | case 'H': 77 | seg = line(x, y, seg[1], y) 78 | break 79 | case 'V': 80 | seg = line(x, y, x, seg[1]) 81 | break 82 | case 'Z': 83 | seg = line(x, y, startX, startY) 84 | break 85 | } 86 | 87 | // update state 88 | prev = command 89 | x = seg[seg.length - 2] 90 | y = seg[seg.length - 1] 91 | if (seg.length > 4) { 92 | bezierX = seg[seg.length - 4] 93 | bezierY = seg[seg.length - 3] 94 | } else { 95 | bezierX = x 96 | bezierY = y 97 | } 98 | result.push(seg) 99 | } 100 | 101 | return result 102 | } 103 | 104 | function line(x1, y1, x2, y2){ 105 | return ['C', x1, y1, x2, y2, x2, y2] 106 | } 107 | 108 | function quadratic(x1, y1, cx, cy, x2, y2){ 109 | return [ 110 | 'C', 111 | x1/3 + (2/3) * cx, 112 | y1/3 + (2/3) * cy, 113 | x2/3 + (2/3) * cx, 114 | y2/3 + (2/3) * cy, 115 | x2, 116 | y2 117 | ] 118 | } 119 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | 2 | The MIT License 3 | 4 | Copyright © 2008-2013 Dmitry Baranovskiy (http://raphaeljs.com) 5 | Copyright © 2008-2013 Sencha Labs (http://sencha.com) 6 | Copyright © 2013 Jake Rosoman 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining 9 | a copy of this software and associated documentation files (the 10 | 'Software'), to deal in the Software without restriction, including 11 | without limitation the rights to use, copy, modify, merge, publish, 12 | distribute, sublicense, and/or sell copies of the Software, and to 13 | permit persons to whom the Software is furnished to do so, subject to 14 | the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be 17 | included in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 20 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "normalize-svg-path", 3 | "version": "1.1.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "balanced-match": { 8 | "version": "1.0.0", 9 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 10 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 11 | "dev": true 12 | }, 13 | "brace-expansion": { 14 | "version": "1.1.11", 15 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 16 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 17 | "dev": true, 18 | "requires": { 19 | "balanced-match": "^1.0.0", 20 | "concat-map": "0.0.1" 21 | } 22 | }, 23 | "concat-map": { 24 | "version": "0.0.1", 25 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 26 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 27 | "dev": true 28 | }, 29 | "deep-equal": { 30 | "version": "1.1.1", 31 | "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", 32 | "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", 33 | "dev": true, 34 | "requires": { 35 | "is-arguments": "^1.0.4", 36 | "is-date-object": "^1.0.1", 37 | "is-regex": "^1.0.4", 38 | "object-is": "^1.0.1", 39 | "object-keys": "^1.1.1", 40 | "regexp.prototype.flags": "^1.2.0" 41 | } 42 | }, 43 | "define-properties": { 44 | "version": "1.1.3", 45 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", 46 | "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", 47 | "dev": true, 48 | "requires": { 49 | "object-keys": "^1.0.12" 50 | } 51 | }, 52 | "defined": { 53 | "version": "1.0.0", 54 | "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", 55 | "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", 56 | "dev": true 57 | }, 58 | "dotignore": { 59 | "version": "0.1.2", 60 | "resolved": "https://registry.npmjs.org/dotignore/-/dotignore-0.1.2.tgz", 61 | "integrity": "sha512-UGGGWfSauusaVJC+8fgV+NVvBXkCTmVv7sk6nojDZZvuOUNGUy0Zk4UpHQD6EDjS0jpBwcACvH4eofvyzBcRDw==", 62 | "dev": true, 63 | "requires": { 64 | "minimatch": "^3.0.4" 65 | } 66 | }, 67 | "es-abstract": { 68 | "version": "1.17.6", 69 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", 70 | "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", 71 | "dev": true, 72 | "requires": { 73 | "es-to-primitive": "^1.2.1", 74 | "function-bind": "^1.1.1", 75 | "has": "^1.0.3", 76 | "has-symbols": "^1.0.1", 77 | "is-callable": "^1.2.0", 78 | "is-regex": "^1.1.0", 79 | "object-inspect": "^1.7.0", 80 | "object-keys": "^1.1.1", 81 | "object.assign": "^4.1.0", 82 | "string.prototype.trimend": "^1.0.1", 83 | "string.prototype.trimstart": "^1.0.1" 84 | }, 85 | "dependencies": { 86 | "is-regex": { 87 | "version": "1.1.1", 88 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", 89 | "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", 90 | "dev": true, 91 | "requires": { 92 | "has-symbols": "^1.0.1" 93 | } 94 | } 95 | } 96 | }, 97 | "es-to-primitive": { 98 | "version": "1.2.1", 99 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", 100 | "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", 101 | "dev": true, 102 | "requires": { 103 | "is-callable": "^1.1.4", 104 | "is-date-object": "^1.0.1", 105 | "is-symbol": "^1.0.2" 106 | } 107 | }, 108 | "for-each": { 109 | "version": "0.3.3", 110 | "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", 111 | "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", 112 | "dev": true, 113 | "requires": { 114 | "is-callable": "^1.1.3" 115 | } 116 | }, 117 | "fs.realpath": { 118 | "version": "1.0.0", 119 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 120 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 121 | "dev": true 122 | }, 123 | "function-bind": { 124 | "version": "1.1.1", 125 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 126 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 127 | "dev": true 128 | }, 129 | "glob": { 130 | "version": "7.1.6", 131 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 132 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 133 | "dev": true, 134 | "requires": { 135 | "fs.realpath": "^1.0.0", 136 | "inflight": "^1.0.4", 137 | "inherits": "2", 138 | "minimatch": "^3.0.4", 139 | "once": "^1.3.0", 140 | "path-is-absolute": "^1.0.0" 141 | } 142 | }, 143 | "has": { 144 | "version": "1.0.3", 145 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 146 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 147 | "dev": true, 148 | "requires": { 149 | "function-bind": "^1.1.1" 150 | } 151 | }, 152 | "has-symbols": { 153 | "version": "1.0.1", 154 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", 155 | "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", 156 | "dev": true 157 | }, 158 | "inflight": { 159 | "version": "1.0.6", 160 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 161 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 162 | "dev": true, 163 | "requires": { 164 | "once": "^1.3.0", 165 | "wrappy": "1" 166 | } 167 | }, 168 | "inherits": { 169 | "version": "2.0.4", 170 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 171 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 172 | "dev": true 173 | }, 174 | "is-arguments": { 175 | "version": "1.0.4", 176 | "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", 177 | "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", 178 | "dev": true 179 | }, 180 | "is-callable": { 181 | "version": "1.2.1", 182 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.1.tgz", 183 | "integrity": "sha512-wliAfSzx6V+6WfMOmus1xy0XvSgf/dlStkvTfq7F0g4bOIW0PSUbnyse3NhDwdyYS1ozfUtAAySqTws3z9Eqgg==", 184 | "dev": true 185 | }, 186 | "is-date-object": { 187 | "version": "1.0.2", 188 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", 189 | "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", 190 | "dev": true 191 | }, 192 | "is-negative-zero": { 193 | "version": "2.0.0", 194 | "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", 195 | "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=", 196 | "dev": true 197 | }, 198 | "is-regex": { 199 | "version": "1.0.5", 200 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", 201 | "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", 202 | "dev": true, 203 | "requires": { 204 | "has": "^1.0.3" 205 | } 206 | }, 207 | "is-symbol": { 208 | "version": "1.0.3", 209 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", 210 | "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", 211 | "dev": true, 212 | "requires": { 213 | "has-symbols": "^1.0.1" 214 | } 215 | }, 216 | "minimatch": { 217 | "version": "3.0.4", 218 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 219 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 220 | "dev": true, 221 | "requires": { 222 | "brace-expansion": "^1.1.7" 223 | } 224 | }, 225 | "minimist": { 226 | "version": "1.2.5", 227 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 228 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", 229 | "dev": true 230 | }, 231 | "object-inspect": { 232 | "version": "1.7.0", 233 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", 234 | "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", 235 | "dev": true 236 | }, 237 | "object-is": { 238 | "version": "1.1.2", 239 | "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.2.tgz", 240 | "integrity": "sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==", 241 | "dev": true, 242 | "requires": { 243 | "define-properties": "^1.1.3", 244 | "es-abstract": "^1.17.5" 245 | } 246 | }, 247 | "object-keys": { 248 | "version": "1.1.1", 249 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", 250 | "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", 251 | "dev": true 252 | }, 253 | "object.assign": { 254 | "version": "4.1.1", 255 | "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz", 256 | "integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==", 257 | "dev": true, 258 | "requires": { 259 | "define-properties": "^1.1.3", 260 | "es-abstract": "^1.18.0-next.0", 261 | "has-symbols": "^1.0.1", 262 | "object-keys": "^1.1.1" 263 | }, 264 | "dependencies": { 265 | "es-abstract": { 266 | "version": "1.18.0-next.0", 267 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.0.tgz", 268 | "integrity": "sha512-elZXTZXKn51hUBdJjSZGYRujuzilgXo8vSPQzjGYXLvSlGiCo8VO8ZGV3kjo9a0WNJJ57hENagwbtlRuHuzkcQ==", 269 | "dev": true, 270 | "requires": { 271 | "es-to-primitive": "^1.2.1", 272 | "function-bind": "^1.1.1", 273 | "has": "^1.0.3", 274 | "has-symbols": "^1.0.1", 275 | "is-callable": "^1.2.0", 276 | "is-negative-zero": "^2.0.0", 277 | "is-regex": "^1.1.1", 278 | "object-inspect": "^1.8.0", 279 | "object-keys": "^1.1.1", 280 | "object.assign": "^4.1.0", 281 | "string.prototype.trimend": "^1.0.1", 282 | "string.prototype.trimstart": "^1.0.1" 283 | } 284 | }, 285 | "is-regex": { 286 | "version": "1.1.1", 287 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", 288 | "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", 289 | "dev": true, 290 | "requires": { 291 | "has-symbols": "^1.0.1" 292 | } 293 | }, 294 | "object-inspect": { 295 | "version": "1.8.0", 296 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", 297 | "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", 298 | "dev": true 299 | } 300 | } 301 | }, 302 | "once": { 303 | "version": "1.4.0", 304 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 305 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 306 | "dev": true, 307 | "requires": { 308 | "wrappy": "1" 309 | } 310 | }, 311 | "parse-svg-path": { 312 | "version": "0.1.2", 313 | "resolved": "https://registry.npmjs.org/parse-svg-path/-/parse-svg-path-0.1.2.tgz", 314 | "integrity": "sha1-en7A0esG+lMlx9PgCbhZoJtdSes=", 315 | "dev": true 316 | }, 317 | "path-is-absolute": { 318 | "version": "1.0.1", 319 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 320 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 321 | "dev": true 322 | }, 323 | "path-parse": { 324 | "version": "1.0.6", 325 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 326 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", 327 | "dev": true 328 | }, 329 | "regexp.prototype.flags": { 330 | "version": "1.3.0", 331 | "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", 332 | "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", 333 | "dev": true, 334 | "requires": { 335 | "define-properties": "^1.1.3", 336 | "es-abstract": "^1.17.0-next.1" 337 | } 338 | }, 339 | "resolve": { 340 | "version": "1.17.0", 341 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", 342 | "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", 343 | "dev": true, 344 | "requires": { 345 | "path-parse": "^1.0.6" 346 | } 347 | }, 348 | "resumer": { 349 | "version": "0.0.0", 350 | "resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz", 351 | "integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=", 352 | "dev": true, 353 | "requires": { 354 | "through": "~2.3.4" 355 | } 356 | }, 357 | "string.prototype.trim": { 358 | "version": "1.2.2", 359 | "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.2.tgz", 360 | "integrity": "sha512-b5yrbl3BXIjHau9Prk7U0RRYcUYdN4wGSVaqoBQS50CCE3KBuYU0TYRNPFCP7aVoNMX87HKThdMRVIP3giclKg==", 361 | "dev": true, 362 | "requires": { 363 | "define-properties": "^1.1.3", 364 | "es-abstract": "^1.18.0-next.0" 365 | }, 366 | "dependencies": { 367 | "es-abstract": { 368 | "version": "1.18.0-next.0", 369 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.0.tgz", 370 | "integrity": "sha512-elZXTZXKn51hUBdJjSZGYRujuzilgXo8vSPQzjGYXLvSlGiCo8VO8ZGV3kjo9a0WNJJ57hENagwbtlRuHuzkcQ==", 371 | "dev": true, 372 | "requires": { 373 | "es-to-primitive": "^1.2.1", 374 | "function-bind": "^1.1.1", 375 | "has": "^1.0.3", 376 | "has-symbols": "^1.0.1", 377 | "is-callable": "^1.2.0", 378 | "is-negative-zero": "^2.0.0", 379 | "is-regex": "^1.1.1", 380 | "object-inspect": "^1.8.0", 381 | "object-keys": "^1.1.1", 382 | "object.assign": "^4.1.0", 383 | "string.prototype.trimend": "^1.0.1", 384 | "string.prototype.trimstart": "^1.0.1" 385 | } 386 | }, 387 | "is-regex": { 388 | "version": "1.1.1", 389 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", 390 | "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", 391 | "dev": true, 392 | "requires": { 393 | "has-symbols": "^1.0.1" 394 | } 395 | }, 396 | "object-inspect": { 397 | "version": "1.8.0", 398 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", 399 | "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", 400 | "dev": true 401 | } 402 | } 403 | }, 404 | "string.prototype.trimend": { 405 | "version": "1.0.1", 406 | "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", 407 | "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", 408 | "dev": true, 409 | "requires": { 410 | "define-properties": "^1.1.3", 411 | "es-abstract": "^1.17.5" 412 | } 413 | }, 414 | "string.prototype.trimstart": { 415 | "version": "1.0.1", 416 | "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", 417 | "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", 418 | "dev": true, 419 | "requires": { 420 | "define-properties": "^1.1.3", 421 | "es-abstract": "^1.17.5" 422 | } 423 | }, 424 | "svg-arc-to-cubic-bezier": { 425 | "version": "3.2.0", 426 | "resolved": "https://registry.npmjs.org/svg-arc-to-cubic-bezier/-/svg-arc-to-cubic-bezier-3.2.0.tgz", 427 | "integrity": "sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g==" 428 | }, 429 | "tape": { 430 | "version": "4.13.3", 431 | "resolved": "https://registry.npmjs.org/tape/-/tape-4.13.3.tgz", 432 | "integrity": "sha512-0/Y20PwRIUkQcTCSi4AASs+OANZZwqPKaipGCEwp10dQMipVvSZwUUCi01Y/OklIGyHKFhIcjock+DKnBfLAFw==", 433 | "dev": true, 434 | "requires": { 435 | "deep-equal": "~1.1.1", 436 | "defined": "~1.0.0", 437 | "dotignore": "~0.1.2", 438 | "for-each": "~0.3.3", 439 | "function-bind": "~1.1.1", 440 | "glob": "~7.1.6", 441 | "has": "~1.0.3", 442 | "inherits": "~2.0.4", 443 | "is-regex": "~1.0.5", 444 | "minimist": "~1.2.5", 445 | "object-inspect": "~1.7.0", 446 | "resolve": "~1.17.0", 447 | "resumer": "~0.0.0", 448 | "string.prototype.trim": "~1.2.1", 449 | "through": "~2.3.8" 450 | } 451 | }, 452 | "through": { 453 | "version": "2.3.8", 454 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 455 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", 456 | "dev": true 457 | }, 458 | "wrappy": { 459 | "version": "1.0.2", 460 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 461 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 462 | "dev": true 463 | } 464 | } 465 | } 466 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "normalize-svg-path", 3 | "version": "1.1.0", 4 | "description": "Convert all segments in a path to curves", 5 | "keywords": [ 6 | "svg", 7 | "path", 8 | "normalize" 9 | ], 10 | "module": "./index.mjs", 11 | "main": "./index.js", 12 | "exports": { 13 | "require": "./index.js", 14 | "import": "./index.mjs" 15 | }, 16 | "dependencies": { 17 | "svg-arc-to-cubic-bezier": "^3.0.0" 18 | }, 19 | "devDependencies": { 20 | "parse-svg-path": "^0.1.2", 21 | "tape": "^4.7.0" 22 | }, 23 | "scripts": { 24 | "test": "node test.mjs" 25 | }, 26 | "repository": "git://github.com/jkroso/normalize-svg-path.git", 27 | "bugs": "https://github.com/jkroso/normalize-svg-path/issues", 28 | "author": "Jake Rosoman", 29 | "license": "MIT" 30 | } 31 | -------------------------------------------------------------------------------- /test.mjs: -------------------------------------------------------------------------------- 1 | import t from 'tape' 2 | import normalize from './index.mjs' 3 | import parse from 'parse-svg-path' 4 | 5 | 6 | t('line-to', t => { 7 | t.deepEqual( 8 | normalize(parse('L100 100')), 9 | parse('C0,0 100,100 100,100') 10 | ) 11 | 12 | t.deepEqual( 13 | normalize(parse('L50 50 100 0')), 14 | parse('C0,0 50,50 50,50 C50,50 100,0 100,0') 15 | ) 16 | 17 | t.deepEqual( 18 | normalize(parse('H50 100')), 19 | parse('C0,0 50,0 50,0 C50,0 100,0 100,0') 20 | ) 21 | 22 | t.deepEqual( 23 | normalize(parse('V50 100')), 24 | parse('C0,0 0,50 0,50 C0,50 0,100 0,100') 25 | ) 26 | 27 | t.end() 28 | }) 29 | 30 | t('curve-to', t => { 31 | t.deepEqual( 32 | normalize(parse('M10 150 C10 10 150 10 150 150')), 33 | parse('M10,150C10,10,150,10,150,150') 34 | ) 35 | 36 | t.deepEqual( 37 | normalize(parse('M0 120 Q60 0 120 120')), 38 | parse('M0,120C40,40,80,40,120,120') 39 | ) 40 | 41 | t.deepEqual( 42 | normalize(parse('M10 80 C10 10 75 10 75 80 S150 150 150 80')), 43 | parse('M10,80C10,10,75,10,75,80C75,150,150,150,150,80') 44 | ) 45 | 46 | t.deepEqual( 47 | normalize(parse('M0 60 Q30 0 60 60 T120 60')), 48 | parse('M0,60C20,20,40,20,60,60C80,100,100,100,120,60') 49 | ) 50 | 51 | t.deepEqual( 52 | normalize(parse('M30 30 Q50 50 84 50 S124 73 107 92 T127 122')), 53 | parse('M30,30C43.33333333333333,43.33333333333333,61.33333333333333,50,84,50C84,50,124,73,107,92C107,92,113.66666666666666,102,127,122') 54 | ) 55 | 56 | t.end() 57 | }) 58 | 59 | t('close-path', t => { 60 | t.deepEqual( 61 | normalize(parse('L100 0 100 100Z')), 62 | parse('C0,0,100,0,100,0C100,0,100,100,100,100C100,100,0,0,0,0') 63 | ) 64 | 65 | t.end() 66 | }) 67 | 68 | 69 | 70 | 71 | t('arc-to', t => { 72 | t.deepEqual( 73 | r(normalize(parse('M10 80 A150 150 0 0 0 150 80'))), 74 | r(parse('M 10 80C 53.80473794537901 103.1133445143787 106.19526205462094 103.1133445143787 149.99999999999997 80.00000000000003')) 75 | ) 76 | 77 | // half circle clockwise 78 | t.deepEqual( 79 | r(normalize(parse('M10 80 A50 50 0 0 1 150 80'))), 80 | r(parse('M 10 80C 10 41.340067511844474 41.34006751184445 10.000000000000014 79.99999999999999 10C 118.65993248815552 10 150 41.34006751184445 150 79.99999999999999')) 81 | ) 82 | 83 | // half circle anticlockwise 84 | t.deepEqual( 85 | r(normalize(parse('M10 80 A50 50 0 1 0 150 80'))), 86 | r(parse('M 10 80C 10.000000000000014 118.65993248815553 41.340067511844474 150 80 150C 118.65993248815553 150 150 118.65993248815553 150 80')) 87 | ) 88 | 89 | // circle 90 | t.deepEqual( 91 | r(normalize(parse('M10 80 A50 50 0 0 1 150 80 A50 50 0 0 1 10 80'))), 92 | r(parse('M 10 80C 10 41.340067511844474 41.34006751184445 10.000000000000014 79.99999999999999 10C 118.65993248815552 10 150 41.34006751184445 150 79.99999999999999C 150 118.65993248815553 118.65993248815553 150 80 150C 41.340067511844474 150 10.000000000000014 118.65993248815553 10 80.00000000000001')) 93 | ) 94 | 95 | t.deepEqual( 96 | normalize(parse('M10 80 A150 75 30 0 0 150 80')), 97 | parse('M 10 80C 72.04149682761658 108.21761044823509 129.85079028483736 108.21761044823509 150 79.99999999999999') 98 | ) 99 | 100 | // the null curve 101 | t.deepEqual( 102 | normalize(parse('M10 80 A50 50 0 0 1 10 80')), 103 | parse('M10,80') 104 | ) 105 | 106 | t.end() 107 | }) 108 | 109 | function r(arr) { return arr.map(function (arr) { return [arr[0]].concat(arr.slice(1).map(round)) }) } 110 | function round(v) { return Math.round(v) } 111 | 112 | //show parsed curve in the doc 113 | function show(src) { 114 | let path = src.map(seg => seg.join(' ')).join('') 115 | 116 | let el = document.body.appendChild(document.createElement('div')) 117 | let svgNS = 'http://www.w3.org/2000/svg' 118 | 119 | el.innerHTML = `` 120 | 121 | let svg = el.firstChild 122 | 123 | let pathEl = svg.appendChild(document.createElementNS(svgNS, 'path')) 124 | pathEl.setAttribute('d', path) 125 | 126 | pathEl.style.strokeWidth = '2px'; 127 | pathEl.style.stroke = 'black'; 128 | pathEl.style.fill = 'transparent'; 129 | 130 | return src 131 | } 132 | --------------------------------------------------------------------------------