瞭解 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 這個企業字型。
攻擊者可以嘗試透過測試是否存在大量已知的企業字型 (例如 Google Sans),來判斷使用者所屬公司。攻擊者會嘗試在畫布上算繪這些字型中的文字,並測量字形。如果字符與公司字型的已知形狀相符,攻擊者就會發生命中。如果字形不相符,攻擊者就會知道系統使用的是預設替換字型,因為公司字型並未安裝。如要進一步瞭解這類攻擊和其他瀏覽器指紋攻擊,請參閱 Laperdix 等人撰寫的調查報告。
除了公司字型,即使只是已安裝的字型清單,也可能會洩漏資訊。這種攻擊媒介的情況已經變得非常糟糕,最近 WebKit 團隊決定只包括作業系統附帶的 [在清單中提供的字型] 網路字型和字型,但不包括本機使用者安裝的字型。(我在這裡發表了 如何授予本機字型存取權的文章)
Local Font Access API
這篇文章開頭的內容可能會讓你感到沮喪。我們真的不能擁有美好的事物嗎?別擔心,我們認為可行,也可能所有事物都不該改變。不過,請先讓我回答你可能會問的問題。
為什麼在有網路字型的情況下,我們還需要本機字型存取 API?
過去,要透過網路提供專業品質的設計和圖像工具一直不容易。有一個容易操作的區塊,就是無法存取及使用設計人員在本機安裝的各種專業建築與含提示的字型。網路字型可用於某些發布用途,但無法啟用程式輔助存取向量字形形狀和字型表,因為轉譯器會使用這些元素來算繪字形輪廓。同樣地,也無法存取網頁字型的二進位資料。
- 設計工具需要存取字型位元組,才能實作自己的 OpenType 版面配置,並允許設計工具在較低層級掛鉤,執行向量濾鏡或字元圖形轉換等動作。
- 開發人員可能會為應用程式提供舊版字體堆疊,以便在網路上使用。如要使用這些堆疊,通常必須直接存取字型資料,網路字型無法提供。
- 部分字型可能未獲得授權,無法透過網路提供。例如,Linotype 擁有部分僅包含電腦版使用的授權。
本機字型存取 API 就是為瞭解決這些問題。這個公式包含兩個部分:
- 字型列舉 API,可讓使用者授予存取權,以便存取可用的完整系統字型集。
- 從每個列舉結果,要求包含完整字型資料的低階 (以位元組為導向) SFNT 容器存取權。
瀏覽器支援
如何使用 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 團隊根據控管強大的 Web Platform 功能存取權中定義的核心原則,設計並實作 Local Font Access API,包括使用者控制權、資訊透明度和人體工學。
使用者控制項
使用者可以完全控制字型的存取權,除非授予 權限註冊表中列出的 "local-fonts"
權限,否則系統不會允許存取。
透明度
網站資訊單會顯示網站是否已獲准存取使用者的本機字型。
權限持續性
"local-fonts"
權限會在網頁重新載入時保留。您可以透過網站資訊工作表撤銷授權。
意見回饋
Chrome 團隊想瞭解您使用 Local Font Access API 的體驗。
請說明 API 設計
API 是否有任何功能無法正常運作?或者,您是否缺少實作想法所需的方法或屬性?對安全性模型有任何疑問或意見嗎?在對應的 GitHub 存放區中提出規格問題,或在現有問題中加入您的想法。
回報實作問題
你是否發現 Chrome 實作項目有錯誤?或者實作方式與規格不同?請前往 new.crbug.com 回報錯誤。請務必盡可能提供詳細資訊,並在「Components」方塊中輸入 Blink>Storage>FontAccess
,以便重現問題。Glitch 有便捷的報復工具,
顯示對 API 的支援
您是否打算使用 Local Font Access API?您的公開支援可協助 Chrome 團隊優先採用特定功能,以及向其他瀏覽器廠商說明支援這些功能的重要性。
使用主題標記 #LocalFontAccess
向 @ChromiumDev 發送推文,告訴我們你在何處使用這項功能,以及使用方式。
實用連結
特別銘謝
本機字型存取權 API 規格由 Emil A. Eklund、Alex Russell、Joshua Bell 和 Olivier Yiptong。本文評論者為 Joe Medley、Dominik Röttsches 和 Olivier Yiptong。主頁橫幅由 Brett Jordan 提供,位於 Unsplash 網站上。