You need to cross two SVG paths and get a path representing their intersection.

It doesn't matter if it will work in the browser or in Node.js.
Need exactly the intersection, the use of the clip-path not suitable.
If after the intersection you suddenly need a transform , then it's not scary (I will remove it myself).

I think for this there is already some library, but I found only

  • svg-intersections - returns an array of intersections, and I need a path
  • path-intersection - I haven't taken off anything at all - I always get an empty array
  • snap.svg - it seems to give something useful, but I do not understand how to use it

For example, when crossing these paths:

 M 24.379464,51.504463 23.434524,23.156249 38.742559,12.572916 c 0,0 29.860118,-9.0714281 17.00893,0.755953 -12.851191,9.82738 13.229166,19.465774 13.229166,19.465774 z m 32.883928,0.28869028 c 0,0 -15.686011,1.51190452 -8.504463,7.18154712 7.181546,5.6696426 50.270836,30.0491076 26.458332,42.3333336 -23.8125,12.284226 47.058036,14.174107 47.058036,14.174107 z 

should get (approximately):

 M 43.943359 11.123047 C 40.995759 11.900151 38.742188 12.572266 38.742188 12.572266 L 35.236328 14.996094 C 44.091432999999995 21.21816 55.052161 29.822765 57.455078 37.628906 L 66.939453 33.650391 63.632812 30.410156 C 58.77426 27.95814 52.364322 23.85552 52.214844 19.224609 L 43.943359 11.123047 z 

Here is an interactive snippet with the paths from the example (do not pay attention to colors - they are just for clarity) - you need to get the Intersection path from #path1 and #path2 :

 svg { width: 10em; width: 100vmin; outline: 1px dotted blue; display: none; } input { display: none; } label { width: 10em; float: left; clear: left; cursor: pointer; line-height: 2em; margin: 0 .5em .25em 0; padding: 0 .25em; border: 1px solid; } :checked + * + * + label { background: antiquewhite; color: blue; } :checked + * + * + * + * + * + svg { display: inline-block; } 
 <input type=radio name=svg id=in checked> <input type=radio name=svg id=out> <input type=radio name=svg id=cp> <label for=in>Данные</label> <label for=out>Пересечение</label> <label for=cp>Обрезка</label> <svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="22 0 76 64"> <path id="path1" style="fill:rgba(255,0,0,.5); stroke:red;stroke-width:0.26458332px;" d="M 24.379464,51.504463 23.434524,23.156249 38.742559,12.572916 c 0,0 29.860118,-9.0714281 17.00893,0.755953 -12.851191,9.82738 13.229166,19.465774 13.229166,19.465774 z" /> <path id="path2" style="fill:rgba(0,255,0,.5);stroke:green;stroke-width:0.26458332px;" d="m 32.883928,0.28869028 c 0,0 -15.686011,1.51190452 -8.504463,7.18154712 7.181546,5.6696426 50.270836,30.0491076 26.458332,42.3333336 -23.8125,12.284226 47.058036,14.174107 47.058036,14.174107 z" /> </svg> <svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="22 0 76 64"> <path style="fill:rgba(0,0,255,.5);stroke:blue;stroke-width:0.26458332px;" d="M 43.943359 11.123047 C 40.995759 11.900151 38.742188 12.572266 38.742188 12.572266 L 35.236328 14.996094 C 44.091432999999995 21.21816 55.052161 29.822765 57.455078 37.628906 L 66.939453 33.650391 63.632812 30.410156 C 58.77426 27.95814 52.364322 23.85552 52.214844 19.224609 L 43.943359 11.123047 z" /> </svg> <svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="22 0 76 64"> <clipPath id="clip2"> <use xlink:href="#path2" /> </clipPath> <use xlink:href="#path1" clip-path="url(#clip2)" /> </svg> 

