Created
October 19, 2011 07:37
-
-
Save YannickBochatay/1297684 to your computer and use it in GitHub Desktop.
javascript workaround for unimplemented normalizedPathSegList
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| Transform paths with only M,L,C,Z as described by w3c | |
| http://www.w3.org/TR/2003/REC-SVG11-20030114/paths.html#InterfaceSVGAnimatedPathData | |
| Example : | |
| var path = document.createElementNS("http://www.w3.org/2000/svg",tag); | |
| path.setAttribute('d','M30 30S40 23 23 42C113,113 136,113 150,80t40,50T230,240q20 20 54 20s40 23 23 42t20,30a20,30 0,0,1 -50,-50'); | |
| path.normalizePath(); | |
| console.log(path.getAttribute(d')); // | |
| */ | |
| SVGPathElement.prototype.normalizePath = function(bezierDegree,defaultFlatness) { | |
| var seg,letter,currentPoint, | |
| list = this.pathSegList, | |
| newpath = document.createElementNS("http://www.w3.org/2000/svg",'path'), | |
| method,newseg, | |
| x,y, | |
| i=0,N = list.numberOfItems, | |
| j,M,bezier, | |
| getCurrentPoint = function(i) { | |
| var j=i, | |
| x=false,y=false, | |
| prev; | |
| while (x===false || y===false) { | |
| j--; | |
| if (j<0) { | |
| if (x === false) { x = 0; } | |
| if (y === false) { y = 0; } | |
| } | |
| else { | |
| prev = list.getItem(j); | |
| if (prev.x!==undefined && x === false) { x = prev.x; } | |
| if (prev.y!==undefined && y === false) { y = prev.y; } | |
| } | |
| } | |
| return {x:x,y:y}; | |
| }; | |
| for (;i<N;i++) { | |
| seg = list.getItem(i); | |
| letter = seg.pathSegTypeAsLetter; | |
| currentPoint = getCurrentPoint(i); | |
| //First transform relative to aboslute segments | |
| if (letter.toUpperCase() !== letter && letter!=='z') { | |
| method = 'createSVGPathSeg'; | |
| switch (letter) { | |
| case 'z' : method+='ClosePath'; break; | |
| case 'm' : method+='Moveto'; break; | |
| case 'l' : method+='Lineto'; break; | |
| case 'c' : method+='CurvetoCubic'; break; | |
| case 'q' : method+='CurvetoQuadratic'; break; | |
| case 'a' : method+='Arc'; break; | |
| case 'h' : method+='LinetoHorizontal'; break; | |
| case 'v' : method+='LinetoVertical'; break; | |
| case 's' : method+='CurvetoCubicSmooth'; break; | |
| case 't' : method+='CurvetoQuadraticSmooth'; break; | |
| } | |
| method+='Abs'; | |
| args = []; | |
| if (letter === 'h') { args.push(currentPoint.x+seg.x); } | |
| else if (letter === 'v') { args.push(currentPoint.y+seg.y); } | |
| else { | |
| args.push(currentPoint.x+seg.x,currentPoint.y+seg.y); | |
| switch (letter) { | |
| case 'c' : args.push(currentPoint.x+seg.x1,currentPoint.y+seg.y1,currentPoint.x+seg.x2,currentPoint.y+seg.y2); break; | |
| case 'q' : args.push(currentPoint.x+seg.x1,currentPoint.y+seg.y1); break; | |
| case 'a' : args.push(seg.r1,seg.r2,seg.angle,seg.largArcFlag,seg.sweepFlag); break; | |
| case 's' : args.push(currentPoint.x+seg.x2,currentPoint.y+seg.y2); break; | |
| } | |
| } | |
| seg = this[method].apply(this,args); | |
| list.replaceItem(seg,i); | |
| letter = letter.toUpperCase(); | |
| } | |
| if (letter === 'H') { | |
| newseg = this.createSVGPathSegLinetoAbs(seg.x,currentPoint.y); | |
| newpath.pathSegList.appendItem(newseg); | |
| } | |
| else if (letter === 'V') { | |
| newseg = this.createSVGPathSegLinetoAbs(currentPoint.x,seg.y); | |
| newpath.pathSegList.appendItem(newseg); | |
| } | |
| else if (letter === 'S') { //transform S to C | |
| if (i === 0 || list.getItem(i-1).pathSegTypeAsLetter !== 'C') { | |
| x = currentPoint.x; | |
| y = currentPoint.y; | |
| } | |
| else { | |
| x = currentPoint.x * 2 - list.getItem(i-1).x2; | |
| y = currentPoint.y * 2 - list.getItem(i-1).y2; | |
| } | |
| newseg = this.createSVGPathSegCurvetoCubicAbs(seg.x,seg.y,x,y,seg.x2,seg.y2); | |
| list.replaceItem(newseg,i); | |
| i--;continue; | |
| } | |
| else if (letter === 'Q') { | |
| newseg = this.createSVGPathSegCurvetoCubicAbs(seg.x,seg.y, 1/3 * currentPoint.x + 2/3 * seg.x1, currentPoint.y/3 + 2/3 *seg.y1,2/3 * seg.x1 + 1/3 * seg.x, 2/3 * seg.y1 + 1/3 * seg.y); | |
| newpath.pathSegList.appendItem(newseg); | |
| } | |
| else if (letter === 'T') { //transform T to Q | |
| if (i === 0 || list.getItem(i-1).pathSegTypeAsLetter !== 'Q') { | |
| x = currentPoint.x; | |
| y = currentPoint.y; | |
| } | |
| else { | |
| x = currentPoint.x * 2 - list.getItem(i-1).x1; | |
| y = currentPoint.y * 2 - list.getItem(i-1).y1; | |
| } | |
| newseg = this.createSVGPathSegCurvetoQuadraticAbs(seg.x,seg.y,x,y,seg.x2,seg.y2); | |
| list.replaceItem( newseg , i ); | |
| i--;continue; | |
| } | |
| else if (letter === 'A') { | |
| bezier = seg.toBezier(currentPoint,bezierDegree,defaultFlatness); | |
| for (j=0,M=bezier.numberOfItems;j<M;j++) { | |
| newpath.pathSegList.appendItem(bezier.getItem(j)); | |
| }; | |
| } | |
| else { newpath.pathSegList.appendItem(seg); } | |
| } | |
| list.clear(); | |
| for (j=0,M=newpath.pathSegList.numberOfItems;j<M;j++) { | |
| list.appendItem(newpath.pathSegList.getItem(j)); | |
| }; | |
| return this; | |
| }; | |
| SVGPathSegArcAbs.prototype.toBezier = function(currentPoint,bezierDegree,defaultFlatness) { | |
| /////////////////////////////////////// | |
| //from Luc Maisonobe www.spaceroots.org | |
| this.angleRad = this.angle*Math.PI/180; | |
| this.x1 = currentPoint.x; | |
| this.y1 = currentPoint.y; | |
| this.r1 = Math.abs(this.r1); | |
| this.r2 = Math.abs(this.r2); | |
| defaultFlatness = defaultFlatness || 0.5; | |
| bezierDegree = bezierDegree || 1; | |
| // find the number of Bézier curves needed | |
| var found = false, | |
| i,n = 1, | |
| dEta,etaA,etaB, | |
| path = document.createElementNS("http://www.w3.org/2000/svg",'path'), | |
| seg, | |
| coefs = { | |
| degree2 : { | |
| low : [ | |
| [ | |
| [ 3.92478, -13.5822, -0.233377, 0.0128206 ], | |
| [ -1.08814, 0.859987, 0.000362265, 0.000229036 ], | |
| [ -0.942512, 0.390456, 0.0080909, 0.00723895 ], | |
| [ -0.736228, 0.20998, 0.0129867, 0.0103456 ] | |
| ], [ | |
| [ -0.395018, 6.82464, 0.0995293, 0.0122198 ], | |
| [ -0.545608, 0.0774863, 0.0267327, 0.0132482 ], | |
| [ 0.0534754, -0.0884167, 0.012595, 0.0343396 ], | |
| [ 0.209052, -0.0599987, -0.00723897, 0.00789976 ] | |
| ] | |
| ], | |
| high : [ | |
| [ | |
| [ 0.0863805, -11.5595, -2.68765, 0.181224 ], | |
| [ 0.242856, -1.81073, 1.56876, 1.68544 ], | |
| [ 0.233337, -0.455621, 0.222856, 0.403469 ], | |
| [ 0.0612978, -0.104879, 0.0446799, 0.00867312 ] | |
| ], [ | |
| [ 0.028973, 6.68407, 0.171472, 0.0211706 ], | |
| [ 0.0307674, -0.0517815, 0.0216803, -0.0749348 ], | |
| [ -0.0471179, 0.1288, -0.0781702, 2.0 ], | |
| [ -0.0309683, 0.0531557, -0.0227191, 0.0434511 ] | |
| ] | |
| ], | |
| safety : [ 0.001, 4.98, 0.207, 0.0067 ] | |
| }, | |
| degree3 : { | |
| low : [ | |
| [ | |
| [ 3.85268, -21.229, -0.330434, 0.0127842 ], | |
| [ -1.61486, 0.706564, 0.225945, 0.263682 ], | |
| [ -0.910164, 0.388383, 0.00551445, 0.00671814 ], | |
| [ -0.630184, 0.192402, 0.0098871, 0.0102527 ] | |
| ],[ | |
| [ -0.162211, 9.94329, 0.13723, 0.0124084 ], | |
| [ -0.253135, 0.00187735, 0.0230286, 0.01264 ], | |
| [ -0.0695069, -0.0437594, 0.0120636, 0.0163087 ], | |
| [ -0.0328856, -0.00926032, -0.00173573, 0.00527385 ] | |
| ] | |
| ], | |
| high : [ | |
| [ | |
| [ 0.0899116, -19.2349, -4.11711, 0.183362 ], | |
| [ 0.138148, -1.45804, 1.32044, 1.38474 ], | |
| [ 0.230903, -0.450262, 0.219963, 0.414038 ], | |
| [ 0.0590565, -0.101062, 0.0430592, 0.0204699 ] | |
| ], [ | |
| [ 0.0164649, 9.89394, 0.0919496, 0.00760802 ], | |
| [ 0.0191603, -0.0322058, 0.0134667, -0.0825018 ], | |
| [ 0.0156192, -0.017535, 0.00326508, -0.228157 ], | |
| [ -0.0236752, 0.0405821, -0.0173086, 0.176187 ] | |
| ] | |
| ], | |
| safety : [ 0.001, 4.98, 0.207, 0.0067 ] | |
| } | |
| }, | |
| rationalFunction = function(x,c) { | |
| return (x * (x * c[0] + c[1]) + c[2]) / (x + c[3]); | |
| }, | |
| estimateError = function(etaA,etaB) { | |
| var eta = 0.5 * (etaA + etaB); | |
| if (bezierDegree < 2) { | |
| // start point | |
| var aCosEtaA = this.r1 * Math.cos(etaA), | |
| bSinEtaA = this.r2 * Math.sin(etaA), | |
| xA = this.cx + aCosEtaA * Math.cos(this.angleRad) - bSinEtaA * Math.sin(this.angleRad), | |
| yA = this.cy + aCosEtaA * Math.sin(this.angleRad) + Math.sin(this.angleRad) * Math.cos(this.angleRad), | |
| // end point | |
| aCosEtaB = this.r1 * Math.cos(etaB), | |
| bSinEtaB = this.r2 * Math.sin(etaB), | |
| xB = this.cx + aCosEtaB * Math.cos(this.angleRad) - bSinEtaB * Math.sin(this.angleRad), | |
| yB = this.cy + aCosEtaB * Math.sin(this.angleRad) + bSinEtaB * Math.cos(this.angleRad), | |
| // maximal error point | |
| aCosEta = this.r1 * Math.cos(eta), | |
| bSinEta = this.r2 * Math.sin(eta), | |
| x = this.cx + aCosEta * Math.cos(this.angleRad) - bSinEta * Math.sin(this.angleRad), | |
| y = this.cy + aCosEta * Math.sin(this.angleRad) + bSinEta * Math.cos(this.angleRad), | |
| dx = xB - xA, | |
| dy = yB - yA; | |
| return Math.abs(x * dy - y * dx + xB * yA - xA * yB) / Math.sqrt(dx * dx + dy * dy); | |
| } | |
| else { | |
| var x = this.r2 / this.r1, | |
| dEta = etaB - etaA, | |
| cos2 = Math.cos(2 * eta), | |
| cos4 = Math.cos(4 * eta), | |
| cos6 = Math.cos(6 * eta), | |
| // select the right coeficients set according to degree and b/a | |
| coeffs = (x < 0.25) ? coefs['degree'+bezierDegree].low : coefs['degree'+bezierDegree].high, | |
| c0 = rationalFunction(x, coeffs[0][0]) + cos2 * rationalFunction(x, coeffs[0][1]) + | |
| cos4 * rationalFunction(x, coeffs[0][2]) + cos6 * rationalFunction(x, coeffs[0][3]), | |
| c1 = rationalFunction(x, coeffs[1][0]) + cos2 * rationalFunction(x, coeffs[1][1]) + cos4 * | |
| rationalFunction(x, coeffs[1][2]) + cos6 * rationalFunction(x, coeffs[1][3]); | |
| return rationalFunction(x, coefs['degree'+bezierDegree].safety) * this.r1 * Math.exp(c0 + c1 * dEta); | |
| } | |
| }; | |
| (function() { //compute center and angles | |
| //from http://www.w3.org/TR/2003/REC-SVG11-20030114/implnote.html#ArcConversionEndpointToCenter | |
| var xp1 = Math.cos(this.angleRad) * (this.x1-this.x) / 2 + | |
| Math.sin(this.angleRad) * (this.y1-this.y) / 2, | |
| yp1 = -Math.sin(this.angleRad) * (this.x1-this.x) / 2 + | |
| Math.cos(this.angleRad) * (this.y1-this.y) / 2, | |
| r1c = Math.pow(this.r1,2), | |
| r2c = Math.pow(this.r2,2), | |
| xp1c = Math.pow(xp1,2), | |
| yp1c = Math.pow(yp1,2), | |
| lambda = xp1c / r1c + yp1c / r2c; //Ensure radii are large enough | |
| if (lambda > 1) { | |
| this.r1*=Math.sqrt(lambda); | |
| this.r2*=Math.sqrt(lambda); | |
| r1c = Math.pow(this.r1,2); | |
| r2c = Math.pow(this.r2,2); | |
| } | |
| var coef = (this.largeArcFlag === this.sweepFlag ? -1 : 1 ) * | |
| Math.sqrt( Math.max(0,( r1c*r2c - r1c*yp1c - r2c*xp1c ) / ( r1c*yp1c + r2c*xp1c)) ), | |
| cpx = coef * ( this.r1 * yp1 ) / this.r2, | |
| cpy = coef * ( - this.r2 * xp1 ) / this.r1, | |
| cx = Math.cos(this.angleRad) * cpx - Math.sin(this.angleRad) * cpy + (this.x1 + this.x) / 2, | |
| cy = Math.sin(this.angleRad) * cpx + Math.cos(this.angleRad) * cpy + (this.y1 + this.y) / 2, | |
| cosTheta = ( (xp1-cpx)/this.r1 ) / Math.sqrt( Math.pow( (xp1-cpx)/this.r1 , 2 ) + Math.pow( (yp1-cpy)/this.r2 , 2 ) ), | |
| theta = ( (yp1-cpy)/this.r2 > 0 ? 1 : -1) * Math.acos(cosTheta), | |
| u = { x : (xp1-cpx) /this.r1 , y : (yp1-cpy) /this.r2 }, | |
| v = { x : (-xp1-cpx)/this.r1 , y : (-yp1-cpy)/this.r2 }, | |
| cosDeltaTheta = ( u.x * v.x + u.y * v.y ) / ( Math.sqrt(Math.pow(u.x,2) + Math.pow(u.y,2)) * Math.sqrt(Math.pow(v.x,2) + Math.pow(v.y,2)) ), | |
| deltaTheta = (u.x*v.y-u.y*v.x > 0 ? 1 : -1) * Math.acos(Math.max(-1,Math.min(1,cosDeltaTheta))) % (Math.PI*2); | |
| if (this.sweepFlag === false && deltaTheta > 0) { deltaTheta-=Math.PI*2; } | |
| else if (this.sweepFlag === true && deltaTheta < 0) { deltaTheta+=Math.PI*2; } | |
| this.eta1 = theta; | |
| this.eta2 = theta + deltaTheta; | |
| this.cx = cx; | |
| this.cy = cy; | |
| }).call(this); | |
| while ((!found) && (n < 1024)) { | |
| dEta = (this.eta2 - this.eta1) / n; | |
| if (dEta <= 0.5 * Math.PI) { | |
| etaB = this.eta1; | |
| found = true; | |
| for (i=0; found && (i<n); ++i) { | |
| etaA = etaB; | |
| etaB += dEta; | |
| found = (estimateError.call(this,etaA, etaB) <= defaultFlatness); | |
| } | |
| } | |
| n = n << 1; | |
| } | |
| dEta = (this.eta2 - this.eta1) / n; | |
| etaB = this.eta1; | |
| var aCosEtaB = this.r1 * Math.cos(etaB), | |
| bSinEtaB = this.r2 * Math.sin(etaB), | |
| aSinEtaB = this.r1 * Math.sin(etaB), | |
| bCosEtaB = this.r2 * Math.cos(etaB), | |
| xB = this.cx + aCosEtaB * Math.cos(this.angleRad) - bSinEtaB * Math.sin(this.angleRad), | |
| yB = this.cy + aCosEtaB * Math.sin(this.angleRad) + bSinEtaB * Math.cos(this.angleRad), | |
| xADot,yADot, | |
| xBDot = -aSinEtaB * Math.cos(this.angleRad) - bCosEtaB * Math.sin(this.angleRad), | |
| yBDot = -aSinEtaB * Math.sin(this.angleRad) + bCosEtaB * Math.cos(this.angleRad); | |
| var t = Math.tan(0.5 * dEta), | |
| alpha = Math.sin(dEta) * (Math.sqrt(4 + 3 * t * t) - 1) / 3, | |
| xA,yA,xB,yB,xADot,xBDot,k; | |
| for (var i = 0; i < n; ++i) { | |
| etaA = etaB; | |
| xA = xB; | |
| yA = yB; | |
| xADot = xBDot; | |
| yADot = yBDot; | |
| etaB += dEta; | |
| aCosEtaB = this.r1 * Math.cos(etaB); | |
| bSinEtaB = this.r2 * Math.sin(etaB); | |
| aSinEtaB = this.r1 * Math.sin(etaB); | |
| bCosEtaB = this.r2 * Math.cos(etaB); | |
| xB = this.cx + aCosEtaB * Math.cos(this.angleRad) - bSinEtaB * Math.sin(this.angleRad); | |
| yB = this.cy + aCosEtaB * Math.sin(this.angleRad) + bSinEtaB * Math.cos(this.angleRad); | |
| xBDot = -aSinEtaB * Math.cos(this.angleRad) - bCosEtaB * Math.sin(this.angleRad); | |
| yBDot = -aSinEtaB * Math.sin(this.angleRad) + bCosEtaB * Math.cos(this.angleRad); | |
| if (bezierDegree == 1) { | |
| seg = path.createSVGPathSegLinetoAbs(xB,yB); | |
| } | |
| else if (bezierDegree == 2) { | |
| k = (yBDot * (xB - xA) - xBDot * (yB - yA)) / (xADot * yBDot - yADot * xBDot); | |
| seg = path.createSVGPathSegCurvetoQuadraticAbs(xB , yB , xA + k * xADot , yA + k * yADot); | |
| } else { | |
| seg = path.createSVGPathSegCurvetoCubicAbs(xB , yB , xA + alpha * xADot , yA + alpha * yADot, xB - alpha * xBDot, yB - alpha * yBDot); | |
| } | |
| path.pathSegList.appendItem(seg); | |
| } | |
| return path.pathSegList; | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment