Pure SVG way to fit text to a box

后端 未结 4 634
小鲜肉
小鲜肉 2020-12-07 13:21

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

\"enter

相关标签:
4条回答
  • 2020-12-07 13:28

    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>

    0 讨论(0)
  • 2020-12-07 13:40

    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);
    
    0 讨论(0)
  • 2020-12-07 13:41

    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.

    0 讨论(0)
  • 2020-12-07 13:51

    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

    0 讨论(0)
提交回复
热议问题