Используйте расширенную типографику с местными шрифтами.

Узнайте, как API доступа к локальным шрифтам позволяет вам получить доступ к локально установленным шрифтам пользователя и получить низкоуровневые сведения о них.

Веб-безопасные шрифты

Если вы занимаетесь веб-разработкой достаточно долго, вы, возможно, помните так называемые веб-безопасные шрифты . Известно, что эти шрифты доступны практически во всех экземплярах наиболее используемых операционных систем (а именно Windows, macOS, наиболее распространенных дистрибутивах Linux, Android и iOS). В начале 2000-х годов Microsoft даже возглавила инициативу под названием TrueType core fonts for the Web , которая предоставляла эти шрифты для бесплатной загрузки с целью, чтобы «всякий раз, когда вы посещаете веб-сайт, на котором они указаны, вы увидите страницы именно такими, какими их задумал дизайнер сайта» . Да, это включало сайты, набранные шрифтом Comic Sans MS . Вот классический стек веб-безопасных шрифтов (с окончательным вариантом любого шрифта sans-serif ) мог бы выглядеть так:

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

Веб-шрифты

Времена, когда веб-безопасные шрифты действительно имели значение, давно прошли. Сегодня у нас есть веб-шрифты , некоторые из которых даже являются переменными шрифтами , которые мы можем дополнительно настраивать, изменяя значения для различных открытых осей. Вы можете использовать веб-шрифты, объявив блок @font-face в начале CSS, который указывает файл(ы) шрифта для загрузки:

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

После этого вы сможете использовать пользовательский веб-шрифт, указав font-family , как обычно:

body {
  font-family: 'FlamboyantSansSerif';
}

Локальные шрифты как вектор отпечатков пальцев

Большинство веб-шрифтов берутся из, ну, веба. Однако интересным фактом является то, что свойство src в объявлении @font-face , помимо функции url() , также принимает функцию local() . Это позволяет загружать пользовательские шрифты (сюрприз!) локально. Если у пользователя установлен FlamboyantSansSerif в операционной системе, будет использоваться локальная копия, а не загружаться:

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

Этот подход обеспечивает хороший резервный механизм, который потенциально экономит пропускную способность. В Интернете, к сожалению, мы не можем иметь хороших вещей. Проблема с функцией local() в том, что ее можно использовать для снятия отпечатков браузера. Оказывается, список шрифтов, установленных пользователем, может быть довольно идентифицирующим. Многие компании имеют собственные корпоративные шрифты, которые устанавливаются на ноутбуках сотрудников. Например, у Google есть корпоративный шрифт под названием Google Sans .

Приложение Font Book для macOS, демонстрирующее предварительный просмотр шрифта Google Sans.
Шрифт Google Sans, установленный на ноутбуке сотрудника Google.

Злоумышленник может попытаться определить, в какой компании работает человек, проверив наличие большого количества известных корпоративных шрифтов, таких как Google Sans . Злоумышленник попытается отобразить текст, набранный этими шрифтами, на холсте и измерить глифы. Если глифы соответствуют известной форме корпоративного шрифта, злоумышленник попал в цель. Если глифы не совпадают, злоумышленник знает, что был использован шрифт замены по умолчанию, поскольку корпоративный шрифт не был установлен. Для получения полной информации об этой и других атаках по отпечаткам браузера прочитайте обзорную статью Лапердикса и др.

Помимо шрифтов компании, даже список установленных шрифтов может быть идентифицирующим. Ситуация с этим вектором атаки стала настолько плохой, что недавно команда WebKit решила «включить [в список доступных шрифтов] только веб-шрифты и шрифты, поставляемые с операционной системой, но не шрифты, установленные локально пользователем» . (И вот я здесь, со статьей о предоставлении доступа к локальным шрифтам.)

API доступа к локальным шрифтам

Начало этой статьи, возможно, настроило вас негативно. Неужели у нас не может быть хороших вещей? Не волнуйтесь. Мы думаем, что можем, и, возможно, не все безнадежно . Но сначала позвольте мне ответить на вопрос, который вы, возможно, задаете себе.

Зачем нам нужен API доступа к локальным шрифтам, если есть веб-шрифты?

Профессиональные инструменты дизайна и графики исторически было трудно реализовать в Интернете. Одним из камней преткновения была невозможность доступа и использования всего многообразия профессионально созданных и хинтованных шрифтов, которые дизайнеры установили локально. Веб-шрифты позволяют использовать некоторые издательские сценарии, но не обеспечивают программный доступ к векторным формам глифов и таблицам шрифтов, используемым растеризаторами для визуализации контуров глифов. Также нет способа получить доступ к двоичным данным веб-шрифта.

  • Инструментам дизайна необходим доступ к байтам шрифта для реализации собственной компоновки OpenType и обеспечения возможности подключения инструментов дизайна на более низких уровнях для таких действий, как выполнение векторных фильтров или преобразований форм глифов.
  • Разработчики могут иметь устаревшие стеки шрифтов для своих приложений, которые они выносят в Интернет. Чтобы использовать эти стеки, им обычно требуется прямой доступ к данным шрифтов, чего веб-шрифты не предоставляют.
  • Некоторые шрифты могут не быть лицензированы для доставки через Интернет. Например, у Linotype есть лицензия на некоторые шрифты, которая включает только использование на рабочем столе .

