textLength y lengthAdjust
Forzar un texto a una anchura exacta: qué distorsiona cada modo, cuándo es la herramienta correcta y cuándo produce tipografía inaceptable, y su interacción con las fuentes.
textLength obliga a un texto a medir exactamente lo que le digas. Es la única primitiva de SVG que resuelve el problema de encajar una etiqueta en un espacio fijo sin medirla desde JavaScript, y viene con un peligro tipográfico real: según el modo que elijas, distorsionas el espaciado o distorsionas las letras, y una de las dos cosas es aceptable y la otra rara vez lo es.
- Aplicar
textLengtha untexty a untspan. - Distinguir
spacingdespacingAndGlyphsy decidir cuál usar. - Explicar qué ocurre cuando la fuente aún no ha cargado.
- Elegir entre
textLengthy la medición manual según el caso.
Forzar la anchura
textLength declara la longitud que debe ocupar el texto en el eje de avance, en unidades de usuario. El motor ajusta el texto para que la cumpla.
<svg viewBox="0 0 220 90" width="440">
<line x1="10" y1="20" x2="190" y2="20" stroke="#45475a" />
<g font-size="18" fill="#cdd6f4" font-family="system-ui, sans-serif">
<text x="10" y="40">Sin forzar</text>
<text x="10" y="70" textLength="180">Forzado a 180</text>
</g>
</svg>
El segundo texto llega exactamente hasta la marca de 180 unidades, sea cual sea su anchura natural.
Funciona tanto para estirar como para encoger. Y funciona sobre tspan además de sobre text, lo que permite ajustar una parte de la cadena:
<text x="10" y="30" font-size="18">
Total: <tspan textLength="60">1.284</tspan> €
</text>
Ese patrón es muy útil en tablas y en listas de cifras: fuerza a que todos los números ocupen lo mismo y queden alineados aunque tengan distinto número de dígitos, sin fuente monoespaciada.
Los dos modos
lengthAdjust decide cómo se consigue la anchura pedida.
spacing (el valor por omisión) reparte la diferencia entre los espacios de avance de los glifos. Las letras conservan su forma y su anchura; lo que cambia es la separación. Es el equivalente al letter-spacing de CSS, con la diferencia de que aquí el valor se calcula para dar exactamente la anchura pedida.
spacingAndGlyphs escala los glifos también, horizontalmente. Las letras se estiran o se comprimen.
<g font-size="18" fill="#cdd6f4">
<text x="10" y="30" textLength="200" lengthAdjust="spacing">Espaciado</text>
<text x="10" y="60" textLength="200" lengthAdjust="spacingAndGlyphs">Escalado</text>
</g>
La diferencia es brutal cuando el factor se aleja de 1. Con spacing, un texto muy estirado se ve con las letras separadas: es legible y a veces incluso elegante. Con spacingAndGlyphs, las letras se deforman: los trazos verticales engordan o adelgazan y los redondeos se convierten en óvalos. Es la deformación que cualquier tipógrafo considera un error, y la que la gente reconoce inmediatamente como «esa fuente está estirada».
La regla:
- Para encajar una etiqueta:
spacing, y solo si el ajuste es pequeño (digamos, menos de un 15 por ciento). - Para un efecto deliberado de letras separadas:
spacing, sin límite. - Para escalar un logotipo tipográfico a un ancho exacto:
spacingAndGlyphs, pero mejor untransform: scaleX()sobre el elemento, que es más explícito. - Para texto de lectura: ninguno de los dos. Mide y reduce el tamaño de fuente.
Un límite importante de spacing: si el texto tiene que encogerse mucho, el espaciado se vuelve negativo y las letras se solapan. No hay un mínimo que el motor respete: se solaparán hasta ser ilegibles. textLength no protege de nada.
La trampa de la fuente
textLength se aplica a lo que el motor renderiza. Si la fuente web aún no ha cargado, el texto se compone con la fuente de reserva y se ajusta a la anchura pedida con esa fuente. Cuando la real llega, se recompone y se vuelve a ajustar.
El resultado visible: un texto que aparece con un espaciado y cambia a otro. Como la anchura total no cambia (es la que has forzado), lo que se ve es que las letras se reorganizan dentro del mismo ancho. Es un salto sutil y molesto, y a diferencia del cambio de fuente normal no produce desplazamiento del contenido de al lado.
Curiosamente, esto convierte a textLength en una herramienta contra el desplazamiento de contenido: un texto con anchura forzada no cambia de tamaño al cargar la fuente, así que no mueve nada de lo que tiene alrededor. En un gráfico con etiquetas junto a formas, eso puede ser preferible al salto.
Si necesitas evitar el cambio por completo, la vía es esperar:
await document.fonts.ready;
// ahora medir y aplicar textLength
La distinción es la que hace que este atributo sea peligroso. textLength no dice «no pases de aquí»: dice «mide exactamente esto». No hay mínimo, no hay máximo y no hay comportamiento de reserva.
Las tres consecuencias, en orden de gravedad.
Un texto vacío o de un solo carácter da resultados indefinidos. Con spacing, el ajuste se reparte entre los espacios de avance, y con un solo glifo no hay espacio entre glifos donde repartir. Los motores lo resuelven de formas distintas: algunos ignoran el atributo, otros escalan el glifo aunque el modo sea spacing. Si generas etiquetas desde datos, el caso de un carácter llegará.
Un cambio de idioma multiplica el problema. Una etiqueta que en castellano tiene 12 caracteres y en alemán 24, forzada a la misma anchura, pasa de un ajuste del 5 por ciento a uno del 60. En el idioma largo el texto sale con las letras superpuestas, y no hay nada en el sistema que avise. Este es el fallo real y frecuente: la interfaz se ve bien en el idioma de desarrollo y es ilegible en otro.
El valor se expresa en unidades de usuario, no en em. Si el viewBox cambia o el gráfico se redimensiona, la relación entre la anchura forzada y el tamaño de fuente se mantiene, pero si cambias el tamaño de fuente sin cambiar textLength, el factor de ajuste se dispara.
La conclusión operativa: textLength sirve para alinear cifras de longitud conocida y acotada, y para efectos deliberados. Para encajar texto arbitrario en un espacio, la herramienta correcta es medir con getComputedTextLength() y reducir el tamaño de fuente o truncar, que degrada de forma controlada. Si aun así usas textLength con contenido variable, pon un guardián que compare la anchura natural con la forzada y desactive el atributo cuando el factor se salga del rango que hayas decidido.
Medir de verdad
Los métodos de SVGTextContentElement son la alternativa, y conviene tenerlos a mano:
const t = document.querySelector('text');
t.getComputedTextLength(); // anchura real del texto renderizado
t.getNumberOfChars(); // numero de caracteres
t.getSubStringLength(0, 5); // anchura de los cinco primeros
t.getStartPositionOfChar(3); // punto donde empieza el cuarto caracter
t.getExtentOfChar(3); // caja del cuarto caracter
Con getSubStringLength se implementa un truncado con puntos suspensivos que corta en el carácter exacto, y con getExtentOfChar se coloca un cursor o un resaltado sobre una parte del texto. Son APIs poco conocidas y hacen posible casi todo lo que el formato no ofrece de serie.
Escribe una función de truncado que, dada una anchura máxima, recorte el texto con puntos suspensivos usando getSubStringLength en una búsqueda binaria en lugar de un bucle carácter a carácter. Compara el número de mediciones de las dos versiones con un texto de doscientos caracteres, y comprueba con el panel de rendimiento el efecto en el número de cálculos de layout forzados.