搭配本機字型使用進階字體排版

瞭解 Local Font Access API 如何讓你存取使用者安裝在本機的字型,並取得關於這些字型的低階詳細資料

網頁安全字型

如果網頁開發時間已過長,不妨記住所謂的網路安全字型。幾乎所有最常使用的作業系統執行個體 (包括 Windows、macOS、最常見的 Linux 發行版、Android 和 iOS) 都知道這些字型。在 2000 年代初期,Microsoft 甚至引領了一項名為網頁版 TrueType 核心字型計畫,藉由免費下載這些字型,目標為「每次您造訪指定這類字型的網站時,您看到的網頁都會與網站設計人員的預期完全相同」。是的,這包括在 Comic Sans MS 中設定的網站。以下是傳統的網頁安全字型堆疊 (包含任何 sans-serif 字型的最終備用版本),如下所示:

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

網頁字型

網路安全字型真正重要的時代已久,如今,我們已經推出網路字型,其中有些甚至是可變字型,我們可以變更各種已暴露軸的值,進一步微調。如要使用網路字型,您可以在 CSS 開頭宣告 @font-face 區塊,用來指定要下載的字型檔案:

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

之後,您就可以照常指定 font-family 來使用自訂網路字型:

body {
  font-family: 'FlamboyantSansSerif';
}

用本機字型做為指紋向量

大多數的網路字型都來自網路,有趣的是,除了 url() 函式外,@font-face 宣告中的 src 屬性也接受 local() 函式。這麼一來,本機載入自訂字型 (驚喜!)如果使用者的作業系統中已安裝 FlamboyantSansSerif,系統會使用本機副本,而非下載:

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

這個方法可提供絕佳的備用機制,可能節省頻寬。可惜的是,網際網路上不能有好事local() 函式的問題在於可能會被濫用瀏覽器指紋識別。由此可知,使用者已安裝的字型清單可以很精確地辨識。許多公司都有自己的企業字型,並安裝在員工的筆電上。舉例來說,Google 的企業字型是「Google Sans」

macOS Font Book 應用程式顯示 Google Sans 字型的預覽畫面。
Google Sans 字型安裝在 Google 員工的筆電上。

攻擊者可以測試 Google Sans 等大量已知的企業字型,嘗試判斷某家公司所代表的公司。攻擊者會嘗試在畫布上算繪這些字型中設定的文字,並測量字符。如果字符與公司字型的已知形狀相符,攻擊者就會遭受攻擊。如果字符不相符,攻擊者就知道因為沒有安裝公司字型,系統會使用預設的替代字型。如要進一步瞭解這項威脅以及其他瀏覽器指紋攻擊,請參閱 Laperdix et al. 撰寫的問卷調查文件

公司字型的區別,即使是已安裝字型清單也沒問題。這種攻擊向量的情況已相當嚴重,因此 WebKit 團隊最近決定「僅納入 [在清單中提供的字型] 隨附於作業系統隨附的網路字型和字型,但不包括由使用者安裝於本機的字型」。(我在這裡寫了一篇文章,說明如何授予本機字型的存取權)。

本機字型存取 API

本文開頭的心情可能令您感到不快。我們能不能有好事嗎?不是,我們認為,一切可能不是無助的。首先,讓我回答您可能遇到的問題。

如果有網頁字型,為何需要使用 Local Font Access API?

在網路上提供專業品質的設計和圖形工具,向來是一大挑戰。其中一個阻塞模塊無法用來存取及使用設計人員在本機安裝的各種由專業打造與提示的字型。網頁字型有部分發布用途,但並未支援透過程式輔助方式存取光柵化工具使用的向量字符形狀和字型表格,以轉譯字符外框。同理,您無法存取網路字型的二進位資料。

  • 設計工具需要存取字型位元組,以實作自己的 OpenType 版面配置,並允許設計工具在較低層級連結,例如執行向量篩選器或以字符形狀轉換等動作。
  • 開發人員可能會針對他們提供給網路的應用程式使用舊版字型堆疊,如要使用這些堆疊,通常必須直接存取字型資料,而網路字型則不提供這項功能。
  • 部分字型可能沒有透過網路傳遞的授權。例如,Lintype 獲得部分字型的授權,僅包含電腦使用的字型。

Local Font Access API 能夠嘗試解決這些挑戰。其中包含兩個部分:

  • 字型列舉 API,可讓使用者授予存取所有可用的系統字型。
  • 透過每個列舉結果,您可以要求包含完整字型資料的低階 (位元組導向) SFNT 容器存取權

瀏覽器支援

瀏覽器支援

  • 103
  • 103
  • x
  • x

資料來源

如何使用本機字型存取 API

功能偵測

如要確認系統是否支援 Local Font Access 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);
}

如果只對部分字型感興趣,也可以加入 postscriptNames 參數,根據 PostScript 名稱篩選字型。

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

存取 SFNT 資料

您可以透過 FontData 物件的 blob() 方法取得完整的 SFNT 存取權。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 團隊根據「控管強大的網路平台功能存取權」中所述的核心原則,設計及實作 Local Font Access API,包括使用者控制選項、資訊公開和人體工學。

使用者控制項

使用者字型的存取權完全由他們掌控,除非授予權限註冊資料庫中所列的 "local-fonts" 權限,否則系統將不允許存取該字型。

資訊公開

網站資訊工作表會顯示網站是否已獲得使用者本機字型的存取權。

權限持續性

頁面重新載入後,"local-fonts" 權限仍會保持不變。您可以透過「網站資訊」工作表撤銷這項權限。

意見回饋:

Chrome 團隊想瞭解你的 Local Font Access API 使用體驗。

告訴我們 API 設計

有什麼 API 功能不如您預期嗎?或者您需要實作提案的方法或屬性嗎?對於安全性模型有任何疑問或意見嗎?在對應的 GitHub 存放區上提交規格問題,或是將您的想法新增至現有問題中。

回報導入問題

您在執行 Chrome 時發現錯誤了嗎?或者,實作項目是否與規格不同? 前往 new.crbug.com 回報錯誤。請務必盡可能提供詳細的細節與重現簡易操作說明,並在「元件」方塊中輸入 Blink>Storage>FontAccessGlitch 適合用來快速分享簡單快速的提案,

展現對 API 的支援

您打算使用 Local Font Access API 嗎?在公開支援的協助下,Chrome 團隊可以優先推出功能,並讓其他瀏覽器廠商瞭解到這項功能有多重要。

請使用主題標記 #LocalFontAccess 將 Tweet 訊息傳送至 @ChromiumDev,讓我們知道您的使用位置和方式。

特別銘謝

Local Font Access API 規格是由 Emil A. EklundAlex RussellJoshua BellOlivier Yiptong。本文由 Joe MedleyDominik RöttschesOlivier Yiptong 審查。Brett JordanUnsplash 上提供的主頁橫幅。