Usa la tipografía avanzada con fuentes locales

Descubre cómo la API de Local Font Access te permite acceder a las fuentes instaladas localmente por el usuario y obtener detalles de bajo nivel sobre ellas

Fuentes seguras para la Web

Si llevas tiempo trabajando con el desarrollo web, es posible que recuerdes las llamadas fuentes seguras para la Web. Se sabe que estas fuentes están disponibles en casi todas las instancias de los sistemas operativos más usados (es decir, Windows, macOS, las distribuciones de Linux, iOS y Android más comunes). A principios de la década de 2000, Microsoft lideró una iniciativa llamada fuentes principales TrueType para la Web que proporcionaba estas fuentes para su descarga gratuita con el objetivo de que “cada vez que visites un sitio web que las especifique, verás páginas exactamente como las pretendía el diseñador del sitio”. Sí, se incluyeron sitios en Comic Sans MS. Esta es una pila de fuentes clásica y segura (con el resguardo final de cualquier fuente sans-serif) que podría verse de la siguiente manera:

body {
  font-family: Helvetica, Arial, sans-serif;
}

Fuentes web

Los días en que las fuentes seguras para la Web realmente importaban quedaron atrás. Hoy en día, tenemos fuentes web, algunas de las cuales son fuentes variables que podemos ajustar aún más si cambiamos los valores de los diversos ejes expuestos. Para usar fuentes web, declara un bloque @font-face al comienzo del CSS, que especifica los archivos de fuente que se descargarán:

@font-face {
  font-family: 'FlamboyantSansSerif';
  src: url('flamboyant.woff2');
}

Luego, puedes especificar font-family para usar la fuente web personalizada, como de costumbre:

body {
  font-family: 'FlamboyantSansSerif';
}

Fuentes locales como vector de huella digital

La mayoría de las fuentes web provienen de la Web. Sin embargo, un dato interesante es que la propiedad src en la declaración @font-face, además de la función url(), también acepta una función local(). Esto permite que las fuentes personalizadas se carguen (¡sorpresa!) de manera local. Si el usuario tiene FlamboyantSansSerif instalado en su sistema operativo, se utilizará la copia local en lugar de descargarla:

@font-face {
  font-family: 'FlamboyantSansSerif';
  src: local('FlamboyantSansSerif'), url('flamboyant.woff2');
}

Este enfoque proporciona un buen mecanismo de resguardo que posiblemente ahorra ancho de banda. Lamentablemente, en Internet no podemos tener cosas buenas. El problema de la función local() es que se puede usar de forma abusiva para la creación de huellas digitales del navegador. Resulta que la lista de fuentes que instaló un usuario puede ser bastante identificativa. Muchas empresas tienen sus propias fuentes corporativas que se instalan en las laptops de los empleados. Por ejemplo, Google tiene una fuente corporativa llamada Google Sans.

La app de macOS Font Book muestra una vista previa de la fuente Google Sans.
La fuente Google Sans instalada en la laptop de un empleado de Google.

Un atacante puede intentar determinar para qué empresa trabaja alguien probando la existencia de una gran cantidad de fuentes corporativas conocidas, como Google Sans. El atacante intentaría renderizar texto establecido en estas fuentes en un lienzo y medir los glifos. Si los glifos coinciden con la forma conocida de la fuente corporativa, el atacante obtendrá una coincidencia. Si los glifos no coinciden, el atacante sabrá que se usó una fuente de reemplazo predeterminada porque no se instaló la fuente corporativa. Para obtener todos los detalles sobre este y otros ataques de huella digital del navegador, lee el informe de encuesta de Laperdix et al.

Aparte de las fuentes de la empresa, incluso la lista de fuentes instaladas puede identificar. La situación con este vector de ataque se ha vuelto tan grave que, hace poco, el equipo de WebKit decidió "incluir solo las fuentes web [en la lista de fuentes disponibles] que vienen con el sistema operativo, pero no las fuentes instaladas por los usuarios de forma local". (Y aquí estoy, con un artículo sobre cómo otorgar acceso a fuentes locales).

La API de Local Font Access

