Usa la tipografía avanzada con fuentes locales

Aprende 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 en 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 (a saber, Windows, macOS, las distribuciones más comunes de Linux, iOS y Android). A principios de la década de 2000, Microsoft incluso lideró una iniciativa llamada Fuentes TrueType principales para la Web que proporcionaba estas fuentes para descargarlas de forma 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 incluía sitios configurados en Comic Sans MS. Esta es una pila de fuentes web seguras clásica (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

Ya no son relevantes las fuentes web seguras. Hoy en día, tenemos fuentes web, algunas de las cuales son incluso fuentes variables que podemos ajustar aún más cambiando los valores de los diversos ejes expuestos. Para usar fuentes web, declara un bloque @font-face al comienzo del CSS, que especifique los archivos de fuente que se descargarán:

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

Después de esto, puedes usar la fuente web personalizada. Para ello, especifica font-family, como de costumbre:

body {
  font-family: 'FlamboyantSansSerif';
}

Fuentes locales como vector de huella dactilar

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 forma local. Si el usuario tiene FlamboyantSansSerif instalado en su sistema operativo, se usará la copia local en lugar de descargarse:

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

Este enfoque proporciona un buen mecanismo de resguardo que potencialmente ahorra ancho de banda. Lamentablemente, en Internet, no podemos tener cosas agradables. El problema con la función local() es que se puede usar de forma inadecuada 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 Font Book de macOS 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 el conjunto de texto en estas fuentes en un lienzo y medir los glifos. Si los glifos coinciden con la forma conocida de la fuente corporativa, el atacante recibe un hit. 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 creación de huellas digitales de navegadores, lee el documento de la encuesta de Laperdix et al.

Se pueden separar fuentes empresariales, incluso solo la lista de fuentes instaladas. La situación con este vector de ataque se volvió tan mala 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 por el usuario de forma local". (Y aquí estoy, con un artículo sobre cómo otorgar acceso a las fuentes locales).

La API de Local Font Access

Es posible que el principio de este artículo te haya puesto de mal humor. ¿Realmente no podemos tener cosas bonitas? No te preocupes. Creemos que sí, y tal vez no todo está perdido. Pero primero, permíteme responder una pregunta que podrías estarte haciendo.

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

Históricamente, las herramientas de diseño y gráficos de calidad profesional han sido difíciles de entregar en la Web. Uno de los obstáculos fue la incapacidad de acceder y usar la variedad completa de fuentes construidas y con indicaciones profesionales que los diseñadores instalaron de forma local. Las fuentes web habilitan algunos casos de uso de publicación, pero no habilitan el acceso programático a las formas de glifos vectoriales y 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 los bytes de la fuente para realizar su propia implementación de diseño OpenType y permitir que las herramientas de diseño se conecten en niveles inferiores, para realizar acciones como aplicar filtros vectoriales o realizar transformaciones en las formas de los glifos.
  • Es posible que los desarrolladores tengan pilas de fuentes heredadas para sus aplicaciones que llevan a la Web. Para usar estas pilas, por lo general, se requiere acceso directo a los datos de fuente, algo que las fuentes web no proporcionan.
  • Es posible que algunas fuentes no tengan licencia para su publicación en la Web. Por ejemplo, Linotype tiene una licencia para algunas fuentes que solo incluye el uso en computadoras de escritorio.

La API de Local Font Access es un intento por 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

  • Chrome: 103.
  • Edge: 103.
  • Firefox: No es compatible.
  • Safari: no es compatible.

Origen

Cómo usar la API de Local Font Access

Detección de atributos

Para verificar si la API de Local Font Access es compatible, usa lo siguiente:

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

Enumeración de fuentes locales

Para obtener una lista de las fuentes instaladas de forma local, debes llamar a window.queryLocalFonts(). La primera vez, se activará un mensaje 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 que puedes aplicar en bucle. 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 filtrar según 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 los datos de SFNT

El acceso completo a SFNT 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 siguiente demo. Asegúrate de revisar 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

El permiso "local-fonts" parece proporcionar una superficie que admite huellas digitales. Sin embargo, los navegadores pueden mostrar lo que quieran. Por ejemplo, los navegadores enfocados en el anonimato pueden optar por solo proporcionar 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 no en un orden aleatorio ni ordenado, sino en el orden de instalación de la fuente. Devolver exactamente la lista de fuentes instaladas que proporciona una API de este tipo puede exponer datos adicionales que se pueden usar para la creación de huellas digitales, y retener este orden no ayuda a los casos de uso que queremos habilitar. 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 con los principios básicos 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

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

Persistencia de permisos

El permiso "local-fonts" se conservará entre las cargas de 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

¿Algo en la API 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 tus comentarios a un problema existente.

Denuncia 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 sea posible, instrucciones simples para reproducirlo y, luego, ingresa Blink>Storage>FontAccess en el cuadro Componentes. Glitch funciona muy bien para compartir repros rápidos y fáciles.

Cómo mostrar compatibilidad con la API

¿Planeas utilizar la API de Local Font Access? Tu apoyo público ayuda al equipo de Chrome a priorizar las funciones y les 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

La especificación de la API de Local Font Access la editó Emil A. Eklund, Alex Russell, Joshua Bell y Olivier Yiptong. Joe Medley, Dominik Röttsches y Olivier Yiptong revisaron este artículo. Imagen hero de Brett Jordan en Unsplash.