├── README.md ├── svgpath.js ├── svgpath.min.js ├── svgpath_externs.js └── svgpath_test.html /README.md: -------------------------------------------------------------------------------- 1 | # SVGPath 2 | 3 | SVG path segments parser and optimiser used at [aydos.com/svgedit](https://aydos.com/svgedit) 4 | 5 | ## Functions 6 | 7 | ### import 8 | 9 | Import SVG Path string. SVGPath works with only one "path". 10 | 11 | ### export 12 | 13 | Export final SVG Path string. 14 | 15 | ### exportlist 16 | 17 | Export final SVG Path as segments array. You can work with this array. 18 | 19 | ### analyse 20 | 21 | Do some optimization on path. Remove consequtive Ms, remove points on the same line. Detect and mark (not delete) segment too close each other. 22 | 23 | ### absolute 24 | 25 | Make all segments absolute. 26 | 27 | ### relative 28 | 29 | Make all segmets relative. 30 | 31 | ### round 32 | 33 | Round decimal points. 34 | 35 | ### move 36 | 37 | Move the path with given dx, dy. 38 | 39 | ### flip 40 | 41 | Flip the path with given point or given axis. 42 | 43 | ### center 44 | 45 | Centralize the path to given point x, y. 46 | 47 | ### scale 48 | 49 | Scale the path. 50 | 51 | ### rotate 52 | 53 | Rotate the path wrt point x, y with angle d. -------------------------------------------------------------------------------- /svgpath.js: -------------------------------------------------------------------------------- 1 | // ==ClosureCompiler== 2 | // @compilation_level ADVANCED_OPTIMIZATIONS 3 | // ==/ClosureCompiler== 4 | 5 | // SVGPath 6 | // Fahri Aydos, aydos.com 7 | // 2016-06-18 8 | // https://aydos.com/svgedit 9 | 10 | /** @constructor */ 11 | (SVGPath = function() { 12 | 'use strict' 13 | 14 | var dec = -1 15 | var segs = [] 16 | 17 | /** @constructor */ 18 | var Segment = function() { // All value are absolute 19 | this.t = "" // relatives are calculate via px and py 20 | this.x = undefined // this is good for optimize, analyse, rotate, etc 21 | this.y = undefined // bad for round, so round logic updated 22 | this.px = undefined 23 | this.py = undefined 24 | this.x1 = undefined 25 | this.y1 = undefined 26 | this.x2 = undefined 27 | this.y2 = undefined 28 | this.r1 = undefined 29 | this.r2 = undefined 30 | this.ar = undefined 31 | this.af = undefined 32 | this.sf = undefined 33 | this.info = "" // Analyse result 34 | } 35 | 36 | // format the segment for export 37 | // check absolute-relative, and round decimals 38 | function formatsegment(s) { 39 | var seg = new Segment() 40 | seg.t = s.t 41 | seg.x = s.t.charCodeAt(0)<96 ? rounddec(s.x) : rounddec(s.x - s.px) 42 | seg.y = s.t.charCodeAt(0)<96 ? rounddec(s.y) : rounddec(s.y - s.py) 43 | seg.px = rounddec(s.px) 44 | seg.py = rounddec(s.py) 45 | seg.x1 = s.x1==undefined ? undefined : s.t.charCodeAt(0)<96 ? rounddec(s.x1) : rounddec(s.x1 - s.px) 46 | seg.y1 = s.y1==undefined ? undefined : s.t.charCodeAt(0)<96 ? rounddec(s.y1) : rounddec(s.y1 - s.py) 47 | seg.x2 = s.x2==undefined ? undefined : s.t.charCodeAt(0)<96 ? rounddec(s.x2) : rounddec(s.x2 - s.px) 48 | seg.y2 = s.y2==undefined ? undefined : s.t.charCodeAt(0)<96 ? rounddec(s.y2) : rounddec(s.y2 - s.py) 49 | seg.r1 = s.r1==undefined ? undefined : rounddec(s.r1) 50 | seg.r2 = s.r1==undefined ? undefined : rounddec(s.r2) 51 | seg.ar = s.ar==undefined ? undefined : rounddec(s.ar) 52 | seg.af = s.af 53 | seg.sf = s.sf 54 | seg.info = s.info 55 | if (s.t == "M") { 56 | seg.info += "m " + rounddec(s.x - s.px) + " " + rounddec(s.y - s.py) 57 | } 58 | if (s.t == "m") { 59 | seg.info += "M " + rounddec(s.x) + " " + rounddec(s.y) 60 | } 61 | return seg 62 | } 63 | 64 | // import path from string 65 | this.import = function(str) { 66 | str = str.replace(/\s/g, " ") // white spaces 67 | str = str.trim() // spaces at begin and end 68 | str = str.replace(/,/g, " ") // commas 69 | str = str.replace(/([A-Za-z])([A-Za-z])/g, "$1 $2") // two chars 70 | str = str.replace(/([A-Za-z])(\d)/g, "$1 $2") // char + decimal 71 | str = str.replace(/([A-Za-z])(\.)/g, "$1 .") // char + dot 72 | str = str.replace(/([A-Za-z])(-)/g, "$1 -") // char + negative number 73 | str = str.replace(/(\d)([A-Za-z])/g, "$1 $2") // decimal + char 74 | str = str.replace(/(\d)(-)/g, "$1 -") // decimal + negative number 75 | var reg = /((?:-?[\d]*)\.\d+)((?:\.\d+)+)/g // decimal + dot + decimal + dot + decimal 76 | while (reg.test(str)) { 77 | str = str.replace(reg, "$1 $2") 78 | } 79 | while (/ /.test(str)) { 80 | str = str.replace(/ /g, " ") // clear double spaces 81 | } 82 | var list = str.split(" ") 83 | var pret = "" 84 | var prex = 0 85 | var prey = 0 86 | var begx = 0 87 | var begy = 0 88 | var j = 0 89 | var i = 0 90 | segs = [] 91 | 92 | while (i64) { 96 | seg.t = list[i++] 97 | } else { 98 | if (pret == "") 99 | break 100 | seg.t = pret == "M" ? "L" : pret == "m" ? "l" : pret 101 | } 102 | pret = seg.t 103 | 104 | switch (seg.t) { 105 | case "Z": 106 | case "z": 107 | seg.x = begx 108 | seg.y = begy 109 | break 110 | case "M": 111 | case "L": 112 | case "H": 113 | case "V": 114 | case "T": 115 | seg.x = seg.t=="V" ? prex : Number(list[i++]) 116 | seg.y = seg.t=="H" ? prey : Number(list[i++]) 117 | begx = seg.t=="M" ? seg.x : begx 118 | begy = seg.t=="M" ? seg.y : begy 119 | break 120 | case "m": 121 | case "l": 122 | case "h": 123 | case "v": 124 | case "t": 125 | seg.x = seg.t=="v" ? prex : prex + Number(list[i++]) 126 | seg.y = seg.t=="h" ? prey : prey + Number(list[i++]) 127 | begx = seg.t=="m" ? seg.x : begx 128 | begy = seg.t=="m" ? seg.y : begy 129 | break 130 | case "A": 131 | case "a": 132 | seg.r1 = Number(list[i++]) 133 | seg.r2 = Number(list[i++]) 134 | seg.ar = Number(list[i++]) 135 | seg.af = Number(list[i++]) 136 | seg.sf = Number(list[i++]) 137 | seg.x = seg.t=="A" ? Number(list[i++]) : prex + Number(list[i++]) 138 | seg.y = seg.t=="A" ? Number(list[i++]) : prey + Number(list[i++]) 139 | break 140 | case "C": 141 | case "Q": 142 | case "S": 143 | seg.x1 = seg.t=="S" ? undefined : Number(list[i++]) 144 | seg.y1 = seg.t=="S" ? undefined : Number(list[i++]) 145 | seg.x2 = seg.t=="Q" ? undefined : Number(list[i++]) 146 | seg.y2 = seg.t=="Q" ? undefined : Number(list[i++]) 147 | seg.x = Number(list[i++]) 148 | seg.y = Number(list[i++]) 149 | break 150 | case "c": 151 | case "q": 152 | case "s": 153 | seg.x1 = seg.t=="s" ? undefined : prex + Number(list[i++]) 154 | seg.y1 = seg.t=="s" ? undefined : prey + Number(list[i++]) 155 | seg.x2 = seg.t=="q" ? undefined : prex + Number(list[i++]) 156 | seg.y2 = seg.t=="q" ? undefined : prey + Number(list[i++]) 157 | seg.x = prex + Number(list[i++]) 158 | seg.y = prey + Number(list[i++]) 159 | break 160 | default: 161 | i++ 162 | } 163 | seg.px = prex 164 | seg.py = prey 165 | prex = seg.x 166 | prey = seg.y 167 | segs[j++] = seg 168 | } 169 | } 170 | 171 | // export path for final usage in 172 | this.export = function() { 173 | var str = "" 174 | var pre = "" 175 | for (var i=0; imaxx ? segs[i].x : maxx 425 | maxy = segs[i].y>maxy ? segs[i].y : maxy 426 | } 427 | var dx = x - minx - (maxx-minx)/2 428 | var dy = y - miny - (maxy-miny)/2 429 | this.move(dx, dy) 430 | } 431 | 432 | // scale path with a given ratio 433 | this.scale = function (ratio) { 434 | ratio = Number(ratio) 435 | if (isNaN(ratio)) 436 | return 437 | if (ratio <= 0) 438 | return 439 | for (var i=0; ia.t.charCodeAt(0)?g(a.x):g(a.x-a.px);b.y=96>a.t.charCodeAt(0)?g(a.y):g(a.y-a.py);b.px=g(a.px);b.py=g(a.py);b.x1=void 0==a.x1?void 0:96>a.t.charCodeAt(0)?g(a.x1):g(a.x1-a.px);b.y1=void 0==a.y1?void 0:96>a.t.charCodeAt(0)?g(a.y1):g(a.y1-a.py);b.x2=void 0==a.x2?void 0:96>a.t.charCodeAt(0)?g(a.x2):g(a.x2-a.px);b.y2=void 0==a.y2?void 0:96>a.t.charCodeAt(0)?g(a.y2):g(a.y2-a.py);b.r1=void 0==a.r1?void 0:g(a.r1);b.r2=void 0==a.r1?void 0:g(a.r2); 2 | b.ar=void 0==a.ar?void 0:g(a.ar);b.af=a.af;b.sf=a.sf;b.info=a.info;"M"==a.t&&(b.info+="m "+g(a.x-a.px)+" "+g(a.y-a.py));"m"==a.t&&(b.info+="M "+g(a.x)+" "+g(a.y));return b}function g(a){if(0>m||0===a%1)return a;if(0==m)return Math.round(a);var b=Math.pow(10,m);return Math.round(a*b)/b}function n(a,b,c,e,h,l){return[l*(a-c)-h*(b-e)+c,h*(a-c)+l*(b-e)+e]}var m=-1,b=[],q=function(){this.t="";this.sf=this.af=this.ar=this.r2=this.r1=this.y2=this.x2=this.y1=this.x1=this.py=this.px=this.y=this.x=void 0;this.info= 3 | ""};this["import"]=function(a){a=a.replace(/\s/g," ");a=a.trim();a=a.replace(/,/g," ");a=a.replace(/([A-Za-z])([A-Za-z])/g,"$1 $2");a=a.replace(/([A-Za-z])(\d)/g,"$1 $2");a=a.replace(/([A-Za-z])(\.)/g,"$1 .");a=a.replace(/([A-Za-z])(-)/g,"$1 -");a=a.replace(/(\d)([A-Za-z])/g,"$1 $2");a=a.replace(/(\d)(-)/g,"$1 -");for(var d=/((?:-?[\d]*)\.\d+)((?:\.\d+)+)/g;d.test(a);)a=a.replace(d,"$1 $2");for(;/ /.test(a);)a=a.replace(/ /g," ");a=a.split(" ");var d="",c=0,e=0,h=0,l=0,g=0,k=0;for(b=[];ka&&(a=0);for(var d=0;de&&(e+=2*Math.PI),e==c&&(b[d-1].info="X"),c=e):c=-1}"M"!=b[0].t.toUpperCase()&&(b[0].t=96>b[0].t.charCodeAt(0)?"M":"m");"M"==b[b.length-1].t.toUpperCase()&&(b[b.length-1].info="X");for(d=b.length;d--;)"X"==b[d].info&&b.splice(d,1);if(0!= 10 | a)for(d=0;da&&(a=-1);m=Math.floor(a)};this.move=function(a,d){for(var c=0;ch?b[g].x:h,l=b[g].y>l?b[g].y:l;this.move(a-c-(h-c)/2,d-e-(l-e)/2)};this.scale=function(a){a=Number(a);if(!(isNaN(a)||0>=a))for(var d=0;d myapp.min.js 4 | var SVGPath = { 5 | "import" : function () {}, 6 | "export" : function () {}, 7 | "exportlist" : function () {}, 8 | "analyse" : function () {}, 9 | "absolute" : function () {}, 10 | "relative" : function () {}, 11 | "round" : function () {}, 12 | "move" : function () {}, 13 | "flip" : function () {}, 14 | "center" : function () {}, 15 | "scale" : function () {}, 16 | "rotate" : function () {}, 17 | } -------------------------------------------------------------------------------- /svgpath_test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SVGPath.js Test Page 6 | 13 | 14 | 15 | 16 |

17 | 18 | 19 | 20 | 165 | 166 | --------------------------------------------------------------------------------