Incremental Font Transfer

Editor’s Draft,

This version:
https://w3c.github.io/IFT/Overview.html
Latest published version:
https://www.w3.org/TR/IFT/
Previous Versions:
Feedback:
public-webfonts-wg@w3.org with subject line “[IFT] … message topic …” (archives)
Issue Tracking:
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 is a public copy of the editors’ draft. It is provided for discussion only and may change at any moment. Its publication here does not imply endorsement of its contents by W3C. Don’t cite this document other than as work in progress.

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

This document was produced by the Web Fonts Working Group

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 15 September 2020 W3C Process Document.

1. Introduction

This section is not normative.

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.

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

There are two different methods which can be used to incrementally transfer fonts. The first method, Patch Subset, uses a backend server which can generate binary patches to an existing font. The second method, Range Request, utilizes HTTP range requests to load only the parts of the original font that are needed.

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 uses cases while still providing material improvments to loading performance for large fonts so it is included in this specification as an alternative method.

2. Patch Based Incremental Transfer

2.1. Overview

In the patch subset approach to incremental font transfer a server generates binary patches which a client applies to a subset of the font in order to extend the coverage of that font 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.

2.1.1. Font Subset

A subset of a font file is a modified version of the font that contains only the data needed to render a subset of the codepoints supported by the original font. When a subsetted font is used to render text using any combination of the subset codepoints it must render identically to the original font. This includes any optional features that a renderer may choose to use from the original font such as hinting instructions, positioning rules, and/or glyph substitutions. Where possible the subsetted font file should not include data from the original font that is not necessary to achieve this equivalence.

2.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.

2.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.

2.2.2. Primitives

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

2.2.3. ProtocolVersion

An Integer describing the version of this communication protocol being used by a PatchRequest or PatchResponse. This value guides the semantics and interpretation of the fields sent.

This field is for future expansion. There currently is only one valid value, 0.

2.2.4. 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))

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 4
01 8
10 16
11 32

Then append the value H - 1 as a 6 bit unsigned integer.

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    |
  [ 10010000 10000100 10001000 10000000 00100000 01000000 00010000 ]

  Which then becomes the ByteString:
  [
    0b00001001,
    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 {0, 1, 2, ..., 17} can be encoded with a branching factor of 4 as:
  BitString:
  |- header | l0 |- lvl 1 -| l2  |
  |         | n0 | n1 | n2 | n3  |
  [ 00010000 1100 0000 1000 1100 ]

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

First determine the height of the tree:

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

Then append

Level 0:

Level 1:

Level 2:

2.2.5. 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.

2.2.5.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]
2.2.5.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 § 2.2.5.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. Psuedo 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]
2.2.5.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 font file 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]
         └┘ └┘ └┘ └┘ └───┘ └┘ └┘ └───┘

2.2.6. 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 § 2.2.5.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.

2.2.7. 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 § 2.2.6 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]

2.2.8. AxisSpace

Stores a set of intervals on one or more open type variation axes [opentype-variations]. 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> § 2.3.2 AxisInterval. The list of intervals for a distinct axis tag must be disjoint.

2.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 § 2.3 Object Schemas. The schema for a type specifies for each field:

2.3. Object Schemas

2.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 the sparse bit set and the list of ranges are unioned together.

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

2.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:

2.3.3. PatchRequest

ID Field Name Value Type
0 protocol_version ProtocolVersion (Integer)
1 accept_patch_format ArrayOf<Integer>
2 codepoints_have CompressedSet
3 codepoints_needed CompressedSet
4 indices_have CompressedSet
5 indices_needed CompressedSet
6 axis_space_have AxisSpace
7 axis_space_needed AxisSpace
8 ordering_checksum Integer
9 original_font_checksum Integer
10 base_checksum Integer

For a PatchRequest object to be well formed:

2.3.4. PatchResponse

ID Field Name Value Type
0 protocol_version ProtocolVersion (Integer)
1 patch_format Integer
2 patch ByteString
3 replacement ByteString
4 original_font_checksum Integer
5 patched_checksum Integer
6 codepoint_ordering IntegerList
7 ordering_checksum Integer
8 subset_axis_space ArrayOf<AxisInterval>
9 original_axis_space ArrayOf<AxisInterval>

