web-dev-qa-db-fra.com

Méthode SVG pure pour adapter le texte à une zone

Taille de la boîte connue. Longueur de chaîne de texte inconnue. Ajuster le texte à la boîte sans ruiner ses proportions.

enter image description here

Après une soirée passée à googler et à lire les spécifications SVG, je suis quasiment sûr que ce n’est pas possible sans JavaScript. Le plus proche que j'ai pu obtenir a été d'utiliser les attributs de texte textLength et lengthAdjust, mais cela étend le texte le long d'un axe uniquement.

<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

Je connais SVG Scaling Text pour adapter le texte du conteneur et au texte

47
Bemmu

Je n'ai pas trouvé le moyen de le faire directement sans Javascript, mais j'ai trouvé une solution assez simple pour JS, sans boucles for et sans modifier la taille de la police, et s'intègre ainsi dans toutes les dimensions le côté le plus court.

Fondamentalement, j'utilise la propriété transform, en calculant la bonne proportion entre la taille souhaitée et celle actuelle.

C'est le 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>

Dans l'exemple précédent, le texte grossit jusqu'au width == 500, mais si j'utilise une taille de boîte de width = 500 et height = 30, le texte s'agrandit jusqu'au height == 30.

36
Roberto

tout d'abord: je viens de voir que la réponse ne répond pas précisément à votre besoin - cela pourrait encore être une option, alors allons-y:

vous constatez à juste titre que svg ne prend pas directement en charge le wrapping Word. Toutefois, vous pourriez tirer profit des éléments foreignObject servant de wrapper pour les fragments xhtml où le wrapping Word est disponible.

jetez un coup d’œil à cette démo indépendante (disponible en ligne ):

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

J'ai développé @Roberto answer, mais au lieu de transformer (mettre à l'échelle) le textNode, nous avons simplement:

  • lui donner font-size de 1em pour commencer
  • calculer l'échelle en fonction de getBBox
  • définir le font-size à cette échelle

(Vous pouvez également utiliser 1px etc.)

Voici le réactif HOC qui fait cela:

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

Je ne pense pas que ce soit la solution pour ce que vous voulez faire, mais vous pouvez utiliser textlenght Avec pourcentage ="100%" pour la largeur totale.

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

vous pouvez également ajouter textanchor="middle" et changer la position x pour centrer parfaitement votre texte

cela ne changera pas la taille de la police et vous aurez un espacement bizarre des lettres ...

JSFIDDLE DEMO

0
Kuro Kumo