Incremental Font Transfer

W3C Working Draft,

More details about this document
This version:
https://www.w3.org/TR/2023/WD-IFT-20230530/
Latest published version:
https://www.w3.org/TR/IFT/
Editor's Draft:
https://w3c.github.io/IFT/Overview.html
History:
https://www.w3.org/standards/history/IFT
Feedback:
public-webfonts-wg@w3.org with subject line “[IFT] … message topic …” (archives)
GitHub
Editors:
Chris Lilley (W3C)
(Apple Inc.)
(Google Inc.)

Abstract

This specification defines two methods to incrementally transfer fonts from server to client. Incremental transfer allows clients to load only the portions of the font they actually need which speeds up font loads and reduces data transfer needed to load the fonts. A font can be loaded over multiple requests where each request incrementally adds additional data.

Status of this document

This section describes the status of this document at the time of its publication. A list of current W3C publications and the latest revision of this technical report can be found in the W3C technical reports index at https://www.w3.org/TR/.

This document was produced by the Web Fonts Working Group as a Working Draft using the Recommendation track. This document is intended to become a W3C Recommendation.

If you wish to make comments regarding this document, please file an issue on the specification repository.

Publication as a Working Draft does not imply endorsement by W3C and its Members. This is a draft document and may be updated, replaced or obsoleted by other documents at any time. It is inappropriate to cite this document as other than work in progress.

This document was produced by groups operating under the W3C Patent Policy. W3C maintains a public list of any patent disclosures made in connection with the deliverables of the group; that page also includes instructions for disclosing a patent. An individual who has actual knowledge of a patent which the individual believes contains Essential Claim(s) must disclose the information in accordance with section 6 of the W3C Patent Policy.

This document is governed by the 2 November 2021 W3C Process Document.

1. Introduction

This section is not normative.

Incremental Font Transfer (IFT) is a collection of technologies to improve the latency of remote fonts (or "web fonts") on the web. Without this technology, a browser needs to download every last byte of a font before it can render any characters using that font. IFT allows the browser to download only some of the bytes in the file, thereby decreasing the perceived latency between the time when a browser realizes it needs a font and when the necessary text can be rendered with that font.

The success of WebFonts is unevenly distributed. This specification allows WebFonts to be used where slow networks, very large fonts, or complex subsetting requirements currently preclude their use. For example, even using WOFF 2 [WOFF2], fonts for CJK languages are too large to be practical.

There are two different methods which can be used to incrementally transfer fonts.

1.1. Patch Subset

In the the first method, Patch Subset, a server generates binary patches which a client applies to a subset of the font in order to extend the coverage of that subset. The server is stateless, it does not maintain any session data for clients between requests. Thus when a client requests the generation of a patch from the server it has to fully describe the current subset of the font that it has in a way which allows the server to recreate it.

Generic binary patch algorithms are used which do not need to be aware of the specifics of the font format. Typically a server will produce a patch by generating two font subsets: one which matches what the client currently has and one which matches the extended subset the client desires. A binary patch is then produced between the two subsets.

1.2. Range Request

The second method, Range Request, has no server-side requirements other than the server should be able to respond to byte-based range requests. The browser makes range requests to the server for the specific bytes in the font file that it needs. In order to know which bytes are necessary, the browser makes one initial special request for the beginning of the file to obtain all required font tables, and then calculates glyph coverage and required byte ranges using font character-to-glyph mapping and glyph substitution / layout tables.

In order for the range request method to be as effective as possible, the font file itself should be internally arranged in a particular way, in order to decrease the number of requests the browser needs to make. Therefore, it is expected that web developers wishing to use the range request method will use font files that have had their contents already arranged optimally.

This method was modeled after video playback on the web, where seeking in a video causes the browser to send a range request to the server.

1.3. Technical Motivation: Evaluation Report

See the Progressive Font Enrichment: Evaluation Report [PFE-report] for the investigation which led to this specification.

The evaluation report found that patch subset was generally more efficient in terms of overall performance and transferred bytes than range request. However, Range Request is simpler to deploy for many use cases while still providing material improvements to loading performance for large fonts.

1.4. Performance Considerations and the use of Incremental Font Transfer

Using incremental transfer may not always be beneficial, depending on the characteristics of the font and the content being rendered. This section provides non-normative guidance to help decide:

  1. When incremental transfer should be utilized.

  2. When used, which of the two methods should be utilized.

Incrementally loading a font has a fundamental performance trade off versus loading the whole font. Simplistically, under incremental transfer less bytes may be transferred at the potential cost of increasing the total number of network requests being made, and/or increased request processing latency. In general incremental font transfer will be beneficial where the reduction in latency from sending less bytes outweighs additional latency introduced by the incremental transfer method.

The first factor to consider is the language of the content being rendered. The evaluation report contains the results of simulating incremental font transfer across three categories of languages (Progressive Font Enrichment: Evaluation Report § langtype). See it’s conclusions Progressive Font Enrichment: Evaluation Report § conclusions for a discussion of the anticipated performance of incremental font transfer across the language categories.

Next, how much of the font is expected to be needed? If it’s expected that most of the font will be needed to render the content then incremental font transfer is unlikely to be beneficial. In many cases however only part of a font is expected to be needed. For example:

An alternative to incremental transfer is to break a font into distinct subsets (typically by script) and use the unicode range feature of @font-face to load only the subsets needed. However, this can break rendering Progressive Font Enrichment: Evaluation Report § fail-subset if there are layout rules between characters in different subsets. Incremental font transfer does not suffer from this issue as it maintains the original font and all it’s layout rules.

1.4.1. Reducing the Number of Network Requests

As discussed in the previous section the most basic implementation of incremental font transfer will tend to increase the total number of requests made vs traditional font loading. Since each request will require at least one round trip time, performance can be negatively impacted if too many requests are made. Both range request and patch subset allow for more codepoints then needed to be requested. Intelligent use of this feature by an implementation can help reduce the total number of requests being made. The evaluation report explored this by testing the performance of a basic character frequency based codepoint prediction scheme and found it improved overall performance.

Performant implementations should incorporate a similar mechanism which can select codepoints which are likely to be needed in the future and preemptively load them. This will improve performance by reducing the need to make additional requests for missing codepoints. The set of codepoints the client has and the set they are requesting can be used in conjunction with codepoint occurrence frequency and codepoint usage in languages/scripts to make predictions on which additional codepoints are likely to be needed.

