纯 SVG 方式将文本适应到一个框中

85

已知盒子大小,文本字符串长度未知。将文本适应于盒子而不破坏其纵横比。

enter image description here

经过一晚的谷歌搜索和阅读SVG规范,我很确定这是无法在JavaScript的情况下实现的。我能做到的最接近的方式是使用textLengthlengthAdjust文本属性,但那样只会沿一个轴向拉伸文本。

<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

我知道SVG文本缩放以适应容器将文本装入框中


2
最终我在JavaScript中创建了一个循环,它会增加字体大小,直到getBBox显示它不再适合为止。这很丑陋,所以仍然希望有其他方法。 - Bemmu
我也一直在尝试让这个功能正常工作。我发现最好的方法与你所使用的相同,即通过JS循环并更改字体直到适合为止。但即使在字体中仍然存在一些上下空白,因此似乎无法完美地解决它。 - Chad
这真糟糕,看起来这是一个非常基本的事情,但规范明确指出它只能在一个方向上进行拉伸。经过一番尝试,我通过仅修改Y轴比例尺来使用变换(即:transform="scale(0,5)")使其接近 - http://jsfiddle.net/G5L8W/ - streetlogics
我在思考是否有一种方法可以计算应用于文本的X轴缩放量并将其应用于Y轴,但这也意味着需要添加检查以确保您不会过度缩放Y,然后您又回到使用JS。不过,我敢打赌你可以更好地使用JS中的transform/scale。 - streetlogics
SVG(或网页中的任何文本元素)内置支持将是非常棒的。 - Andy
1
http://tavmjong.free.fr/SVG/TEXT_IN_A_BOX/index.html - Mohsen
5个回答

55

我没有找到直接在没有 JavaScript 的情况下完成它的方法,但我找到了一个相当容易的 JS 解决方案,不需要 for 循环也不需要修改字体大小,适合所有尺寸,即文本增长到最短边界限为止。

基本上,我使用 transform 属性,计算所需大小和当前大小之间的正确比例。

这是代码:

<?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>

在前面的例子中,文本会增长直到width == 500,但是如果我使用width = 500height = 30的框大小,则文本将增长直到height == 30


10
简单来说,这行代码的作用是设置文本节点的缩放比例。具体实现是通过setAttribute方法来给transform属性赋值,其中value变量表示缩放比例大小。 - Charlie Martin
4
简要说明- scale属性还会影响当前物品的坐标系,所以如果您希望该元素处于相同位置,则需要将x和y位置都除以缩放因子,以获得相同的相对位置。 - DragonJawad
2
甚至更简单的方法是将文本的font-size设置为1 (1em在我们的情况下);然后一旦你有了比例因子(value在这个回答中),就将font-size设置为它。请参见下面的示例。 - Izhaki
我正在使用ImageMagick将SVG转换为PNG,但正如我所想的那样,这个JS在那里不起作用。在我将SVG转换为PNG时,有什么建议可以让它工作吗? - Krunal
1
@Krunal,我建议尝试使用Inkscape CLI:https://dev59.com/questions/N1sW5IYBdhLWcg3wqYt7#34725966 - Roberto
显示剩余4条评论

19

首先:刚看到答案并没有精确地解答您的需求-但可能仍然是一个选项,因此我们来试试:

你正确地观察到SVG不能直接支持换行。但是,您可以从foreignObject元素中受益,它可以作为xhtml片段的包装器,在那里可以使用换行。

请查看这个自包含演示(在线可用online):

<?xml version="1.0" encoding="utf-8"?>
<!-- SO: https://dev59.com/zWUp5IYBdhLWcg3wNVhb  -->
<!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>


3
谢谢,但我对自动换行不感兴趣。 - Bemmu
5
@collapsar,您的演示链接已损坏。 - shanabus
如果我添加更多的文本,它会被隐藏,并且文本的字体大小不会缩小。 - Peter Rosemann

6

我已经开发了@Roberto的答案,但是我们不会对textNode进行转换(缩放),而是:

  • 给它一个font-size1em
  • 基于getBBox计算比例尺
  • font-size设置为该比例尺

(你也可以使用1px等)

这里是执行此操作的React HOC:

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);

4

我不认为这是你想要做的解决方案,但你可以使用textLength与百分比="100%"来实现全宽度。

<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>

您还可以添加text-anchor="middle"并将x位置更改为完美居中您的文本

这不会改变字体大小,但会产生奇怪的空格字距...

JSFIDDLE演示


1
它只是扩大了字母间距。 :( - MGM
是的,关于文本居中,属性是“text-anchor”,你还需要x=50%,像这样<text x="50%" y="50%" textLength="100%" text-anchor="middle">AB</text> - jave.web

2
这仍然是2022年的一个问题。在纯可缩放矢量图形中,没有办法定义边界并使文本按比例缩放。手动调整字体大小似乎仍然是唯一的解决方案,而给出的示例相当有缺陷。有人找到了有效的清洁解决方案吗?根据svg规范的判断,似乎不存在纯粹的解决方案。
为了提供一些答案,我发现这个资源是最好的,虽然有点hacky,但更加稳健: fitrsvgtext - storybook | fitrsvgtext - GitHub

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接