Search code examples
svginkscape

Change SVG paths to be clipped according to clipPath element


I have a SVG that looks like this:

<svg id="Layer_1" data-name="Layer 1"
  xmlns="http://www.w3.org/2000/svg"
  xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 576 576">
  <defs>
    <style>...</style>
    <clipPath id="clip-path">
      <rect class="cls-1" x="0.02" width="575.04" height="576"/>
    </clipPath>
  </defs>
  <g class="cls-2">
    <path class="cls-3" d="M137.91-147.28c-4-1.67-8.25-3.46-12.37-3.86-5.43-.53-9.26,1.73-12.55,5a18.75,18.75,0,0,0-4.69-9.42,19.23,19.23,0,0,0-6.45-...
    <path class="cls-4" d="M.08,502.59c-.79-5.67-6.22-4.3-5.81-.22a17.15,17.15,0,0,1,0,2.95c-.22,2.82-1.46,7.6-5,7.61-1.35,0-2.61-1-3.12...
    ...

I want to change it such that:

  • there is no grouping (this is easy)
  • change the path, rect, etc. elements such that they are clipped according to the what's in the clipPath element. The clipPath should no longer be present in the SVG (because it isn't needed anymore)

I've tried this:

inkscape --actions \
 "select-all:groups; SelectionUnGroup; ObjectUnSetClipPath; export-filename: output.svg; export-plain-svg; export-do;" \
 Decorations.svg

This removes the grouping, but the path elements are not clipped. The clipPath is still present.


Solution

  • ObjectUnSetClipPath will remove the clipping attribute clip-path="url(#clip-path)" – not the <def> clipping path itself.

    But you can't clip any element, if your clipPath definition is stripped.

    function stripClip(el){
      let clipPaths = document.querySelector(el).querySelectorAll('clipPath');
      if(clipPaths.length){
        clipPaths.forEach(function(item, i){
          item.remove();
        }
        )
      }
    }
    svg{
    width: 20vw;
    }
    
    .clipped{
    clip-path: url(#clip-path2);
    }
      <p><button onclick="stripClip('#svg1')" >remove ClipPath</button></p>
      
    <svg id="svg1" data-name="Layer 1"
      xmlns="http://www.w3.org/2000/svg"
      xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 36 36">
      <defs>
        <style>...</style>
        <clipPath id="clip-path">
          <rect class="cls-1" x="10" width="36" height="18"/>
        </clipPath>
      </defs>
      <g class="cls-2">
       <path clip-path="url(#clip-path)" d="M18 2.0845
      a 15.9155 15.916 0 0 1 0 31.83
      a 15.916 15.916 0 0 1 0 -31.83z" fill="red" />
      <rect  clip-path="url(#clip-path)" x="0" y="0" width="10" height="25" />
      </g>
      </svg>
      
      
      <svg id="svg2" data-name="Layer 1"
      xmlns="http://www.w3.org/2000/svg"
      xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 36 36">
      <defs>
        <style>...</style>
        <clipPath id="clip-path2">
          <rect class="cls-1" x="10" width="36" height="18"/>
        </clipPath>
      </defs>
      <g class="cls-2">
       <path class="clipped" d="M18 2.0845
      a 15.9155 15.916 0 0 1 0 31.83
      a 15.916 15.916 0 0 1 0 -31.83z" fill="red" />
      <rect class="clipped" x="0" y="0" width="10" height="25" />
      </g>
      </svg>
      

    Edit: Get svg intersection paths using paper.js

    Admittedly, not the most convenient approach but it is possible:
    In addition to paper.js, we also need some script/helper to convert svg shapes (circles, rects etc.) to <path> elements (I'm using 'pathThatSvg' , since paper.js can only calculate new intersection paths based on 2 path elements like so:

    var intersectionPath = path1.intersect(clipPath);

    Based on this answer

    var svg = document.querySelector("#svgIntersect");
    
    // set auto ids for processing
    function setAutoIDs(svg) {
      var svgtEls = svg.querySelectorAll(
        "path, polygon, rect, circle, line, text, g"
      );
      svgtEls.forEach(function (el, i) {
        if (!el.getAttribute("id")) {
          el.id = el.nodeName + "-" + i;
        }
      });
    }
    setAutoIDs(svg);
    
    // convert shapes to paths
    function shapesToPath(svg) {  
            pathThatSvg(svg.outerHTML).then((converted) => {
              var tmp = document.createElement("div");
              tmp.innerHTML = converted;
              svg.innerHTML = tmp.querySelector("svg").innerHTML;
            });
    }
    shapesToPath(svg);
    
    
    function intersectPath(svg, decimals=2) {
      // init paper.js and add canvas
      canvas = document.createElement('canvas');
      canvas.id = "canvasPaper";
      canvas.setAttribute('style','display:none')
      document.body.appendChild(canvas);
      paper.setup("canvasPaper");
    
      // process clipped elements
      var all = paper.project.importSVG(svg, function (item, i) {
        item.position = new paper.Point(
          item.bounds.width / 2,
          item.bounds.height / 2
        );
        //item.scale(0.5, new Point(0, 0) )
        var items = item.getItems();
        var ids = item._namedChildren;
    
        var groups = item.children;
        groups.forEach(function (gr, i) {
          var group = gr["_namedChildren"];
          if (group) {
            for (key in group) {
              //get clip path
              var clip = group["clipPath"][0];
              if (key !== "clipPath") {
                var el = group[key][0];
                 //get intersection path and generate d commands
                var elClipped = el.intersect(clip);
                var elClippedD = elClipped
                  .exportSVG({ precision: decimals })
                  .getAttribute("d");
                // select path by id and overwrite d attribute
                var newEl = svg.querySelector("#" + key);
                newEl.setAttribute("d", elClippedD);
              }
            }
          }
        });
    
        // remove clip defs and attributes
        var clippedEls = svg.querySelectorAll("[clip-path]");
        clippedEls.forEach(function (clippedEl, e) {
          clippedEl.removeAttribute("clip-path");
        });
        svg.querySelector("defs").remove();
        svg.classList.add("svg-intersect");
        console.log(svg.outerHTML)
      });
    }
    svg{
      border: 1px solid #ccc;
      display:inline-block;
      width:200px;
    }
    
    .svg-intersect path{
      stroke:red;
      stroke-width: 0.25;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.12.0/paper-full.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/pathThatSvg.umd.min.js"></script>
    <p>
      <button type="button" onclick="intersectPath(svg)">get Path Intersect</button>
    </p>
    
    <svg id="svgIntersect" viewBox="0 0 100 100">
      <defs>
        <clipPath id="clipPath">
          <circle cx="25" cy="25" r="25"/>
        </clipPath>
      </defs>
      <g id="clipGroup" clip-path="url(#clipPath)">
        <circle fill="#999" data-id="circle" cx="25" cy="25" r="25" />
        <rect fill="#555" id="rect" x="25" y="25" width="50" height="50" />
      </g>
      
      <g  clip-path="url(#clipPath)">
        <circle fill="#444" id="circle2" cx="66" cy="25" r="25" />
        <rect fill="#22" id="rect2" x="15" y="12.5" width="20" height="75" />
      </g>
    </svg>