6

Given an SVG Path element, how can I convert all path commands into relative coordinates? For example, convert this path (which includes every command, absolute and relative, interleaved):

<path d="M3,7 L13,7 m-10,10 l10,0 V27 H23 v10 h10
         C33,43 38,47 43,47 c0,5 5,10 10,10
         S63,67 63,67       s-10,10 10,10
         Q50,50 73,57       q20,-5 0,-10
         T70,40             t0,-15
         A5,5 45 1 0 40,20  a5,5 20 0 1 -10,-10
         Z" />

into this equivalent path:

<path d="m3,7 l10,0 m-10 10 l10,0 v10 h10 v10 h10
         c0,6 5,10 10,10    c0,5 5,10 10,10
         s10,10 10,10       s-10,10 10,10
         q-23,-27 0,-20     q20,-5 0,-10
         t-3,-7             t0-15
         a5,5 45 1 0 -30,-5 a5,5 20 0 1 -10,-10
         z"/>

This question was motivated by this question.

Community
  • 1
  • 1
ecmanaut
  • 4,823
  • 2
  • 41
  • 66
  • 1
    One way of doing that non programatically (in case it works for you) is by using the service from this website: https://lea.verou.me/2019/05/utility-convert-svg-path-to-all-relative-or-all-absolute-commands/ – Adriel Jr Nov 18 '21 at 00:28

2 Answers2

7

I tweaked Phrogz' convertToAbsolute into this convertToRelative function:

function convertToRelative(path) {
  function set(type) {
    var args = [].slice.call(arguments, 1)
      , rcmd = 'createSVGPathSeg'+ type +'Rel'
      , rseg = path[rcmd].apply(path, args);
    segs.replaceItem(rseg, i);
  }
  var dx, dy, 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 (/[MLHVCSQTAZz]/.test(c)) {
      if ('x1' in seg) x1 = seg.x1 - x;
      if ('x2' in seg) x2 = seg.x2 - x;
      if ('y1' in seg) y1 = seg.y1 - y;
      if ('y2' in seg) y2 = seg.y2 - y;
      if ('x'  in seg) dx = -x + (x = seg.x);
      if ('y'  in seg) dy = -y + (y = seg.y);
      switch (c) {
        case 'M': set('Moveto',dx,dy);                   break;
        case 'L': set('Lineto',dx,dy);                   break;
        case 'H': set('LinetoHorizontal',dx);            break;
        case 'V': set('LinetoVertical',dy);              break;
        case 'C': set('CurvetoCubic',dx,dy,x1,y1,x2,y2); break;
        case 'S': set('CurvetoCubicSmooth',dx,dy,x2,y2); break;
        case 'Q': set('CurvetoQuadratic',dx,dy,x1,y1);   break;
        case 'T': set('CurvetoQuadraticSmooth',dx,dy);   break;
        case 'A': set('Arc',dx,dy,seg.r1,seg.r2,seg.angle,
                      seg.largeArcFlag,seg.sweepFlag);   break;
        case 'Z': case 'z': x = x0; y = y0; break;
      }
    }
    else {
      if ('x' in seg) x += seg.x;
      if ('y' in seg) y += seg.y;
    }
    // store the start of a subpath
    if (c == 'M' || c == 'm') {
      x0 = x;
      y0 = y;
    }
  }
  path.setAttribute('d', path.getAttribute('d').replace(/Z/g, 'z'));
}

Used like so with the path from the question:

var path = document.querySelector('path');
convertToRelative(path);
console.log(path.getAttribute('d'));
// m 3 7 l 10 0 m -10 10 l 10 0 v 10 h 10 v 10 h 10 c 0 6 5 10 10 10 c 0 5 5 10 10 10 s 10 10 10 10 s -10 10 10 10 q -23 -27 0 -20 q 20 -5 0 -10 t -3 -7 t 0 -15 a 5 5 45 1 0 -30 -5 a 5 5 20 0 1 -10 -10 z

I also made a little phantomjs shell utility svg2rel which transforms all paths in an svg this way (there's a corresponding svg2abs in the same gist, for good measure).

Community
  • 1
  • 1
ecmanaut
  • 4,823
  • 2
  • 41
  • 66
  • `pathSegList` is now deprecated: https://stackoverflow.com/questions/34352624/alternative-for-deprecated-svg-pathseglist – Denilson Sá Maia Feb 07 '16 at 00:22
  • 1
    As this answer no longer is correct after above mentioned API deprecation in Chrome (and other browsers), I'm switching over to the Snap hack - or a similar solution in its elder sibling Rafael: http://jsbin.com/voxemav/ – ecmanaut Jan 25 '17 at 18:36
7

Snap.SVG has Snap.path.toRelative().

var rel = Snap.path.toRelative(abspathstring);

Fiddle

Nathan Arthur
  • 6,757
  • 5
  • 49
  • 71
Iktys
  • 782
  • 1
  • 7
  • 16
  • Worth noting: at least the Snap.SVG version linked here (http://cdn.jsdelivr.net/snap.svg/0.3.0/snap.svg.js) won't retain the coordinate precision from your input, but round to 3 decimals for all coordinates. – ecmanaut Jan 25 '17 at 19:18