Local Font Access API — это попытка решить эти проблемы. Он состоит из двух частей:

  • API перечисления шрифтов , который позволяет пользователям предоставлять доступ ко всему набору доступных системных шрифтов.
  • Из каждого результата перечисления можно запросить низкоуровневый (байтово-ориентированный) доступ к контейнеру SFNT , который включает полные данные шрифта.

Поддержка браузера

Browser Support

  • Хром: 103.
  • Край: 103.
  • Firefox: не поддерживается.
  • Safari: не поддерживается.

Source

Как использовать API доступа к локальным шрифтам

Обнаружение особенностей

Чтобы проверить, поддерживается ли API доступа к локальным шрифтам, используйте:

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

Перечисление локальных шрифтов

Чтобы получить список локально установленных шрифтов, вам нужно вызвать window.queryLocalFonts() . В первый раз это вызовет запрос на разрешение, который пользователь может одобрить или отклонить. Если пользователь одобрит запрос своих локальных шрифтов, браузер вернет массив с данными шрифтов, по которым можно выполнить цикл. Каждый шрифт представлен как объект FontData со свойствами family (например, "Comic Sans MS" ), fullName (например, "Comic Sans MS" ), postscriptName (например, "ComicSansMS" ) и style (например, "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);
}

Если вас интересует только подмножество шрифтов, вы также можете отфильтровать их по именам PostScript, добавив параметр postscriptNames .

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

Доступ к данным SFNT

Полный доступ к SFNT возможен через метод blob() объекта FontData . SFNT — это формат файла шрифта, который может содержать другие шрифты, такие как PostScript, TrueType, OpenType, шрифты Web Open Font Format (WOFF) и другие.

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);
}

Демо

Вы можете увидеть API Local Font Access в действии в демо ниже. Обязательно ознакомьтесь с исходным кодом . Демо демонстрирует пользовательский элемент под названием <font-select> , который реализует локальный выбор шрифтов.

Соображения конфиденциальности

Разрешение "local-fonts" , по-видимому, обеспечивает поверхность с высокой степенью отпечатков пальцев. Однако браузеры вольны возвращать все, что им нравится. Например, ориентированные на анонимность браузеры могут выбрать предоставление только набора шрифтов по умолчанию, встроенных в браузер. Аналогично, браузеры не обязаны предоставлять табличные данные точно так же, как они отображаются на диске.

Везде, где это возможно, API локального доступа к шрифтам предназначен для предоставления только той информации, которая необходима для включения упомянутых вариантов использования. Системные API могут создавать список установленных шрифтов не в случайном или отсортированном порядке, а в порядке установки шрифтов. Возврат точного списка установленных шрифтов, предоставленного таким системным API, может предоставить дополнительные данные, которые могут использоваться для снятия отпечатков, и варианты использования, которые мы хотим включить, не поддерживаются сохранением этого порядка. В результате этот API требует, чтобы возвращаемые данные были отсортированы перед возвратом.

Безопасность и разрешения

Команда Chrome разработала и реализовала API локального доступа к шрифтам, используя основные принципы, определенные в документе «Управление доступом к мощным функциям веб-платформы» , включая пользовательский контроль, прозрачность и эргономичность.

Пользовательский контроль

Доступ к шрифтам пользователя находится под его полным контролем и не будет разрешен, если ему не предоставлено разрешение "local-fonts" , указанное в реестре разрешений .

Прозрачность

Информация о том, предоставлен ли сайту доступ к локальным шрифтам пользователя, будет отображаться в информационном листе сайта .

Сохранение разрешения

Разрешение "local-fonts" будет сохраняться между перезагрузками страницы. Его можно отозвать через информационный лист сайта .

Обратная связь

Команда Chrome хочет узнать о вашем опыте работы с API локального доступа к шрифтам.

Расскажите нам о дизайне API

Есть ли что-то в API, что работает не так, как вы ожидали? Или отсутствуют методы или свойства, которые вам нужны для реализации вашей идеи? У вас есть вопрос или комментарий по модели безопасности? Отправьте запрос спецификации в соответствующий репозиторий GitHub или добавьте свои мысли в существующий запрос.

Сообщить о проблеме с реализацией

Вы нашли ошибку в реализации Chrome? Или реализация отличается от спецификации? Сообщите об ошибке на new.crbug.com . Обязательно включите как можно больше подробностей, простые инструкции по воспроизведению и введите Blink>Storage>FontAccess в поле Components .

Показать поддержку API

Планируете ли вы использовать API Local Font Access? Ваша публичная поддержка помогает команде Chrome расставлять приоритеты функций и показывает другим поставщикам браузеров, насколько важно их поддерживать.

Отправьте твит @ChromiumDev , используя хэштег #LocalFontAccess , и расскажите нам, где и как вы его используете.

Благодарности

Спецификация API Local Font Access была отредактирована Эмилем А. Эклундом , Алексом Расселом , Джошуа Беллом и Оливье Йиптонгом . Эта статья была прорецензирована Джо Медли , Домиником Рётчесом и Оливье Йиптонгом . Изображение героя от Бретта Джордана на Unsplash .