Es posible que el comienzo de este artículo te haya puesto en un estado de ánimo negativo. ¿En verdad no podemos tener cosas lindas? No traspases. Creemos que podemos y, tal vez, no todo es desesperado. Pero, primero, déjame responder una pregunta que podrías estar haciendo.

¿Por qué necesitamos la API de Local Font Access cuando hay fuentes web?

Las herramientas gráficas y de diseño de calidad profesional siempre han sido difíciles de publicar en la Web. Un problema es la imposibilidad de acceder a toda la variedad de fuentes optimizadas y construidas de manera profesional que los diseñadores instalaron de forma local. Las fuentes web permiten algunos casos de uso de publicación, pero no habilitan el acceso programático a las formas de glifos vectoriales ni a las tablas de fuentes que usan los rasterizadores para renderizar los contornos de los glifos. Tampoco hay forma de acceder a los datos binarios de una fuente web.

  • Las herramientas de diseño necesitan acceso a bytes de fuente para realizar su propia implementación de diseño de OpenType y permiten que las herramientas de diseño se conecten en niveles inferiores, para acciones como aplicar filtros vectoriales o transformaciones en las formas de glifos.
  • Es posible que los desarrolladores tengan pilas de fuentes heredadas para las aplicaciones que incorporan a la Web. Por lo general, para usar estas pilas, requieren acceso directo a los datos de fuentes, algo que las fuentes web no proporcionan.
  • Es posible que algunas fuentes no tengan licencia para publicarse en la Web. Por ejemplo, Linotype tiene una licencia para algunas fuentes que solo incluye el uso de escritorio.

La API de Local Font Access intenta resolver estos desafíos. Está compuesto por dos partes:

  • Una API de enumeración de fuentes, que permite a los usuarios otorgar acceso al conjunto completo de fuentes del sistema disponibles
  • De cada resultado de enumeración, la capacidad de solicitar acceso al contenedor de SFNT de bajo nivel (orientado a bytes) que incluye los datos completos de la fuente.

Navegadores compatibles

Navegadores compatibles

  • 103
  • 103
  • x
  • x

Origen

Cómo usar la API de Local Font Access

Detección de funciones

Para verificar si se admite la API de Local Font Access, usa el siguiente código:

if ('queryLocalFonts' in window) {
  // The Local Font Access API is supported
}

Cómo enumerar fuentes locales

Para obtener una lista de las fuentes instaladas de forma local, debes llamar a window.queryLocalFonts(). La primera vez, se activará una solicitud de permiso, que el usuario puede aprobar o rechazar. Si el usuario aprueba que se consulten las fuentes locales, el navegador mostrará un array con datos de fuentes sobre los que puedes repetir indefinidamente. Cada fuente se representa como un objeto FontData con las propiedades family (por ejemplo, "Comic Sans MS"), fullName (por ejemplo, "Comic Sans MS"), postscriptName (por ejemplo, "ComicSansMS") y style (por ejemplo, "Regular").

// Query for all available fonts and log metadata.
try {
  const availableFonts = await window.queryLocalFonts();
  for (const fontData of availableFonts) {
    console.log(fontData.postscriptName);
    console.log(fontData.fullName);
    console.log(fontData.family);
    console.log(fontData.style);
  }
} catch (err) {
  console.error(err.name, err.message);
}

Si solo te interesa un subconjunto de fuentes, también puedes filtrarlas en función de los nombres de PostScript. Para ello, agrega un parámetro postscriptNames.

const availableFonts = await window.queryLocalFonts({
  postscriptNames: ['Verdana', 'Verdana-Bold', 'Verdana-Italic'],
});

Cómo acceder a datos de SFNT

El acceso SFNT completo está disponible a través del método blob() del objeto FontData. SFNT es un formato de archivo de fuente que puede contener otras fuentes, como PostScript, TrueType, OpenType, Web Open Font Format (WOFF), entre otras.

