It's highly likely that incorporating some JavaScript will be necessary to achieve the desired outcome.
The primary issue:
svg <text>
elements do not support multi-line text or line heights. Therefore, you must divide your text content into multiple <tspan>
elements with varying y
offsets to simulate a similar structure to an HTML <p>
.
Additionally, numerous critical properties cannot currently be styled using CSS. This includes x
and y
, which are essential for replicating a line height.
Example: simulating multi-line SVG text - adjusting font size based on width
let svg = document.querySelector('svg')
let svgPseudoP = document.querySelector('.svgPseudoP');
svgSplitTextLines(svgPseudoP)
//split newlines
function svgSplitTextLines(el) {
let texts = el.querySelectorAll('text');
for (let t = 0; t < texts.length; t++) {
let text0 = texts[t];
let [x0, y0] = [text0.getAttribute('x'), text0.getAttribute('y')];
//trim empty elements and whitespace
let lines = text0.innerHTML.split(/\r?\n/);
lines = lines.map((str) => {
return str.trim()
}).filter(Boolean)
//set first line as textContent
text0.textContent = lines[0];
// calculate proportions
let width0 = text0.getComputedTextLength();
let style0 = window.getComputedStyle(text0);
let fontSize0 = parseFloat(style0.getPropertyValue('font-size'));
// ratio between capital letter height and font size
let ascenderRatio = 0.71582;
// define ideal leading
let leading = fontSize0 * 0.2;
for (let i = 1; i < lines.length; i++) {
let str = lines[i];
let tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
tspan.textContent = str;
tspan.setAttribute('x', x0);
text0.appendChild(tspan);
// scale font size according to width
let width = tspan.getComputedTextLength();
let scale = width0 / width;
let newFontSize = parseFloat(fontSize0) * scale;
tspan.setAttribute('style', 'font-size:' + newFontSize + 'px');
// emulate line height by increasing Y offset
let tspanPrev = tspan.previousElementSibling;
let yPrev = tspanPrev ? +tspanPrev.getAttribute('y') : +text0.getAttribute('y');
let newY = yPrev + (newFontSize * ascenderRatio)
tspan.setAttribute('y', newY + leading);
}
}
}
svg {
width: 50%;
border: 1px solid #ccc;
}
text {
font-family: Arial;
font-weight: bold;
text-anchor: middle;
text-transform: uppercase;
}
<svg class="svgPseudoP" viewBox="0 0 100 100">
<text x="50%" y="20" font-size="10">
Example
Text
Goes here
</text>
<text x="25%" y="60" font-size="8">
Example2
Text
Goes
here
</text>
</svg>
These steps are essential for achieving the desired result:
https://i.sstatic.net/mCO6p.png
For instance, create a capital letter in Arial at 100 points using software like Inkscape or Illustrator, convert it to paths/outlines, and check its height: 71.582 pt. Consequently, the capital to em square ratio would be: 100/71.582 = 0.71582
This value varies based on the metrics of the actual font files, meaning there is no standardized capital letter height. However, a ratio around 0.72–0.75 typically works well for many font families.
https://i.sstatic.net/XineV.png
Example: Irregular leading due to a non-ideal capital to em square ratio.
The provided example code will also divide markup based on new lines into <tspan>
elements:
<text x="50%" y="20" font-size="10">
Example
Text
Goes here
</text>
will be transformed into:
<text x="50%" y="20" font-size="10">
Example
<tspan x="50%" style="font-size:18.9px" y="35.5">Text</tspan>
<tspan x="50%" style="font-size:8.1px" y="43.3">Goes here</tspan>
</text>