I'm working on a script to reverse the draw direction of SVG path commands, everything is working smoothly so far but not with S
path commands or T
.
In one of my implementations for path reverse functionality based only on cubic bezier C
curves, works flawlessly, however the output path string is considerably larger, sometimes double or triple the length.
Here's a simplified version of the reversePath.js
which, so far, implements some basic handling for S
and a test page:
// the script I'm working on works with these arrays
var path = [['M',10,80],['C',40, 10, 65, 10, 95, 80],['S',150,150,180,80],['S',230,10,270,80]],
target = document.getElementById('target');
// our focus is RIGHT HERE
function reversePath(pathInput){
let isClosed = pathInput.slice(-1)[0][0] === 'Z',
params = {x1: 0, y1: 0, x2: 0, y2: 0, x: 0, y: 0, qx: null, qy: null},
pathCommand = '', pLen = 0,
reversedPath = [];
reversedPath = pathInput.map((seg,i,pathArray)=>{
pLen = pathArray.length
pathCommand = seg[0]
switch(pathCommand){
case 'M':
x = seg[1]
y = seg[2]
break
case 'Z':
x = pathArray[0][1]
y = pathArray[0][2]
break
default:
x = seg[seg.length - 2]
y = seg[seg.length - 1]
}
return {
c: pathCommand,
x: x,
y: y,
seg: seg
}
}).map((seg,i,pathArray)=>{
let segment = seg.seg,
prevSeg = i && pathArray[i-1],
nextSeg = pathArray[i+1] && pathArray[i+1],
result = []
pLen = pathArray.length
pathCommand = seg.c
params.x = i ? pathArray[i-1].x : pathArray[pLen-1].x
params.y = i ? pathArray[i-1].y : pathArray[pLen-1].y
switch(pathCommand){
case 'M':
result = isClosed ? ['Z'] : [pathCommand, params.x,params.y]
break
case 'C':
if ('S' === nextSeg.c) {
params.x2 = params.x1 + params.x2 / 2
params.y2 = params.y1 + params.y2 / 2
result = ['S', params.x2,params.y2, params.x,params.y]
} else {
params.x1 = segment[3]
params.y1 = segment[4]
params.x2 = segment[1]
params.y2 = segment[2]
result = [pathCommand, params.x1,params.y1, params.x2,params.y2, params.x,params.y];
}
break
case 'S':
params.x2 = params.x1 + params.x2 / 2
params.y2 = params.y1 + params.y2 / 2
if (nextSeg && 'S' === nextSeg.c) {
result = [pathCommand, params.x2,params.y2, params.x,params.y]
} else {
params.x1 = params.x1 + params.x2 / 2
params.y1 = params.y1 + params.y2 / 2
params.x2 = segment[1];
params.y2 = segment[2];
result = ['C', params.x1,params.y1, params.x2,params.y2, params.x,params.y];
}
break
case 'Z':
result = ['M',params.x,params.y]
break
default:
result = segment.slice(0,-2).concat([params.x,params.y])
}
return result
})
return isClosed ? reversedPath.reverse() : [reversedPath[0]].concat(reversedPath.slice(1).reverse())
}
function pathToString(pathArray) {
return pathArray.map(x=>x[0].concat(x.slice(1).join(' '))).join(' ')
}
function reverse(){
var reversed = pathToString(reversePath(path));
target.setAttribute('d',reversed)
target.closest('.col').innerHTML += '<br><p class="text-left">'+reversed+'</p>'
}
.row {width: 100%; display: flex; flex-direction: row}
.col {width: 50%; text-align: center}
.text-left {text-align: left}
<button onclick="reverse()">REVERSE</button>
<hr>
<div class="row">
<div class="col">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 270 160">
<path id="example" d="M10 80 C40 10, 65 10, 95 80S 150 150, 180 80S 230 10 270 80" stroke="green" stroke-width="2" fill="transparent" />
</svg>
normal path
</div>
<div class="col">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 270 160">
<path id="target" d="M0 0L0 0" stroke="orange" stroke-width="2" fill="transparent" />
</svg>
reversed path (click the button)
</div>
</div>
I started here from the the Raphael.js implementation on converting S and Q and T path commands to C
(cubicBezier), thinking and reverse engineering perhaps I could find a way to make it work.
So I need a little help figuring out a correct formula for these S
and T
path commands when reversing the shape. If someone can help me with S
I can figure out myself on T
.
Thanks for any reply.
Alright, the normalization did the trick. Also some new additions and more powerful value processing, but let's get to it, here's the updated function with new additions:
You can check the latest SVGPathCommander version on npm or the demo page. It can also manage multiple
T
path commands, noo problem.