Snap.svg example

 var p1 = "M 24.379464,51.504463 23.434524,23.156249 38.742559,12.572916 c 0,0 29.860118,-9.0714281 17.00893,0.755953 -12.851191,9.82738 13.229166,19.465774 13.229166,19.465774 z" var p2 = "m 32.883928,0.28869028 c 0,0 -15.686011,1.51190452 -8.504463,7.18154712 7.181546,5.6696426 50.270836,30.0491076 26.458332,42.3333336 -23.8125,12.284226 47.058036,14.174107 47.058036,14.174107 z" var intersection = Snap.path.intersection(p1, p2) console.log(intersection) 
 .as-console-wrapper.as-console-wrapper { max-height: 100vh } 
 <script src=//cdnjs.cloudflare.com/ajax/libs/snap.svg/0.5.1/snap.svg-min.js></script> 

PS: This question is in English.

  • Qwertiy, found such a thing. I did not understand myself, but it seems to work. You'll figure it out better than me. Paperjs.org/examples/path-intersections - Air
  • I asked the same question earlier, and there was no answer - sta.overckflow.com/questions/772089/… - Dmitry Polyanin
  • @DmitryPolyanin, you have a slightly different question and it looks harder than mine) - Qwertiy
  • @Air, there are intersections again, not paths ... - Qwertiy
  • 2
    @Qwertiy I just ask for the interest, but why this smut? - user33274

4 answers 4

After many days of searching, I still found a solution where I didn’t expect to see it at all, namely here . This answer pushed me to remember what I had long seen on PaperJS in Boolean Operations.

PaperJS has 5 different boolean operations: exclude , subtract , unite , intersect , divide and we will use one of them called intersect . These operations are also functions of the same name and all return a new item object, from which you can use the exportSVG() function to get the real SVG Path element , which contains the path of intersection of our paths.

