├── .github └── workflows │ └── auto-publish.yml ├── .pr-preview.json ├── CONTRIBUTING.md ├── LICENSE ├── LICENSE.md ├── README.md ├── index.bs ├── logo-font-enumeration.svg ├── logo-font-table-access.svg ├── security-privacy.md └── w3c.json /.github/workflows/auto-publish.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: {} 4 | push: 5 | branches: [main] 6 | jobs: 7 | main: 8 | name: Build, Validate and Deploy 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: w3c/spec-prod@v2 13 | with: 14 | GH_PAGES_BRANCH: gh-pages 15 | -------------------------------------------------------------------------------- /.pr-preview.json: -------------------------------------------------------------------------------- 1 | { 2 | "src_file": "index.bs", 3 | "type": "bikeshed" 4 | } 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Web Platform Incubator Community Group 2 | 3 | This repository is being used for work in the W3C Web Platform Incubator Community Group, governed by the [W3C Community License 4 | Agreement (CLA)](http://www.w3.org/community/about/agreements/cla/). To make substantive contributions, 5 | you must join the CG. 6 | 7 | If you are not the sole contributor to a contribution (pull request), please identify all 8 | contributors in the pull request comment. 9 | 10 | To add a contributor (other than yourself, that's automatic), mark them one per line as follows: 11 | 12 | ``` 13 | +@github_username 14 | ``` 15 | 16 | If you added a contributor by mistake, you can remove them in a comment with: 17 | 18 | ``` 19 | -@github_username 20 | ``` 21 | 22 | If you are making a pull request on behalf of someone else but you had no part in designing the 23 | feature, you can remove yourself with the above syntax. 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | All Reports in this Repository are licensed by Contributors 2 | under the 3 | [W3C Software and Document License](http://www.w3.org/Consortium/Legal/2015/copyright-software-and-document). 4 | 5 | Contributions to Specifications are made under the 6 | [W3C CLA](https://www.w3.org/community/about/agreements/cla/). 7 | 8 | Contributions to Test Suites are made under the 9 | [W3C 3-clause BSD License](https://www.w3.org/Consortium/Legal/2008/03-bsd-license.html) 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Local Font Access Explained 4 | 5 | [![CI](https://github.com/WICG/local-font-access/actions/workflows/auto-publish.yml/badge.svg)](https://github.com/WICG/local-font-access/actions/workflows/auto-publish.yml) 6 | 7 | > August 14th, 2018
8 | > Last Update: April 6th, 2022 9 | > 10 | > Josh Bell `` 11 | 12 | 13 | 14 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 15 | 16 | - [What’s all this then?](#whats-all-this-then) 17 | - [Goals](#goals) 18 | - [Possible/Future Goals](#possiblefuture-goals) 19 | - [Non-goals](#non-goals) 20 | - [Key scenarios](#key-scenarios) 21 | - [Enumerating Local Fonts](#enumerating-local-fonts) 22 | - [Styling with Local Fonts](#styling-with-local-fonts) 23 | - [Accessing Full Font Data](#accessing-full-font-data) 24 | - [Requesting specific fonts](#requesting-specific-fonts) 25 | - [Detailed design discussion (data)](#detailed-design-discussion-data) 26 | - [Detailed design discussion (enumeration)](#detailed-design-discussion-enumeration) 27 | - [Privacy and Security Considerations](#privacy-and-security-considerations) 28 | - [Considered alternatives](#considered-alternatives) 29 | - [`FontFaceSource`](#fontfacesource) 30 | - [Add a browser/OS-provided font chooser](#add-a-browseros-provided-font-chooser) 31 | - [Exposing Font Tables as a map](#exposing-font-tables-as-a-map) 32 | - [Metadata Properties](#metadata-properties) 33 | - [Exposing Building Blocks](#exposing-building-blocks) 34 | 35 | 36 | 37 | ## What’s all this then? 38 | 39 | Professional-quality design and graphics tools have historically been difficult to deliver on the web. 40 | These tools provide extensive typographic features and controls as core capabilities. 41 | 42 | One stumbling block has been an inability to access and use the full variety of professionally constructed and hinted fonts which designers have locally installed. The web's answer to this situation has been the introduction of [Web Fonts](https://developer.mozilla.org/en-US/docs/Learn/CSS/Styling_text/Web_fonts) which are loaded dynamically by browsers and are subsequently available to use via CSS. This level of flexibility enables some publishing use-cases but fails to fully enable high-fidelity, platform independent vector-based design tools for several reasons: 43 | 44 | * System font engines (and browser stacks) may display certain glyphs differently. These differences are necessary, in general, to create fidelity with the underlying OS (so web content doesn't "look wrong"). These differences reduce consistency for applications that span across multiple platforms, e.g. when pixel-accurate layout and rendering is required. 45 | * Design tools need access to font bytes to do their own OpenType layout implementation and allow design tools to hook in at lower levels, for actions such as performing vector filters or transforms on the glyph shapes. 46 | * Developers may have custom font handling strategies for their applications they are bringing to the web. To use these strategies, they usually require direct access to font data, something web fonts do not provide. 47 | * Some fonts may not be licensed for delivery over the web. For example, Linotype has a license for some fonts that only includes desktop use. 48 | 49 | We propose a two-part API to help address this gap: 50 | 51 | * A font enumeration API, which allows users to grant access to the full set of available system font metadata. 52 | * From each enumeration result, the ability to request low-level (byte-oriented) SFNT container access that includes the full font data. 53 | 54 | The API provides the aforementioned tools access to the same underlying data tables that browser layout and rasterization engines use for drawing text. Examples of these data tables include the [glyf](https://docs.microsoft.com/en-us/typography/opentype/spec/glyf) table for glyph vector data, the [GPOS](https://docs.microsoft.com/en-us/typography/opentype/spec/gpos) table for glyph placement, and the [GSUB](https://docs.microsoft.com/en-us/typography/opentype/spec/gsub) table for ligatures and other glyph substitution. This information is necessary for these tools in order to guarantee both platform-independence of the resulting output (by embedding vector descriptions rather than codepoints) and to enable font-based art (treating fonts as the basis for manipulated shapes). 55 | 56 | Note that this implies that the web application provides its own shaper and libraries for Unicode, bidirectional text, text segmentation, and so on, duplicating the user agent and/or operating system's text stack. See the "Considered alternatives" section below. 57 | 58 | > NOTE: Long term, we expect that this proposal would merge into an existing CSS-related spec rather than stand on its own. 59 | 60 | ### Goals 61 | 62 | A successful API should: 63 | 64 | * Where allowed, provide efficient enumeration of all local fonts without blocking the main thread 65 | * Ensure UAs are free to return anything they like. If a browser implementation prefers, for security and privacy, they may choose to only provide a set of default fonts built into the browser. 66 | * Be available from Workers 67 | * Allow multiple levels of privacy preservation; e.g. full access for "trusted" sites and degraded access for untrusted scenarios 68 | * Reflect local font access state in the [Permissions API](https://developer.mozilla.org/en-US/docs/Web/API/Permissions_API) 69 | * Provide the ability to uniquely identify a specific font in the case of conflicting names (e.g. Web Font aliases vs. local PostScript font names) 70 | * Enable a memory efficient implementation, avoiding leaks and copies by design 71 | * Shield applications from unnecessary complexity by requiring that browser implementations produce valid SFNT data in the returned data 72 | * Restrict access to local font data to Secure Contexts and to only the top-most frame by default via the [Permissions Policy](https://w3c.github.io/webappsec-permissions-policy/) spec 73 | * Sort any result list by font name to reduce possible fingerprinting entropy bits; e.g. .query() returns an iterable which will be [sorted](https://infra.spec.whatwg.org/#list-sort-in-ascending-order) by given font names 74 | * Provide access to commonly used properties of fonts (for example, metrics used in CSS selectors, or when building font selection UI) without requiring parsing every font. 75 | 76 | #### Possible/Future Goals 77 | 78 | * Direct access to localized font names (can be done via data API) 79 | * Access to font table data for web (network-loaded) fonts 80 | * Registration of new font families (extensibility) 81 | * Additional metadata available during enumeration (ascender, descender, baseline, x-height, etc.). Will require feedback from developers; can be determined using data access, even if not exposed during enumeration. 82 | * Signals when system font configuration changes (fonts added/removed); some designers work with tools that swap font portfolios at the system level 83 | * Provide access to named instances and subfamilies (e.g. "semibold", "light") 84 | 85 | ### Non-goals 86 | 87 | This API will not try to: 88 | 89 | * Fully describe how font loading works within the web platform. Fonts are a complex topic and Web Font loading implicates aspects of layout and style recalculation which are not at this time pluggable. As this design isn't addressing those aspects, we will not describe font application or CSS recalculation semantics 90 | * Standardize font family detection or grouping 91 | * Describe or provide full access to an existing WOFF/TTF/PS parser. 92 | * Provide access to the underlying WOFF/TTF/PS font files or describe their locations on disk. 93 | * Provide a guarantee that the set of available font data matches the font on disk byte to byte. 94 | * Normalize differences in processed font data across browser implementations. The font data that will be exposed will have been processed by browser-provided parsers, but we will not describe or constrain them except to say that their output will continue to be in a valid OpenType format. For instance, if a library like [OTS](https://chromium.googlesource.com/external/ots/+/master/docs/DesignDoc.md) reduces the available information for a font, this spec will not require implementations to do more than they already would or provide alternative ways of getting such information back from the source font files. 95 | 96 | ## Key scenarios 97 | 98 | > Note: Earlier versions of this document attempted to sketch out two versions of each API; one based on `FontFaceSet` and the other the fully-asynchronous version that survives in this doc. While attractive from a re-use perspective, [`FontFaceSet`](https://drafts.csswg.org/css-font-loading/#fontfaceset) (and the implied global `document.fonts`) implies synchronous iteration over a potentially unbounded (and perhaps slow) set of files, and each item may require synchronous IPCs and I/O. This, combined with the lack of implementations of `FontFaceSet` caused us to abandon this approach. 99 | 100 | ### Enumerating Local Fonts 101 | 102 | Web developers historically lack anything more than heuristic information about which local fonts are available for use in styling page content. Web developers often include complex lists of `font-family` values in their CSS to control font fallback in a heuristic way. Generating good fallbacks is such a complex task for designers that [tools have been built to help "eyeball"](https://meowni.ca/font-style-matcher/) likely-available local matches. 103 | 104 | At the same time, when creating font-based content, specific fonts need to be identified and used. 105 | 106 | Font enumeration can help by enabling: 107 | 108 | * Logging of likely-available fonts to improve server-side font rule generation. 109 | * Scripts to generate style rules based on "similar" local fonts, perhaps saving a download 110 | * Improving styling options for user-generated content, allowing the generation of style rules via more expressive font selection menus 111 | 112 | ```js 113 | // Asynchronous Query and Iteration 114 | 115 | // User activation is required. 116 | showLocalFontsButton.onclick = async function() { 117 | // This sketch returns individual FontMetadata instances rather than families: 118 | // In the future, query() could take filters e.g. family name, and/or options 119 | // e.g. locale. 120 | try { 121 | const array = await self.queryLocalFonts(); 122 | 123 | array.forEach(metadata => { 124 | console.log(metadata.postscriptName); 125 | console.log(` full name: ${metadata.fullName}`); 126 | console.log(` family: ${metadata.family}`); 127 | console.log(` style: ${metadata.style}`); 128 | 129 | console.log(` italic: ${metadata.italic}`); 130 | console.log(` stretch: ${metadata.stretch}`); 131 | console.log(` weight: ${metadata.weight}`); 132 | }); 133 | } catch(e) { 134 | // Handle error, e.g. user cancelled the operation. 135 | console.warn(`Local font access not available: ${e.message}`); 136 | } 137 | }; 138 | ``` 139 | 140 | ### Styling with Local Fonts 141 | 142 | Advanced creative tools may wish to use CSS to style text using all available local fonts. In this case, getting access to the local font name can allow the user to select from a richer set of choices: 143 | 144 | ```js 145 | // User activation is required. 146 | useLocalFontsButton.onclick = async function() { 147 | 148 | try { 149 | // Query for allowed local fonts. 150 | const array = await self.queryLocalFonts(); 151 | 152 | // Create an element to style. 153 | const exampleText = document.createElement("p"); 154 | exampleText.id = "exampleText"; 155 | exampleText.innerText = "The quick brown fox jumps over the lazy dog"; 156 | exampleText.style.fontFamily = "dynamic-font"; 157 | 158 | // Create a list of fonts to select from, and a selection handler. 159 | const textStyle = document.createElement("style"); 160 | const fontSelect = document.createElement("select"); 161 | fontSelect.onchange = e => { 162 | console.log("selected:", fontSelect.value); 163 | // An example of styling using @font-face src: local matching. 164 | textStyle.textContent = ` 165 | @font-face { 166 | font-family: "dynamic-font"; 167 | src: local("${postscriptName}"); 168 | }`; 169 | }; 170 | 171 | // Populate the list with the available fonts. 172 | array.forEach(metadata => { 173 | const option = document.createElement("option"); 174 | option.text = metadata.fullName; 175 | // postscriptName works well as an identifier of sorts. 176 | // It is unique as returned by the API, the OpenType spec expects 177 | // it to be in ASCII, and it can be used by @font-face src: local 178 | // matching to be used to style elements. 179 | option.value = metadata.postscriptName; 180 | fontSelect.append(option); 181 | }); 182 | 183 | // Add all of the elements to the page. 184 | document.body.appendChild(textStyle); 185 | document.body.appendChild(exampleText); 186 | document.body.appendChild(fontSelect); 187 | } catch(e) { 188 | // Handle error, e.g. user cancelled the operation. 189 | console.warn(`Local font access not available: ${e.message}`); 190 | } 191 | }; 192 | ``` 193 | 194 | ### Accessing Full Font Data 195 | 196 | Here we use the `FontMetadata` `blob()` method to access a full and valid SFNT font data payload; we can use this to parse out specific data or feed it into, e.g., WASM version of [HarfBuzz](https://www.freedesktop.org/wiki/Software/HarfBuzz/) or [Freetype](https://www.freetype.org/): 197 | 198 | ```js 199 | // User activation is required. 200 | useLocalFontsButton.onclick = async function() { 201 | // This sketch returns individual FontMetadata instances rather than families: 202 | // In the future, query() could take filters e.g. family name, and/or options 203 | // e.g. locale. A user agent may return all fonts, or show UI allowing selection 204 | // of a subset of fonts. 205 | try { 206 | const array = await self.queryLocalFonts(); 207 | 208 | array.forEach(metadata => { 209 | // blob() returns a Blob containing valid and complete SFNT 210 | // wrapped font data. 211 | const sfnt = await metadata.blob(); 212 | 213 | // Slice out only the bytes we need: the first 4 bytes are the SFNT 214 | // version info. 215 | // Spec: https://docs.microsoft.com/en-us/typography/opentype/spec/otff#organization-of-an-opentype-font 216 | const sfntVersion = await sfnt.slice(0, 4).text(); 217 | 218 | let outlineFormat = "UNKNOWN"; 219 | switch (sfntVersion) { 220 | case '\x00\x01\x00\x00': 221 | case 'true': 222 | case 'typ1': 223 | outlineFormat = "truetype"; 224 | break; 225 | case 'OTTO': 226 | outlineFormat = "cff"; 227 | break; 228 | } 229 | console.log(`${metadata.fullName} outline format: ${outlineFormat}`); 230 | } 231 | } catch(e) { 232 | // Handle error. It could be a permission error. 233 | console.warn(`Local font access not available: ${e.message}`); 234 | } 235 | }; 236 | ``` 237 | 238 | ### Requesting specific fonts 239 | 240 | In some cases, a web application may wish to request access to specific fonts. For example, it may be presenting previously authored content that embeds font names. The `query()` call takes a `postscriptNames` option that scopes the request to fonts identified by PostScript names. Only matching fonts will be returned. 241 | 242 | User agents may provide a different user interface to support this. For example, if the fingerprinting risk is deemed minimal, the request may be satisfied without prompting the user for permission. Alternately, a picker could be shown with only the requested fonts included. 243 | 244 | ```js 245 | // User activation is required. 246 | requestFontsButton.onclick = async function() { 247 | try { 248 | const array = await self.queryLocalFonts({postscriptNames: ['Verdana', 'Verdana-Bold', 'Verdana-Italic']}); 249 | 250 | array.forEach(metadata => { 251 | console.log(`Access granted for ${metadata.postscriptName}`); 252 | }); 253 | 254 | } catch(e) { 255 | // Handle error. It could be a permission error. 256 | console.warn(`Local font access not available: ${e.message}`); 257 | } 258 | }; 259 | ``` 260 | 261 | 262 | ## Detailed design discussion (data) 263 | 264 | Several aspects of this design need validation: 265 | 266 | * This design tries to address concerns with `FontFaceSet` and friends at the cost of introducing a new API surface. 267 | 268 | ## Detailed design discussion (enumeration) 269 | 270 | Several aspects of this design need validation: 271 | 272 | * What precisely is being iterated over needs to be identified. Is it over files on disk, families, or other groupings that a system level enumeration API provides? There is not a 1:1 relationship between files and named instances. 273 | * Grouping of related fonts and variants into a parent object is difficult. Some families can be represented by one file or many, and the definition of a "family" is heuristic to start with. Is grouping needed? Design currently leaves this open to future additions. 274 | * This design tries to address concerns with `FontFace`, `FontFaceSet` and friends at the cost of introducing a new API surface. 275 | 276 | Other issues that feedback is needed on: 277 | 278 | * Font "name" propertes in OpenType are quite logically a map of (language tag → string) rather than just a string. The sketch just provides a single name (the "en" variant or first?) - should we introduce a map? Or have `query()` take a language tag? Or defer for now? 279 | 280 | ### Privacy and Security Considerations 281 | 282 | * The `local-fonts` permission appears to provide a highly fingerprintable surface. However, UAs are free to return anything they like. For example, the Tor Browser or Brave may choose to only provide a set of default fonts built into the browser. Similarly, UAs are not required to provide table data exactly as it appears on disk. Browsers, e.g., may choose to only provide access to table data after sanitization via [OTS](https://github.com/khaledhosny/ots) and would fail to reflect certain tables entirely. 283 | 284 | * Another fingerprinting vector could be the font version. Providing access to the raw font data could provide further bits of entropy, due to developers being able to discern between users having certain versions of a given font file. 285 | 286 | * Some users (mostly in big organizations) have custom fonts installed on their system. Listing these could provide highly identifying information about the user's company. 287 | 288 | * Wherever possible, these APIs are designed to only expose exactly the information needed to enable the mentioned use cases. System APIs may produce a list of installed fonts not in a random or a sorted order, but in the order of font installation. Returning exactly the list of installed fonts given by such a system API can expose additional entropy bits, and use cases we want to enable aren't assisted by retaining this ordering. As a result, this API requires that the returned data be sorted before being returned. 289 | 290 | 291 | ## Considered alternatives 292 | 293 | ### `FontFaceSource` 294 | 295 | [`FontFaceSource`](https://drafts.csswg.org/css-font-loading/#font-face-source) is specified in the [CSS 3 Font Loading draft](https://drafts.csswg.org/css-font-loading/). At first glance, this is the most appropriate interface from which to hang something like the proposed `query()` method. It is, however, a synchronous iterator. In conversation with implemeners, this contract may be problematic from a performance perspective across OSes. Instead of providing a potentially unbounded way for developers to naively lock up the main thread, we've chosen to introduce a different root object from which to hang asynchronous iteratation and query methods. 296 | 297 | This might be the wrong thing to do! Hopefully vendors can weigh in more thoroughly on this point. 298 | 299 | ### Add a browser/OS-provided font chooser 300 | 301 | The proposed API exposes some more bits about the user via the web that could 302 | improve fingerprinting efforts. The bits are based on the presence or lack of 303 | presence of certain fonts in the enumeration-returned list. 304 | 305 | An alternative to the API that only exposes a single user-selected font was 306 | considered. This alternative enumeration API would trigger a 307 | browser/OS-provided font chooser and, from that chooser, the user would select 308 | a single font. This would reduce the bits exposed to help mitigate 309 | fingerprinting at the cost of significant new functionality. 310 | 311 | We've heard interest from partners in a full-fledged enumeration API to get 312 | access to the list of available fonts on the system, and haven't heard interest 313 | in a font-chooser approach to the enumeration API. However, we're keeping the 314 | alternative in mind as we balance the need for new functionality with privacy 315 | concerns. 316 | 317 | ### Exposing Font Tables as a map 318 | 319 | The proposed API exposes font data as [`Blob`](https://w3c.github.io/FileAPI/#blob-section) 320 | containing a complete and valid SFNT data payload, itself containing valid OpenType font data. 321 | 322 | An alternative to the API is to expose the data as a map of the tables contained in the SFNT 323 | wrapper. This alternative would provide a higher level API whereby font table data could be parsed 324 | individually instead of the font data as a whole. 325 | 326 | We've heard from partners that this alternative does not provide a lot of value, and may in fact be 327 | counter-productive, because intended use-cases of this API subsume font data parsing tasks and 328 | require re-assembling the tables into a whole. 329 | 330 | ### Metadata Properties 331 | 332 | Including a subset of useful font metrics (`ascender`, `descender`, `xheight`, `baseline`) in the metadata was considered. Some are complicated (`baseline`), others more straightforward but may not be of practical use, especially if the full pipeline involves passing tables into Harfbuzz/FreeType for rendering. They are not included in the latest version of the sketch. 333 | 334 | Additional metadata properties such whether the font uses color (`SBIX`, `CBDT`, `SVG` etc), or is a variable font could be provided, but may not be of use. 335 | 336 | ## Exposing Building Blocks 337 | 338 | To be of use, font table data must be consumed by a shaping engine such as [HarfBuzz](https://www.freedesktop.org/wiki/Software/HarfBuzz/), in conjunction with Unicode libraries such as [ICU](http://site.icu-project.org/home) for bidirectional text support, text segmentation, and so on. Web applications could include these existing libraries, for example compiled via WASM, or equivalents. Necessarily, user agents and operating systems already provide this functionality, so requiring web applications to include their own copies leads to additional download and memory cost. In some cases, this may be required by the web application to ensure identical behavior across browsers, but in other cases exposing some of these libraries directly to script as additional web APIs could be beneficial. 339 | 340 | We are not considering these options right now for the API, but we'll keep them in mind in case there's demand for them. 341 | 342 | (Parts of ICU are being incrementally exposed to the web via the [ECMA-402](https://ecma-international.org/ecma-402/) effort.) 343 | 344 | -------------------------------------------------------------------------------- /index.bs: -------------------------------------------------------------------------------- 1 |
  2 | Title: Local Font Access API
  3 | Shortname: LocalFonts
  4 | Level: 1
  5 | Status: CG-DRAFT
  6 | Group: WICG
  7 | ED: https://wicg.github.io/local-font-access/
  8 | Repository: WICG/local-font-access
  9 | Abstract: This specification documents web browser support for allowing users to grant web sites access to the full set of available system fonts for enumeration, and access to the raw data of fonts, allowing for more detailed custom text rendering.
 10 | Editor: Joshua Bell, Google Inc. https://google.com, jsbell@google.com, w3cid 61302
 11 | Former Editor: Emil A. Eklund
 12 | Former Editor: Alex Russell
 13 | Former Editor: Olivier Yiptong
 14 | Assume Explicit For: yes
 15 | Markup Shorthands: markdown yes, css yes
 16 | Complain About: accidental-2119 yes, missing-example-ids yes
 17 | Favicon: logo-font-enumeration.svg
 18 | Test Suite: https://github.com/web-platform-tests/wpt/tree/master/font-access
 19 | 
20 | 21 |
 22 | spec: html; urlPrefix: https://html.spec.whatwg.org/multipage/
 23 |     urlPrefix: system-state.html
 24 |         type: dfn
 25 |             text: a plausible language; url: a-plausible-language
 26 | 
27 | 28 | 46 | 47 | 48 | 49 | 54 | 60 | 61 | 62 | # Introduction # {#introduction} 63 | 64 | 65 | This specification describes a font enumeration API for web browsers which may, optionally, allow users to grant access to the full set of available system fonts. For each font, low-level (byte-oriented) access to an SFNT [[!SFNT]] container or the equivalent provides full font data. 66 | 67 | Web developers historically lack anything more than heuristic information about which local fonts are available for use in styling page content. Web developers often include complex lists of `font-family` values in their CSS to control font fallback in a heuristic way. Generating good fallbacks is such a complex task for designers that tools have been built to help "eyeball" likely-available local matches. 68 | 69 | Font enumeration helps: 70 | 71 | * Improving styling options for user-generated content. 72 | * Matching fonts declared by existing content. 73 | 74 | While the web has its origins as a text-focused medium and user agents provide very high quality typography support, they have limitations that impact some classes of web-based applications: 75 | 76 | * System font engines (and browser stacks) may display certain glyphs differently. These differences are necessary, in general, to create fidelity with the underlying OS (so web content doesn't "look wrong"). These differences reduce consistency for applications that span across multiple platforms, e.g. when pixel-accurate layout and rendering is required. 77 | * Design tools need access to font data to do their own layout in a platform-independent way, and for actions such as performing vector filters or transforms on the glyph shapes. 78 | * Developers may provide font selection UI based on metrics or themes, or automatic font matching based on metrics and other data, which require direct access to font data. 79 | * Some fonts may not be licensed for delivery over the web. For example, Linotype has a license for some fonts that only includes desktop use. 80 | 81 | Professional-quality design and graphics tools have historically been difficult to deliver on the web. These tools provide extensive typographic features and controls as core capabilities. 82 | 83 | This API provides these tools access to the same underlying font data that browser layout and rasterization engines use for drawing text. Examples include the [[OPENTYPE|OpenType]] glyf table for glyph vector data, the GPOS table for glyph placement, and the GSUB table for ligatures and other glyph substitution. This information is necessary for these tools in order to guarantee both platform-independence of the resulting output (by embedding vector descriptions rather than codepoints) and to enable font-based art (treating fonts as the basis for manipulated shapes). 84 | 85 | 86 | 87 | # Goals # {#goals} 88 | 89 | 90 | The API should: 91 | 92 | * Provide efficient enumeration of all local fonts without blocking the main thread 93 | * Ensure UAs are free to return anything they like. If a browser implementation prefers, they may choose to only provide a set of default fonts built into the browser. 94 | * Be available from Workers 95 | * Allow multiple levels of privacy preservation; e.g., full access for "trusted" sites and degraded access for untrusted scenarios 96 | * Reflect local font access state in the Permissions API 97 | * Provide unique identification of families and instances (variants like "bold" and "italic"), including PostScript names 98 | * Enable a memory efficient implementation, avoiding leaks and copies by design 99 | * Restrict access to local font data to Secure Contexts and to only the top-most frame by default via the Permissions Policy spec 100 | * Sort any result list by font name to reduce possible fingerprinting entropy bits; e.g. .queryLocalFonts() returns an iterable which will be sorted by given font names 101 | * Provide access to the raw bytes of the font data. Most uses of this API will be to provide the full font data to existing libraries that only expect to consume entire font files. Providing only access to pre-parsed font table data would require developers to reassemble a blob containing all of the data in order to use such libraries. 102 | 103 | Issue: Although Worker support is called as a goal out above, the API as specified is currently only exposed to Window contexts. 104 | 105 | 108 | 109 | 110 | 111 | 112 | # Examples # {#examples} 113 | 114 | 115 | *This section is non-normative.* 116 | 117 | 118 | ## Enumerating local fonts ## {#example-enumerate-local-fonts} 119 | 120 | 121 | The API allows script to enumerate local fonts, including properties about each font. 122 | 123 |
124 | The following code queries the available local fonts, and logs the names and metrics of each to the console. 125 | 126 | ```js 127 | showLocalFontsButton.onclick = async function() { 128 | try { 129 | const array = await self.queryLocalFonts(); 130 | 131 | array.forEach(font => { 132 | console.log(font.postscriptName); 133 | console.log(` full name: ${font.fullName}`); 134 | console.log(` family: ${font.family}`); 135 | console.log(` style: ${font.style}`); 136 | }); 137 | } catch(e) { 138 | // Handle error, e.g. user cancelled the operation. 139 | console.warn(`Local font access not available: ${e.message}`); 140 | } 141 | }; 142 | ``` 143 |
144 | 145 | 146 | ## Styling with local fonts ## {#example-style-with-local-fonts} 147 | 148 | 149 | Advanced creative tools can offer the ability to style text using all available local fonts. In this case, getting access to the local font name allows the user to select from a richer set of choices: 150 | 151 |
152 | 153 | The following code populates a drop-down selection form element with the available local fonts, and could be used as part of the user interface for an editing application. 154 | 155 | ```js 156 | useLocalFontsButton.onclick = async function() { 157 | try { 158 | // Query for allowed local fonts. 159 | const array = await self.queryLocalFonts(); 160 | 161 | // Create an element to style. 162 | const exampleText = document.createElement("p"); 163 | exampleText.id = "exampleText"; 164 | exampleText.innerText = "The quick brown fox jumps over the lazy dog"; 165 | exampleText.style.fontFamily = "dynamic-font"; 166 | 167 | // Create a list of fonts to select from, and a selection handler. 168 | const textStyle = document.createElement("style"); 169 | const fontSelect = document.createElement("select"); 170 | fontSelect.onchange = e => { 171 | const postscriptName = fontSelect.value; 172 | console.log("selected:", postscriptName); 173 | // An example of styling using @font-face src: local matching. 174 | textStyle.textContent = ` 175 | @font-face { 176 | font-family: "dynamic-font"; 177 | src: local("${postscriptName}"); 178 | }`; 179 | }; 180 | 181 | // Populate the list with the available fonts. 182 | array.forEach(font => { 183 | const option = document.createElement("option"); 184 | option.text = font.fullName; 185 | // postscriptName can be used with @font-face src: local to style elements. 186 | option.value = font.postscriptName; 187 | fontSelect.append(option); 188 | }); 189 | 190 | // Add all of the elements to the page. 191 | document.body.appendChild(textStyle); 192 | document.body.appendChild(exampleText); 193 | document.body.appendChild(fontSelect); 194 | } catch(e) { 195 | // Handle error, e.g. user cancelled the operation. 196 | console.warn(`Local font access not available: ${e.message}`); 197 | } 198 | }; 199 | ``` 200 |
201 | 202 | 203 | 204 | ## Accessing font data ## {#example-accessing-font-data} 205 | 206 | 207 | The API allows script to request font data. 208 | 209 |
210 | The following code queries the available local fonts, and logs details about each to the console. 211 | 212 | Here we use enumeration to access specific local font data; we can use this to parse out specific tables or feed it into, e.g., WASM version of [HarfBuzz](https://www.freedesktop.org/wiki/Software/HarfBuzz/) or [Freetype](https://www.freetype.org/): 213 | 214 | ```js 215 | useLocalFontsButton.onclick = async function() { 216 | try { 217 | const array = await self.queryLocalFonts(); 218 | 219 | array.forEach(font => { 220 | // blob() returns a Blob containing the bytes of the font. 221 | const bytes = await font.blob(); 222 | 223 | // Inspect the first four bytes, which for SFNT define the format. 224 | // Spec: https://docs.microsoft.com/en-us/typography/opentype/spec/otff#organization-of-an-opentype-font 225 | const sfntVersion = await bytes.slice(0, 4).text(); 226 | 227 | let outlineFormat = "UNKNOWN"; 228 | switch (sfntVersion) { 229 | case '\x00\x01\x00\x00': 230 | case 'true': 231 | case 'typ1': 232 | outlineFormat = "truetype"; 233 | break; 234 | case 'OTTO': 235 | outlineFormat = "cff"; 236 | break; 237 | } 238 | console.log(`${font.fullName} outline format: ${outlineFormat}`); 239 | } 240 | } catch(e) { 241 | // Handle error. It could be a permission error. 242 | console.warn(`Local font access not available: ${e.message}`); 243 | } 244 | }; 245 | ``` 246 | 247 | Parsing font files in more detail, for example enumerating the contained tables, is beyond the scope of this specification. 248 |
249 | 250 | 251 | 252 | ## Requesting specific fonts ## {#example-requesting-specific-fonts} 253 | 254 | 255 | In some cases, a web application may wish to request access to specific fonts. For example, it may be presenting previously authored content that embeds font names. The `queryLocalFonts()` call takes a `postscriptNames` option that scopes the request to fonts identified by PostScript names. Only fonts exactly matching the names in the list will be returned. 256 | 257 | User agents may provide a different user interface to support this. For example, if the fingerprinting risk is deemed minimal, the request may be satisfied without prompting the user for permission. Alternately, a picker could be shown with only the requested fonts included. 258 | 259 |
260 | 261 | ```js 262 | // User activation is needed. 263 | requestFontsButton.onclick = async function() { 264 | try { 265 | const array = await self.queryLocalFonts({postscriptNames: ['Verdana', 'Verdana-Bold', 'Verdana-Italic']}); 266 | 267 | array.forEach(font => { 268 | console.log(`Access granted for ${font.postscriptName}`); 269 | }); 270 | 271 | } catch(e) { 272 | // Handle error. It could be a permission error. 273 | console.warn(`Local font access not available: ${e.message}`); 274 | } 275 | }; 276 | ``` 277 | 278 |
279 | 280 | 281 | # Concepts # {#concepts} 282 | 283 | 284 | The user language is a valid BCP 47 language tag representing either [=/a plausible language=] or the user's most preferred language. [[!BCP47]] 285 | 286 | 287 | ## Font Representation ## {#concept-font-representation} 288 | 289 | 290 | A font representation is some concrete representation of a font. Examples include [[OPENTYPE|OpenType]], [[TRUETYPE|TrueType]], bitmap fonts, Type1 fonts, SVG fonts, and future font formats. This specification defines the properties of a [=/font representation=] as: 291 | 292 |
293 | 294 | * The data bytes, which is a [=/byte sequence=] containing a serialization of the font. 295 | 296 | note: The [=/font representation=]'s [=font representation/data bytes=] are generally expected to be the exact byte-by-byte representation of font files on the user's filesystem. UAs aren't expected to normalize the font data, so font representations would not vary across user agents for a given user on a particular OS. The lack of normalization supports the goal of enabling web applications that perform text rendering for content creation with the full fidelity of the font. 297 | 298 | 299 | * The PostScript name, which is a {{DOMString}}. This is commonly used as a unique identifier for the font during font loading, e.g. "Optima-Bold". 300 | * The full name, which is a {{DOMString}}. This is usually a human-readable name used to identify the font, e.g. "Optima Bold". 301 | * The family name, which is a {{DOMString}}. This defines a set of fonts that vary among attributes such as weight and slope, e.g. "Optima" 302 | * The style name, which is a {{DOMString}}. This defines the variation of the font within a family, e.g. "Bold". 303 | 304 |
305 | 306 |
307 | note: 308 | This specification doesn't precisely define the values of the above fields for any particular font format, so that differences between operating systems don't make UAs non-compliant. However, for an OpenType [[!OPENTYPE]] font the following values would be sensible: 309 | * The [=font representation/data bytes=] are the SFNT [[!SFNT]] serialization of the font. 310 | * The [=font representation/PostScript name=] is found in the font's name table, in the name record with nameID = 6; if multiple localizations are available, the US English version is used if provided, or the first localization otherwise. 311 | * The [=font representation/full name=] is found in the font's name table, in the name record with nameID = 4; if multiple localizations are available, the [=/user language=] version is used if provided, or the first localization otherwise. 312 | * The [=font representation/family name=] is found in the font's name table, in the name record with nameID = 1; if multiple localizations are available, the [=/user language=] version is used if provided, or the first localization otherwise. 313 | * The [=font representation/style name=] is found in the font's name table, in the name record with nameID = 2; if multiple localizations are available, the [=/user language=] version is used if provided, or the first localization otherwise. 314 | 315 | These name properties are exposed to align with property usage in [[CSS-FONTS-4]], e.g. ''@font-face'', 'font-family', and so on. The [=font representation/PostScript name=] can be used as a unique key, for example when specifying a font when creating content or matching fonts against existing content. The [=font representation/full name=] or [=font representation/family name=] can be used for user-visible font selection UI, and the [=font representation/style name=] can be used to provide more specific selections. 316 |
317 | 318 | A valid PostScript name is a [=/scalar value string=] with [=string/length=] less than 64 and consisting only of characters in the range U+0021 (!) to U+007E (~) except for the 10 code units 319 | U+005B ([), 320 | U+005D (]), 321 | U+0028 LEFT PARENTHESIS, 322 | U+0029 RIGHT PARENTHESIS, 323 | U+007B ({), 324 | U+007D (}), 325 | U+003C (<), 326 | U+003E (>), 327 | U+002F (/), and 328 | U+0025 (%). 329 | 330 | note: This is intended to match the requirements for nameID = 6 in [[!OPENTYPE]]. 331 | 332 | 333 | 334 | ## System Fonts ## {#concept-system-fonts} 335 | 336 | 337 | A system font is a font that is available system-wide provided by the operating system. 338 | 339 | To read a system font as a font representation is to provide a [=/font representation=] equivalent to the font. This means providing all properties of a [=/font representation=]: 340 | * [=font representation/Data bytes=]. 341 | * [=font representation/PostScript name=], which must be valid. 342 | * [=font representation/Full name=]. 343 | * [=font representation/Family name=]. 344 | * [=font representation/Style name=]. 345 | 346 | This operation can fail if providing a [=/font representation=] is not possible. 347 | 348 | A user agent may use any algorithm to provide a [=/font representation=] for a [=/system font=]. In practice, contemporary operating systems and system APIs support fonts that are persisted in SFNT [[!SFNT]] font file formats, such as [[OPENTYPE|OpenType]], [[TRUETYPE|TrueType]], [[WOFF|Web Open Font Format]], etc. which satisfy these requirements, as well as the means to efficiently enumerate font collections with these common name properties provided for each font. 349 | 350 |
351 | To get all system font representations, run these steps: 352 | 1. Let |fonts| be a [=/list=] of all [=/system fonts=]. 353 | 1. Let |result| be a new [=/list=]. 354 | 1. [=list/For each=] |font| in |fonts|. 355 | 1. Let |representation| be |font| [=/read as a font representation=]. On failure, [=iteration/continue=]. 356 | 1. If the user agent determines that the user should never expose the font to the web, then it may [=iteration/continue=]. 357 | 1. Append |representation| to |result|. 358 | 1. Return |result|. 359 | 360 |
361 | 362 | 363 | # Permissions Integration # {#permissions-integration} 364 | 365 | 366 | Enumeration of local fonts requires a permission to be granted. 367 | 368 | 369 | ## Permissions ## {#permissions} 370 | 371 | 372 | The Local Font Access API is a [=/default powerful feature=] that is identified by the [=powerful feature/name=] "local-fonts". 373 | 374 | When the {{Window/queryLocalFonts()}} API is invoked, the user agent may present a list of font choices, a yes/no choice, or other interface options. The user agent should present the results of the choice in the permission in an appropriate way. For example, if the user has selected a set of fonts to expose to the site and further API calls will return the same set of fonts, the permission state could be "granted". If the user will be prompted again, the permission state could be "prompt". 375 | 376 |
377 | Permission to enumerate local fonts can be queried using the `navigator.permissions` API: 378 | 379 | ```js 380 | // This just queries the existing state of the permission, it does not change it. 381 | const status = await navigator.permissions.query({ name: "local-fonts" }); 382 | if (status.state === "granted") 383 | console.log("permission was granted 👍"); 384 | else if (status.state === "prompt") 385 | console.log("permission will be requested"); 386 | else 387 | console.log("permission was denied 👎"); 388 | ``` 389 |
390 | 391 | 392 | ## Permissions policy ## {#permissions-policy} 393 | 394 | 395 | This specification defines a [=/policy-controlled feature=] identified by the string "local-fonts". Its [=policy-controlled feature/default allowlist=] is `'self'`. 396 | 397 |
398 | note: 399 | The [=policy-controlled feature/default allowlist=] of `'self'` allows usage of this feature on same-origin nested frames by default but prevents access by third-party content. 400 | 401 | Third-party usage can be selectively enabled by adding the `allow="local-fonts"` attribute to an <{iframe}> element: 402 | 403 |
404 | ```html 405 | 406 | ``` 407 |
408 | 409 | Alternatively, this feature can be disabled completely in first-party contexts by specifying the permissions policy in an HTTP response header: 410 | 411 |
412 | ```http 413 | Permissions-Policy: local-fonts 'none' 414 | ``` 415 |
416 | 417 | See [[PERMISSIONS-POLICY]] for more details. 418 |
419 | 420 | 421 | 422 | # API # {#api} 423 | 424 | 425 | 426 | ## Font task source ## {#font-task-source-dfn} 427 | 428 | 429 | The font task source is a new generic [=/task source=] which is used for all [=/queue a task|tasks that are queued=] in this specification. 430 | 431 | 432 | 433 | ## Font manager ## {#font-manager-api} 434 | 435 | 436 |
437 | 438 | : await self . {{Window/queryLocalFonts()}} 439 | : await self . {{Window/queryLocalFonts()|queryLocalFonts}}({ {{QueryOptions/postscriptNames}}: [ ... ] }) 440 | :: Asynchronously query for available/allowed fonts. If successful, the returned promise resolves to an array of {{FontData}} objects. 441 | 442 | If the method is not called while the document has [=/transient activation=] (e.g. in response to a click event), the returned promise will be rejected. 443 | 444 | The user will be prompted for permission for access local fonts or to select fonts to provide to the site. If the permission is not granted, the returned promise will rejected. 445 | 446 | If the {{QueryOptions/postscriptNames}} option is given, then only fonts with matching PostScript names will be included in the results. 447 | 448 |
449 | 450 | 451 | 452 | [SecureContext] 453 | partial interface Window { 454 | Promise<sequence<FontData>> queryLocalFonts(optional QueryOptions options = {}); 455 | }; 456 | 457 | dictionary QueryOptions { 458 | sequence<DOMString> postscriptNames; 459 | }; 460 | 461 | 462 |
463 | The queryLocalFonts(|options|) method steps are: 464 | 465 | 1. Let |promise| be [=/a new promise=]. 466 | 1. Let |descriptor| be a {{PermissionDescriptor}} with its {{PermissionDescriptor/name}} set to {{"local-fonts"}}. 467 | 1. If [=/this=]’s [=relevant settings object=]'s [=origin=] is an [=/opaque origin=], then [=/reject=] |promise| with a "{{SecurityError}}" {{DOMException}}, and return |promise|. 468 | 1. If [=/this=]’s [=relevant global object=]'s [=/associated Document=] is not [=/allowed to use=] the [=/policy-controlled feature=] named {{PermissionPolicy/"local-fonts"}}, then [=/reject=] |promise| with a "{{SecurityError}}" {{DOMException}}, and return |promise|. 469 | 1. If [=/this=]’s [=relevant global object=] does not have [=/transient activation=], then [=/reject=] |promise| with a "{{SecurityError}}" {{DOMException}}, and return |promise|. 470 | 1. Otherwise, run these steps [=in parallel=]: 471 | 1. Let |system fonts| be the result of [=/getting all system font representations=]. 472 | 1. Let |selectable fonts| be a new [=/list=]. 473 | 1. [=list/For each=] font |representation| in |system fonts|, run these steps: 474 | 1. Let |postscriptName| be |representation|'s [=font representation/PostScript name=]. 475 | 1. Assert: |postscriptName| is a [=/valid PostScript name=]. 476 | 1. If |options|[{{QueryOptions/"postscriptNames"}}] [=map/exists=] and |options|[{{QueryOptions/"postscriptNames"}}] does not [=list/contain=] |postscriptName|, then [=iteration/continue=]. 477 | 1. [=list/Append=] a new {{FontData}} instance associated with |representation| to |selectable fonts|. 478 | 1. [=/Prompt the user to choose=] one or more items from |selectable fonts|, with |descriptor| and allowMultiple set to true, and let |result| be the result. 479 | User agents may present a yes/no choice instead of a list of choices, and in that case they should set |result| to |selectable fonts|. 480 | 1. If |result| is {{PermissionState/"denied"}}, then [=/reject=] |promise| with a "{{NotAllowedError}}" {{DOMException}}, and abort these steps. 481 | 1. Sort |result| in [=list/sort in ascending order|ascending order=] by using {{FontData/postscriptName}} as the sort key and store the result as |result|. 482 | 1. [=/Queue a task=] on the [=/font task source=] to [=/resolve=] |promise| with |result|. 483 | 1. Return |promise|. 484 | 485 |
486 | 487 | Issue: Move to {{WindowOrWorkerGlobalScope}} and sort out permission issues. 488 | 489 | 490 | ## The {{FontData}} interface ## {#fontdata-interface} 491 | 492 | 493 | A {{FontData}} provides details about a font face. Each {{FontData}} has an associated [=/font representation=]. 494 | 495 |
496 | 497 | : |fontdata| . {{FontData/postscriptName}} 498 | :: The PostScript name for the font. Example: "`Arial-Bold`". 499 | 500 | : |fontdata| . {{FontData/fullName}} 501 | :: The full font name, including family subfamily names. Example: "`Arial Bold`" 502 | 503 | : |fontdata| . {{FontData/family}} 504 | :: The font family name. This corresponds with the CSS 'font-family' property. Example: "`Arial`" 505 | 506 | : |fontdata| . {{FontData/style}} 507 | :: The font style (or subfamily) name. Example: "`Regular`", "`Bold Italic`" 508 | 509 |
510 | 511 | 512 | 513 | [Exposed=Window] 514 | interface FontData { 515 | Promise<Blob> blob(); 516 | 517 | // Names 518 | readonly attribute USVString postscriptName; 519 | readonly attribute USVString fullName; 520 | readonly attribute USVString family; 521 | readonly attribute USVString style; 522 | }; 523 | 524 | 525 |
526 | 527 |
528 | The postscriptName getter steps are: 529 | 530 | 1. Let |postscriptName| be [=/this=]'s [=font representation/PostScript name=]. 531 | 1. Assert: |postscriptName| is a [=/valid PostScript name=]. 532 | 1. Return |postscriptName|. 533 | 534 |
535 | 536 | The fullName getter steps are to return [=/this=]'s associated [=/font representation=]'s [=font representation/full name=]. 537 | 538 | The family getter steps are to return [=/this=]'s associated [=/font representation=]'s [=font representation/family name=]. 539 | 540 | The style getter steps are to return [=/this=]'s associated [=/font representation=]'s [=font representation/style name=]. 541 | 542 |
543 | 544 | Issue: Consider making {{FontData}} [=/serializable objects=] so that the results of {{Window/queryLocalFonts()}} can be passed to Workers. 545 | 546 |
547 | 548 | : await |blob| = |fontdata| . {{FontData/blob()}} 549 | :: Request the underlying bytes of a font. The result |blob| contains [=font representation/data bytes=]. 550 | 551 |
552 | 553 |
554 | 555 | The blob() method steps are: 556 | 557 | 1. Let |realm| be [=/this=]'s [=/relevant Realm=]. 558 | 1. Let |promise| be [=/a new promise=] in |realm|. 559 | 1. Run these steps [=in parallel=]: 560 | 1. Let |bytes| be [=this=]'s associated [=/font representation=]'s [=font representation/data bytes=]. 561 | 1. Let |type| be \``application/octet-stream`\`. 562 | 1. [=/Queue a task=] on the [=/font task source=] to: 563 | 1. Let |blob| be a new {{Blob}} in |realm| whose contents are |bytes| and {{Blob/type}} attribute is |type|. 564 | 1. [=/Resolve=] |promise| with |blob|. 565 | 1. Return |promise|. 566 | 567 |
568 | 569 | 570 | # Internationalization considerations # {#i18n} 571 | 572 | 573 | Issue: Document internationalization considerations other than string localization, e.g. https://github.com/WICG/local-font-access/issues/72, https://github.com/WICG/local-font-access/issues/59, etc. 574 | 575 | 576 | ## Font Names ## {#i18n-names} 577 | 578 | 579 | The \``name`\` table in [[OPENTYPE|OpenType]] fonts allows names (family, subfamily, etc) to have multilingual strings, using either platform-specific numeric language identifiers or language-tag strings conforming to [[BCP47]]. For example, a font could have family name strings defined for both \``en-US`\` and \``zh-Hant-HK`\`. 580 | 581 | The {{FontData}} properties {{FontData/postscriptName}}, {{FontData/fullName}}, {{FontData/family}}, and {{FontData/style}} are provided by this API simply as strings, using either the US English localization or the [=/user language=] localization depending on the name, or the first localization as a fallback. 582 | 583 | Web applications that need to provide names in other languages can request and parse the \``name`\` table directly. 584 | 585 | Issue(69): Should we define an option to the {{Window/queryLocalFonts()}} method to specify the desired language for strings (e.g. `{lang: 'zh'}`), falling back to \``en-US`\` if not present? Or provide access to all the names, e.g. as a map from [[BCP47]] language tag to name? 586 | 587 | 588 | 589 | # Accessibility considerations # {#a11y} 590 | 591 | 592 | There are no known accessibility impacts of this feature. 593 | 594 | 595 | # Security considerations # {#security} 596 | 597 | 598 | There are no known security impacts of this feature. 599 | 600 | 601 | # Privacy considerations # {#privacy} 602 | 603 | 604 | 605 | ## Fingerprinting ## {#privacy-fingerprinting} 606 | 607 | 608 | The font data includes: 609 | 610 | * Fonts included in the operating system distribution. 611 | * Fonts installed by particular applications installed on the system, for example office suites. 612 | * Fonts directly installed by the system administrator and/or end user. 613 | * The version of the font installed on the system, obtained via the font data. 614 | 615 | This provides several "bits of entropy" to distinguish users. 616 | 617 | User agents could mitigate this in certain cases (e.g. when the permission is denied, or in Private Browsing / "incognito" mode) by providing an enumeration of a fixed set of fonts provided with the user agent. 618 | 619 | User agents may also allow the user to select a set of fonts to make available via the API. 620 | 621 | When multiple localizations of font names are provided by a font, the user's locale is potentially exposed through the font name. User agents should ensure that if a locale is exposed in this way, it's the same locale that's exposed by navigator.{{NavigatorLanguage/language}}. 622 | 623 | 624 | ## Identification ## {#privacy-identification} 625 | 626 | 627 | Users from a particular organization could have specific fonts installed. Employees of "Example Co." could all have an "Example Corporate Typeface" installed by their system administrator, which would allow distinguishing users of a site as employees. 628 | 629 | There are services which create fonts based on handwriting samples. If these fonts are given names including personally identifiable information (e.g. "Alice's Handwriting Font"), then personally identifiable information would be made available. This may not be apparent to users if the information is included as properties within the font, not just the font name. 630 | 631 | 632 | 633 | # Acknowledgements # {#acknowledgements} 634 | 635 | 636 | We'd like to acknowledge the contributions of: 637 | 638 | * Daniel Nishi, Owen Campbell-Moore, and Mike Tsao who helped pioneer the previous local font access proposal. 639 | * Evan Wallace, Biru, Leah Cassidy, Katie Gregorio, Morgan Kennedy, and Noah Levin of Figma who have patiently enumerated the needs of their ambitious web product. 640 | * Alex Russell, who drafted the initial version of this proposal. 641 | * Olivier Yiptong, who provided an initial implementation and iteration on the API shape. 642 | * Tab Atkins, Jr. and the CSS Working Group who have provided usable base-classes which only need slight extension to enable these cases. 643 | * Dominik Röttsches and Igor Kopylov for their thoughtful feedback. 644 | * We would like to express our gratitude to former editor Emil A. Eklund, who passed away in 2020. Emil was instrumental in getting this proposal underway, providing technical guidance, and championing the needs of users and developers. 645 | 646 | Special thanks (again!) to Tab Atkins, Jr. for creating and maintaining [Bikeshed](https://github.com/tabatkins/bikeshed), the specification authoring tool used to create this document. 647 | 648 | And thanks to 649 | Anne van Kesteren, 650 | Chase Phillips, 651 | Domenic Denicola, 652 | Dominik Röttsches, 653 | Igor Kopylov, 654 | Jake Archibald, and 655 | Jeffrey Yasskin 656 | for suggestions, reviews, and other feedback. 657 | -------------------------------------------------------------------------------- /logo-font-enumeration.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /logo-font-table-access.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /security-privacy.md: -------------------------------------------------------------------------------- 1 | Questions from https://www.w3.org/TR/security-privacy-questionnaire/ 2 | 3 | # 2. Questions to Consider 4 | 5 | ## 2.1. What information might this feature expose to Web sites or other parties, and for what purposes is that exposure necessary? 6 | 7 | This feature intentionally reveals a list of local fonts to the Web site, and optionally tables within each font. This can include common fonts, fonts purchased from type foundries, or even custom fonts such as personal handwriting fonts. 8 | 9 | In addition, the data returned in the tables can include metadata about the font, as well as the actual font contents. Providing access to this information is necessary in order to correctly render text using the font. 10 | 11 | Browsers currently provide for the use of local fonts, but not enumeration. For example, the CSS `font-family` property can be used to request the use of a local font by name. If the font is not available, the browser will provide a fallback. Through the use of measurement APIs, it is usually possible to determine if the requested font or a fallback was used. Given a dictionary of font names, this can be used to determine which are available on a user's system. 12 | 13 | The feature will require the user to grant permission before providing the data to a site. 14 | 15 | ## 2.2. Is this specification exposing the minimum amount of information necessary to power the feature? 16 | 17 | The feature exposes the names and a handful of additional properties of each font. For example, the name, "PostScript" name, metrics, color and variability information. These are needed by Web applications that will present a list of fonts to users - e.g. illustration tools - to group and classify options. Some of these properties would also be available indirectly e.g. through measurement APIs. 18 | 19 | System APIs may in some cases expose more than the minimum amount of information necessary. For example, a list of fonts given by system APIs may be returned in the order in which the fonts were installed. This API intends to remove this additional information by sorting the returned result by Unicode code point given font names. 20 | 21 | ## 2.3. How does this specification deal with personal information or personally-identifiable information or information derived thereof? 22 | 23 | There are services which create fonts based on handwriting samples. If these fonts are given names including personally identifiable information (e.g. "Alice's Handwriting Font"), then personally identifiable information would be made available. This may not be apparent to users if the information is included as properties within the font, not just the font name. 24 | 25 | User agents should make the risks of granting the permission clear to users. 26 | 27 | ## 2.4. How does this specification deal with sensitive information? 28 | 29 | Fonts installed on particular operating system versions could reveal information about the user's location. 30 | 31 | Fonts may be installed by particular applications installed on the system, for example office suites. This could allow identifying the other applications on the system. 32 | 33 | Users from a particular organization could have specific fonts installed. Employees of "Example Co." could all have an "Example Corporate Typeface" installed by their system administrator, which would allow distinguishing users of a site as employees. 34 | 35 | User agents should make the risks of granting the permission clear to users. 36 | 37 | ## 2.5. Does this specification introduce new state for an origin that persists across browsing sessions? 38 | 39 | Yes, user agents could persist the `local-fonts` permission grant, but at least in the Chrome implementation, this permission grant will only be persistent for installed PWAs. The drive-by web will only have enough state to allow it to re-prompt for access, but the access itself won't be persistent. 40 | 41 | Furthermore, the user will be able to revoke permission to clear the state that was persisted, similarly to how other permissions work. 42 | 43 | ## 2.6. What information from the underlying platform, e.g. configuration data, is exposed by this specification to an origin? 44 | 45 | The font list includes: 46 | 47 | * Fonts included in the operating system distribution. 48 | * Fonts installed by particular applications installed on the system, for example office suites. 49 | * Fonts directly installed by the system administrator and/or end user. 50 | 51 | This will identify the operating system and version and potentially some installed applications. 52 | 53 | Font table access provides overall access to the raw data stored in a font's tables. This information includes all of the information about a specific font, including: 54 | 55 | * Metadata contained in a font's tables, including its various names, color and variability data, and copyright information 56 | * The raw table data used in OpenType fonts (e.g. the cmap table, etc) 57 | * The raw table data used in TrueType or CFF fonts (e.g. 'CFF2' or 'glyf' tables, etc) 58 | * Any other valid tables contained in the font beyond what may be required for traditional font rendering 59 | 60 | From this information, it may be possible to identify the operating system and version and potentially some installed applications. 61 | 62 | ## 2.7. Does this specification allow an origin access to sensors on a user’s device 63 | 64 | No. 65 | 66 | ## 2.8. What data does this specification expose to an origin? Please also document what data is identical to data exposed by other features, in the same or different contexts. 67 | 68 | The data described above is exposed to script, which can then transmit it to the origin. 69 | 70 | ## 2.9. Does this specification enable new script execution/loading mechanisms? 71 | 72 | No. 73 | 74 | ## 2.10. Does this specification allow an origin to access other devices? 75 | 76 | No. 77 | 78 | ## 2.11. Does this specification allow an origin some measure of control over a user agent’s native UI? 79 | 80 | No. (Other than potentially triggering the display of a permission prompt.) 81 | 82 | ## 2.12. What temporary identifiers might this this specification create or expose to the web? 83 | 84 | None. 85 | 86 | ## 2.13. How does this specification distinguish between behavior in first-party and third-party contexts? 87 | 88 | A user agent may decline to grant permissions requested by third-party contexts. Cooperating origins could work around this limitation via `postMessage()`. 89 | 90 | ## 2.14. How does this specification work in the context of a user agent’s Private Browsing or "incognito" mode? 91 | 92 | Some possibilities for incognito-specific behaviors have been considered: 93 | 94 | * The user agent could automatically deny the permission request. 95 | * The user agent could grant the permission request, but provide "anonymous" data, e.g. a fixed set of fonts, rather than providing access to the actual local fonts. 96 | 97 | While eliminating the ability for accessing local fonts, these choices can unintentionally expose that the user is in incognito mode. For example, 98 | automatically rejecting may indicate to a web page that, while a given UA normally supports the API, the rejection may hint that the UA has turned 99 | off the API due to being in incognito mode. 100 | 101 | Many permission-based web capabilities are exposed in incognito mode, so there's precedent that capabilities themselves are not problematic in incognito mode. Other capabilities 102 | can identify the user (e.g. file upload) which also aren't limited by incognito mode. Finally, eliminating capabilities from incognito mode reduces incognito mode's usefulness, 103 | which itself could impact the value of that feature. Based on this, an incognito mode session is most likely best served by continuing to offer these local font access APIs. 104 | 105 | ## 2.15. Does this specification have a "Security Considerations" and "Privacy Considerations" section? 106 | 107 | TBD. 108 | 109 | ## 2.16. Does this specification allow downgrading default security characteristics? 110 | 111 | No. 112 | 113 | ## 2.17. What should this questionnaire have asked? 114 | 115 | 116 | 117 | # 3. Threat Models 118 | 119 | ## 3.1 Passive Network Attackers 120 | 121 | ## 3.2 Active Network Attackers 122 | 123 | ## 3.3 Same-Origin Policy Violations 124 | 125 | ## 3.4 Third-Party Tracking 126 | 127 | ## 3.5 Legitimate Misuse 128 | -------------------------------------------------------------------------------- /w3c.json: -------------------------------------------------------------------------------- 1 | { 2 | "group": [80485] 3 | , "contacts": ["cwilso"] 4 | , "repo-type": "cg-report" 5 | } 6 | --------------------------------------------------------------------------------