let pathData = path.getPathData();
// decompose shorthands
pathData = pathDataToLonghands(pathData);
path.setPathData(pathData)
outputLong.value = path.getAttribute('d')
// normalized to cubics
let pathDataCubic = path.getPathData({
normalize: true
});
pathDataCubic = roundPathData(pathDataCubic, 2);
path.setPathData(pathDataCubic)
outputNorm.value = path.getAttribute('d')
/**
* round pathData
*/
function roundPathData(pathData, decimals = 3) {
pathData.forEach((com, c) => {
if (decimals >= 0) {
com.values.forEach((val, v) => {
pathData[c].values[v] = +val.toFixed(decimals);
});
}
});
return pathData;
}
/**
* decompose/convert shorthands to "longhand" commands:
* H, V, S, T => L, L, C, Q
* reversed method: pathDataToShorthands()
*/
function pathDataToLonghands(pathData, decimals = -1) {
pathData = pathDataToAbsolute(pathData, decimals);
let pathDataLonghand = [];
let comPrev = {
type: "M",
values: pathData[0].values
};
pathDataLonghand.push(comPrev);
for (let i = 1; i < pathData.length; i++) {
let com = pathData[i];
let type = com.type;
let values = com.values;
let valuesL = values.length;
let valuesPrev = comPrev.values;
let valuesPrevL = valuesPrev.length;
let [x, y] = [values[valuesL - 2], values[valuesL - 1]];
let cp1X, cp1Y, cp2X, cp2Y;
let [prevX, prevY] = [
valuesPrev[valuesPrevL - 2],
valuesPrev[valuesPrevL - 1]
];
switch (type) {
case "H":
comPrev = {
type: "L",
values: [values[0], prevY]
};
break;
case "V":
comPrev = {
type: "L",
values: [prevX, values[0]]
};
break;
case "T":
[cp1X, cp1Y] = [valuesPrev[0], valuesPrev[1]];
[prevX, prevY] = [
valuesPrev[valuesPrevL - 2],
valuesPrev[valuesPrevL - 1]
];
// new control point
cpN1X = prevX + (prevX - cp1X);
cpN1Y = prevY + (prevY - cp1Y);
comPrev = {
type: "Q",
values: [cpN1X, cpN1Y, x, y]
};
break;
case "S":
[cp1X, cp1Y] = [valuesPrev[0], valuesPrev[1]];
[cp2X, cp2Y] =
valuesPrevL > 2 ? [valuesPrev[2], valuesPrev[3]] : [valuesPrev[0], valuesPrev[1]];
[prevX, prevY] = [
valuesPrev[valuesPrevL - 2],
valuesPrev[valuesPrevL - 1]
];
// new control points
cpN1X = 2 * prevX - cp2X;
cpN1Y = 2 * prevY - cp2Y;
cpN2X = values[0];
cpN2Y = values[1];
comPrev = {
type: "C",
values: [cpN1X, cpN1Y, cpN2X, cpN2Y, x, y]
};
break;
default:
comPrev = {
type: type,
values: values
};
}
pathDataLonghand.push(comPrev);
}
return pathDataLonghand;
}
function pathDataToAbsolute(pathData, decimals = -1, unlink = false) {
// remove object reference
pathData = unlink ? JSON.parse(JSON.stringify(pathData)) : pathData;
let M = pathData[0].values;
let x = M[0],
y = M[1],
mx = x,
my = y;
// loop through commands
for (let i = 1; i < pathData.length; i++) {
let cmd = pathData[i];
let type = cmd.type;
let typeAbs = type.toUpperCase();
let values = cmd.values;
if (type != typeAbs) {
type = typeAbs;
cmd.type = type;
// check current command types
switch (typeAbs) {
case "A":
values[5] = +(values[5] + x);
values[6] = +(values[6] + y);
break;
case "V":
values[0] = +(values[0] + y);
break;
case "H":
values[0] = +(values[0] + x);
break;
case "M":
mx = +values[0] + x;
my = +values[1] + y;
default:
// other commands
if (values.length) {
for (let v = 0; v < values.length; v++) {
// even value indices are y coordinates
values[v] = values[v] + (v % 2 ? y : x);
}
}
}
}
// is already absolute
let vLen = values.length;
switch (type) {
case "Z":
x = +mx;
y = +my;
break;
case "H":
x = values[0];
break;
case "V":
y = values[0];
break;
case "M":
mx = values[vLen - 2];
my = values[vLen - 1];
default:
x = values[vLen - 2];
y = values[vLen - 1];
}
// round coordinates
if (decimals >= 0) {
cmd.values = values.map((val) => {
return +val.toFixed(decimals);
});
}
}
// round M (starting point)
if (decimals >= 0) {
[M[0], M[1]] = [+M[0].toFixed(decimals), +M[1].toFixed(decimals)];
}
return pathData;
}
body {
padding: 2em;
font-family: sans-serif;
}
svg {
border: 1px solid #ccc;
overflow: visible;
width: 50%;
}
textarea {
min-height: 20em;
width: 100%;
}
<svg id="svg" class="svgPreview" viewBox="3 7 80 70">
<path id="path" d="M3,7 L13,7 m-10,10 l10,0 V27 H23 v10 h10C 33,43 38,47 43,47 c 0,5 5,10 10,10S 63,67 63,67 s -10,10 10,10Q 50,50 73,57q 20,-5 0,-10T 70,40t 0,-15A 5,5 45 1 0 40,20 a5,5 20 0 1 -10,-10Z" fill="#cccccc" stroke="#000000" stroke-width="0" stroke-linecap="butt"></path>
</svg>
<h3>Longhand output</h3>
<textarea id="outputLong"></textarea>
<h3>Normalized output (only M, L, C, Z)</h3>
<textarea id="outputNorm"></textarea>
<p>Svg by @Phrogz: http://phrogz.net/svg/convert_path_to_absolute_commands.svg
</p>
<p>https://stackoverflow.com/questions/9677885/convert-svg-path-to-absolute-commands#9677915</p>
<script src="https://cdn.jsdelivr.net/npm/path-data-polyfill@1.0.4/path-data-polyfill.min.js"></script>