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 llevas el tiempo desarrollando 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 más comunes, Android y iOS). 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í, se incluyeron sitios configurados en Comic Sans MS. A continuación, se muestra una pila clásica de fuentes seguras para la Web (con el resguardo definitivo de cualquier fuente sans-serif) que pueda 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 de 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. 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 instalado FlamboyantSansSerif en su sistema operativo, se usará 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 podría ahorrar 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 una persona probando la existencia de una gran cantidad de fuentes corporativas conocidas, como Google Sans. El atacante intentaría renderizar el texto configurado en estas fuentes en un lienzo y medir los glifos. Si los glifos coinciden con la forma conocida de la fuente corporativa, el atacante tiene un hit. Si los glifos no coinciden, el atacante sabrá que se utilizó una fuente de reemplazo predeterminada porque la fuente corporativa no estaba instalada. Para obtener más información sobre este y otros ataques de huella digital del navegador, lee el documento de encuesta 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 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. ¿Acaso no podemos tener cosas lindas? 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, ha sido difícil ofrecer herramientas gráficas y de diseño de calidad profesional en la Web. Un obstáculo fue la imposibilidad de acceder y utilizar toda la variedad de fuentes creadas profesionalmente y con sugerencias 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 bytes de fuente para realizar su propia implementación de diseño de OpenType y permiten que estas herramientas se conecten en los niveles inferiores para acciones como realizar filtros vectoriales o transformaciones en las formas del glifo.
  • 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 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 se admite.

Origen

Cómo usar la API de Local Font Access

Detección de funciones

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'],
});

Acceso a 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) 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 Font Access en acción en la siguiente demo. 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

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

Si se le otorgó acceso a las fuentes locales del usuario a un sitio, esta información se podrá ver en la hoja de información del sitio.

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 tus experiencias 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 es excelente para compartir reproducciones rápidas y fáciles.

Cómo mostrar compatibilidad con la API

¿Piensas usar 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

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.