Under range request the prediction mechanism will need to be part of the client implementation. In patch subset either the client and/or server implementation can include a prediction mechanism. A side benefit to a client side prediction mechanism is providing some obfuscation of the specific codepoints required by the client. This is further discussed in Content inference from character set.

1.4.2. Deciding between Patch Subset and Range Request

From a purely performance perspective patch subset is more performant than range request. There are three main factors for this:

  1. Range request requires an extra round trip on the very first load of a font to fetch the non-outline font data.

  2. Patch subset is able to incrementally transfer all tables in the font, while range request is limited to only incrementally transferring glyph outline tables. In many fonts the majority of data is in the glyph outlines, however there are fonts that have significant amounts of data in the non outline tables.

  3. Patch subset is able to compress incremental font data against previously loaded data from that font. This leads to better compression overall compared to range request.

See the evaluation report for a quantitative assessment of the difference in performance of patch subset versus range request Progressive Font Enrichment: Evaluation Report § analysis.

The downside of using patch subset is that it requires server side processing to produce the patches, while range request can work on any standard HTTP server that supports range requests:

2. Opt-In Mechanism

This section is general to both IFT methods.

Web pages can choose to opt-in to either patch subset or range request incremental transfer for a font via the use of a CSS font tech keyword (CSS Fonts 4 § 11.1 Font tech) inside the ''@font-face'' block.

There are three tech keywords available:

The use of the incremental-patch keyword in this CSS rule indicates to the browser they should use the patch subset IFT method to load the font.
@font-face {
    font-family: "MyCoolWebFont";
    src: url("MyCoolWebFont.otf") tech(incremental-patch);
}

@font-face’s that include one of the incremental tech keywords should also include a unicode-range descriptor. This informs the client which codepoints are available in the font prior to making the first request, which can be used to avoid requesting unavailable codepoints.

Note: Each individual @font-face block may or may not opt-in to IFT. This is due to the variety of ways fonts are used on web pages. Authors have control over which fonts they want to use this technology with, and which they do not.

Note: the use of incremental-auto may incur a CORS pre-flight request for the initial request of method negotiation, as the initial request sets a custom header.

Note: the IFT tech keywords can be used in conjunction with other font tech specifiers to perform font feature selection. For example a @font-face could include two URLs one with tech(incremental-patch, color-COLRv1) and the other with tech(incremental-patch, color-COLRv0). The client would initiate an incremental patch subset transfer to one of the URLs depending on which version of COLR is supported.

3. IFT Method Selection

This section is general to both IFT methods.

The client should have support for both patch subset and range request IFT. The client selects the IFT method to utilize for a font load using the following procedure:

The most recently specified method by the content takes precedence. For example if on a site the first page specifies to use the patch subset method for a font URL, but the second page specifies range request for the same font URL then the second page should load the font using range request. Such cases will require the client to reset any previously stored state for that URL and start fresh with the new method. In the case that incremental-auto is encountered where previously a specific method was specified then the client can choose to continue using the previously selected method.

3.1. IFT Method Negotiation

When the particular IFT method(s) that are supported by a server are not known, the client must determine which method to use. Different clients may support different IFT methods, and different servers may support different IFT methods, so a negotiation occurs as such:

  1. The browser makes the first request to the server using the GET HTTP method (HTTP Semantics § 9.3.1 GET). If the client prefers the patch-subset method, it sends the relevant patch request header. If the client prefers the range-request method, it does not send the header.

  2. If the server receives the patch request header and wishes to honor it, the server must reply according to § 4.5 Server: Responding to a PatchRequest. Otherwise, the server must reply with the Accept-Ranges header, if it supports HTTP Range Requests.

  3. If the client receives a font with a table identified by the 4-byte tag "IFTP", it commences using the patch-subset method. Otherwise, if the client receives the Accept-Ranges: bytes header, it commences using the range-request method. Otherwise, the whole font file is downloaded, and the current non-incremental loading behavior is used.

3.2. IFT Method Fallback

This section is not normative.

This summarizes behaviors that result from the above method selection and negotiation sections.

If the content specifies the range request method:

Client supports range-request method Client does not support range-request method
Server supports range-request method Range-request method is used. Client falls back to non-incremental load of the full font file.
Server does not support range-request method. Response is missing accept-ranges header. Client falls back to non-incremental load of the full font file. Client falls back to non-incremental load of the full font file.

If the content specifies the patch subset method:

Client supports patch-subset method Client does not support patch-subset method
Server supports patch-subset method Patch-subset method is used. Client falls back to non-incremental load of the full font file.
Server does not support patch-subset method Response is missing magic number. Client falls back to non-incremental load of the full font file. Client falls back to non-incremental load of the full font file.

If the content does not specify a specific method:

Client prefers range-request method Client prefers patch-subset method
Server supports both range-request method and patch-subset method Client makes initial request without the patch request header, and possibly with the Range header. Because all patch-subset servers must support the range-request method, the server replies with Accept-Ranges and initial font data. Client/server commence using range-request method. Client makes initial request with the patch request header. Server replies with the patch-subset magic number, and client/server commence using patch-subset method.
Server supports only range-request method Same as above. Client makes initial request with the patch request header. Server replies with Accept-Ranges and initial font data. Client/server commence using range-request method.
Server supports neither Client makes initial request without the patch request header, and possibly with the Range header. Server replies without Accept-Ranges header, and sends the full font file to the client from beginning to end. Client makes initial request to server with the patch request header. Server does not reply with the patch-subset magic number, and sends the full font file to the client from beginning to end.

3.3. Offline Usage

Special consideration must be taken when saving a page for offline usage that uses an incrementally transferred font since the saved page won’t be able to increment the font if content changes (eg. due to JavaScript execution). In these cases the page saving mechanism should download the full font by making a normal GET request without the patch request header to the font url. Additionally when URLs are rewritten to point to the saved full font any of the incremental tech specifiers should be removed.

4. Patch Based Incremental Transfer

4.1. Font Subset

A font subset is a modified version of a font file [iso14496-22]that contains only the data needed to render a subset of:

supported by the original font. When a subsetted font is used to render text using any combination of the subset codepoints, layout features, or design-variation space it must render identically to the original font. This includes rendering with the use of any optional typographic features that a renderer may choose to use from the original font, such as hinting instructions.

A font subset definition describes the minimum data (codepoints, layout features, variation axis space) that a font subset must possess.

Note: For convenience the remainder of this document links to the [open-type] specification which is a copy of [iso14496-22].

