Używanie zaawansowanej typografii z czcionkami lokalnymi

Dowiedz się, jak interfejs Local Font Access API umożliwia dostęp do czcionek zainstalowanych lokalnie na urządzeniu użytkownika i uzyskiwanie szczegółowych informacji o nich.

Bezpieczne czcionki internetowe

Jeśli od dłuższego czasu zajmujesz się tworzeniem stron internetowych, być może pamiętasz tak zwane czcionki bezpieczne dla internetu. Te czcionki są dostępne w prawie wszystkich instancjach najczęściej używanych systemów operacyjnych (czyli Windows, macOS, najpopularniejszych dystrybucji Linuxa, Androida i iOS). Na początku XXI wieku firma Microsoft zainicjowała nawet inicjatywę o nazwie Podstawowe czcionki TrueType dla internetu, która udostępniała te czcionki do bezpłatnego pobrania. Jej celem było „zapewnienie, że za każdym razem, gdy odwiedzisz witrynę, która je określa, zobaczysz strony dokładnie tak, jak zamierzał projektant witryny”. Tak, dotyczy to również witryn, w których użyto czcionki Comic Sans MS. Oto klasyczny zestaw czcionek bezpiecznych dla internetu (z ostateczną czcionką zastępczą sans-serif):

body {
  font-family: Helvetica, Arial, sans-serif;
}

Czcionki internetowe

Czasy, w których bezpieczne czcionki internetowe miały duże znaczenie, dawno minęły. Obecnie mamy czcionki internetowe, z których niektóre to nawet czcionki zmienne, które możemy dodatkowo dostosowywać, zmieniając wartości różnych osi. Aby używać czcionek internetowych, na początku pliku CSS zadeklaruj blok @font-face, który określa pliki czcionek do pobrania:

@font-face {
  font-family: 'FlamboyantSansSerif';
  src: url('flamboyant.woff2');
}

Następnie możesz użyć niestandardowej czcionki internetowej, podając element font-family w zwykły sposób:

body {
  font-family: 'FlamboyantSansSerif';
}

Czcionki lokalne jako wektor odcisków cyfrowych

Większość czcionek internetowych pochodzi z internetu. Ciekawostką jest jednak to, że właściwość src w deklaracji @font-face oprócz funkcji url() akceptuje też funkcję local(). Dzięki temu czcionki niestandardowe mogą być wczytywane lokalnie. Jeśli użytkownik ma zainstalowaną czcionkę FlamboyantSansSerif w systemie operacyjnym, zostanie użyta lokalna kopia, a nie pobrana czcionka:

@font-face {
  font-family: 'FlamboyantSansSerif';
  src: local('FlamboyantSansSerif'), url('flamboyant.woff2');
}

To podejście zapewnia dobry mechanizm rezerwowy, który może zaoszczędzić przepustowość. W internecie niestety nie możemy mieć fajnych rzeczy. Problem z funkcją local() polega na tym, że można jej nadużywać do tworzenia odcisków przeglądarki. Okazuje się, że lista zainstalowanych przez użytkownika czcionek może być dość identyfikująca. Wiele firm ma własne czcionki firmowe, które są zainstalowane na laptopach pracowników. Na przykład Google ma czcionkę firmową o nazwie Google Sans.

Aplikacja Font Book w systemie macOS wyświetlająca podgląd czcionki Google Sans.
Czcionka Google Sans zainstalowana na laptopie pracownika Google.

Osoba atakująca może próbować ustalić, w jakiej firmie pracuje dana osoba, sprawdzając, czy istnieje duża liczba znanych czcionek firmowych, takich jak Google Sans. Osoba przeprowadzająca atak spróbuje wyrenderować tekst ustawiony w tych czcionkach na kanwie i zmierzyć glify. Jeśli glify pasują do znanego kształtu czcionki firmowej, atakujący osiągnął swój cel. Jeśli glify nie pasują, atakujący wie, że użyto domyślnej czcionki zastępczej, ponieważ czcionka firmowa nie była zainstalowana. Szczegółowe informacje o tym i innych atakach wykorzystujących odcisk cyfrowy przeglądarki znajdziesz w artykule Laperdixa i innych.

