const svg = document.querySelector('svg');
const svgMarkup = document.querySelector('#svgMarkup');
svgMarkup.value = new XMLSerializer().serializeToString(svg);
/**
* example script
**/
function getConvertedMarkup(svg, markupEl, decimals = 1) {
convertPrimitivesNative(svg, decimals);
markupEl.value = new XMLSerializer().serializeToString(svg);
}
/**
* parse svg attributes and convert relative units
**/
function parseSvgAttributes(svg, atts) {
let calcW = 0;
let calcH = 0;
let calcR = 0;
//1. check viewBox
let viewBoxAtt = svg.getAttribute('viewBox');
let viewBox = viewBoxAtt ? viewBoxAtt.split(' ') : [];
[calcW, calcH] = [viewBox[2], viewBox[3]];
//2. check width attributes
if (!calcW || !calcH) {
widthAtt = svg.getAttribute('width') ? parseFloat(svg.getAttribute('width')) : '';
heightAtt = svg.getAttribute('height') ? parseFloat(svg.getAttribute('height')) : '';
[calcW, calcH] = [widthAtt, heightAtt];
}
//3. calculate by getBBox()
if (!calcW || !calcH) {
let bb = svg.getBBox();
[calcW, calcH] = [(calcW ? calcW : bb.width), (calcH ? calcH : bb.height)];
}
// calculate relative radius: needed for non square aspect ratios
calcR = Math.sqrt(Math.pow(calcW, 2) + Math.pow(calcH, 2)) / Math.sqrt(2);
let attArr = [...atts];
let attObj = {};
attArr.forEach(function(att) {
let attName = att.nodeName;
// convert percentages to absolute svg units
let val = att.nodeValue;
let percentAtts = ['x', 'y', 'x1', 'y1', 'x2', 'y2', 'r', 'rx', 'ry', 'cx', 'cy', 'width', 'height']
if (val.toString().indexOf('%') !== -1 && percentAtts.indexOf(attName) !== -1) {
// strip units
val = parseFloat(val);
switch (attName) {
case 'cx':
case 'rx':
case 'width':
case 'x':
case 'x1':
case 'x2':
val = 1 / 100 * val * calcW;
break;
case 'cy':
case 'ry':
case 'height':
case 'y':
case 'y1':
case 'y2':
val = 1 / 100 * val * calcH;
break;
case 'r':
val = 1 / 100 * val * calcR;
break;
}
}
attObj[att.nodeName] = val;
});
return attObj;
}
/**
* convert primitive attributes to relative path commands
*/
function convertPrimitivesNative(svg, decimals = 3) {
let primitives = svg.querySelectorAll('line, polyline, polygon, circle, ellipse, rect');
if (primitives.length) {
primitives.forEach(function(primitive) {
let pathData = [];
let type = primitive.nodeName;
let atts = parseSvgAttributes(svg, primitive.attributes, 2);
let path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
//exclude attributes not needed for paths
let exclude = ['x', 'y', 'x1', 'y1', 'x2', 'y2', 'cx', 'cy', 'r', 'rx', 'ry', 'points', 'height',
'width'
];
switch (type) {
case 'rect':
let [rx, ry] = [atts.rx, atts.ry];
rx = !rx && ry ? ry : rx;
ry = !ry && rx ? rx : ry;
let [x, y, width, height] = [atts.x, atts.y, atts.width, atts.height];
let [widthInner, heightInner] = [width - rx * 2, height - ry * 2];
if (rx) {
pathData.push({
type: 'M',
values: [x, (y + ry)]
}, {
type: 'a',
values: [rx, ry, 0, 0, 1, rx, -ry]
}, {
type: 'h',
values: [widthInner]
}, {
type: 'a',
values: [rx, ry, 0, 0, 1, rx, ry]
}, {
type: 'v',
values: [heightInner]
}, {
type: 'a',
values: [rx, ry, 0, 0, 1, -rx, ry]
}, {
type: 'h',
values: [-widthInner]
}, {
type: 'a',
values: [rx, ry, 0, 0, 1, -rx, -ry]
}, {
type: 'z',
values: []
});
} else {
pathData.push({
type: 'M',
values: [x, y]
}, {
type: 'h',
values: [width]
}, {
type: 'v',
values: [height]
}, {
type: 'h',
values: [-width]
}, {
type: 'z',
values: []
});
}
break;
case 'line':
let [x1, y1, x2, y2] = [atts.x1, atts.y1, atts.x2, atts.y2];
pathData.push({
type: 'M',
values: [x1, y1]
}, {
type: 'l',
values: [(x2 - x1), (y2 - y1)]
});
break;
case 'circle':
case 'ellipse':
if (type == 'circle') {
let r = atts.r;
let [cX, cY] = [atts.cx, atts.cy - atts.r];
pathData.push({
type: 'M',
values: [cX, cY]
}, {
type: 'a',
values: [r, r, 0, 0, 1, r, r]
}, {
type: 'a',
values: [r, r, 0, 0, 1, -r, r]
}, {
type: 'a',
values: [r, r, 0, 0, 1, -r, -r]
}, {
type: 'a',
values: [r, r, 0, 0, 1, r, -r]
}, {
type: 'z',
values: []
});
} else {
let rx = atts.rx;
let ry = atts.ry;
let [cX, cY] = [atts.cx, atts.cy - atts.ry];
pathData.push({
type: 'M',
values: [cX, cY]
}, {
type: 'a',
values: [rx, ry, 0, 0, 1, rx, ry]
}, {
type: 'a',
values: [rx, ry, 0, 0, 1, -rx, ry]
}, {
type: 'a',
values: [rx, ry, 0, 0, 1, -rx, -ry]
}, {
type: 'a',
values: [rx, ry, 0, 0, 1, rx, -ry]
}, {
type: 'z',
values: []
});
}
break;
case 'polygon':
case 'polyline':
let closePath = type == 'polygon' ? 'z' : '';
let points = atts.points.replace(/^\s+|\s+$|\s+(?=\s)/g, "").replaceAll(",", " ");
let pointArr = points.split(' ');
pathData.push({
type: 'M',
values: [+pointArr[0], +pointArr[1]]
});
for (let i = 2; i < pointArr.length; i += 2) {
let [x0, y0] = [+pointArr[i - 2], +pointArr[i - 1]];
let [x, y] = [+pointArr[i], +pointArr[i + 1]];
let com = {};
if (y == y0) {
com = {
type: 'h',
values: [x - x0]
}
} else if (x == x0) {
com = {
type: 'v',
values: [y - y0]
}
} else {
com = {
type: 'l',
values: [x - x0, y - y0]
}
}
pathData.push(com);
}
if (closePath) {
pathData.push({
type: 'z',
values: []
});
}
break;
//paths
default:
let dClean = atts.d.replace(/^\s+|\s+$|\s+(?=\s)/g, "").replaceAll(",", " ");
let dArr = dClean.replace(/([a-zA-Z])/g, " | $1").split(' | ');
dArr.shift();
for (let i = 0; i < dArr.length; i++) {
let command = dArr[i].trim().split(' ');
let type = command.shift();
command = command.map((x) => {
return parseFloat(x);
});
pathData.push({
type: type,
values: command
});
}
break;
}
// copy primitive's attributes to path
setAttributes(path, atts, exclude);
// round coordinates and replace primitive with path
path.setPathDataOpt(pathData, decimals);
primitive.replaceWith(path);
})
}
};
function setAttributes(el, attributes, exclude = []) {
for (key in attributes) {
if (exclude.indexOf(key) === -1) {
el.setAttribute(key, attributes[key]);
}
}
}
function getAttributes(el) {
let attArr = [...el.attributes];
let attObj = {};
attArr.forEach(function(att) {
attObj[att.nodeName] = att.nodeValue;
});
return attObj;
}
/**
* return rounded path data
* based on:
* https://github.com/jarek-foksa/path-data-polyfill/blob/master/path-data-polyfill.js
*/
if (!SVGPathElement.prototype.setPathDataOpt) {
SVGPathElement.prototype.setPathDataOpt = function(pathData, decimals = 3) {
let d = "";
if (pathData.length) {
for (let i = 0; i < pathData.length; i++) {
let seg = pathData[i];
let [type, values] = [seg.type, seg.values];
let valArr = [];
if (values.length) {
for (let v = 0; v < values.length; v++) {
val = parseFloat(values[v]);
valArr.push(+val.toFixed(decimals));
}
}
d += type;
if (valArr.length) {
d += valArr.join(" ").trim();
}
}
d = d.
replaceAll(' -', '-').
replaceAll(' 0.', ' .').
replaceAll(' z', 'z');
this.setAttribute("d", d);
}
};
}
<p><button type="button" onclick="getConvertedMarkup(svg, svgMarkup, 2)">Convert Primitives</button></p>
<svg id="svg" xmlns="http://www.w3.org/2000/svg" data-width="150px" data-height="30px" viewBox="0 0 150 30">
<polygon id="polygon" fill="#CCCCCC" stroke="#E3000F" points="7.9,22.8 3,14.3 7.9,5.8 17.6,5.8 22.5,14.3 17.6,22.8 " />
<polyline id="polyline" fill="none" stroke="#E3000F" points="40.9,22.8 31.1,22.8 26.2,14.3 31.1,5.8
40.9,5.8 45.8,14.3 " />
<rect id="rect" x="37.5%" y="20%" rx="2%" ry="5%" fill="none" stroke="#E3000F" width="6%" height="56%" />
<line id="line" fill="none" stroke="#E3000F" x1="50.5" y1="22.8" x2="52.5" y2="5.8" />
<circle id="circle" fill="none" stroke="#E3000F" cx="52%" cy="49%" r="8%" />
<ellipse id="ellipse" fill="none" stroke="#E3000F" cx="68%" cy="49%" rx="7%" ry="25%" />
<path id="piechart" transform="scale(0.9) translate(130, 6)" d="M 10 0 A 10 10 0 1 1 1.34 15 L 10 10 z"
fill="red" class="segment segment-1 segment-class" id="segment-01" />
</svg>
<h3>Output</h3>
<textarea name="svgMarkup" id="svgMarkup" style="width:100%; height:20em;"></textarea>