13

Given an SVG Path element, how can I convert all path commands into absolute coordinates? For example, convert this path:

<path d="M17,42 l100,0 v100 h-100 z"/>

into this equivalent path:

<path d="M17,42 L117,42 V142 H17 Z"/>

This question was motivated by this question.

Community
  • 1
  • 1
Phrogz
  • 296,393
  • 112
  • 651
  • 745

3 Answers3

14

Here's the JavaScript code I came up with:

function convertToAbsolute(path){
  var x0,y0,x1,y1,x2,y2,segs = path.pathSegList;
  for (var x=0,y=0,i=0,len=segs.numberOfItems;i<len;++i){
    var seg = segs.getItem(i), c=seg.pathSegTypeAsLetter;
    if (/[MLHVCSQTA]/.test(c)){
      if ('x' in seg) x=seg.x;
      if ('y' in seg) y=seg.y;
    }else{
      if ('x1' in seg) x1=x+seg.x1;
      if ('x2' in seg) x2=x+seg.x2;
      if ('y1' in seg) y1=y+seg.y1;
      if ('y2' in seg) y2=y+seg.y2;
      if ('x'  in seg) x+=seg.x;
      if ('y'  in seg) y+=seg.y;
      switch(c){
        case 'm': segs.replaceItem(path.createSVGPathSegMovetoAbs(x,y),i);                   break;
        case 'l': segs.replaceItem(path.createSVGPathSegLinetoAbs(x,y),i);                   break;
        case 'h': segs.replaceItem(path.createSVGPathSegLinetoHorizontalAbs(x),i);           break;
        case 'v': segs.replaceItem(path.createSVGPathSegLinetoVerticalAbs(y),i);             break;
        case 'c': segs.replaceItem(path.createSVGPathSegCurvetoCubicAbs(x,y,x1,y1,x2,y2),i); break;
        case 's': segs.replaceItem(path.createSVGPathSegCurvetoCubicSmoothAbs(x,y,x2,y2),i); break;
        case 'q': segs.replaceItem(path.createSVGPathSegCurvetoQuadraticAbs(x,y,x1,y1),i);   break;
        case 't': segs.replaceItem(path.createSVGPathSegCurvetoQuadraticSmoothAbs(x,y),i);   break;
        case 'a': segs.replaceItem(path.createSVGPathSegArcAbs(x,y,seg.r1,seg.r2,seg.angle,seg.largeArcFlag,seg.sweepFlag),i);   break;
        case 'z': case 'Z': x=x0; y=y0; break;
      }
    }
    // Record the start of a subpath
    if (c=='M' || c=='m') x0=x, y0=y;
  }
}

Used like so with the path from the question:

var path = document.querySelector('path');
convertToAbsolute(path);
console.log(path.getAttribute('d'));
// M 17 42 L 117 42 V 142 H 17 Z

Edit: Here's a test page with a path that includes every command (absolute and relative) interleaved and shows that the conversion works in the now-current versions of IE, Chrome, FF, and Safari.
http://phrogz.net/svg/convert_path_to_absolute_commands.svg

Phrogz
  • 296,393
  • 112
  • 651
  • 745
  • seg.pathSegTypeAsLetter is always returning a caps letter even if i have small letter commmands in the path. Any other idea behind the switch statements – Rajkamal Subramanian Mar 13 '12 at 07:35
  • If it's not a problem that you don't get all kinds of segments preserved then it's also an option to use the built-in functionality in svg, [pathElm.normalizedPathSegList][1] which gives you absolute moveto, lineto, curveto, and close. [1]: http://www.w3.org/TR/SVG11/paths.html#__svg__SVGAnimatedPathData__normalizedPathSegList – Erik Dahlström Mar 13 '12 at 10:34
  • @ErikDahlström I wanted to use that, but `normalizedPathSegList` doesn't exist on WebKit (always returns `undefined`). – Phrogz Mar 13 '12 at 12:47
  • @rajkamal Oh? What OS/browser/version? You can switch on `pathSegType` which returns one of [these constants](http://objjob.phrogz.net/svg/object/102). – Phrogz Mar 13 '12 at 12:48
  • windows/firefox8.0.1/. Let me try with pathSegType – Rajkamal Subramanian Mar 13 '12 at 14:37
  • @rajkamal Perhaps you should upgrade your Firefox. [This test page](http://phrogz.net/svg/convert_absolute_path_to_relative.svg) works with IE9, Chrome 18, and FF10. – Phrogz Mar 13 '12 at 17:43
  • 1
    Thanks for this! I turned it into a handy phantomjs [svg2abs](https://github.com/johan/kilobyte-svg-challenge/blob/master/tools/svg2abs) utility. – ecmanaut Jan 06 '13 at 02:14
  • 4
    This not longer works in Chrome 40+ as `pathSegList` have been deprecated and to be replaced with something that's not even beta or implemented. – thednp Mar 19 '16 at 18:16
9

If you have Raphaël, you have both Raphael.pathToRelative and Raphael._pathToAbsolute.

Raphael._pathToAbsolute is not in documentation (like pathToRelative) and is used in many places in Raphael source internally. But it can be used also externally, if you add _ before function name this way: Raphael._pathToAbsolute.

Online conversion: http://jsbin.com/mudusiseta

The usage is the same as relative one:

<script src="raphael.js"></script>
<script>
  // ...

  var paper = Raphael(10, 50, 320, 200);
  var path_string = "M10 10 L 20 20 L 100 10"; // Original coordinates
  var path = paper.path(path_string);

  // To relative coordinates
  var path_string_rel = Raphael.pathToRelative(path_string);
  console.log(path_string_rel);

  // To absolute coordinates
  var path_string_abs = Raphael._pathToAbsolute(path_string_rel);
  console.log(path_string_abs);

  // ...    
</script>

I don't know why pathToAbsolute is not in documentation. It should.

If you want this to work in Web Workers (to speedup code), it is easy to grab the desired function from Raphael (which should be allowed by the licence) and use it as a DOM free method. Raphael is a DOM manipulating library (as well as jQuery) and it cannot be used in Workers, because DOM is not supported in Workers. If you have very complex paths, the browser may hang and to prevent this Web Workers provide a solution in modern browsers.

Timo Kähkönen
  • 11,962
  • 9
  • 71
  • 112
1

Here is library for svg path (d attr) manipulations: https://github.com/fontello/svgpath.

Vitaly
  • 3,340
  • 26
  • 21