Oprócz czcionek firmowych nawet sama lista zainstalowanych czcionek może być identyfikująca. Sytuacja związana z tym wektorem ataku stała się tak poważna, że zespół WebKit zdecydował, że „w liście dostępnych czcionek będą uwzględniane tylko czcionki internetowe i czcionki dostarczane z systemem operacyjnym, a nie czcionki zainstalowane lokalnie przez użytkownika”. (A ja piszę artykuł o przyznawaniu dostępu do czcionek lokalnych).

Interfejs Local Font Access API

Początek tego artykułu mógł Cię wprawić w zły nastrój. Czy naprawdę nie możemy mieć ładnych rzeczy? Nie martw się. Uważamy, że tak, i może nie wszystko stracone. Najpierw jednak odpowiem na pytanie, które być może sobie zadajesz.

Dlaczego potrzebujemy interfejsu Local Font Access API, skoro istnieją czcionki internetowe?

Narzędzia do projektowania i tworzenia grafiki o profesjonalnej jakości były dotychczas trudne do udostępnienia w internecie. Jedną z przeszkód była niemożność uzyskania dostępu do pełnej gamy profesjonalnie zaprojektowanych i wskazanych czcionek, które projektanci mają zainstalowane lokalnie, oraz korzystania z nich. Czcionki internetowe umożliwiają niektóre przypadki użycia w publikowaniu, ale nie zapewniają programowego dostępu do kształtów glifów wektorowych i tabel czcionek używanych przez rasteryzatory do renderowania konturów glifów. Nie ma też możliwości uzyskania dostępu do danych binarnych czcionki internetowej.

  • Narzędzia do projektowania potrzebują dostępu do bajtów czcionki, aby samodzielnie implementować układ OpenType i umożliwiać narzędziom do projektowania podłączanie się na niższych poziomach w przypadku działań takich jak stosowanie filtrów wektorowych lub przekształceń do kształtów glifów.
  • Deweloperzy mogą mieć starsze stosy czcionek dla swoich aplikacji, które przenoszą do internetu. Aby korzystać z tych stosów, zwykle wymagają one bezpośredniego dostępu do danych o czcionkach, czego czcionki internetowe nie zapewniają.
  • Niektóre czcionki mogą nie być licencjonowane do udostępniania w internecie. Na przykład firma Linotype ma licencję na niektóre czcionki, która obejmuje tylko użycie na komputerze.

Interfejs Local Font Access API ma na celu rozwiązanie tych problemów. Składa się z 2 części:

  • Interfejs API wyliczania czcionek, który umożliwia użytkownikom przyznanie dostępu do pełnego zestawu dostępnych czcionek systemowych.
  • Z każdego wyniku wyliczenia można wysłać żądanie dostępu do kontenera SFNT na niskim poziomie (zorientowanego na bajty), który zawiera pełne dane czcionki.

Obsługa przeglądarek

Browser Support

  • Chrome: 103.
  • Edge: 103.
  • Firefox: not supported.
  • Safari: not supported.

Source

Jak korzystać z interfejsu Local Font Access API

Wykrywanie cech

Aby sprawdzić, czy interfejs Local Font Access API jest obsługiwany, użyj tego kodu:

if ('queryLocalFonts' in window) {
  // The Local Font Access API is supported
}

Wyliczanie czcionek lokalnych

Aby uzyskać listę lokalnie zainstalowanych czcionek, musisz wywołać funkcję window.queryLocalFonts(). Za pierwszym razem wyświetli się prośba o przyznanie uprawnień, którą użytkownik może zaakceptować lub odrzucić. Jeśli użytkownik zezwoli na wysyłanie zapytań o czcionki lokalne, przeglądarka zwróci tablicę z danymi czcionek, które możesz przetworzyć w pętli. Każda czcionka jest reprezentowana jako obiekt FontData z właściwościami family (np. "Comic Sans MS"), fullName (np. "Comic Sans MS"), postscriptName (np. "ComicSansMS") i style (np. "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);
}

Jeśli interesuje Cię tylko podzbiór czcionek, możesz je też filtrować na podstawie nazw PostScript, dodając parametr postscriptNames.

const availableFonts = await window.queryLocalFonts({
  postscriptNames: ['Verdana', 'Verdana-Bold', 'Verdana-Italic'],
});

Dostęp do danych SFNT

Pełny dostęp do SFNT jest dostępny za pomocą metody blob() obiektu FontData. SFNT to format pliku czcionki, który może zawierać inne czcionki, takie jak PostScript, TrueType, OpenType, Web Open Font Format (WOFF) i inne.

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);
}

