Usa la tipografía avanzada con fuentes locales

Obtén información sobre cómo la API de Local Font Access te permite acceder a las fuentes instaladas localmente del usuario y obtener detalles de bajo nivel sobre ellas.

Fuentes seguras para la Web

Si has trabajado en el desarrollo web durante el tiempo suficiente, tal vez 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 más comunes, Android y iOS). A principios de la década de 2000, Microsoft incluso encabezó una iniciativa llamada Fuentes básicas 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 las páginas exactamente como las diseñó el diseñador del sitio". Sí, esto incluye los sitios configurados en Comic Sans MS. A continuación, se muestra cómo podría verse una pila de fuentes web seguras clásicas (con la fuente sans-serif como último recurso):

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

Fuentes web

Los días en que las fuentes seguras para la Web eran realmente importantes quedaron atrás. Actualmente, tenemos fuentes web, algunas de las cuales son incluso fuentes variables que podemos ajustar aún más cambiando los valores de los distintos ejes expuestos. Puedes usar fuentes web declarando un bloque @font-face al comienzo del CSS, que especifica los archivos de fuentes que se descargarán:

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

Después de esto, puedes usar la fuente web personalizada especificando font-family, 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 de @font-face, además de la función url(), también acepta una función local(). Esto permite que las fuentes personalizadas se carguen de forma local (¡sorpresa!). Si el usuario tiene FlamboyantSansSerif instalado en su sistema operativo, se usará la copia local en lugar de descargarlo:

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

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

La app Libro de fuentes de macOS que 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 una persona probando la existencia de una gran cantidad de fuentes corporativas conocidas, como Google Sans. El atacante intentaría renderizar el conjunto de 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 tendrá un acierto. Si los glifos no coinciden, el atacante sabe que se usó una fuente de reemplazo predeterminada, ya que no se instaló la fuente corporativa. Para obtener todos los detalles sobre este y otros ataques de huellas digitales del navegador, consulta el documento de investigación de Laperdix et al.

Además de las fuentes de la empresa, incluso la lista de fuentes instaladas puede ser identificativa. La situación con este vector de ataque se ha vuelto tan grave que, recientemente, el equipo de WebKit decidió "incluir solo [en la lista de fuentes disponibles] las fuentes web y las fuentes que vienen con el sistema operativo, pero no las fuentes instaladas localmente por el usuario". (Y aquí estoy, con un artículo sobre cómo otorgar acceso a fuentes locales).

La API de Local Fonts Access

El comienzo de este artículo puede haberte puesto de mal humor. ¿De verdad no podemos tener cosas lindas? No te preocupes. Creemos que podemos hacerlo y que no todo está perdido. Pero primero, permítame responder una pregunta que tal vez se esté haciendo.

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

Históricamente, las herramientas de diseño y gráficos de calidad profesional han sido difíciles de ofrecer en la Web. Un obstáculo fue la incapacidad de acceder y usar la variedad completa de fuentes creadas y sugeridas de forma profesional que los diseñadores tienen instaladas de forma local. Las fuentes web habilitan algunos casos de uso de publicación, pero no permiten el acceso programático a las formas de los 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 acceder a los bytes de las fuentes para implementar su propio diseño de OpenType y permitir que las herramientas de diseño se conecten en niveles inferiores para acciones como realizar filtros o transformaciones vectoriales en las formas de los glifos.
  • Es posible que los desarrolladores tengan pilas de fuentes heredadas para sus aplicaciones que están llevando a la Web. Para usar estas pilas, generalmente se requiere acceso directo a los datos de la fuente, algo que las fuentes web no proporcionan.
  • Es posible que algunas fuentes no tengan licencia para su distribución en la Web. Por ejemplo, Linotype tiene una licencia para algunas fuentes que solo incluye el uso en computadoras.

La API de Local Font Access es un intento de 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.
  • Desde cada resultado de enumeración, se puede solicitar acceso al contenedor SFNT de bajo nivel (orientado a bytes) que incluye los datos completos de la fuente.

Navegadores compatibles

Browser Support

  • Chrome: 103.
  • Edge: 103.
  • Firefox: not supported.
  • Safari: not supported.

Source

Cómo usar la API de Local Font Access

Detección de características

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 sus fuentes locales, el navegador devolverá un array con datos de fuentes sobre los que puedes iterar. 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 según los nombres de PostScript agregando un parámetro postscriptNames.

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

Cómo acceder a los datos de SFNT

Se puede acceder a SFNT completo 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) y 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 Fonts Access en acción en la demostración. No olvides consultar 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

El permiso "local-fonts" parece proporcionar una superficie con una huella digital muy identificable. Sin embargo, los navegadores pueden devolver 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, los navegadores no están obligados a proporcionar 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 para habilitar los casos de uso mencionados. Las APIs del sistema pueden producir una lista de fuentes instaladas que no estén en un orden aleatorio o ordenado, sino en el orden de instalación de las fuentes. Devolver exactamente la lista de fuentes instaladas que proporciona una API del sistema de este tipo puede exponer datos adicionales que se pueden usar para la huella digital, y los casos de uso que queremos habilitar no se ven favorecidos por conservar este orden. Como resultado, esta API requiere que los datos devueltos se ordenen antes de devolverse.

Seguridad y permisos

El equipo de Chrome diseñó e implementó la API de Local Font Access con los principios fundamentales definidos en Controlling Access to Powerful Web Platform Features, incluidos el control del usuario, la transparencia y la ergonomía.

Control de usuarios

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

Transparencia

Si se le otorgó acceso a un sitio a las fuentes locales del usuario, se mostrará en la ficha de información del sitio.

Persistencia de permisos

El permiso "local-fonts" se conservará entre las recargas de la página. Se puede revocar a través de la hoja de información del sitio.

Comentarios

El equipo de Chrome quiere conocer tu experiencia con la API de Local Font Access.

Cuéntanos sobre el diseño de la API

¿Hay algo sobre 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 especificación en el repositorio de GitHub correspondiente o agrega tu opinión a un problema existente.

Informa 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? Presenta un error en new.crbug.com. Asegúrate de incluir tantos detalles como sea posible, instrucciones sencillas para reproducir el error y, luego, ingresa Blink>Storage>FontAccess en el cuadro Components.

Cómo mostrar compatibilidad con la API

¿Planeas usar la API de Local Font Access? Tu apoyo público ayuda al equipo de Chrome a priorizar funciones y muestra a otros proveedores de navegadores lo importante que es admitirlas.

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

Agradecimientos

Emil A. editó la especificación 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. Imagen de héroe de Brett Jordan en Unsplash.