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