wandres.dev
TEXTO EN SVG · text, tspan y textPath

Las fuentes en un SVG exportado

Por qué un SVG servido como imagen no carga fuentes externas, las cuatro salidas reales con sus costes, y cómo decidir entre conservar el texto y convertirlo a contornos.

⏱ 17 min

Un SVG con texto se ve perfecto en tu página y aparece con una fuente distinta en cuanto alguien lo abre en otro sitio. La causa no es un fallo: es el modo seguro con el que los navegadores renderizan un SVG cargado como imagen, que prohíbe cargar recursos externos. Entender exactamente qué se bloquea y por qué es lo que permite elegir entre las cuatro salidas con conocimiento de causa en lugar de por prueba y error.

🎯 Al terminar esta lección sabrás
  • Explicar qué es el modo seguro de renderizado de un SVG como imagen y qué bloquea.
  • Enumerar las cuatro estrategias para el texto en un SVG que se va a distribuir.
  • Incrustar una fuente en un SVG y calcular lo que eso cuesta.
  • Decidir entre conservar texto y convertirlo a contornos con criterio.

El modo seguro

Cuando un SVG se carga a través de img, background-image, border-image, content o mask, el navegador lo renderiza en un modo restringido. En ese modo:

  • No se ejecuta JavaScript.
  • No se cargan recursos externos: ni hojas de estilo, ni imágenes, ni ficheros de fuente.
  • El documento no tiene acceso al documento anfitrión ni al revés.
  • No hay interacción: ni eventos, ni enlaces, ni hover.

Esas restricciones existen porque un SVG es un documento con capacidad de ejecutar código y de hacer peticiones. Permitir que cualquier img de la web arrastrase eso convertiría a la etiqueta de imagen en un vector de ataque. La decisión es correcta y no va a cambiar.

Lo que significa para las fuentes: un @font-face con src: url(...) dentro del SVG no carga el fichero. Y la fuente de la página anfitriona tampoco está disponible, porque el SVG es un documento independiente. El motor cae a la fuente que resuelva la familia declarada dentro del propio SVG, o a la de reserva del sistema.

El mismo bloqueo aplica cuando dibujas un SVG en un lienzo pasando por un img, que es la vía estándar para exportar a PNG. Volveremos sobre ello en el nivel 26.

Las cuatro salidas

Uno: usar solo fuentes del sistema. Si el SVG declara font-family: system-ui, sans-serif o una pila de fuentes que existen en el sistema operativo, el resultado es razonable en todas partes. Se pierde la identidad tipográfica y se gana peso cero y cero complicaciones.

Es la opción correcta para gráficos generados, diagramas técnicos e ilustraciones donde el texto es informativo y no de marca. Y es la que casi nadie considera porque parece un renuncia; en un gráfico de datos, no lo es.

Dos: incrustar la fuente como data URI. Un @font-face dentro del SVG con la fuente codificada en base64:

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 100">
  <defs>
    <style>
      @font-face {
        font-family: 'MiFuente';
        src: url('data:font/woff2;base64,d09GMgABAAAAA...') format('woff2');
      }
      text { font-family: 'MiFuente', sans-serif; font-size: 24px; }
    </style>
  </defs>
  <text x="20" y="50" fill="#cdd6f4">Texto con fuente incrustada</text>
</svg>

Funciona: la data URI no es un recurso externo, así que el modo seguro la permite. El coste es el tamaño. Una fuente WOFF2 completa ronda los 20 a 40 kB, y la codificación en base64 añade un 33 por ciento. Un SVG de 3 kB pasa a pesar 45.

La mitigación obligatoria es subconjuntar: generar una versión de la fuente con solo los glifos que el SVG usa. Con las herramientas de subconjuntado habituales, un texto de veinte caracteres distintos produce una fuente de 2 a 4 kB. Ahí sí compensa.

Tres: convertir el texto a contornos. La herramienta de diseño convierte cada glifo en un path. El resultado se ve idéntico en todas partes, sin fuentes ni dependencias.

Lo que se pierde es considerable y hay que decirlo entero: el texto deja de ser texto. No se puede seleccionar, no se puede buscar, no lo lee un lector de pantalla, no se puede traducir, no se puede cambiar sin volver a la herramienta, y el peso sube porque un glifo como contorno son cientos de bytes frente a un carácter.

