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:
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.
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>
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>