Prezentacja

Interfejs Local Font Access API możesz zobaczyć w działaniu w demonstracji. Zapoznaj się też z kodem źródłowym. W wersji demonstracyjnej zaprezentowano element niestandardowy o nazwie <font-select>, który implementuje lokalny selektor czcionek.

Kwestie dotyczące prywatności

Uprawnienie "local-fonts" wydaje się zapewniać powierzchnię, która umożliwia tworzenie bardzo dokładnych odcisków cyfrowych. Przeglądarki mogą jednak zwracać dowolne elementy. Na przykład przeglądarki, które stawiają na anonimowość, mogą udostępniać tylko zestaw domyślnych czcionek wbudowanych w przeglądarkę. Podobnie przeglądarki nie muszą udostępniać danych tabeli dokładnie w takiej postaci, w jakiej są one zapisane na dysku.

Interfejs Local Font Access API został zaprojektowany tak, aby w miarę możliwości udostępniać tylko informacje niezbędne do realizacji wymienionych przypadków użycia. Interfejsy API systemu mogą generować listę zainstalowanych czcionek w kolejności instalacji, a nie w kolejności losowej lub posortowanej. Zwracanie dokładnie listy zainstalowanych czcionek podanej przez taki interfejs API systemu może ujawniać dodatkowe dane, które mogą być używane do tworzenia odcisków cyfrowych, a przypadki użycia, które chcemy włączyć, nie są obsługiwane przez zachowanie tej kolejności. W związku z tym ten interfejs API wymaga, aby zwracane dane były posortowane przed zwróceniem.

Zabezpieczenia i uprawnienia

Zespół Chrome zaprojektował i wdrożył interfejs Local Font Access API zgodnie z podstawowymi zasadami określonymi w artykule Controlling Access to Powerful Web Platform Features, w tym z zasadami kontroli użytkownika, przejrzystości i ergonomii.

Kontrola sprawowana przez użytkowników

Dostęp do czcionek użytkownika jest w pełni kontrolowany przez niego i nie będzie dozwolony, chyba że zostanie przyznane uprawnienie "local-fonts" wymienione w rejestrze uprawnień.

Przejrzystość

Informacje o tym, czy witryna ma dostęp do czcionek lokalnych użytkownika, będą widoczne w arkuszu informacji o witrynie.

Trwałość uprawnień

Uprawnienie "local-fonts" będzie zachowywane między ponownymi wczytaniami strony. Możesz ją cofnąć w arkuszu informacji o stronie.

Prześlij opinię

Zespół Chrome chce poznać Twoje wrażenia związane z korzystaniem z interfejsu Local Font Access API.

Opisz projekt interfejsu API

Czy coś w API nie działa tak, jak oczekujesz? Czy brakuje metod lub właściwości, które są potrzebne do realizacji Twojego pomysłu? Masz pytania lub uwagi dotyczące modelu zabezpieczeń? Zgłoś problem ze specyfikacją w odpowiednim repozytorium GitHub lub dodaj swoje uwagi do istniejącego problemu.

Zgłaszanie problemu z implementacją

Czy w implementacji Chrome występuje błąd? A może implementacja różni się od specyfikacji? Zgłoś błąd na stronie new.crbug.com. Podaj jak najwięcej szczegółów, proste instrukcje odtwarzania i wpisz Blink>Storage>FontAccess w polu Komponenty.

Wyrażanie poparcia dla interfejsu API

Czy planujesz używać interfejsu Local Font Access API? Twoje publiczne wsparcie pomaga zespołowi Chrome ustalać priorytety funkcji i pokazuje innym dostawcom przeglądarek, jak ważne jest ich obsługiwanie.

Wyślij tweeta do @ChromiumDev z hasztagiem #LocalFontAccess i napisz, gdzie i jak korzystasz z tego narzędzia.

Podziękowania

Specyfikacja interfejsu Local Font Access API została zmodyfikowana przez Emila A. Eklund,Alex Russell,Joshua Bell iOlivier Yiptong. Ten artykuł został sprawdzony przez Joe Medleya, Dominika RöttschesaOliviera Yiptonga. Baner powitalny autorstwa Bretta Jordana z serwisu Unsplash.