Cuatro: no meter el texto en el SVG. Si el SVG se usa en línea en una página, el texto puede ir en HTML superpuesto y todo el problema desaparece. Si el SVG tiene que ser autónomo, esta opción no existe.

La tabla de decisión

Destino del SVG Estrategia
En línea en tu propia página Cualquiera; la fuente de la página funciona
img en tu propia página Fuente del sistema, o incrustada y subconjuntada
Descarga que el usuario abrirá en otro programa Contornos, o incrustada
Correo electrónico Contornos, y aun así comprueba el cliente
Favicon o icono de aplicación Contornos, sin duda
Gráfico generado en servidor para un informe Fuente del sistema o incrustada subconjuntada
Logotipo de marca en una biblioteca de componentes Contornos

El logotipo merece un comentario: es el caso donde los contornos son claramente correctos. Un logotipo es una forma, no un texto. Que no sea seleccionable es lo apropiado, y el nombre accesible se pone con title y aria-labelledby, que es de lo que trata el nivel siguiente.

El texto convertido a contornos es invisible para el lector de pantalla, y eso convierte una decisión de diseño en un fallo de accesibilidad

Cuando una herramienta convierte texto a contornos, el resultado no contiene la cadena por ninguna parte. No hay un atributo con el texto original, no hay metadatos, no hay nada. Para una tecnología de asistencia, ese SVG contiene formas y solo formas.

En un logotipo es aceptable porque el nombre accesible se puede poner aparte. En un gráfico exportado es una catástrofe silenciosa: las etiquetas de los ejes, los nombres de las series y los valores desaparecen del contenido accesible, y lo que queda es un dibujo sin ninguna alternativa textual. El gráfico se ve perfecto y es completamente inaccesible.

Y el fallo es doblemente invisible porque las herramientas de auditoría automática no lo detectan. Un verificador de accesibilidad ve un img con un alt, o un svg con un title, y da el visto bueno. No tiene forma de saber que el título dice «Gráfico de ventas» y que dentro había veinte etiquetas de datos que ya no existen.

Tres reglas que lo evitan.

Regla uno: si el SVG contiene datos, el texto se conserva. Un gráfico se exporta con texto real o no se exporta.

Regla dos: si hay que convertir a contornos por una razón imperiosa, el contenido textual viaja aparte. Una tabla de datos junto al gráfico, o un desc con el resumen completo. El patrón concreto está en el nivel 24.

Regla tres: el paso de conversión no se automatiza sin una decisión explícita por fichero. Un plugin de compilación que convierta todo el texto a contornos «para que se vea igual en todas partes» destruye la accesibilidad de todo el sitio en un commit, y nadie lo nota hasta la auditoría.

Subconjuntar la fuente

Cuando eliges incrustar, el subconjuntado no es opcional. El procedimiento:

  1. Recoge los caracteres únicos que aparecen en el SVG.
  2. Genera la fuente con solo esos glifos.
  3. Codifica en base64 y sustituye el src.
// Los caracteres unicos que hay que conservar
function caracteresDe(svgTexto) {
  const textos = svgTexto.match(/<text[\s\S]*?<\/text>/g) ?? [];
  const soloTexto = textos.join('').replace(/<[^>]+>/g, '');
  return [...new Set(soloTexto)].sort().join('');
}

Con esa cadena se alimenta la herramienta de subconjuntado que uses. Conviene añadir siempre los dígitos completos y los signos habituales aunque no aparezcan, porque un gráfico con datos dinámicos generará caracteres que no estaban en el momento de compilar.

Y una advertencia de licencia: incrustar una fuente en un SVG es redistribuirla. Muchas licencias de fuentes comerciales permiten el uso web con @font-face desde tu dominio y no permiten la incrustación en un fichero que se descarga. Antes de meter una fuente en base64 en un SVG que los usuarios van a descargar, lee la licencia. Las fuentes con licencia libre no tienen este problema.

⚔️ Reto práctico

Toma un gráfico generado con etiquetas de eje y expórtalo de las tres formas: con fuente del sistema, con la fuente subconjuntada e incrustada, y con el texto convertido a contornos. Compara los tres pesos, ábrelos con img en una página para ver cuál conserva la tipografía, y pásalos por un lector de pantalla para ver cuál conserva el contenido. Los tres números juntos son el argumento completo.