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.
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.
Subconjuntar la fuente
Cuando eliges incrustar, el subconjuntado no es opcional. El procedimiento:
- Recoge los caracteres únicos que aparecen en el SVG.
- Genera la fuente con solo esos glifos.
- Codifica en base64 y sustituye el
src.
// Los caracteres unicos que hay que conservarfunction 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.