svg{
border: 1px solid #ccc;
width:10em;
}
textarea{
width:100%;
min-height:20em;
}
<p><button onclick="removeTextTransforms();">Remove text transforms</button></p>
<svg id="svg" viewBox="0 0 100 100">
<text x="10" y="50" font-size="10" paint-order="stroke" font-family="sans-serif" stroke="red" stroke-width="2" transform="scale(1.2) translate(10, 20)">Text</text>
</svg>
<textarea id="output"></textarea>
<script>
function removeTextTransforms() {
let texts = document.querySelectorAll('text');
texts.forEach(el => {
let parent = el.farthestViewportElement;
// check elements transformations
let matrix = parent.getScreenCTM().inverse().multiply(el.getScreenCTM());
let {
a,
b,
c,
d,
e,
f
} = matrix;
// round matrix
[a, b, c, d, e, f] = [a, b, c, d, e, f].map(val => {
return +val.toFixed(3)
});
let matrixStr = [a, b, c, d, e, f].join('');
let isTransformed = matrixStr !== "100100" ? true : false;
if (isTransformed) {
// matrix to readable transform functions
let transObj = qrDecomposeMatrix(matrix);
let scale = (transObj.scaleX + transObj.scaleY) / 2;
// convert transforms to attributes
let {
translateX,
translateY,
rotate
} = transObj;
let x = +el.getAttribute('x')
let y = +el.getAttribute('y')
let fontSize = +el.getAttribute('font-size')
if (rotate) {
return false
} else {
// scale stroke-width
scaleStrokeWidthText(el, scale);
el.setAttribute('x', (x * scale + translateX));
el.setAttribute('y', (y * scale + translateY));
el.setAttribute('font-size', (fontSize * scale));
el.removeAttribute('transform');
}
}
});
output.value = new XMLSerializer().serializeToString(svg)
}
function scaleStrokeWidthText(el, scale) {
let styles = window.getComputedStyle(el);
let strokeWidth = styles.getPropertyValue('stroke-width');
let stroke = styles.getPropertyValue('stroke');
strokeWidth = stroke != 'none' ? parseFloat(strokeWidth) * scale : 0;
el.setAttribute('stroke-width', strokeWidth);
el.style.removeProperty('stroke-width');
}
/**
* Decompose matrix to readable transform properties
* translate() rotate() scale() etc.
* based on @AndreaBogazzi's answer
* https://stackoverflow.com/questions/5107134/find-the-rotation-and-skew-of-a-matrix-transformation#32125700
* return object with seperate transform properties
* and ready to use css or svg attribute strings
*/
function qrDecomposeMatrix(matrix, precision = 3) {
let {
a,
b,
c,
d,
e,
f
} = matrix;
// matrix is array
if (Array.isArray(matrix)) {
[a, b, c, d, e, f] = matrix;
}
let angle = Math.atan2(b, a),
denom = Math.pow(a, 2) + Math.pow(b, 2),
scaleX = Math.sqrt(denom),
scaleY = (a * d - c * b) / scaleX,
skewX = Math.atan2(a * c + b * d, denom) / (Math.PI / 180),
translateX = e ? e : 0,
translateY = f ? f : 0,
rotate = angle ? angle / (Math.PI / 180) : 0;
let transObj = {
translateX: translateX,
translateY: translateY,
rotate: rotate,
scaleX: scaleX,
scaleY: scaleY,
skewX: skewX,
skewY: 0
};
let cssTransforms = [];
let svgTransforms = [];
for (let prop in transObj) {
transObj[prop] = +parseFloat(transObj[prop]).toFixed(precision);
let val = transObj[prop];
let unit = "";
if (prop == "rotate" || prop == "skewX") {
unit = "deg";
}
if (prop.indexOf("translate") != -1) {
unit = "px";
}
// combine these properties
let convert = ["scaleX", "scaleY", "translateX", "translateY"];
if (val !== 0) {
cssTransforms.push(`${prop}(${val}${unit})`);
}
if (convert.indexOf(prop) == -1 && val !== 0) {
svgTransforms.push(`${prop}(${val})`);
} else if (prop == "scaleX") {
svgTransforms.push(
`scale(${+scaleX.toFixed(precision)} ${+scaleY.toFixed(precision)})`
);
} else if (prop == "translateX") {
svgTransforms.push(
`translate(${transObj.translateX} ${transObj.translateY})`
);
}
}
// append css style string to object
transObj.cssTransform = cssTransforms.join(" ");
transObj.svgTransform = svgTransforms.join(" ");
return transObj;
}
</script>