try {
  const availableFonts = await window.queryLocalFonts({
    postscriptNames: ['ComicSansMS'],
  });
  for (const fontData of availableFonts) {
    // `blob()` returns a Blob containing valid and complete
    // SFNT-wrapped font data.
    const sfnt = await fontData.blob();
    // Slice out only the bytes we need: the first 4 bytes are the SFNT
    // version info.
    // Spec: https://docs.microsoft.com/en-us/typography/opentype/spec/otff#organization-of-an-opentype-font
    const sfntVersion = await sfnt.slice(0, 4).text();

    let outlineFormat = 'UNKNOWN';
    switch (sfntVersion) {
      case '\x00\x01\x00\x00':
      case 'true':
      case 'typ1':
        outlineFormat = 'truetype';
        break;
      case 'OTTO':
        outlineFormat = 'cff';
        break;
    }
    console.log('Outline format:', outlineFormat);
  }
} catch (err) {
  console.error(err.name, err.message);
}

Demostración

Puedes ver la API de Local Font Access en acción en la demostración que aparece a continuación. Asegúrate de consultar también el código fuente. En la demostración, se muestra un elemento personalizado llamado <font-select> que implementa un selector de fuentes local.

Consideraciones de privacidad

Al parecer, el permiso "local-fonts" proporciona una plataforma altamente personalizable. Sin embargo, los navegadores tienen la libertad de mostrar lo que quieran. Por ejemplo, los navegadores centrados en el anonimato pueden optar por proporcionar solo un conjunto de fuentes predeterminadas integradas en el navegador. Del mismo modo, no es necesario que los navegadores proporcionen los datos de la tabla exactamente como aparecen en el disco.

Siempre que sea posible, la API de Local Font Access está diseñada para exponer solo la información necesaria a fin de habilitar los casos de uso mencionados. Las APIs del sistema pueden producir una lista de fuentes instaladas, no en un orden aleatorio o ordenado, sino en el orden de instalación de las fuentes. Mostrar exactamente la lista de fuentes instaladas que proporciona esa API del sistema puede exponer datos adicionales que se pueden usar para la creación de huellas digitales. Los casos de uso que queremos habilitar no se ayudan si se retiene este orden. Como resultado, esta API requiere que los datos que se muestran se ordenen antes de mostrarse.

Seguridad y permisos

El equipo de Chrome diseñó e implementó la API de Local Font Access conforme a los principios básicos definidos en Cómo controlar el acceso a funciones potentes de la plataforma web, incluidos el control del usuario, la transparencia y la ergonomía.

Control de usuarios

El acceso a las fuentes de un usuario está bajo su control y no se permitirá, a menos que se otorgue el permiso "local-fonts", que aparece en el registro de permisos.

Transparencia

En la hoja de información del sitio, podrás ver si un sitio tiene acceso a las fuentes locales del usuario.

Persistencia de permisos

El permiso "local-fonts" persistirá entre las recargas de páginas. Se puede revocar mediante la hoja de información del sitio.

Comentarios

El equipo de Chrome quiere conocer tus experiencias con la API de Local Font Access.

Cuéntanos sobre el diseño de la API

¿Hay algo acerca de la API que no funciona como esperabas? ¿O faltan métodos o propiedades que necesitas para implementar tu idea? ¿Tienes alguna pregunta o comentario sobre el modelo de seguridad? Informa un problema de especificaciones en el repositorio de GitHub correspondiente o agrega tus ideas a un problema existente.

Informar un problema con la implementación

¿Encontraste un error en la implementación de Chrome? ¿O la implementación es diferente de la especificación? Informa un error en new.crbug.com. Asegúrate de incluir tantos detalles como puedas, además de instrucciones simples para reproducir el error, y, luego, ingresa Blink>Storage>FontAccess en el cuadro Componentes. Glitch funciona muy bien para compartir repros rápidos y fáciles.

Muestra compatibilidad con la API

¿Planeas usar la API de Local Font Access? Tu asistencia pública ayuda al equipo de Chrome a priorizar funciones y le muestra a otros proveedores de navegadores la importancia de brindar compatibilidad.

Envía un tweet a @ChromiumDev con el hashtag #LocalFontAccess y cuéntanos dónde y cómo lo usas.

Agradecimientos

Emil A. editó las especificaciones de la API de Local Font Access. Eklund, Alex Russell, Joshua Bell y Olivier Yiptong. Joe Medley, Dominik Röttsches y Olivier Yiptong revisaron este artículo. Hero image de Brett Jordan en Unsplash.