For a PatchResponse object to be well formed:

2.4. Client

2.4.1. Client State

The client will need to maintain at minimum the following state for each font file being incrementally transferred:

2.4.2. Extending the Font Subset

A client extends its font subset to cover additional codepoints by making requests to a Patch Subset server. The request requirements are described here in terms of a fetch according to Fetch Standard §4 Fetching. If the implementing user agent does not support fetch, then the request should be made using an equivalent HTTP request.

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:

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.

2.4.3. Handling PatchResponse

If a server is able to succsessfully process a PatchRequest it will respond with HTTP status code 200 and the body of the response will be a 4 byte magic number (0x49, 0x46, 0x54, 0x20) followed by a single PatchResponse object encoded via CBOR. The client should interpret and process the fields of the object as follows:

  1. If field replacement is set then: the byte array in this field is a binary patch in the format specified by patch_format. Apply the binary patch to a base which is an empty byte array. Replace the saved font subset with the result of the patch application.

  2. If field patch is set then: the byte array in this field is a binary patch in the format specified by patch_format. Apply the binary patch to the saved font subset. Replace the saved font subset with the result of the patch application.

  3. If either replacement or patch is set then: compute the checksum of the font subset produced by the patch application in steps 1 or 2. If the computed checksum is not equal to patched_checksum this is a recoverable error. Follow the procedure in § 2.4.5 Client Side Checksum Mismatch. Otherwise update the saved original font checksum with the value in original_font_checksum.

  4. If fields codepoint_ordering and ordering_checksum are set then update the saved codepoint ordering and checksum with the new values specified by these two fields. If neither replacement nor patch are set, then the client should resend the request that triggered this response but use the new codepoint ordering provided in this response.

  5. If original_axis_space is set then update the saved original axis space with the value specified in this field.

  6. If subset_axis_space is set then update the saved subset axis space with the value specified in this field.

2.4.4. Handling Invalid Response from the Server

If the response a client receives from the server has a status code other than 200:

If the response the client receives has a status code of 200, but the body is malformed. That is, it is missing the magic number, not decodable with CBOR, or the PatchResponse is not well formed:

2.4.5. Client Side Checksum Mismatch

If the checksum of the font subset computed by the client does not match the patched_checksum in the server’s response then the client should:

  1. Discard all currently saved state for this font.

  2. Resend the request. Set the codepoints_needed field to the union of the codepoints in the discarded font subset and the set of code points that the previous request was trying to add.

    If the resent request also results in a checksum mismatch then this is an error. The client must not resend the request again and should follow § 2.4.6 Font Load Failed

2.4.6. Font Load Failed

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. This will load the entire original font.

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

Regardless of which of the above options are used, the saved client state for this font must be discarded.

2.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 § 2.4.2 Extending the Font Subset then it must respond with HTTP status code 200. The first 4 bytes of the response body must be set to 0x49, 0x46, 0x54, 0x20 ("IFT " encoded as ASCII) followed by a single PatchResponse object encoded via CBOR.

The path in the request url identifies the specific font that a patch is desired for. 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.

Likewise, the server can produce two variable axis spaces:

  1. Axis space the client has: provided 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: provided 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 a response that will cause the client to update it’s codepoint ordering to one the server will recognize via the process described in § 2.4.3 Handling PatchResponse and not include any patch. That is the patch and replacement fields must not be set.

Otherwise when the response is applied by the client following the process in § 2.4.3 Handling PatchResponse to a font subset with checksum base_checksum it must result in an extended font subset:

Additionally:

Note: the server can respond with either a patch or a replacement but should try to produce a patch where possible. Replacement’s should only be used in situations where the server is unable to recreate the client’s state in order to generate a patch against it.

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:

2.5.1. Range Request Support

A patch subset support server must also support incremental transfer via § 3 Range Request Incremental Transfer. To support range request incremental tranfser the patch subset server must support HTTP range requests [rfc7233] against the font files it provides via patch subset.

2.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 must be 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

2.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 CompressedList. 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.

2.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 must be in little endian order.

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

2.8. Patch Formats

The following patch formats may be used by the server to create binary diffs between a source file and a target file:

