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 modelled 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 uses cases while still providing material improvments to loading performance for large fonts.
2. Opt-In Mechanism
This section is general to both IFT methods.
The collection of IFT technologies utilizes a single shared opt-in mechanism. Each method does not use its own opt-in mechanism; instead, the webpage opts into the IFT technologies as a whole, and the browser and server negotiate to decide which specific method will be employed.
The opt-in mechanism is the incremental
keyword inside the @font-face block. Websites specify this keyword inside their @font-face
block, and the browser is then responsible for using IFT technologies, and for negotiating with the server to determine which specific IFT method to use.
incremental
keyword in this CSS rule indicates to the browser they should use IFT.
@font-face { font-family: "MyCoolWebFont"; src: url("MyCoolWebFont.otf") tech(incremental); }
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.
3. IFT Method Selection
This section is general to both IFT methods.
When a page has indicated that a particular font should utilize IFT technology, the browser must determine which method to use. Different browsers may support different IFT methods, and different servers may support different IFT methods, so a negotation occurs as such:
-
The browser makes the first request to the server. If the client prefers the patch-subset method, it sends the relevant query parameter. If the client prefers the range-request method, it does not send the query parameter.
-
If the server receives the patch-subset query parameter and wishes to honor it, the server must reply with a valid patch subset response which includes the patch-subset magic number. Otherwise, the server must reply with the [RFC9110]
Accept-Ranges
header, if it supports HTTP Range Requests. -
If the client receives the patch-subset magic number, 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.1. IFT Method Fallback
This section is not normative.
This summarizes behaviors that result from the above method selection.
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 query parameter, 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 query parameter. 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 query parameter. Server replies with Accept-Ranges and initial font data. Client/server commence using range-request method.
|
Server supports neither | Client makes initial request without query parameter, 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 query parameter. Server does not reply with the patch-subset magic number, and sends the full font file to the client from beginning to end. |
4. Patch Based Incremental Transfer
4.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, 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.
4.1.1. Font Subset Definition
A font subset definition describes the minimum data (codepoints, layout features, variation axis space) that a font subset must possess.
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 [-264 - 1, 264 - 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. 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.
4.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))
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:
-
D(n) is depth of node n: the number of edges between the root node and n.
-
Start(ci) is the start (inclusive) of the interval covered by ci :
Start(ci) = Start(p) + i * BH - D(ci) -
End(ci) is the end (exclusive) of the interval covered by ci :
End(ci) = Start(p) + (i + 1) * BH - D(ci) -
Start(root node) = 0
-
The value of node p is a string of B bits. If its bits are numbered from 0 (least significant) to B - 1 (most significant) then bit i will be 1 if the set S contains at least one member in the interval [Start(ci), End(ci)), otherwise bit i will be 0.
-
If for node p, End(p) - Start(p) = B, then p will have no children.
-
An empty set is considered to have no nodes.
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.
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
-
branching factor = 8 = 10
-
H - 1 = 2 = 00010
-
reserved bit = 0
Level 0:
-
root node, n0 append 00100001. Bit 0 is set because there are set members in the interval [0, 64), and bit 5 is set due to members in the interval [320, 384).
Level 1:
-
There will be two non-zero children corresponding to bit 0 and bit 5 in n0:
-
n1 append 00010001. It is child 0 of n0 and subdivides the interval [0, 64). Bit 0 is set since there are set members in [0, 8) and bit 4 for [32, 40).
-
n2 append 00000001. It is child 5 of n0 it subdivides the interval [320, 384). Bit 0 is set since there are set members in [320 - 328).
Level 2:
-
n3 append 00000100. Child 0 of n1, bit 2 is set for the interval [2, 3) or 2.
-
n4 append 00000010. Child 4 of n1, bit 1 is set for the interval [33, 34) or 33.
-
n5 append 00001000. Child 0 of n2, bit 3 is set for the interval [323, 324) or 323.
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
-
branching factor = 2 = 00
-
H - 1 = 0 = 00000
-
reserved bit = 0
Empty sets have no nodes, so no bytes beyond the header need to be appended.
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
-
branching factor = 4 = 01
-
H - 1 = 2 = 00010
-
reserved bit = 0
Level 0:
-
n0 append 0011. Bit 0 set for [0, 16), bit 1 set for [16, 32)
Level 1:
-
n1 append 0000. All bits zero to indicate interval [0, 16) is fully filled.
-
n2 append 0001. Bit 0 set for [16, 20)
Level 2:
-
n3 append 0011. Bit 0 set for value 16, bit 1 set for value 17.
4.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.
4.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:
-
D0 = L0
-
Di = 1..n-1 = Li - Li-1
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.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 § 4.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
delta_list = [23, 20, -31, -9, 64, 1, 1, -69] zig_zag_encoded_list = [46, 40, 61, 17, 128, 2, 2, 137]
4.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 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.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 § 4.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.
4.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 § 4.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]
4.2.8. FeatureTagSet
A FeatureTagSet encodes a set of zero or more opentype layout feature tags. Each feature tag is mapped to an integer value and then the set of mapped integers are encoded in a § 4.2.6 SortedIntegerList. Feature tags are mapped to integers as follows:
-
If the tag is found in Appendix B: Default Feature Tags and Encoding IDs:
-
If the "Encoded As" column corresponding to the tag is "default" then the tag is skipped and not encoded.
-
Else, the tag is mapped to the integer value in the "Encoded As" column.
-
-
Otherwise: the tag is converted to an integer by treating the tag’s 4 byte string as a 4 byte little endian integer.
The final encoding is produced by sorting the mapped integers (exlcuding tags which are skipped) into ascending order and then encoding the sorted list as a § 4.2.6 SortedIntegerList.
When decoding a FeatureTagSet the integer values are mapped back to the original tags by reversing the above mapping rules. Additionally all default features in Appendix B: Default Feature Tags and Encoding IDs must be added to the decoded set.
4.2.9. 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>
§ 4.3.2 AxisInterval. The list of intervals for a
each axis tag must be disjoint.
4.2.10. 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:
-
A human readable name for the field. For reference only, not used in the encoding.
-
A unsigned integer id for the field. This is used as the key in the encoding.
-
The type of the value stored in this field. Can be any of the types defined in § 4.2 Data Types including object types.
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 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) |
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:
-
start
must be set. -
end
is optional, if set it must be greater thanstart
. Ifend
is not set then this interval is a single point,start
.
4.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 | features_have | FeatureTagSet |
7 | features_needed | FeatureTagSet |
8 | axis_space_have | AxisSpace |
9 | axis_space_needed | AxisSpace |
10 | ordering_checksum | Integer |
11 | original_font_checksum | Integer |
12 | base_checksum | Integer |
13 | fragment_id | String |
For a PatchRequest object to be well formed:
-
protocol_version
must be set to 0. -
accept_patch_format
can include any of the values listed in § 4.8 Patch Formats. -
If either of
indices_have
orindices_needed
is set to a non-empty set thenordering_checksum
must be set. -
If
codepoints_have
orindices_have
is set to a non-empty set thenoriginal_font_checksum
andbase_checksum
must be set.
4.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 | AxisSpace |
9 | original_axis_space | AxisSpace |
10 | original_features | FeatureTagSet |
For a PatchResponse object to be well formed:
-
protocol_version
must be set to 0. -
Only one of
patch
orreplacement
must be set. -
If either
patch
orreplacement
is set thenpatch_format
, andpatched_checksum
must be set. -
If
patch_format
is set then it must be one of the values listed in § 4.8 Patch Formats. -
If
codepoint_ordering
is set thenordering_checksum
must be set.
4.4. Client
4.4.1. Client State
The client will need to maintain at minimum the following state for each font file being incrementally transferred:
-
Font subset: a byte array containing the binary data for the most recent version of the subset of the font being incrementally transferred. For a new font this is initialized to empty byte array.
-
Original font checksum: the most recent value of
PatchResponse.original_font_checksum
received from the server for this font. -
Codepoint Reordering Map: The most recent § 4.7 Codepoint Reordering received from the server for this font.
-
Codepoint Reordering Checksum: The most recent
PatchResponse.ordering_checksum
for this font. -
Original Font Axis Space: the variations axis space that the original font covers. Supplied by
PatchResponse.original_axis_space
. -
Subset Axis Space: the most recent variations axis space that the subsetted font covers. Supplied by
PatchResponse.subset_axis_space
.
Additionally, the client can optionally store:
-
Original font feature list: a list of the opentype layout feature tags that the original font has data for. This information can be used by the client to avoid sending unnecessary requests for features which the original font does not contain. Supplied by
PatchResponse.original_features
.
4.4.2. 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. The inputs to this algorithm are:
-
Font URL: a URL where the font to be extended is located.
-
Fragement Identifier (optional): if the font at the font url is a collection of fonts, the fragment identifier (The "font" Top-Level Media Type § section-4.2) identifies a single font within the collection.
-
Client State (optional): previously saved client state for the given font url, or null.
-
Desired Subset Definition: a description of the desired minimum font subset.
-
Fetch Algorithm: algorithm for fetching HTTP resources, such as [fetch]. The remainder of this section is desribed in terms of Fetch Standard § 4 Fetching, but it is allowed to substitute whatever HTTP fetching algorithm the user agent supports.
The algorithm outputs:
-
Client State: client state that has been updated to contain a font subset which covers at least the requested subset definition.
-
Cache headers: HTTP Cache headers HTTP Caching § name-cache-control describing how client state can be cached, or null.
Extend font subset algorithm:
-
Compare the input font subset definition to the input client state. If the input client state already satisifies the font subset definition. Then return client state, and null for the cache headers.
-
Otherwise make an HTTP request using the input fetching 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.
-
If method is "POST" then, request body must be a single
PatchRequest
object encoded via CBOR. -
Otherwise if method is "GET" then, URL query must contain a query parameter "request" whose value is a single
PatchRequest
object encoded via CBOR and then base64url encoding [rfc4648].
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:-
protocol_version
: set to 0. -
accept_patch_format
: set to the list of § 4.8 Patch Formats that this client is capable of decoding. Must contain at least one format. -
codepoints_have
: set to exactly the set of codepoints that the current font subset contains data for. If the current font subset is an empty byte array this field is left unset. If the client has a codepoint ordering for this font 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. If the client has a codepoint ordering for this font 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 § 4.7 Codepoint Reordering to each codepoint value. If the client does not have a codepoint ordering for this font then this field should not be set. -
indices_needed
: encodes the set of codepoints that the client wants to add to its font subset. The codepoint values are transformed to indices by applying § 4.7 Codepoint Reordering to each codepoint value. If the client does not have a codepoint ordering for this font then this field should not be set. -
features_have
: set to the list of opentype layout feature tags that the current font subset has data for. If the current font subset is an empty byte array 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. Alternatively, if the client wishes to add all features from the original font to it’s subset then this field should be unset. -
axis_space_have
: set to the current value ofsubset_axis_space
saved in the state for this font. -
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. If the client wants an entire axis from the original font then that axis should not be listed. -
ordering_checksum
: If either ofindices_have
orindices_needed
is set then this must be set to the current value ofordering_checksum
saved in the state for this font. -
original_font_checksum
: Set to saved value fororiginal_font_checksum
in the state for this font. If there is no saved value leave this field unset. -
base_checksum
: Set to the checksum of the font subset byte array saved in the state for this font. See: § 4.6 Computing Checksums. -
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.
-
4.4.2.1. 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:
-
If field
replacement
is set then: the byte array in this field is a binary patch in the format specified bypatch_format
. Apply the binary patch to a base which is an empty byte array. Replace the font subset in the input client state with the result of the patch application. -
If field
patch
is set then: the byte array in this field is a binary patch in the format specified bypatch_format
. Apply the binary patch to the font subset from the input client state. Replace the font subset in the input client state with the result of the patch application. -
If either
replacement
orpatch
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 topatched_checksum
this is a recoverable error. Follow the procedure in § 4.4.2.3 Client Side Checksum Mismatch. Otherwise update the original font checksum in the input client state with the value inoriginal_font_checksum
. -
If fields
codepoint_ordering
andordering_checksum
are set then update the codepoint ordering and checksum in the input client state with the new values specified by these two fields. If neitherreplacement
norpatch
are set, then the client should resend the request that triggered this response but use the new codepoint ordering provided in this response. -
If
original_features
is set and the client has opted to save them then replace the original feature list in the input client state with the value from the response. -
If
original_axis_space
is set then update the original axis space in the input client state with the value specified in this field. -
If
subset_axis_space
is set then update the subset axis space in the input client state with the value specified in this field.
After processing the response, return the updated input client state and any cache headers that were set on the response.
4.4.2.2. Handling Invalid Response from the Server
If the response a client receives from the server has a status code other than 200:
-
If it is a redirect status: follow normal redirect handling, such as Fetch Standard § 4.4 HTTP-redirect fetch and then go back to § 4.4.2.1 Handling PatchResponse.
-
All other statuses, the font subset extension has failed. Follow § 4.4.2.4 Font Load Failed.
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:
-
This is an error. Follow § 4.4.2.4 Font Load Failed.
4.4.2.3. 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:
-
Discard the input client state for this font.
-
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 § 4.4.2.4 Font Load Failed
4.4.2.4. Font Load Failed
If the font load or extension has failed the client should choose one of the following options:
-
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.
-
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.
-
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.
4.4.3. Load a Font in a User Agent with a HTTP Cache
The previous section § 4.4.2 Extending the Font Subset provides no guidance on how a user agent should handle saving client state between invocations of the subset extension algorithm. This section provides an algorithm that user agents which implement [fetch] should use to save client state to the user agent’s HTTP cache ([RFC9111]).
The inputs to this algorithm:
-
Font URL: a URL where the font to be extended is located.
-
Desired Subset Definition: a description of the desired minimum font subset.
The algorithm outputs:
-
A Font Subset which covers at minimum the input subset definition.
The algorithm:
-
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".
-
-
If the request is successful and the response is "fresh" (HTTP Caching § name-freshness) then invoke § 4.4.2 Extending the Font Subset with:
-
Font url set to the input font url.
-
Client state set to the response.
-
Desired subset definition set to the input subset definition.
-
Fetch algorithm set to [fetch].
Once that returns go to step 4.
-
-
Otherwise, invoke § 4.4.2 Extending the Font Subset with:
-
Font url set to the input font url.
-
Client state set to null.
-
Desired subset definition set to the input subset definition.
-
Fetch algorithm set to [fetch].
Once that returns go to step 4.
-
-
If the returned cache headers are non-null update the cache entry for the input font url with the returned client state and returned cache headers.
-
Return the font subset contained in the returned client state.
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.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. If the request has the fragment_id
field set and the file identified by path is a collection of fonts, 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:
-
Codepoints the client has: formed by the union of the codepoint sets specified by
codepoints_have
andindices_have
. The indices inindices_have
must be mapped to codepoints by the application of the codepoint reordering with a checksum matchingordering_checksum
. -
Codepoints the client needs: formed by the union of the codepoint sets specified by
codepoints_needed
andindices_needed
. The indices inindices_needed
must be mapped to codepoints by the application of the codepoint reordering with a checksum matchingordering_checksum
.
Likewise, the server can produce two sets of opentype layout feature tags:
-
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. -
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:
-
Axis space the client has: specified by
axis_space_have
. If any axes in the font are not specified inaxis_space_have
then for those axes add their entire interval from the original font. -
Axis space the client needs: specified by
axis_space_needed
. If any axes in the font are not specified inaxis_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 § 4.4.2.1 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 § 4.4.2.1 Handling PatchResponse to a font subset with checksum base_checksum
it must result
in an extended font subset:
-
That contains data for at least the union of the set of codepoints needed and the sets of codepoints the client already has.
-
That contains data for at least the union of the set of features needed and the sets of features the client already has.
-
That contains a variation axis space that covers at least the union of the axis space the client has and the axis space the client needs.
If the checksum of the original font computed by the procedure in § 4.6 Computing Checksums does not
match the checksum in PatchRequest.original_font_checksum
or PatchRequest.original_font_checksum
is unset, then:
-
The value of
original_font_checksum
must be set to the checksum of the original font computed by the procedure in § 4.6 Computing Checksums. -
The response must set the
codepoint_ordering
andordering_checksum
fields following § 4.7 Codepoint Reordering. -
The response must set the
original_features
field to the list of opentype layout feature tags that the original font has data for. -
If the original font has variation axes, the response must set the
original_axis_space
field to the axis space covered by the original font.
Additionally:
-
The format of the patch in the either the
patch
orreplace
fields must be one of those listed inaccept_patch_format
. -
If
patch
orreplacement
fields are set the value ofpatched_checksum
must be set to the checksum of the extended font subset. The checksum value must be computed by the procedure in § 4.6 Computing Checksums. -
If
patch
orreplacement
fields are set and the original font has variation axes, the response must set thesubset_axis_space
field to the axis space covered by the font subset. -
If
accept_patch_format
contains any unrecognized patch formats the server must ignore the unrecognized ones.
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:
-
If the request is malformed the server must instead respond with http status code 400 to indicate an error.
-
If the requested font is not recognized by the server it should respond with http status code 404 to indicate a not found error.
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 tranfser the patch subset server must support HTTP range requests [RFC9110] 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 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.
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.
4.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. |
5. Range Request Incremental Transfer
5.1. Introduction to Range Request
This section is not normative.
The Range Request method is a very simple method of incremental transfer, and has no server-side requirements (other than the server should be able to respond to byte-based range requests). The browser simply 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.
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 modelled after video playback on the web, where seeking in a video causes the browser to send a range request to the server.
5.2. Font organization
5.2.1. Background
This section is not normative.
A particular organization of font files is beneficial for improving the performance of the range-request IFT method. The range-request IFT method only works with [TRUETYPE], [OPENTYPE], [WOFF], or [WOFF2] files. All of these file formats use an sfnt
wrapper which provides a directory of tables inside the font file. A sfnt
-based font file is mainly composed of a collection of independent tables.
Using WOFF2 files with range-request mechanism doesn’t seem to be a viable option
5.2.2. Introduction
The term range-request optimized font is used to describe a font file organized for use with the range-request IFT method. Optimizing a font for the range-request IFT method does not change the file format of the font.
Note: Because optimizing a font does not change its file format, no new additional tooling is necessary to interact with these optimized fonts. They are still valid fonts, but with a particular internal organization.
Note: There are no MUST
-level requirements on the organization of a range-request optimized font. Any arbitrary font file may be considered to be a range-request optimized font. However, additional optimizations should increase the performance of loading in a browser via the range-request IFT method. Font creators are encouraged to enact as many of the optimizations listed in this section as are reasonable for the fonts they create.
5.2.3. Compression
Servers supporting the range-request IFT method should support compression via the [RFC9110] Content-Encoding
header or the [RFC9112] Transfer-Encoding
header, rather than having the font file itself be statically compressed.
A range-request optimized font file (the file itself) should not use any kind of compression other than [RFC7932] (commonly referred to as "Brotli") compression.
If Brotli compression is used in a range-request optimized font, it should use only one meta-block.
If Brotli compression is used in a range-request optimized font, its one meta-block should have the ISUNCOMPRESSED
bit set to 1.
Static file compression compatibility with range-request method
5.2.4. Table Ordering
The term outline table is used to describe these three tables, which carry different types of glyph outlines:
A range-request optimized font should have only one outline table.
No two tables in a range-request optimized font should share a tag name.
The outline table data in a range-request optimized font should lie at the end of the file.
If a CFF
table exists, the CharString data should lie at the end of the CFF
table.
If a CFF2
table exists, the CharString data should lie at the end of the CFF2
table.
5.2.5. Glyph Independence
Note: The goal of this section is to make every glyph independent from each other.
A range-request optimized font should not use Compound glyphs.
Supporting fonts with composite glyphs via range-request
Note: Compound glyphs can be flattened by inlining their component glyphs to become additional contours.
A range-request optimized font should not use Subroutines.
Note: CFF glyph CharStrings can be flattened by inlining subroutines to become additional CharString bytes.
5.2.6. Glyph Order
Glyphs inside a range-request optimized font should be sorted in the file to keep glyphs often used in the same documents close together in the file.
Note: Putting the most frequently used glyphs together in the font increases the likelihood that the browser can download a contiguous sequence of necessary glyphs in a single range-request, thereby minimizing overhead.
Note: Reordering glyphs in a font is the same conceptual operation as renaming glyphs to have different glyph IDs. Therefore, this operation cannot be completed if glyph IDs must be preserved. Because glyph IDs are internal to text processing procedures and are not persisted, this requirement is not expected to be particularly burdensome.
One suggested method of sorting the glyphs in the file is by usage document frequency inside a relevant corpus.
A corpus is defined to be a collection of documents, where a documents includes a collection of glyphs necessary to render some textual content.
Note: For a particular website, a corpus might be defined to be individual page loads of pages on that website.
The usage document frequency of a particular glyph inside a corpus is defined by the number of documents in the corpus which use that glyph, divided by the number of documents in the corpus.
Note: This is distinct, but similar, to the number of times the glyph is used throughout the entire corpus.
The usage document frequency of the glyphs in a range-request optimized font should be decreasing throughout the font; that is, the most frequently used glyphs should have the lowest glyph IDs.
Note: Glyph ID 0 cannot be renamed in OpenType, TrueType, WOFF, and WOFF 2 fonts. All other glyphs can be renamed.
Note: Because the goal is simply to minimize overhead by placing similarly-used glyphs together, it may actually be possible to do better than ordering by simple frequency for a particular corpus. For example, some corpuses may have cliques of glyphs which have different frequencies but which nevertheless always seem to be used together.
A suggested ordering is included in Appendix A below.
5.3. Browser Behaviors
5.3.1. First Request
When a browser encounters the CSS opt-in mechanism, it is instructed to use IFT to load the fonts. First, it follows the steps in the IFT method selection section above. If those steps result in using the range-request method, the rest of this section applies.
The IFT method selection involves a single round-trip to the server, and if the range-request method is being used, the server’s response starts sending the font file to the browser. The browser should start parsing the partial font data as it is being loaded from the server. The browser should not wait until the entire file has been received before parsing its contents.
There is a certain amount of data from the beginning of the font file which the browser should unconditionally download. The boundary at the end of this data is called the range-request threshold.
Note: The first request does not have to be a range request. If the browser expects the range-request threshold to lie within the first n
bytes of the font, the first request may be a range request for the first n
bytes of the font. However, a browser may instead make a non-range request, parse the data as it is being streamed from the server, and discover that it has reached the range-request threshold while data is still being streamed.
Once all the data before the range-request threshold has been loaded by the browser, the browser may either close this connection to the server, or it may choose to leave the connection open and let the font data continue loading in the background.
A browser may choose to add a [RFC9110] Range
header to the initial request during the IFT method selection if it has reason to believe the range it requests will be large enough and it prefers to not close this connection to the server.
Note: Different browsers may choose different range-request thresholds. Some browsers may treat this threshold as occuring at the end of the sfnt tableDirectory. Other browsers may treat this threshold as occurring just before any outline data, provided the outline data appears at the end of the font. Other browsers may place this threshold at the very beginning of the file, thereby treating the whole file as able to be downloaded with range-requests.
5.3.2. Subsequent Requests
After all the data before the range-request threshold has been loaded by the browser, the browser will determine which additional byte ranges in the file are necessary to load. It will then issue [RFC9110] HTTP Range Requests for at least those ranges.
Note: Browsers are encouraged to coalesce range requests for nearby areas of the file, to minimize the amount of range-request overhead required. Browsers are encouraged to inform these coalescing decisions from network configuration parameters and bandwidth / latency observations.
Note: If the font file has followed all of the organization guidelines above, all information required for laying out content and performing shaping will lie before any of the outline data in the file, and every glyph’s outline will be independent from every other glyph. Therefore, the browser can treat the range-request threshold as being just before outline data begins, and once it has loaded up to that threshold, it can lay out page content. After laying out the page, downloading all the necessary outlines can be done with a collection of independent and parallel range requests. This works particularly well for Chinese, Japanese, and Korean fonts, where 90% or more of the font data is outline data.
Note: Another valid alternative is to treat the entire font as residing on an asynchronous virtual filesystem, and have the browser track which ranges of the font it ended up reading during its normal operation. The browser could then request those regions in range requests.
5.4. Server Behaviors
Servers supporting the range-request IFT method must support [RFC9110] range requests.
Servers supporting the range-request IFT method should support compression via the [RFC9110] Content-Encoding
header or the [RFC9112] Transfer-Encoding
header, rather than having the font file itself be statically compressed.
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
In the patch subset method 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
Appendix A: Suggested glyph/character ordering
Note: This section describes ordering of characters, not glyph IDs, because the meaning of glyph IDs are not consistent across different fonts. To optimize a particular font according to the ordering listed here, the characters will have to be mapped to glyph IDs inside the font. This approach of mapping characters to glyphs for ordering purposes works particularly well for ideographic languages with large character sets.
Populate suggested character ordering for range-request method
Appendix B: Default Feature Tags and Encoding IDs
Tag | Encoded As |
---|---|
aalt | 1 |
abvf | default |
abvm | default |
abvs | default |
afrc | 2 |
akhn | default |
blwf | default |
blwm | default |
blws | default |
calt | default |
case | 3 |
ccmp | default |
cfar | default |
chws | default |
cjct | default |
clig | default |
cpct | 4 |
cpsp | 5 |
cswh | default |
curs | default |
cv01-cv99 | 6-104 |
c2pc | 105 |
c2sc | 106 |
dist | default |
dlig | 107 |
dnom | default |
dtls | default |
expt | 108 |
falt | 109 |
fin2 | default |
fin3 | default |
fina | default |
flac | default |
frac | default |
fwid | 110 |
half | default |
haln | default |
halt | 111 |
hist | 112 |
hkna | 113 |
hlig | 114 |
hngl | 115 |
hojo | 116 |
hwid | 117 |
init | default |
isol | default |
ital | 118 |
jalt | default |
jp78 | 119 |
jp83 | 120 |
jp90 | 121 |
jp04 | 122 |
kern | default |
lfbd | 123 |
liga | default |
ljmo | default |
lnum | 124 |
locl | default |
ltra | default |
ltrm | default |
mark | default |
med2 | default |
medi | default |
mgrk | 125 |
mkmk | default |
mset | default |
nalt | 126 |
nlck | 127 |
nukt | default |
numr | default |
onum | 128 |
opbd | 129 |
ordn | 130 |
ornm | 131 |
palt | 132 |
pcap | 133 |
pkna | 134 |
pnum | 135 |
pref | default |
pres | default |
pstf | default |
psts | default |
pwid | 136 |
qwid | 137 |
rand | default |
rclt | default |
rkrf | default |
rlig | default |
rphf | default |
rtbd | 138 |
rtla | default |
rtlm | default |
ruby | 139 |
rvrn | default |
salt | 140 |
sinf | 141 |
size | 142 |
smcp | 143 |
smpl | 144 |
ss01-ss20 | 145-164 |
ssty | default |
stch | default |
subs | 165 |
sups | 166 |
swsh | 167 |
titl | 168 |
tjmo | default |
tnam | 169 |
tnum | 170 |
trad | 171 |
twid | 172 |
unic | 173 |
valt | default |
vatu | default |
vchw | default |
vert | default |
vhal | 174 |
vjmo | default |
vkna | 175 |
vkrn | default |
vpal | default |
vrt2 | default |
vrtr | default |
zero | 176 |