0

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.

user2233706
  • 6,148
  • 5
  • 44
  • 86

1 Answers1

1

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/path-that-svg@1.2.4/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>
herrstrietzel
  • 11,541
  • 2
  • 12
  • 34
  • I want to modify the path elements themselves such that they are clipped to the boundary of the clipPath. There would be no need to have a clipPath element. – user2233706 Dec 08 '21 at 18:35
  • You can't do this programmatically (by inkscape scripts, js etc), since cropping and eventually "merging/subtracting" different types of element such as `` would involve a massive amount of path command recalculation/conversion – not per se impossible, but most likely only available using GUI app (inksace/illustrator etc.) based path combining/merging operations. – herrstrietzel Dec 08 '21 at 21:48
  • Yes, the error message says to use the GUI. – user2233706 Dec 08 '21 at 22:16
  • ... sorry, but quite often the assumed overhead of clipped "off-canvas" elements is actually still quite neglactable compared to hard-coded/recalculated new path data. E.g a flattened cropped circle will most likely result in way more path commands than the initial slick circle definition. If you're aiming at omiting invisible off-canvas elements to reduce filesize – you might strip those elements by a viewBox vs. svgel.getBBox() check. – herrstrietzel Dec 08 '21 at 22:45
  • I wanted to clip the paths because the library I'm using to display the SVG, flutter_svg, can't handle clipPath tags. – user2233706 Dec 08 '21 at 23:02
  • @user2233706 – I've added a new example using paper.js as image processor. However, using build-in editing path operations in GUIs will be way more convenient. – herrstrietzel Dec 13 '21 at 18:00
  • Thanks, that's neat. The issue I was facing with flutter_svg was actually because it can't handle where the case where the clip-path URL is in a class. So I just had to inline instead of using a class, and I moved on. However, I'd still like to use this in the future. There doesn't seem to be a python equivalent of those packages, though. – user2233706 Dec 15 '21 at 05:32