An example of the correct solution

 paper.install(window); window.onload = function() { paper.setup('canvas'); var p1 = 'M 24.379464,51.504463 23.434524,23.156249 38.742559,12.572916 c 0,0 29.860118,-9.0714281 17.00893,0.755953 -12.851191,9.82738 13.229166,19.465774 13.229166,19.465774 z', p2 = 'm 32.883928,0.28869028 c 0,0 -15.686011,1.51190452 -8.504463,7.18154712 7.181546,5.6696426 50.270836,30.0491076 26.458332,42.3333336 -23.8125,12.284226 47.058036,14.174107 47.058036,14.174107 z', path1 = new Path(p1), path2 = new Path(p2); path1.fillColor = 'rgba(255,0,0,.5)'; path1.position = new Point(25, 25); path2.fillColor = 'rgba(0,255,0,.5)'; path2.position = new Point(40, 25); var result = path2.intersect(path1); result.selected = true; result.fillColor = '#77f'; //exportSVG() docu: http://paperjs.org/reference/item/#exportsvg var svgPathElement = result.exportSVG(), dPath = svgPathElement.getAttribute('d'); document.querySelector('path').setAttribute('d', dPath); var output = document.querySelector('#output'); output.innerHTML = '<pre>' + dPath + '</pre>'; output.innerHTML += '<xmp>' + svgPathElement.outerHTML + '</xmp>'; }; 
 table { margin-left:14px; padding-left:14px; border-left:1px solid gray; display:inline-block } 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.12.0/paper-full.min.js"></script> <canvas id="canvas" width="75" height="75" resize></canvas> <table><tr><td><b>Наш путь пересечения в виде отдельного SVG:</b></td></tr> <tr><td> <svg width="75" height="75" viewBox="0 0 75 75"> <path fill="rgba(0,0,255,.5)" d=""/> </svg> </td></tr></table> <div id="output"></div> 

See also:

    1. Here is jsclipper

    2. A terrible perversion, but it works: for one project, I had bolted raster mixing of 2 color zones, highlighting the mixed color with its subsequent trace back into the vector.

    PS: tracing - potrace, if someone is interested in it - write

    • Interesting, but is it only for polygons? - Qwertiy
    • With this transformation, the line turns into a polygon, but where I use it, moreover, I need it, it turns out the merge of many figures - Stranger in the Q
    • one
      @Bharata youtu.be/SEqZHPaz6Tg , here it is very superficially removed - Stranger in the Q
    • one
      @Bharata in the first tool draws svg floors, logical zones and meta information, and in the viewer from this on the fly 3d model is built, backendless - Stranger in the Q
    • one
      @Bharata editor, fabric js, but it didn’t fit, a lot of things were rewritten to d3, in the end I’ll give it up, converting a vector into a model - of course, I didn’t come up with the basic algorithms, but I had to cross a lot, otdebazhit and twist, there are a lot stages, tracing, simplification, rasterization, search for contours, then all sorts of shaders, a lot of things .. - Stranger in the Q

    The idea is as follows:

    • For each intersection point, divide the intersecting segments into 2 fragments each
    • Slightly deviating from the intersection point in each of the parties, we check which side was inside another path.
    • Select the desired fragments, if necessary, change the beginning and end in some places
    • Segments that are not participating in the intersection are added for no particular reason according to who owned the last fragment.
    • It is necessary to somehow carefully handle the cases where the segment intersects repeatedly

    While there is only an incomplete attempt to calculate the first point:

     var p1 = "M 24.379464,51.504463 23.434524,23.156249 38.742559,12.572916 c 0,0 29.860118,-9.0714281 17.00893,0.755953 -12.851191,9.82738 13.229166,19.465774 13.229166,19.465774 z" var p2 = "m 32.883928,0.28869028 c 0,0 -15.686011,1.51190452 -8.504463,7.18154712 7.181546,5.6696426 50.270836,30.0491076 26.458332,42.3333336 -23.8125,12.284226 47.058036,14.174107 47.058036,14.174107 z" function binSearch(l, r, f, eps) { while (1) { var m = (l+r) / 2 if (f(m) > 0) r = m; else l = m; if (rl < eps) return m } } var intersection = Snap.path.intersection(p1, p2) var cx = intersection[0].x, cy = intersection[0].y var b1 = intersection[0].bez1, b2 = intersection[0].bez2 var pp1 = `M${b1[0]},${b1[1]} C ${b1.slice(2).join(" ")}` var pp2 = `M${b2[0]},${b2[1]} C ${b2.slice(2).join(" ")}` var t1 = intersection[0].t1 var t2 = intersection[0].t2 function selectFragment(p, b, t, dt, x, y, isSecond) { var bp = `M${b[0]},${b[1]} C ${b.slice(2).join(" ")}` var len = Snap.path.getTotalLength(bp) // Почему-то значение intersection[0].t1 неточно работает в findDotsAtSegment // Ищем точное бинпоиском, но это только для выбора сегмента // Обрезать путь надо по оригинальному значению var tt = binSearch(0, 1, t => Snap.path.findDotsAtSegment(...b, t).x - x, dt / 4) var ptl = Snap.path.findDotsAtSegment(...b, tt - dt) var ptr = Snap.path.findDotsAtSegment(...b, tt + dt) var isl = Snap.path.isPointInside(p, ptl.x, ptl.y) var isr = Snap.path.isPointInside(p, ptr.x, ptr.y) if (isl ^ isr) { if (isl) { var l = 0, r = t * len // Берём начало пути b } else { var l = t * len, r = len // Берём конец пути b } } else { throw new Error("Both points are on the same side") } var pt1 = Snap.path.findDotsAtSegment(...b, l) var pt2 = Snap.path.findDotsAtSegment(...b, r) // не работает ////// Для второго фрагмента точка пересечения должна быть в начале, а для первого - в конце ////if (isSecond ^ (Math.abs(pt1.xx)+Math.abs(pt1.yy) > Math.abs(pt2.xx)+Math.abs(pt2.yy))) { //// [l, r] = [r, l] // Нужно перевернуть фрагмент ////} // getSubpath может вернуть начало и конец неточно, поэтому // начальную M меняем на L чтобы исправить его начало - пока не готово var res = Snap.path.getSubpath(bp, l, r) // .replace('M', 'L') // Добавляем переход в нужную точку через L чтобы исправить неточности return res // isSecond ? `L ${x},${y} ${res}` : `${res}` } // Самый первый фрагмент не должен начинаться с линии - возвращаем M var p1i = selectFragment(p1, b2, t2, .001, cx, cy, false).replace('L', 'M') var p2i = selectFragment(p2, b1, t1, .001, cx, cy, true) console.log(p1i) console.log(p2i) document.getElementById('res').setAttribute('d', p1i + p2i) //[ // { // "x": 35.21843116823025, // "y": 15.00935009348013, // "t1": 0.769785747695916, // "t2": 0.25341822203708086, // "segment1": 2, // "segment2": 2, // "bez1": [ // 23.434524, // 23.156249, // 23.434524, // 23.156249, // 38.742559, // 12.572916, // 38.742559, // 12.572916 // ], // "bez2": [ // 24.379464999999996, // 7.4702374, // 31.561010999999997, // 13.139880000000002, // 74.650301, // 37.519345, // 50.837796999999995, // 49.803571000000005 // ] // }, // ... //] 
     svg { width: 10em; width: 100vmin; outline: 1px dotted blue; display: none; } input { display: none; } label { width: 10em; float: left; clear: left; cursor: pointer; line-height: 2em; margin: 0 .5em .25em 0; padding: 0 .25em; border: 1px solid; } :checked + * + * + * + label { background: antiquewhite; color: blue; } :checked + * + * + * + * + * + * + * + svg { display: inline-block; } /* svg:last-of-type { display: inline-block; position: absolute; transform: translateX(-100%); } /**/ 
     <script src=//cdnjs.cloudflare.com/ajax/libs/snap.svg/0.5.1/snap.svg-min.js></script> <input type=radio name=svg id=in checked> <input type=radio name=svg id=out> <input type=radio name=svg id=cp> <input type=radio name=svg id=int> <label for=in>Данные</label> <label for=out>Пересечение</label> <label for=cp>Обрезка</label> <label for=int>Вычисленное</label> <svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="22 0 76 64"> <path id="path1" style="fill:rgba(255,0,0,.5); stroke:red;stroke-width:0.26458332px;" d="M 24.379464,51.504463 23.434524,23.156249 38.742559,12.572916 c 0,0 29.860118,-9.0714281 17.00893,0.755953 -12.851191,9.82738 13.229166,19.465774 13.229166,19.465774 z" /> <path id="path2" style="fill:rgba(0,255,0,.5);stroke:green;stroke-width:0.26458332px;" d="m 32.883928,0.28869028 c 0,0 -15.686011,1.51190452 -8.504463,7.18154712 7.181546,5.6696426 50.270836,30.0491076 26.458332,42.3333336 -23.8125,12.284226 47.058036,14.174107 47.058036,14.174107 z" /> </svg> <svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="22 0 76 64"> <path style="fill:rgba(0,0,255,.5);stroke:blue;stroke-width:0.26458332px;" d="M 43.943359 11.123047 C 40.995759 11.900151 38.742188 12.572266 38.742188 12.572266 L 35.236328 14.996094 C 44.091432999999995 21.21816 55.052161 29.822765 57.455078 37.628906 L 66.939453 33.650391 63.632812 30.410156 C 58.77426 27.95814 52.364322 23.85552 52.214844 19.224609 L 43.943359 11.123047 z" /> </svg> <svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="22 0 76 64"> <clipPath id="clip2"> <use xlink:href="#path2" /> </clipPath> <use xlink:href="#path1" clip-path="url(#clip2)" /> </svg> <svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="22 0 76 64"> <path id="res" style="fill:none;stroke:black;stroke-width:0.26458332px;" /> </svg> 

      If without plug-ins, then just make a mask, look

       <svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" height="500" width="500"> <defs> <style> #path1{ fill:#fff; } #path2{ fill:#000; } </style> <mask id="mask"> <path id="path1" d="M 24.379464,51.504463 23.434524,23.156249 38.742559,12.572916 c 0,0 29.860118,-9.0714281 17.00893,0.755953 -12.851191,9.82738 13.229166,19.465774 13.229166,19.465774 z" /> </mask> </defs> <path id="path2" d="m 32.883928,0.28869028 c 0,0 -15.686011,1.51190452 -8.504463,7.18154712 7.181546,5.6696426 50.270836,30.0491076 26.458332,42.3333336 -23.8125,12.284226 47.058036,14.174107 47.058036,14.174107 z" mask="url(#mask)"/> </svg> 

      • Not that. I need to programmatically calculate the value of d at the intersection of the paths, and not display it on the screen. - Qwertiy