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