Pure SVG way to fit text to a box

Svg

Svg Problem Overview


Box size known. Text string length unknown. Fit text to box without ruining its aspect ratio.

enter image description here

After an evening of googling and reading the SVG spec, I'm pretty sure this isn't possible without JavaScript. The closest I could get was using the textLength and lengthAdjust text attributes, but that stretches the text along one axis only.

<svg width="436" height="180"
    style="border:solid 6px"
	xmlns="http://www.w3.org/2000/svg">
	<text y="50%" textLength="436" lengthAdjust="spacingAndGlyphs">UGLY TEXT</text>
</svg>

enter image description here

I am aware of https://stackoverflow.com/questions/2938779/svg-scaling-text-to-fit-container and https://stackoverflow.com/questions/5933857/fitting-text-into-the-box

Svg Solutions


Solution 1 - Svg

I didn't find a way to do it directly without Javascript, but I found a JS quite easy solution, without for loops and without modify the font-size and fits well in all dimensions, that is, the text grows until the limit of the shortest side.

Basically, I use the transform property, calculating the right proportion between the desired size and the current one.

This is the code:

<?xml version="1.0" encoding="UTF-8" ?>
<svg version="1.2" viewBox="0 0 1000 1000" width="1000" height="1000" xmlns="http://www.w3.org/2000/svg" >
 <text id="t1" y="50" >MY UGLY TEXT</text>
 <script type="application/ecmascript"> 

    var width=500, height=500;

    var textNode = document.getElementById("t1");
    var bb = textNode.getBBox();
    var widthTransform = width / bb.width;
    var heightTransform = height / bb.height;
    var value = widthTransform < heightTransform ? widthTransform : heightTransform;
    textNode.setAttribute("transform", "matrix("+value+", 0, 0, "+value+", 0,0)");

 </script>
</svg>

In the previous example the text grows until the width == 500, but if I use a box size of width = 500 and height = 30, then the text grows until height == 30.

Solution 2 - Svg

first of all: just saw that the answer doesn't precisely address your need - it might still be an option, so here we go:

you are rightly observing that svg doesn't support word-wrapping directly. however, you might benefit from foreignObject elements serving as a wrapper for xhtml fragments where word-wrapping is available.

have a look at this self-contained demo (available online):

<?xml version="1.0" encoding="utf-8"?>
<!-- SO: http://stackoverflow.com/questions/15430189/pure-svg-way-to-fit-text-to-a-box  -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg"
   xmlns:xhtml="http://www.w3.org/1999/xhtml"
   xmlns:xlink="http://www.w3.org/1999/xlink"
   version="1.1"
   width="20cm" height="20cm"
   viewBox="0 0 500 500"
   preserveAspectRatio="xMinYMin"
   style="background-color:white; border: solid 1px black;"
>
  <title>simulated wrapping in svg</title>
  <desc>A foreignObject container</desc>

   <!-- Text-Elemente -->
   <foreignObject
      x="100" y="100" width="200" height="150"
      transform="translate(0,0)"
   >
      <xhtml:div style="display: table; height: 150px; overflow: hidden;">
         <xhtml:div style="display: table-cell; vertical-align: middle;">
            <xhtml:div style="color:black; text-align:center;">Demo test that is supposed to be word-wrapped somewhere along the line to show that it is indeed possible to simulate ordinary text containers in svg.</xhtml:div>
         </xhtml:div>
      </xhtml:div>
   </foreignObject>

  <rect x="100" y="100" width="200" height="150" fill="transparent" stroke="red" stroke-width="3"/>
</svg>

Solution 3 - Svg

I've developed @Roberto answer, but instead of transforming (scaling) the textNode, we simply:

  • give it font-size of 1em to begin with
  • calculate the scale based on getBBox
  • set the font-size to that scale

(You can also use 1px etc.)

Here's the React HOC that does this:

import React from 'react';
import TextBox from './TextBox';

const AutoFitTextBox = TextBoxComponent =>
  class extends React.Component {
    constructor(props) {
      super(props);
      this.svgTextNode = React.createRef();
      this.state = { scale: 1 };
    }

    componentDidMount() {
      const { width, height } = this.props;
      const textBBox = this.getTextBBox();
      const widthScale = width / textBBox.width;
      const heightScale = height / textBBox.height;
      const scale = Math.min(widthScale, heightScale);

      this.setState({ scale });
    }

    getTextBBox() {
      const svgTextNode = this.svgTextNode.current;
      return svgTextNode.getBBox();
    }

    render() {
      const { scale } = this.state;
      return (
        <TextBoxComponent
          forwardRef={this.svgTextNode}
          fontSize={`${scale}em`}
          {...this.props}
        />
      );
    }
  };

export default AutoFitTextBox(TextBox);

Solution 4 - Svg

I don't think its the solution for what you want to do but you can use textLength with percentage ="100%" for full width.

<svg width="436" height="180"
    style="border:solid 6px"
    xmlns="http://www.w3.org/2000/svg">
    <text x="0%" y="50%" textLength="100%">blabla</text>
</svg>

you can also add textanchor="middle" and change the x position to center perfectly your text

this will not change the fontsize and you will have weird space letterspacing...

JSFIDDLE DEMO

Solution 5 - Svg

This is still an issue in 2022. There is no way to define bounds and get text to scale in a pure scalable vector graphic. Adjusting the font size manually is still the only solution it seems, and the examples given are quite buggy. Has anybody figured out a clean solution that works? Judging by the svg spec it looks like a pure solution doesn't exist.

And to provide some sort of answer myself, this resource is the best I've found, is hacky, but works much more robustly: fitrsvgtext - storybook | fitrsvgtext - GitHub

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBemmuView Question on Stackoverflow
Solution 1 - SvgRobertoView Answer on Stackoverflow
Solution 2 - SvgcollapsarView Answer on Stackoverflow
Solution 3 - SvgIzhakiView Answer on Stackoverflow
Solution 4 - SvgKuro KumoView Answer on Stackoverflow
Solution 5 - SvgEbenView Answer on Stackoverflow