Узнайте, как 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 .

Злоумышленник может попытаться определить, в какой компании работает человек, проверив наличие большого количества известных корпоративных шрифтов, таких как Google Sans . Злоумышленник попытается отобразить текст, набранный этими шрифтами, на холсте и измерить глифы. Если глифы соответствуют известной форме корпоративного шрифта, злоумышленник попал в цель. Если глифы не совпадают, злоумышленник знает, что был использован шрифт замены по умолчанию, поскольку корпоративный шрифт не был установлен. Для получения полной информации об этой и других атаках по отпечаткам браузера прочитайте обзорную статью Лапердикса и др.
Помимо шрифтов компании, даже список установленных шрифтов может быть идентифицирующим. Ситуация с этим вектором атаки стала настолько плохой, что недавно команда WebKit решила «включить [в список доступных шрифтов] только веб-шрифты и шрифты, поставляемые с операционной системой, но не шрифты, установленные локально пользователем» . (И вот я здесь, со статьей о предоставлении доступа к локальным шрифтам.)
API доступа к локальным шрифтам
Начало этой статьи, возможно, настроило вас негативно. Неужели у нас не может быть хороших вещей? Не волнуйтесь. Мы думаем, что можем, и, возможно, не все безнадежно . Но сначала позвольте мне ответить на вопрос, который вы, возможно, задаете себе.
Зачем нам нужен API доступа к локальным шрифтам, если есть веб-шрифты?
Профессиональные инструменты дизайна и графики исторически было трудно реализовать в Интернете. Одним из камней преткновения была невозможность доступа и использования всего многообразия профессионально созданных и хинтованных шрифтов, которые дизайнеры установили локально. Веб-шрифты позволяют использовать некоторые издательские сценарии, но не обеспечивают программный доступ к векторным формам глифов и таблицам шрифтов, используемым растеризаторами для визуализации контуров глифов. Также нет способа получить доступ к двоичным данным веб-шрифта.
- Инструментам дизайна необходим доступ к байтам шрифта для реализации собственной компоновки OpenType и обеспечения возможности подключения инструментов дизайна на более низких уровнях для таких действий, как выполнение векторных фильтров или преобразований форм глифов.
- Разработчики могут иметь устаревшие стеки шрифтов для своих приложений, которые они выносят в Интернет. Чтобы использовать эти стеки, им обычно требуется прямой доступ к данным шрифтов, чего веб-шрифты не предоставляют.
- Некоторые шрифты могут не быть лицензированы для доставки через Интернет. Например, у Linotype есть лицензия на некоторые шрифты, которая включает только использование на рабочем столе .
Local Font Access 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 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
, и расскажите нам, где и как вы его используете.
Полезные ссылки
- Объяснитель
- Проект спецификации
- Ошибка Chromium при перечислении шрифтов
- Ошибка Chromium для доступа к таблице шрифтов
- ChromeЗапись статуса
- Репозиторий GitHub
- Обзор ТЭГа
- Позиция стандартов Mozilla
Благодарности
Спецификация API Local Font Access была отредактирована Эмилем А. Эклундом , Алексом Расселом , Джошуа Беллом и Оливье Йиптонгом . Эта статья была прорецензирована Джо Медли , Домиником Рётчесом и Оливье Йиптонгом . Изображение героя от Бретта Джордана на Unsplash .