Узнайте, как API доступа к локальным шрифтам позволяет получить доступ к локально установленным шрифтам пользователя и получить подробную информацию о них.
Опубликовано: 24 августа 2020 г.
Веб-безопасные шрифты
Если вы достаточно долго занимаетесь веб-разработкой, вы, возможно, помните так называемые веб-безопасные шрифты . Известно, что эти шрифты доступны практически во всех версиях наиболее распространенных операционных систем (а именно, Windows, macOS, наиболее распространенные дистрибутивы Linux, Android и iOS). В начале 2000-х годов Microsoft даже возглавила инициативу под названием «Основные шрифты TrueType для веб-разработки », которая предоставляла эти шрифты для бесплатной загрузки с целью, чтобы «при посещении веб-сайта, на котором они указаны, вы видели страницы именно такими, какими их задумал дизайнер сайта» . Да, это включало сайты, использующие 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 .

Злоумышленник может попытаться определить, в какой компании работает человек, проверив наличие большого количества известных корпоративных шрифтов, таких как Google Sans . Злоумышленник попытается отобразить текст, набранный этими шрифтами, на холсте и измерить глифы. Если глифы совпадают с известной формой корпоративного шрифта, злоумышленник получает результат. Если глифы не совпадают, злоумышленник знает, что был использован шрифт-заменитель по умолчанию, поскольку корпоративный шрифт не был установлен. Для получения полной информации об этой и других атаках по идентификации браузера ознакомьтесь с обзорной статьей Лапердикса и др.
Помимо корпоративных шрифтов, даже просто список установленных шрифтов может служить индикатором. Ситуация с этим вектором атаки настолько ухудшилась, что недавно команда WebKit решила «включать в список доступных шрифтов только веб-шрифты и шрифты, поставляемые с операционной системой, но не локально установленные пользователем шрифты» . (И вот я пишу статью о предоставлении доступа к локальным шрифтам.)
API доступа к локальным шрифтам
Начало этой статьи, возможно, настроило вас на негативный лад. Неужели у нас действительно не может быть ничего хорошего? Не волнуйтесь. Мы думаем, что может, и, возможно, не всё так безнадёжно . Но сначала позвольте мне ответить на вопрос, который, возможно, задаётся у вас.
Зачем нам нужен API доступа к локальным шрифтам, если существуют веб-шрифты?
Создание профессиональных инструментов для дизайна и графики в интернете исторически представляло собой сложную задачу. Одной из проблем была невозможность доступа и использования всего многообразия профессионально разработанных и хинтированных шрифтов, которые дизайнеры устанавливают локально. Веб-шрифты позволяют использовать их в некоторых издательских целях, но не обеспечивают программного доступа к векторным формам глифов и таблицам шрифтов, используемым растеризаторами для отображения контуров глифов. Аналогично, нет возможности получить доступ к двоичным данным веб-шрифта.
- Инструментам дизайна необходим доступ к байтам шрифта для реализации собственной компоновки OpenType и для обеспечения возможности подключения к более низким уровням, например, для выполнения векторных фильтров или преобразований форм глифов.
- Разработчики могут использовать устаревшие наборы шрифтов для своих приложений, которые они переносят в веб-среду. Для использования этих наборов им обычно требуется прямой доступ к данным шрифтов, чего веб-шрифты не предоставляют.
- Для некоторых шрифтов лицензия на распространение через Интернет может быть недоступна. Например, у Linotype есть лицензия на некоторые шрифты, которая распространяется только на использование на настольных компьютерах .
API доступа к локальным шрифтам — это попытка решить эти проблемы. Он состоит из двух частей:
- API для перечисления шрифтов , позволяющий пользователям предоставлять доступ ко всему набору доступных системных шрифтов.
- Из каждого результата перечисления выводится возможность запросить низкоуровневый (байтовый) доступ к контейнеру SFNT , включающий полные данные шрифта.
Поддержка браузеров
Как использовать 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 доступа к локальным шрифтам в действии в демонстрационном примере . Обязательно также ознакомьтесь с исходным кодом . В демонстрационном примере показан пользовательский элемент <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 доступа к локальным шрифтам? Ваша публичная поддержка помогает команде Chrome расставлять приоритеты в разработке новых функций и показывает другим производителям браузеров, насколько важно поддерживать их.
Отправьте твит @ChromiumDev с хэштегом #LocalFontAccess и расскажите, где и как вы его используете.
Полезные ссылки
- Пояснительная записка
- Проект проекта
- Ошибка Chromium в перечислении шрифтов.
- Ошибка в Chromium, связанная с доступом к таблице шрифтов.
- запись ChromeStatus
- Репозиторий GitHub
- Обзор TAG
- Позиция Mozilla в отношении стандартов
Благодарности
Спецификация API доступа к локальным шрифтам была отредактирована Эмилем А. Эклундом , Алексом Расселом , Джошуа Беллом и Оливье Йиптонгом . Данная статья была рецензирована Джо Медли , Домиником Рёттшесом и Оливье Йиптонгом .