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

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

如果您從事網頁開發工作已久,可能會記得所謂的網頁安全字型。這些字型已知可在大多數最常用的作業系統 (例如 Windows、macOS、最常見的 Linux 發行版、Android 和 iOS) 上使用。在 2000 年代初期,Microsoft 甚至還推出了名為「TrueType core fonts for the Web」計畫,提供這些字型供使用者免費下載,目的是「只要您造訪指定這些字型的網站,就會看到網站設計者所預期的網頁」。是的,這類網站包括在漫畫 Sans MS 中設定的網站。以下是傳統 Web 安全字型堆疊 (任何 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';
}

將本機字型當做指紋向量

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

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

這種做法提供不錯的備用機制,可能可節省頻寬。但遺憾的是,網際網路上沒有好地方。local() 函式的缺點是,可能會遭到濫用,用於瀏覽器指紋記錄。事實證明,使用者安裝的字型清單可以提供相當明確的資訊。許多公司都會在員工的筆電上安裝自家的企業字型。舉例來說,Google 有一個名為 Google Sans 的企業字型。

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

攻擊者可以嘗試透過測試是否存在大量已知的企業字型 (例如 Google Sans),來判斷使用者所屬公司。攻擊者會嘗試在畫布轉譯這些字型中的文字,並測量字符。如果圖形與公司字型的已知形狀相符,攻擊者就會成功。如果字形不相符,攻擊者就會知道系統使用的是預設替換字型,因為公司字型並未安裝。如需這項和其他瀏覽器數位指紋採集攻擊的完整詳細資訊,請參閱 Laperdix 等人問卷調查

除了公司字型,即使只是已安裝的字型清單,也可能會洩漏資訊。這個攻擊向量的問題日益嚴重,因此 WebKit 團隊最近決定「只在可用字型清單中納入網路字型和作業系統附帶的字型,但不納入使用者在本機安裝的字型」。(我在這裡發表了 如何授予本機字型存取權的文章)

本機字型存取權 API

這篇文章的開頭可能使您產生負面的心情。我們真的不能擁有美好的事物嗎?別擔心,我們認為我們可以,也許一切都不是毫無希望。不過,請先讓我回答你可能會問的問題。

為什麼在有網路字型的情況下,我們還需要本機字型存取 API?

過去,要透過網路提供專業品質的設計和圖像工具一直不容易。有一個容易操作的區塊,就是無法存取及使用設計人員在本機安裝的各種專業建築與含提示的字型。網路字型可用於某些發布用途,但無法啟用程式輔助存取向量字形形狀和字型表,因為轉譯器會使用這些元素來算繪字形輪廓。同樣地,無法存取網路字型的二進位資料。

  • 設計工具需要存取字型位元組,才能實作自己的 OpenType 版面配置,並允許設計工具在較低層級掛鉤,執行向量濾鏡或字元圖形轉換等動作。
  • 開發人員可能會將舊版應用程式字型堆疊納入網路,要使用這些堆疊,通常需要直接存取字型資料,而這正是網路字型無法提供的功能。
  • 部分字型可能未獲得授權,無法透過網路提供。舉例來說,Linotype 擁有部分字型的授權,但僅限於電腦使用

Local Font Access API 會嘗試解決這些挑戰。這個公式包含兩個部分:

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

瀏覽器支援

瀏覽器支援

  • Chrome:103。
  • Edge:103。
  • Firefox:不支援。
  • Safari:不支援。

資料來源

如何使用 Local Font Access 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" 權限似乎可提供可用於建立指紋的介面。不過,瀏覽器可以自由傳回任何內容。舉例來說,以隱私為重點的瀏覽器可能會選擇只提供瀏覽器內建的一組預設字型。同樣地,瀏覽器也不必提供與磁碟上顯示的完全相同的資料表資料。

在可行情況下,Local Font Access API 會只提供啟用上述用途所需的資訊。系統 API 可能會產生已安裝字型的清單,但並非以隨機或排序的順序,而是以字型安裝順序排列。如要傳回系統 API 提供的已安裝字型清單,可以揭露可能用於指紋辨識的其他資料,而保留這個排序無法協助我們啟用所需的用途。因此,這個 API 要求傳回的資料必須先排序再傳回。

安全性和權限

Chrome 團隊根據「控制強大網路平台功能的存取權」一文中定義的核心原則設計並實作本機字型存取 API,包括使用者控制、透明度和人體工學。

使用者控制項

使用者可以完全控制字型的存取權,除非授予 權限註冊表中列出的 "local-fonts" 權限,否則系統不會允許存取。

透明度

指出網站是否已取得使用者的本機字型存取權,都會顯示在網站資訊表中。

權限持續性

重新載入網頁之間會保留 "local-fonts" 權限。您可透過「site information」(網站資訊) 工作表執行這項操作。

意見回饋

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

請說明 API 設計

您覺得這個 API 有任何不如預期的運作方式嗎?或者,您是否缺少實作想法所需的方法或屬性?對安全性模型有任何疑問或意見嗎?在對應的 GitHub 存放區中提出規格問題,或在現有問題中加入您的想法。

回報實作問題

你是否發現 Chrome 實作項目有錯誤?或者實作方式與規格不同?前往 new.crbug.com 回報錯誤。請務必盡可能提供所有細節、重現問題的簡單操作說明,然後在「Components」(元件) 方塊中輸入 Blink>Storage>FontAccessGlitch 可讓您輕鬆快速地分享重現內容。

顯示 API 支援

您是否打算使用 Local Font Access API?你的公開支持有助於 Chrome 團隊決定功能優先順序,並向其他瀏覽器供應商顯示支援這些功能的重要性。

使用主題標記 #LocalFontAccess@ChromiumDev 發送推文,告訴我們你在何處使用這項功能,以及使用方式。

特別銘謝

Local Font Access API 規格是由 Emil A. EklundAlex RussellJoshua BellOlivier Yiptong。本文評論者為 Joe MedleyDominik RöttschesOlivier Yiptong。主頁橫幅圖片由 Brett Jordan 提供,取自 Unsplash