Format Value Notes
VCDIFF 0 Uses VCDIFF format [rfc3284] to produce the patch. All client and server implementations must support this format.
Brotli Shared Dictionary 1 Uses brotli compression [rfc7932] to produce the patch. The source file is used as a shared dictionary [Shared-Brotli] given to the brotli compressor and decompressor.

3. Range Request Incremental Transfer

The specification for range request based incremental transfer is currently being drafted and is located in a separate document: Incremental Font Transfer via Range Request

4. Declaring Incremental Fonts

CSS stylesheets (or HTML or SVG content using stylesheets) can specify that incremental font transfer can be used for a particular font URL by using the "tech" syntax in the 'src:' attribute of a font face:

@font-face {
  ...
  src: url(a_font.ttf) format(truetype) tech(incremental);
}

An incrementally transferred font must be in a raw format such as truetype or opentype.

5. Negotiating Incremental Font Transfer

For the initial request for a font the client should assume that the server supports the patch subset based method and send a PatchRequest via HTTP GET according to the requirements in § 2.4.2 Extending the Font Subset.

If the server does support the patch subset protocol it should respond appropriately following the instructions in the patch subset section. However, if the server does not support the patch subset protocol it should ignore the additional HTTP GET parameters and instead just begin sending the umodified font file.

If the client receives a response where the first 4 bytes of the body are 0x49, 0x46, 0x54, 0x20 that indicates it contains a PatchResponse. Follow the instructions in § 2.4.3 Handling PatchResponse to handle the response and all future requests should use the patch subset approach over POST.

Otherwise the client should follow the instructions in the Range Request section and all future extension requests should be sent according to the Range Request specificiation.

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 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.

One mitigation, which was originally introduced for reasons of networking efficiency so is likely to be implemented in practice, is to request additional, un-needed characters to dilute the ability to infer what content the user is viewing. Requesting characters which are statistically likely to occur may avoid a subsequent request.

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

Checksums and possible fingerprinting

64 bit checksums are generated and transferred between client and server. These are used for error detection, are not persistent across browsing sessions, change frequently in the course of a single browsing session, and thus should not pose a tracking risk.

Also, browsers typically cache resources keyed by the origin domain; thus the checksums and the set of characters the client requires would only be available to that domain and the patch subset server.

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

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.

Conformance Classes

A conformant user agent must implement all the requirements listed in this specification that are applicable to user agents.

A conformant server must implement all the requirements listed in this specification that are applicable to servers.

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. 17 November 2020. 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. 13 December 2021. Living Standard. URL: https://fetch.spec.whatwg.org/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://tools.ietf.org/html/rfc2119
[RFC3284]
D. Korn; et al. The VCDIFF Generic Differencing and Compression Data Format. June 2002. Proposed Standard. URL: https://datatracker.ietf.org/doc/html/rfc3284
[RFC7233]
R. Fielding, Ed.; Y. Lafon, Ed.; J. Reschke, Ed.. Hypertext Transfer Protocol (HTTP/1.1): Range Requests. June 2014. Proposed Standard. URL: https://httpwg.org/specs/rfc7233.html
[RFC7932]
J. Alakuijala; Z. Szabadka. Brotli Compressed Data Format. July 2016. Informational. URL: https://datatracker.ietf.org/doc/html/rfc7932
[RFC8949]
C. Bormann; P. Hoffman. Concise Binary Object Representation (CBOR). December 2020. Internet Standard. URL: https://datatracker.ietf.org/doc/html/rfc8949
[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-08
[URL]
Anne van Kesteren. URL Standard. Living Standard. URL: https://url.spec.whatwg.org/

Informative References

[OPENTYPE-VARIATIONS]
OpenType Font Variations Overview. 23 October 2020. Note. URL: https://docs.microsoft.com/en-us/typography/opentype/spec/otvaroverview
[PFE-report]
Chris Lilley. Progressive Font Enrichment: Evaluation Report. 15 October 2020. Note. URL: https://www.w3.org/TR/PFE-evaluation/
[RFC4648]
S. Josefsson. The Base16, Base32, and Base64 Data Encodings. October 2006. Proposed Standard. URL: https://datatracker.ietf.org/doc/html/rfc4648
[WOFF2]
Vladimir Levantovsky; Raph Levien. WOFF File Format 2.0. 1 March 2018. REC. URL: https://www.w3.org/TR/WOFF2/