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