4.2. Data Types

This section lists all of the data types that are used to form the request and response messages sent between the client and server.

4.2.1. Encoding

All data types defined here are encoded into a byte representation for transport using CBOR (Concise Binary Object Representation) [RFC8949]. More information on how each data types should be encoded by CBOR are given in the definition of those data types.

4.2.2. Primitives

Data Type Description CBOR Major Type
Integer An integer value range [-263, 263 - 1] inclusive. 0 or 1
Float IEEE 754 Single-Precision Float. 7
ByteString Variable number of bytes. 2
String UTF-8 [rfc3629] text string. 3
ArrayOf<Type> Array of a variable number of items of Type. 4

4.2.3. SparseBitSet

A data structure which compactly stores a set of distinct unsigned integers. The set is represented as a tree where each node has a fixed number of children that recursively sub-divides an interval into equal partitions. A tree of height H with branching factor B can store set membership for integers in the interval [0 to BH-1] inclusive. The tree is encoded into a ByteString for transport.

To construct the tree T which encodes set S first select the branching factor B (how many children each node has). B can be 4, 8, 16, or 32.

Note: the encoder can use any of the possible branching factors, but it is recommended to use 4 as that has been shown to give the smallest encodings for most sets typically encountered.

Next, determine the height, H, of the tree:

H = ceil(logB(max(S) + 1))

If S is an empty set then H = 1.

Next create a tree of height H where all non-leaf nodes have B children. Each node in the tree has a single value composed of B bits. Given a node p which has B children: c0 ... cB - 1 and is in a tree, T, of height H, then:

The tree is encoded into a bit string. When appending multiple-bit values to the bit string, bits are added in order from least significant bit to most significant bit.

First append 2 bits which encode the branching factor:

Bits  Branching Factor
00 2
01 4
10 8
11 32

Then append the value H - 1 as a 5 bit unsigned integer. Next append a single 0 bit, which is reserved for future use.

Next the nodes are encoded into the bit string by traversing the nodes of the T in level order and appending the value for each non-zero node to the bit string. If all of the set values covered by a node’s interval are present within set S, then that node can instead be encoded in the bit string as B bits all set to zero. All children of that node must not be encoded.

Lastly the bit string is converted into a ByteString by converting each consecutive group of 8 bits into the next byte of the string. If the number of bits in the bit string is not a multiple of 8, zero bits are appended to the next multiple of 8. The bit with the smallest index in the bit string is the least significant bit in the byte and the bit with the largest index is the most significant bit.

The set {2, 33, 323} in a tree with a branching factor of 8 is encoded as the bit string:
BitString:
|- header |- lvl 0 |---- level 1 ----|------- level 2 -----------|
|         |   n0   |   n1       n2   |   n3       n4       n5    |
[ 01010000 10000100 10001000 10000000 00100000 01000000 00010000 ]

Which then becomes the ByteString:
[
  0b00001010,
  0b00100001,
  0b00010001,
  0b00000001,
  0b00000100,
  0b00000010,
  0b00001000
]

First determine the height of the tree:

H = ceil(log8(323 + 1)) = 3

Then append

Level 0:

Level 1:

Level 2:

The set {} in a tree with a branching factor of 4 is encoded as the bit string:
BitString:
|- header- |
|          |
[ 00000000 ]

Which then becomes the ByteString:
[
  0b00000000,
]

First determine the height of the tree. Because we are encoding an empty set height is:

H = 1

Then append

Empty sets have no nodes, so no bytes beyond the header need to be appended.

The set {0, 1, 2, ..., 17} can be encoded with a branching factor of 4 as:
BitString:
|- header | l0 |- lvl 1 -| l2  |
|         | n0 | n1 | n2 | n3  |
[ 10010000 1100 0000 1000 1100 ]

ByteString:
[
  0b00001001,
  0b00000011,
  0b00110001
]

First determine the height of the tree:

H = ceil(log4(17 + 1)) = 3

Then append

Level 0:

Level 1:

Level 2:

4.2.4. IntegerList

A data structure which compactly represents a list of non-negative integers from 0 to 231-1. The list is encoded into a ByteString for transport.

There are three steps of encoding/compression: first delta, second zig-zag, and finally UIntBase128. The final ByteString result is simply the concatenation of the individual UIntBase128 encoded bytes.

IntegerList encoding must reject an input list which contains values not in the range 0 to 231-1. Likewise if decoding an IntegerList results in values which are not in the range 0 to 231-1 the list is invalid and must be rejected.

4.2.4.1. Delta Encoding

Delta encoding converts a list of integers to a list of deltas between them.

A list L of n integers Li0..n-1 is converted into a list of N integers Di0..n-1 as follows:

This has the effect of reducing the magnitude of the values, which reduces the number of bytes required in the UIntBase128 encoding, below.

// Note: unsorted
int_list = [23, 43, 12, 3, 67, 68, 69, 0]
delta_list = [23, 20, -31, -9, 64, 1, 1, -69]
4.2.4.2. Zig-Zag Encoding

Zig-Zag encoding reversibly converts signed integers to unsigned integers, using the same number of bits. The entire range of values is supported. This step is required, as the § 4.2.4.3 UIntBase128 Encoding step works on unsigned integers only. The encoding maps positive integer values to even positive integers and negative integer values to odd positive integers. Pseudo code:

encode(n):
  if n >= 0:
    return n * 2
  else:
    return (n * -2) - 1

decode(n) {
  if n & 1:
    return -((n + 1) / 2)
  else:
    return n / 2
Value Zig-Zag Encoding
0 0
1 2
2 4
3 6
4 8
-1 1
-2 3
-3 5
-4 7
delta_list = [23, 20, -31, -9, 64, 1, 1, -69]
zig_zag_encoded_list = [46, 40, 61, 17, 128, 2, 2, 137]
4.2.4.3. UIntBase128 Encoding

UIntBase128 is a variable length encoding of unsigned integers, suitable for values up to 232-1. A UIntBase128 encoded number is a sequence of bytes for which the most significant bit is set for all but the last byte, and clear for the last byte. The number itself is base 128 encoded in the lower 7 bits of each byte. Thus, a decoding procedure for a UIntBase128 is: start with value = 0. Consume a byte, setting value = old value times 128 + (byte bitwise-and 127). Repeat last step until the most significant bit of byte is false.

UIntBase128 encoding format allows a possibility of sub-optimal encoding, where e.g. the same numerical value can be represented with variable number of bytes (utilizing leading zeros). For example, the value 63 could be encoded as either one byte 0x3F or two (or more) bytes: [0x80, 0x3f]. An encoder must not allow this to happen and must produce shortest possible encoding. A decoder must reject the response/request if it encounters a UIntBase128-encoded value with leading zeros (a value that starts with the byte 0x80), if UIntBase128-encoded sequence is longer than 5 bytes, or if a UIntBase128-encoded value exceeds 232-1. Pseudo-code:

bool ReadUIntBase128( data, *result ) {
  UInt32 accum = 0;

  for (i = 0; i < 5; i++) {
    UInt8 data_byte = data.getNextUInt8();

    // No leading 0’s
    if (i == 0 && data_byte == 0x80) return false;

    // If any of top 7 bits are set then << 7 would overflow
    if (accum & 0xFE000000) return false;

    accum = (accum << 7) | (data_byte & 0x7F);

    // Spin until most significant bit of data byte is false
    if ((data_byte & 0x80) == 0) {
      *result = accum;
      return true;
    }
  }
  // UIntBase128 sequence exceeds 5 bytes
  return false;
}
Value       Output Bytes
0           00000000
1           00000001
2           00000010
3           00000011
127         01111111
128         10000001 00000000
255         10000001 01111111
16256       11111111 00000000
2080768     11111111 10000000 00000000
266338304   11111111 10000000 10000000 00000000
4294967295  10001111 11111111 11111111 11111111 01111111
zig_zag_encoded_list = [46, 40, 61, 17, 128, 2, 2, 137]
bytes = [2E 28 3D 11 81 00 02 02 81 09]
         └┘ └┘ └┘ └┘ └───┘ └┘ └┘ └───┘

4.2.5. SortedIntegerList

A data structure which compactly represents a sorted list of ascending non-negative integers (0 to 232-1). The list is encoded into a ByteString for transport.

This is a variation on IntegerList with better compression. Sorted lists only use two steps of encoding/compression: first deltas and then UIntBase128. The § 4.2.4.2 Zig-Zag Encoding step is skipped. This allows twice the range in UIntBase128, so that single bytes may be used more often.

SortedIntegerList encoding must reject an input list which contains values not in the range 0 to 232-1. Likewise if decoding an IntegerList results in values which are not in the range 0 to 232-1 the list is invalid and must be rejected.

4.2.6. RangeList

A RangeList encodes a set of non-negative integers (0 to 232-1). The set is encoded as a list of disjoint intervals. Each interval is represented by two integers, a start (inclusive) and end (inclusive).

A RangeList is a list of n pairs [mini0..n-1, maxi0..n-1]. The list must be non-decreasing, i.e. mini=1..n-1 >= maxi-1.

To encode this list, we convert it to a list L of 2n integers, where L2i = mini and L2i+1 = maxi for i = 0..n-1.

L is a sorted list of integers, so a SortedIntegerList is used to encode it as a ByteString.

range_list = [3, 10], [13, 268]
int_list = [3, 10, 13, 268]
delta_list = [3, 7, 3, 255]
bytes = [03 07 03 81 7F]

4.2.7. FeatureTagSet

A FeatureTagSet encodes a set of zero or more layout feature tags. To encode a set of feature tags:

  1. Union the set of feature tags to be encoded and the list of feature tags in Appendix A: Default Feature Tags and Encoding IDs. For each tag in the union:

  2. The final encoding is produced by sorting the encoded ID list produced in step 1 into ascending order and then encoding the sorted list as a SortedIntegerList.

When decoding a FeatureTagSet the integer values are mapped back to the original tags by reversing the above encoding rules.

4.2.8. AxisSpace

Stores a set of intervals on one or more variation axes OpenType Specification § otvaroverview#. Encoded as a CBOR map (major type 5). The key in each pair is an axis tag. It is encoded as a ByteString containing exactly 4 ASCII characters. The value in each pair is an ArrayOf<AxisInterval>. The list of intervals for a each axis tag must be disjoint.

4.2.9. Objects

Objects are data structures comprised of key and value pairs. Objects are encoded via CBOR as maps (major type 5). Each key and value pair is encoded as a single map entry. Keys are always unsigned integers and are encoded using major type 0. Values are encoded using the encoding specified by the type of the value.

All fields in an object are optional and do not need to have an associated value. Conversely when decoding and object fields may be present which are not specified in the schema. The decoder must ignore without error any key and value pairs where the key is not recognized.

There are several types of object used, each type is defined by a schema in § 4.3 Object Schemas. The schema for a type specifies for each field:

4.3. Object Schemas

4.3.1. CompressedSet

Encodes a set of unsigned integers. The set is not ordered and does not allow duplicates. Members of the set are encoded into either a SparseBitSet or a RangeList. To obtain the final set the members of sparse_bit_set and the list of ranges in range_deltas are unioned together.

ID  Field Name Type
0 sparse_bit_set SparseBitSet (ByteString)
1 range_deltas RangeList (ByteString)

4.3.2. AxisInterval

ID Field Name Value Type
0 start Float
1 end Float

AxisInterval defines an interval (from start to end inclusive) on some variable axis in a font.

For an AxisInterval object to be well formed:

4.3.3. PatchRequest

ID Field Name Value Type
0 codepoints_have CompressedSet
1 codepoints_needed CompressedSet
2 indices_have CompressedSet
3 indices_needed CompressedSet
4 features_have FeatureTagSet
5 features_needed FeatureTagSet
6 axis_space_have AxisSpace
7 axis_space_needed AxisSpace
8 ordering_checksum Integer
9 original_font_checksum Integer
10 fragment_id String
11 codepoint_ordering IntegerList

For a PatchRequest object to be well formed:

4.3.4. ClientState

ID Field Name Value Type
0 original_font_checksum Integer
1 codepoint_ordering IntegerList
2 subset_axis_space AxisSpace
3 original_axis_space AxisSpace
4 original_features FeatureTagSet
5 missing_codepoints CompressedSet

4.4. Client

4.4.1. Extending the Font Subset

This algorithm is used by the client to extends its font subset to cover additional codepoints, features, and/or design-variation space.

Extend the font subset

The inputs to this algorithm are:

The algorithm outputs:

The algorithm:

  1. If font subset is set then load the client state from the font subset. Client state is stored in the font subset as a table identified by the 4-byte tag 'IFTP'. The contents of the table are a single ClientState object encoded via CBOR.

    • If font subset does not have an "IFTP" table, then this is not an incrementally loaded font and cannot be extended any further. Return font subset.

  2. If client state was loaded in the previous step, remove all codepoints in desired subset definition which are not listed in codepoint_ordering. Otherwise if codepoint coverage hint is non-null, remove all codepoint in desired subset definition which are not found in codepoint coverage hint.

  3. Compare the desired subset definition to the font subset. If the font subset is a superset of desired subset definition then return font subset, and null for the cache fields.

  4. Invoke Add codepoint groups. The requested codepoints input is the codepoint set from desired subset definition minus the codepoints already in the font subset. Additionally pass in codepoint coverage hint, and client state. Add the returned codepoints to desired subset definition. All client implementations must perform this step.

  5. Make an HTTP request using the fetch algorithm:

    • The request method must be either "GET" or "POST".

    • The request destination must be "font".

    • The request CORS mode must be "cors".

    • The request cache mode should be "no-store".

    • The request URL scheme must be "https".

    • The request URL path is set to the input font URL path.

    • The request must include an Accept-Encoding header which lists at minimum one of the encodings from § 4.8 Patch Encodings.

    • The request must include a sec-available-dictionary header whose value is the SHA-256 hash of the input font subset.

    • If method is "POST" then, request body must be a single PatchRequest object encoded via CBOR.

    • Otherwise if method is "GET" then, a header with name Font-Patch-Request (the patch request header) and whose value is a single PatchRequest object encoded via CBOR and then base64url encoding [rfc4648] must be added to the request’s header list.

    Any request and/or url parameters which are not specified here should be set based on the user agent’s normal handling for font requests. For example if this font load is from a CSS font face, then CSS Fonts 4 § 4.8.2 Font fetching requirements should be followed.

    The fields of the PatchRequest object should be set as follows:

    • codepoints_have: set to exactly the set of codepoints that the current font subset contains data for plus the set of missing codepoints specified in missing_codepoints. If the current font subset is not set then this field is left unset. If client state is available and has a codepoint_ordering and missing_codepoints is empty then this field should not be set.

    • codepoints_needed: set to the set of codepoints that the client wants to add to its font subset. That is the codepoint set from desired subset definition minus the codepoints already in the font subset. If client state is available and has a codepoint_ordering then this field should not be set.

    • indices_have: encodes the set of additional codepoints that the current font subset contains data for. The codepoint values are transformed to indices by applying the § 4.7 Codepoint Reordering specified by codepoint_ordering to each codepoint value. If the client state is not available or it does not have a codepoint_ordering then this field should not be set.

    • indices_needed: encodes the set of codepoints that the client wants to add to its font subset. That is the codepoint set from desired subset definition minus the codepoints already in font subset. The codepoint values are transformed to indices by applying the § 4.7 Codepoint Reordering specified by codepoint_ordering to each codepoint value. If the client state is not available or it does not have a codepoint_ordering then this field should not be set.

    • features_have: set to the list of layout feature tags that the current font subset has data for. If the current font subset is not set then this field is left unset. Additionally, if the current font subset has all data for features present in the original font then this field can be unset.

    • features_needed: set to the list of feature tags that the client wants to add to the current font subset. That is the feature set from desired subset definition minus the set of features already in font subset. If the client wishes to add all remaining layout features from the original font to it’s subset then this field should be unset.

    • axis_space_have: set to the current value of subset_axis_space saved in the client state for this font. If client state is not available then this field is unset.

    • axis_space_needed: set to the intervals of each variable axis in the original font that the client wants to add to its font subset as defined in the desired subset definition. If the client wants an entire axis from the original font then that axis should not be listed.

    • ordering_checksum: If either of indices_have or indices_needed is set then this must be set to the checksum of the codepoint_ordering saved in the client state. The checksum is computed via § 4.7.1 Codepoint Reordering Checksum.

    • original_font_checksum: Set to saved value for original_font_checksum in the client state for this font. If there is no client state leave this field unset.

    • fragment_id: If a fragment identifier was provided as an input then this field must be set to the provided fragment identifier, otherwise it must be left unset.

    Note: It is allowed for the client to request more codepoints then it strictly needs. For example, on slower connections it may be more performant to request extra codepoints if that is likely to prevent a future request from needing to be sent.

  6. Invoke Handle server response with the response from the server and the font subset then return the result.

Note: POST is preferred for the HTTP method since it will not cause a CORS pre-flight request and the request object is more compactly encoded. GET should only be used during method negotiation.

4.4.2. Handling Server Response

If a server is able to successfully process a PatchRequest it will respond with HTTP status code 200 and the body of the response will be an encoded representation of the extended font subset. The encoded representation may be a binary patch against the current font subset.

Handle server response

Inputs:

The algorithm outputs:

The algorithm:

  1. If the server response has status other than 200:

  2. Decode the server response body by applying the appropriate decoding as specified by the Content-Encoding header. If the Content-Encoding is one of those from § 4.8 Patch Encodings then the input font subset will be used as the source file for the decoding operation. The decoded response is the new extended font subset.

  3. Load the client state from the extended font subset. Client state is stored in the extended font subset as a table identified by the 4-byte tag 'IFTP'. The contents of the table are a single ClientState object encoded via CBOR.

  4. If a client state table is not present then this is the full font and no further augmentation is possible. Goto step 6.

  5. Verify that the union of the codepoints present in the extended font subset and all codepoints listed in the client state field missing_codepoints is equal to the set of codepoints asked for (union of codepoints/indices needed and codepoints/indices have) in the request which generated this response. If they are not equal, this is an invalid response. Invoke Handle failed font load and return the result.

  6. Return the extended font subset, any cache headers, and the use-as-dictionary header that were set on the server response.

Handle failed font load

If the font load or extension has failed the client should choose one of the following options:

  1. If the client has a saved font subset, it may choose to use that and then use the user agent’s existing font fallback mechanism for codepoints not covered by the subset.

  2. The client may re-issue the request as a regular non incremental font fetch to the same path. It must not include the patch subset request parameter or header. This will load the entire original font.

  3. Discard the saved font subset, and use the user agent’s existing font fallback mechanism.

4.4.3. Adding Codepoint Groups to a Request

This algorithm is used by the client when forming a request which includes Appendix B: Codepoints Requiring Obfuscation to obscure the specific codepoints needed by the request to improve privacy (see Content inference from character set for the rationale behind this).

Add codepoint groups

The inputs to this algorithm:

The algorithm outputs:

The algorithm:

  1. Create a new set obfuscation codepoints by removing all codepoints from requested codepoints which are not listed in Appendix B: Codepoints Requiring Obfuscation.

  2. If obfuscation codepoints is an empty set then return an empty set.

  3. Form a set available codepoints:

  4. Partition available codepoints into one or more disjoint sets that each contain at least 7 codepoints. The specific partitioning algorithm is left up to the implementation. However, the implementation must not use the requested codepoints or obfuscation codepoints set in any way to influence the partitioning.

  5. Find the all of the partitioned sets which contain at least one codepoint from obfuscation codepoints and return the union of them.

4.4.4. Load a Font in a User Agent with a HTTP Cache

The previous section § 4.4.1 Extending the Font Subset provides no guidance on how a user agent should handle saving the font subset and client state between invocations of the subset extension algorithm. This section provides an algorithm that user agents which implement [fetch] should use to save the font subset to the user agent’s HTTP cache ([RFC9111]).

Load a font with a HTTP Cache

The inputs to this algorithm:

The algorithm outputs:

The algorithm:

  1. Make a HTTP fetch:

    • The request method is "GET".

    • The request destination must be "font".

    • The request CORS mode must be "cors".

    • The request URL scheme must be "https".

    • The request URL path is set to the input font URL.

    • The request cache mode is "only-if-cached".

  2. If the request is successful and the response is "fresh" (HTTP Caching § 4.2 Freshness) then invoke Extend the font subset with:

    • Font url set to the input font URL.

    • Fragment identifier set to the input fragment identifier

    • Font subset set to the response.

    • Desired subset definition set to the input desired subset definition.

    • Fetch algorithm set to the input fetch algorithm.

    Once that returns go to step 4.

  3. Otherwise, invoke Extend the font subset with:

    • Font url set to the input font URL.

    • Fragment identifier set to the input fragment identifier

    • Font subset set to null.

    • Desired subset definition set to the input desired subset definition.

    • Fetch algorithm set to the input fetch algorithm.

    Once that returns go to step 4.

  4. If the returned cache fields are non-null update the cache entry for the input font url with the returned client state and returned cache fields.

  5. Return the returned font subset.

4.5. Server: Responding to a PatchRequest

If the server receives a well formed PatchRequest over HTTPS for a font the server has and that was populated according to the requirements in § 4.4.1 Extending the Font Subset then it must respond with HTTP status code 200.

The path in the request url identifies the specific font that a patch is desired for. If the request has the fragment_id field set and the file identified by path is a font collection, then fragment_id identifies the font within that collection that a patch is desired for. The identified font is referred to as the original font in the rest of this section.

From the request object the server can produce two codepoint sets:

  1. Codepoints the client has: formed by the union of the codepoint sets specified by codepoints_have and indices_have. The indices in indices_have must be mapped to codepoints by the application of the codepoint reordering with a checksum matching ordering_checksum.

  2. Codepoints the client needs: formed by the union of the codepoint sets specified by codepoints_needed and indices_needed. The indices in indices_needed must be mapped to codepoints by the application of the codepoint reordering with a checksum matching ordering_checksum.

Note: the request may optionally set codepoint_ordering which is used by the client to provide the exact codepoint ordering that was used to encode indices_have and indices_needed.

Likewise, the server can produce two sets of layout feature tags:

  1. Feature tags the client’s subset has: specified by features_have. If the field is unset this indicates the client’s subset contains all features in the original font.

  2. Feature tags the client needs: specified by features_needed. If the field is unset this indicates the client wants all features in the original font.

Lastly, the server can produce two variable axis spaces:

  1. Axis space the client has: specified by axis_space_have. If any axes in the font are not specified in axis_space_have then for those axes add their entire interval from the original font.

  2. Axis space the client needs: specified by axis_space_needed. If any axes in the font are not specified in axis_space_needed then for those axes add their entire interval from the original font.

If the server does not recognize the codepoint ordering used by the client, it must respond with status code 409. This will instruct the client to resend the request including the codepoint ordering it has.

Otherwise when the response is decoded by the client following the process in § 4.4.2 Handling Server Response with an input font subset whose SHA-256 matches the value of the sec-available-dictionary header if it is present, it must result in an extended font subset:

Additionally:

Note: if a patch subset service is composed of more than one server task and some subset of those tasks are using a subsetter version which produces different binary results than the rest, there is a risk that consecutive extend requests may result in unnecessary replacement responses. For example if consecutive requests alternate between server backends with different subsetters, then each response will be a replacement as the server tasks will be unable to recreate the previously generated subset. This scenario might occur during software updates to the server tasks. To combat this it’s recommended that sticky load balancing is used which aims to send consecutive requests from the same client to the same server task.

Possible error responses:

4.5.1. Range Request Support

A patch subset support server must also support incremental transfer via § 5 Range Request Incremental Transfer. To support range request incremental transfer the patch subset server must support HTTP range requests (HTTP Semantics §  14. Range Requests) against the font files it provides via patch subset.

4.6. Computing Checksums

64 bit checksums of byte strings are computed using the [fast-hash] algorithm. A python like pseudo code version of the algorithm is presented below:

# Constant values come fast hash: https://github.com/ztanml/fast-hash
SEED = 0x11743e80f437ffe6
M = 0x880355f21e6d1965

mix(value):
  value = value ^ (value >> 23)
  value = value * 0x2127599bf4325c37
  value = value ^ (value >> 47)
  return value

fast_hash(byte[] data):
  # When casting byte arrays into unsigned 64 bit integers the bytes are in little
  # endian order. That is the smallest index is the least significant byte.
  uint64 hash = SEED ^ (length(data) * M)
  for (i = 0; i <= length(data) - 8; i += 8)
    hash = (hash ^ mix((uint64) data[i:i+8])) * M

  remaining = length(data) % 8
  if not remaining:
    return mix(hash)

  uint64 last_value = (uint64) concat(data[length(data) - remaining:],
                                      [0] * (8 - remaining))
  return mix((hash ^ mix(last_value)) * M)

To ensure checksums are consistent across all platforms, all integers during the computation are in little endian order.

Note: a C implementation of fast hash can be found here: [fast-hash]

Bytes Checksum value
0f 7b 5a e5 0xe5e0d1dc89eaa189
1d f4 02 5e d3 b8 43 21 3b ae de 0xb31e9c70768205fb

4.7. Codepoint Reordering

A codepoint reordering for a font defines a function which maps unicode codepoint values from the font to a continuous space of [0, number of codepoints in the font). This transformation is intended to reduce the cost of representing codepoint sets.

A codepoint ordering is encoded into a IntegerList. The list must contain all unicode codepoints that are supported by the font. The index of a particular unicode codepoint in the list is the new value for that codepoint.

A server is free to choose any codepoint ordering, but should try to pick one that will minimize the size of encoded codepoint sets for that font.

4.7.1. Codepoint Reordering Checksum

A checksum of a codepoint reordering can be computed as follows:

SEED = 0x11743e80f437ffe6
M = 0x880355f21e6d1965

mix(value):
  value = value ^ (value >> 23)
  value = value * 0x2127599bf4325c37
  value = value ^ (value >> 47)
  return value

fast_hash_ordering(uint64[] ordering):
  uint64 hash = SEED ^ (length(ordering) * 8 * M)
  for i in ordering:
    hash = (hash ^ mix(ordering[i])) * M

  return mix(hash)

To ensure checksums are consistent across all platforms, all integers during the computation are in little endian order.

Codepoint Ordering Checksum value
[106, 97, 105, 120, 100] 0x6986dc19f4e621e

4.8. Patch Encodings

The following content encodings can be used to encode a target file as a patch against a source file:

Name Description Notes
sbr Brotli Shared Dictionary The target file is encoded with brotli compression using the source file as a shared LZ77 dictionary. If the source file is empty then the target file is just compressed using brotli compression with no shared dictionary.

All client and server implementations must support this format.

vcdiff VCDIFF Patch Uses VCDIFF format [RFC3284] to produce the patch.

5. Range Request Incremental Transfer

Range request incremental font transfer is specified in a separate document: [RangeRequest]

Privacy Considerations

Content inference from character set

IFT exposes, to the server hosting a Web font, the set of characters that the browser can already render in a given Web font, and also the set of characters that it cannot render, but wants to (for example, to render a new Web page). For details, see § 4.4.1 Extending the Font Subset.

The purpose of doing so is to allow the server to compute a binary patch to the existing font, adding more characters. Thus, fonts are transferred incrementally, as needed, which greatly reduces the bytes transferred and the overall network cost.

For some languages, which use a very large character set (Chinese and Japanese are examples) the vast reduction in total bytes transferred means that Web fonts become usable, including on mobile networks, for the first time.

However, for those languages, it is possible that individual requests might be analyzed by a rogue font server to obtain intelligence about the type of content which is being read. It is unclear how feasible this attack is, or the computational complexity required to exploit it, unless the characters being requested are very unusual.

There are two main mitigations in place:

  1. IFT mandates HTTPS, so no in-the-middle attack is possible; the trust is between the client, and the server hosting the fonts.

  2. IFT obscures the specific codepoints the client needs by adding additional codepoints to the request that are not present in the content. This specification defines a set of codepoints which require obfuscation (Appendix B: Codepoints Requiring Obfuscation) and then § 4.4.3 Adding Codepoint Groups to a Request is used to add additional codepoints to the request. This method was found to be effective in reducing the ability to determine the content which is the source of a request via simulations.

Checksums and possible fingerprinting

In the patch subset method 64 bit checksums are generated and transferred between client and server. These are used to ensure the client and server are in sync. They detect cases such as where the original font being patched has changed or the server is not able to reproduce the clients font subset. The checksums will be stored in a browsers HTTP cache as part of the font subset and thus are subject to the cache partitioning applied by the browser. Since modern browsers cache resources keyed by the site domain, this will limit checksum availability to within the site domain and prevent them from being used for tracking.

Per-origin restriction avoids fingerprinting

As required by [css-fonts-4], Web Fonts must not be accessible in any other Document from the one which either is associated with the @font-face rule or owns the FontFaceSet. Other applications on the device must not be able to access Web Fonts. This avoids information leaking across origins.

Similarly, font palette values must only be available to the documents that reference it. Using an author-defined color palette outside of the documents that reference it would constitute a security leak since the contents of one page would be able to affect other pages, something an attacker could use as an attack vector.

Security Considerations

No Security issues have been raised against this document

Changes

Since the Working Draft of 28 June 2022 (see commit history):

Appendix A: Default Feature Tags and Encoding IDs

This appendix is normative. It lists the IDs used to encode feature tags into a FeatureTagSet. This table was assembled from several sources:

The columns should be interpreted as follows:

Note: the set of features selected to be included by default is purely an optimization mechanism and should not be used by clients as a guide for which features they should be asking for in requests. To minimize the number of bytes loaded clients should only request features that are needed for the rendering of the content used by the font.

Note: If the client does not wish to customize the loaded features, then the set of default included features provides a good starting point as it is the set of features which are typically used by renderers by default.

Tag Encoded As Encode If Encoding Version
abvf 1 not needed 1
abvm 2 not needed 1
abvs 3 not needed 1
akhn 4 not needed 1
blwf 5 not needed 1
blwm 6 not needed 1
blws 7 not needed 1
calt 8 not needed 1
ccmp 9 not needed 1
cfar 10 not needed 1
chws 11 not needed 1
cjct 12 not needed 1
clig 13 not needed 1
cswh 14 not needed 1
curs 15 not needed 1
dist 16 not needed 1
dnom 17 not needed 1
dtls 18 not needed 1
fin2 19 not needed 1
fin3 20 not needed 1
fina 21 not needed 1
flac 22 not needed 1
frac 23 not needed 1
half 24 not needed 1
haln 25 not needed 1
init 26 not needed 1
isol 27 not needed 1
jalt 28 not needed 1
kern 29 not needed 1
liga 30 not needed 1
ljmo 31 not needed 1
locl 32 not needed 1
ltra 33 not needed 1
ltrm 34 not needed 1
mark 35 not needed 1
med2 36 not needed 1
medi 37 not needed 1
mkmk 38 not needed 1
mset 39 not needed 1
nukt 40 not needed 1
numr 41 not needed 1
pref 42 not needed 1
pres 43 not needed 1
pstf 44 not needed 1
psts 45 not needed 1
rand 46 not needed 1
rclt 47 not needed 1
rkrf 48 not needed 1
rlig 49 not needed 1
rphf 50 not needed 1
rtla 51 not needed 1
rtlm 52 not needed 1
rvrn 53 not needed 1
ssty 54 not needed 1
stch 55 not needed 1
tjmo 56 not needed 1
valt 57 not needed 1
vatu 58 not needed 1
vchw 59 not needed 1
vert 60 not needed 1
vjmo 61 not needed 1
vkrn 62 not needed 1
vpal 63 not needed 1
vrt2 64 not needed 1
vrtr 65 not needed 1
aalt 66 needed 1
afrc 67 needed 1
case 68 needed 1
cpct 69 needed 1
cpsp 70 needed 1
c2pc 71 needed 1
c2sc 72 needed 1
dlig 73 needed 1
expt 74 needed 1
falt 75 needed 1
fwid 76 needed 1
halt 77 needed 1
hist 78 needed 1
hkna 79 needed 1
hlig 80 needed 1
hngl 81 needed 1
hojo 82 needed 1
hwid 83 needed 1
ital 84 needed 1
jp78 85 needed 1
jp83 86 needed 1
jp90 87 needed 1
jp04 88 needed 1
lfbd 89 needed 1
lnum 90 needed 1
mgrk 91 needed 1
nalt 92 needed 1
nlck 93 needed 1
onum 94 needed 1
opbd 95 needed 1
ordn 96 needed 1
ornm 97 needed 1
palt 98 needed 1
pcap 99 needed 1
pkna 100 needed 1
pnum 101 needed 1
pwid 102 needed 1
qwid 103 needed 1
rtbd 104 needed 1
ruby 105 needed 1
salt 106 needed 1
sinf 107 needed 1
size 108 needed 1
smcp 109 needed 1
smpl 110 needed 1
subs 111 needed 1
sups 112 needed 1
swsh 113 needed 1
titl 114 needed 1
tnam 115 needed 1
tnum 116 needed 1
trad 117 needed 1
twid 118 needed 1
unic 119 needed 1
vhal 120 needed 1
vkna 121 needed 1
zero 122 needed 1
ss01-ss20 123-142 needed 1
cv01-cv99 143-241 needed 1

Appendix B: Codepoints Requiring Obfuscation

This appendix is normative. The following codepoints are considered to need obfuscation:

Conformance

Document conventions

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.

Conformant Algorithms

Requirements phrased in the imperative as part of algorithms (such as "strip any leading space characters" or "return false and abort these steps") are to be interpreted with the meaning of the key word ("must", "should", "may", etc) used in introducing the algorithm.

Conformance requirements phrased as algorithms or specific steps can be implemented in any manner, so long as the end result is equivalent. In particular, the algorithms defined in this specification are intended to be easy to understand and are not intended to be performant. Implementers are encouraged to optimize.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[CSS-FONTS-4]
John Daggett; Myles Maxfield; Chris Lilley. CSS Fonts Module Level 4. 21 December 2021. WD. URL: https://www.w3.org/TR/css-fonts-4/
[FAST-HASH]
ztanml. fast-hash. 22 October 2018. Note. URL: https://github.com/ztanml/fast-hash
[FETCH]
Fetch Standard. 22 May 2023. Living Standard. URL: https://fetch.spec.whatwg.org/
[ISO14496-22]
Information technology — Coding of audio-visual objects — Part 22: Open Font Format. January 2019. Published. URL: https://www.iso.org/standard/74461.html
[OPEN-TYPE]
OpenType Specification. December 2021. Note. URL: https://docs.microsoft.com/en-us/typography/opentype/spec
[PFE-report]
Chris Lilley. Progressive Font Enrichment: Evaluation Report. 15 October 2020. Note. URL: https://www.w3.org/TR/PFE-evaluation/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[RFC3284]
D. Korn; et al. The VCDIFF Generic Differencing and Compression Data Format. June 2002. Proposed Standard. URL: https://www.rfc-editor.org/rfc/rfc3284
[RFC3629]
F. Yergeau. UTF-8, a transformation format of ISO 10646. November 2003. Internet Standard. URL: https://www.rfc-editor.org/rfc/rfc3629
[RFC4648]
S. Josefsson. The Base16, Base32, and Base64 Data Encodings. October 2006. Proposed Standard. URL: https://www.rfc-editor.org/rfc/rfc4648
[RFC7932]
J. Alakuijala; Z. Szabadka. Brotli Compressed Data Format. July 2016. Informational. URL: https://www.rfc-editor.org/rfc/rfc7932
[RFC8081]
C. Lilley. The "font" Top-Level Media Type. February 2017. Proposed Standard. URL: https://www.rfc-editor.org/rfc/rfc8081
[RFC8949]
C. Bormann; P. Hoffman. Concise Binary Object Representation (CBOR). December 2020. Internet Standard. URL: https://www.rfc-editor.org/rfc/rfc8949
[RFC9110]
R. Fielding, Ed.; M. Nottingham, Ed.; J. Reschke, Ed.. HTTP Semantics. June 2022. Internet Standard. URL: https://www.rfc-editor.org/rfc/rfc9110
[RFC9111]
R. Fielding, Ed.; M. Nottingham, Ed.; J. Reschke, Ed.. HTTP Caching. June 2022. Internet Standard. URL: https://www.rfc-editor.org/rfc/rfc9111
[Shared-Brotli]
J. Alakuijala; et al. Shared Brotli Compressed Data Format. 27 Jul 2021. Internet Draft. URL: https://datatracker.ietf.org/doc/html/draft-vandevenne-shared-brotli-format-09#section-3.2
[UAX44]
Ken Whistler. Unicode Character Database. 2 September 2022. Unicode Standard Annex #44. URL: https://www.unicode.org/reports/tr44/tr44-30.html
[URL]
Anne van Kesteren. URL Standard. Living Standard. URL: https://url.spec.whatwg.org/

Informative References

[ENABLING-TYPOGRAPHY]
John Hudson. Enabling Typography: towards a general model of OpenType Layout. April 15th, 2014. Note. URL: https://tiro.com/John/Enabling_Typography_(OTL).pdf
[FIPS-180-4]
FIPS PUB 180-4: Secure Hash Standard (SHS). August 2015. National Standard. URL: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf
[RangeRequest]
Chris Lilley; Garret Rieger; Myles Maxfield. Range Request Incremental Font Transfer. 30 May 2023. WD. URL: https://www.w3.org/TR/RangeRequest/
[WOFF2]
Vladimir Levantovsky; Raph Levien. WOFF File Format 2.0. 10 March 2022. REC. URL: https://www.w3.org/TR/WOFF2/