single-file-cli 2.6.2 → 2.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,294 @@
1
+ // Writes a TrueType font in which every printable ASCII character is the same filled rectangle.
2
+ // Text set in it is a solid bar, so a page that loses the face does not merely reflow slightly —
3
+ // it changes from a black block to readable words, which no pixel comparison can miss.
4
+ //
5
+ // It is generated rather than borrowed for two reasons. A real font carries a licence and a few
6
+ // hundred kilobytes into a fixture directory, and, more importantly, the fonts already lying around
7
+ // for probing turned out to map no letters at all: text set in them silently fell back to a system
8
+ // family, which made a working fix look broken and cost an afternoon. A font built here maps
9
+ // exactly what it claims to map, and the shapes differ per variant so that "the right family was
10
+ // kept" is a visible statement and not only "some family was kept".
11
+ //
12
+ // Not a test. Run it to regenerate the committed .ttf files:
13
+ //
14
+ // node test/fidelity/make-font.js
15
+ //
16
+ import { writeFile } from "node:fs/promises";
17
+ import { join, dirname } from "node:path";
18
+ import { fileURLToPath } from "node:url";
19
+ import process from "node:process";
20
+
21
+ const UNITS_PER_EM = 1000;
22
+ const FIRST_CHARACTER_CODE = 0x20;
23
+ const LAST_CHARACTER_CODE = 0x7e;
24
+ const ASCENT = 800;
25
+ const DESCENT = -200;
26
+ const ADVANCE_WIDTH = 600;
27
+ const MAGIC_NUMBER = 0x5f0f3cf5;
28
+ const CHECKSUM_MAGIC = 0xb1b0afba;
29
+ const TABLE_RECORD_SIZE = 16;
30
+ const HEAD_CHECKSUM_ADJUSTMENT_OFFSET = 8;
31
+
32
+ // each fixture family is a different shape, so a screenshot says which face was used and not just
33
+ // that one was: a full block, a band across the middle, a bar sitting on the baseline
34
+ const VARIANTS = {
35
+ "block": { bottom: 0, top: 760, left: 60, right: 540 },
36
+ "band": { bottom: 260, top: 500, left: 40, right: 560 },
37
+ "bar": { bottom: 0, top: 160, left: 40, right: 560 }
38
+ };
39
+
40
+ export { createFont, VARIANTS };
41
+
42
+ if (import.meta.url === ("file://" + process.argv[1]) || import.meta.filename === process.argv[1]) {
43
+ const directory = join(dirname(fileURLToPath(import.meta.url)), "pages", "fonts");
44
+ await Promise.all(Object.keys(VARIANTS).map(async name => {
45
+ const path = join(directory, name + ".ttf");
46
+ await writeFile(path, createFont(Object.assign({ familyName: "Fidelity " + name }, VARIANTS[name])));
47
+ console.log("wrote " + path); // eslint-disable-line no-console
48
+ }));
49
+ }
50
+
51
+ function createFont({ familyName, top, bottom, left, right }) {
52
+ const glyph = buildGlyph({ top, bottom, left, right });
53
+ const tables = [
54
+ ["OS/2", buildOS2({ top, bottom })],
55
+ ["cmap", buildCmap()],
56
+ ["glyf", glyph],
57
+ ["head", buildHead({ top, bottom, left, right })],
58
+ ["hhea", buildHhea()],
59
+ ["hmtx", buildHmtx()],
60
+ ["loca", buildLoca(glyph.length)],
61
+ ["maxp", buildMaxp()],
62
+ ["name", buildName(familyName)],
63
+ ["post", buildPost()]
64
+ ];
65
+ return assemble(tables);
66
+ }
67
+
68
+ // the offset table, the table records and the tables themselves, followed by the one value that can
69
+ // only be computed once the whole file exists: the checksum adjustment in head
70
+ function assemble(tables) {
71
+ const headerSize = 12 + tables.length * TABLE_RECORD_SIZE;
72
+ const size = tables.reduce((total, [, content]) => total + align(content.length), headerSize);
73
+ const font = new Uint8Array(size);
74
+ const view = new DataView(font.buffer);
75
+ const entrySelector = Math.floor(Math.log2(tables.length));
76
+ const searchRange = 16 * (2 ** entrySelector);
77
+ view.setUint32(0, 0x00010000);
78
+ view.setUint16(4, tables.length);
79
+ view.setUint16(6, searchRange);
80
+ view.setUint16(8, entrySelector);
81
+ view.setUint16(10, tables.length * 16 - searchRange);
82
+ let offset = headerSize;
83
+ let headOffset;
84
+ tables.forEach(([tag, content], index) => {
85
+ const record = 12 + index * TABLE_RECORD_SIZE;
86
+ Array.from(tag).forEach((character, position) => view.setUint8(record + position, character.charCodeAt(0)));
87
+ view.setUint32(record + 4, checksum(content));
88
+ view.setUint32(record + 8, offset);
89
+ view.setUint32(record + 12, content.length);
90
+ font.set(content, offset);
91
+ if (tag == "head") {
92
+ headOffset = offset;
93
+ }
94
+ offset += align(content.length);
95
+ });
96
+ view.setUint32(headOffset + HEAD_CHECKSUM_ADJUSTMENT_OFFSET, (CHECKSUM_MAGIC - checksum(font)) >>> 0);
97
+ return font;
98
+ }
99
+
100
+ function checksum(content) {
101
+ const view = new DataView(content.buffer, content.byteOffset, content.byteLength);
102
+ let sum = 0;
103
+ for (let offset = 0; offset + 4 <= content.length; offset += 4) {
104
+ sum = (sum + view.getUint32(offset)) >>> 0;
105
+ }
106
+ // a table is padded to a multiple of four with zeroes, and the checksum is taken over the
107
+ // padded form: the trailing bytes are read as if those zeroes were already there
108
+ if (content.length % 4) {
109
+ let tail = 0;
110
+ for (let offset = content.length - content.length % 4; offset < content.length; offset++) {
111
+ tail = (tail << 8) | content[offset];
112
+ }
113
+ sum = (sum + (tail << (8 * (4 - content.length % 4)))) >>> 0;
114
+ }
115
+ return sum;
116
+ }
117
+
118
+ function align(length) {
119
+ return length + (length % 4 ? 4 - length % 4 : 0);
120
+ }
121
+
122
+ function buildHead({ top, bottom, left, right }) {
123
+ const writer = createWriter(54);
124
+ writer.uint32(0x00010000);
125
+ writer.uint32(0x00010000);
126
+ writer.uint32(0);
127
+ writer.uint32(MAGIC_NUMBER);
128
+ writer.uint16(0x0003);
129
+ writer.uint16(UNITS_PER_EM);
130
+ writer.uint32(0); writer.uint32(0);
131
+ writer.uint32(0); writer.uint32(0);
132
+ writer.int16(left); writer.int16(bottom); writer.int16(right); writer.int16(top);
133
+ writer.uint16(0);
134
+ writer.uint16(8);
135
+ writer.int16(2);
136
+ // the long form of loca, so that the offsets are plain byte counts rather than halves
137
+ writer.int16(1);
138
+ writer.int16(0);
139
+ return writer.content;
140
+ }
141
+
142
+ function buildHhea() {
143
+ const writer = createWriter(36);
144
+ writer.uint32(0x00010000);
145
+ writer.int16(ASCENT); writer.int16(DESCENT); writer.int16(0);
146
+ writer.uint16(ADVANCE_WIDTH);
147
+ writer.int16(0); writer.int16(0); writer.int16(ADVANCE_WIDTH);
148
+ writer.int16(1); writer.int16(0); writer.int16(0);
149
+ writer.int16(0); writer.int16(0); writer.int16(0); writer.int16(0);
150
+ writer.int16(0);
151
+ writer.uint16(2);
152
+ return writer.content;
153
+ }
154
+
155
+ function buildMaxp() {
156
+ const writer = createWriter(32);
157
+ writer.uint32(0x00010000);
158
+ writer.uint16(2);
159
+ writer.uint16(4); writer.uint16(1);
160
+ writer.uint16(0); writer.uint16(0);
161
+ writer.uint16(1); writer.uint16(0);
162
+ writer.uint16(0); writer.uint16(0); writer.uint16(0); writer.uint16(0); writer.uint16(0);
163
+ writer.uint16(0); writer.uint16(0);
164
+ return writer.content;
165
+ }
166
+
167
+ function buildHmtx() {
168
+ const writer = createWriter(8);
169
+ writer.uint16(ADVANCE_WIDTH); writer.int16(0);
170
+ writer.uint16(ADVANCE_WIDTH); writer.int16(0);
171
+ return writer.content;
172
+ }
173
+
174
+ // format 4, one segment covering printable ASCII and the terminator the format requires. Every
175
+ // character in the segment maps to the single glyph, including the space: text set in this font is
176
+ // one unbroken bar, which is exactly the point.
177
+ //
178
+ // The segment therefore has to name its glyph per character, through idRangeOffset and an array of
179
+ // indices. The shorter-looking route, a single idDelta added to the character code, maps a range
180
+ // LINEARLY — a font written that way claims a different glyph for every character, and the browser
181
+ // rejects the whole table as soon as one of them is past the last glyph
182
+ function buildCmap() {
183
+ const characterCount = LAST_CHARACTER_CODE - FIRST_CHARACTER_CODE + 1;
184
+ const subtableLength = 32 + characterCount * 2;
185
+ const writer = createWriter(12 + subtableLength);
186
+ writer.uint16(0); writer.uint16(1);
187
+ writer.uint16(3); writer.uint16(1); writer.uint32(12);
188
+ writer.uint16(4); writer.uint16(subtableLength); writer.uint16(0);
189
+ writer.uint16(4); writer.uint16(4); writer.uint16(1); writer.uint16(0);
190
+ writer.uint16(LAST_CHARACTER_CODE); writer.uint16(0xffff);
191
+ writer.uint16(0);
192
+ writer.uint16(FIRST_CHARACTER_CODE); writer.uint16(0xffff);
193
+ writer.uint16(0); writer.uint16(1);
194
+ // counted from the position of this very field, which is why the first segment's offset is the
195
+ // four bytes that the second segment's offset occupies
196
+ writer.uint16(4); writer.uint16(0);
197
+ for (let index = 0; index < characterCount; index++) {
198
+ writer.uint16(1);
199
+ }
200
+ return writer.content;
201
+ }
202
+
203
+ // glyph 0 is the empty .notdef, so the whole table is glyph 1: one closed contour of four on-curve
204
+ // points, given as deltas from the previous point. It is padded here rather than at assembly time,
205
+ // so that the offset loca gives for the end of the glyph stays inside the length glyf declares
206
+ function buildGlyph({ top, bottom, left, right }) {
207
+ const writer = createWriter(36);
208
+ writer.int16(1);
209
+ writer.int16(left); writer.int16(bottom); writer.int16(right); writer.int16(top);
210
+ writer.uint16(3);
211
+ writer.uint16(0);
212
+ writer.uint8(1); writer.uint8(1); writer.uint8(1); writer.uint8(1);
213
+ writer.int16(left); writer.int16(right - left); writer.int16(0); writer.int16(left - right);
214
+ writer.int16(bottom); writer.int16(0); writer.int16(top - bottom); writer.int16(0);
215
+ return writer.content;
216
+ }
217
+
218
+ function buildLoca(glyphLength) {
219
+ const writer = createWriter(12);
220
+ writer.uint32(0); writer.uint32(0); writer.uint32(align(glyphLength));
221
+ return writer.content;
222
+ }
223
+
224
+ function buildOS2({ top, bottom }) {
225
+ const writer = createWriter(96);
226
+ writer.uint16(4);
227
+ writer.int16(ADVANCE_WIDTH);
228
+ writer.uint16(400); writer.uint16(5); writer.uint16(0);
229
+ writer.int16(650); writer.int16(600); writer.int16(0); writer.int16(75);
230
+ writer.int16(650); writer.int16(600); writer.int16(0); writer.int16(350);
231
+ writer.int16(50); writer.int16(300);
232
+ writer.int16(0);
233
+ for (let index = 0; index < 10; index++) {
234
+ writer.uint8(0);
235
+ }
236
+ writer.uint32(1); writer.uint32(0); writer.uint32(0); writer.uint32(0);
237
+ Array.from("SFTD").forEach(character => writer.uint8(character.charCodeAt(0)));
238
+ writer.uint16(0x0040);
239
+ writer.uint16(FIRST_CHARACTER_CODE); writer.uint16(LAST_CHARACTER_CODE);
240
+ writer.int16(ASCENT); writer.int16(DESCENT); writer.int16(0);
241
+ writer.uint16(ASCENT); writer.uint16(-DESCENT);
242
+ writer.uint32(1); writer.uint32(0);
243
+ writer.int16(Math.round((top - bottom) / 2)); writer.int16(top);
244
+ writer.uint16(0); writer.uint16(FIRST_CHARACTER_CODE); writer.uint16(1);
245
+ return writer.content;
246
+ }
247
+
248
+ function buildName(familyName) {
249
+ const names = [[1, familyName], [2, "Regular"], [3, familyName + " Regular"], [4, familyName], [5, "Version 1.0"], [6, familyName.replace(/ /g, "")]];
250
+ const strings = names.map(([, value]) => encodeUTF16(value));
251
+ const header = 6 + names.length * 12;
252
+ const writer = createWriter(header + strings.reduce((total, string) => total + string.length, 0));
253
+ writer.uint16(0); writer.uint16(names.length); writer.uint16(header);
254
+ let offset = 0;
255
+ names.forEach(([identifier], index) => {
256
+ writer.uint16(3); writer.uint16(1); writer.uint16(0x0409); writer.uint16(identifier);
257
+ writer.uint16(strings[index].length); writer.uint16(offset);
258
+ offset += strings[index].length;
259
+ });
260
+ strings.forEach(string => string.forEach(byte => writer.uint8(byte)));
261
+ return writer.content;
262
+ }
263
+
264
+ function encodeUTF16(value) {
265
+ const bytes = [];
266
+ Array.from(value).forEach(character => {
267
+ const code = character.charCodeAt(0);
268
+ bytes.push(code >> 8, code & 0xff);
269
+ });
270
+ return bytes;
271
+ }
272
+
273
+ function buildPost() {
274
+ const writer = createWriter(32);
275
+ writer.uint32(0x00030000);
276
+ writer.uint32(0);
277
+ writer.int16(-100); writer.int16(50);
278
+ writer.uint32(1);
279
+ writer.uint32(0); writer.uint32(0); writer.uint32(0); writer.uint32(0);
280
+ return writer.content;
281
+ }
282
+
283
+ function createWriter(size) {
284
+ const content = new Uint8Array(size);
285
+ const view = new DataView(content.buffer);
286
+ let offset = 0;
287
+ return {
288
+ content,
289
+ uint8: value => view.setUint8(offset++, value),
290
+ int16: value => (view.setInt16(offset, value), offset += 2),
291
+ uint16: value => (view.setUint16(offset, value & 0xffff), offset += 2),
292
+ uint32: value => (view.setUint32(offset, value >>> 0), offset += 4)
293
+ };
294
+ }
@@ -0,0 +1,126 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="utf-8">
6
+ <title>Duplicate stylesheet</title>
7
+ <!--
8
+ Two <style> elements with byte-identical content. The archive writer groups them, stores the
9
+ content once as an entry, and points a <link> at it. Both elements are rewritten: the second
10
+ because it is a duplicate, the first because it is the one the duplicates were folded into —
11
+ leaving that one inline stored the same stylesheet twice, once as the entry and once in the
12
+ page.
13
+
14
+ The attributes are here because the rewrite replaces the element rather than editing it. An
15
+ id a script looks up and a class a selector matches have to survive that, and they were
16
+ being dropped: the archive came back with a bare <link>.
17
+ -->
18
+ <style id="palette" class="theme" data-role="tokens">
19
+ :root {
20
+ --ink: #17202a;
21
+ --paper: #f4f4f2;
22
+ --accent: #1f5673;
23
+ --edge: #c9c9c4;
24
+ }
25
+
26
+ body {
27
+ background: var(--paper);
28
+ color: var(--ink);
29
+ font: 16px/1.6 Georgia, "Times New Roman", serif;
30
+ margin: 0;
31
+ padding: 40px;
32
+ }
33
+
34
+ h1 {
35
+ font-size: 34px;
36
+ margin: 0 0 24px;
37
+ border-bottom: 3px solid var(--accent);
38
+ padding-bottom: 12px;
39
+ }
40
+
41
+ .grid {
42
+ display: grid;
43
+ grid-template-columns: repeat(3, 1fr);
44
+ gap: 16px;
45
+ margin: 24px 0;
46
+ }
47
+
48
+ .cell {
49
+ background: #fff;
50
+ border: 1px solid var(--edge);
51
+ padding: 20px;
52
+ }
53
+
54
+ .cell b {
55
+ color: var(--accent);
56
+ }
57
+
58
+ .rule {
59
+ height: 8px;
60
+ background: var(--accent);
61
+ margin: 32px 0;
62
+ }
63
+ </style>
64
+ <style>
65
+ :root {
66
+ --ink: #17202a;
67
+ --paper: #f4f4f2;
68
+ --accent: #1f5673;
69
+ --edge: #c9c9c4;
70
+ }
71
+
72
+ body {
73
+ background: var(--paper);
74
+ color: var(--ink);
75
+ font: 16px/1.6 Georgia, "Times New Roman", serif;
76
+ margin: 0;
77
+ padding: 40px;
78
+ }
79
+
80
+ h1 {
81
+ font-size: 34px;
82
+ margin: 0 0 24px;
83
+ border-bottom: 3px solid var(--accent);
84
+ padding-bottom: 12px;
85
+ }
86
+
87
+ .grid {
88
+ display: grid;
89
+ grid-template-columns: repeat(3, 1fr);
90
+ gap: 16px;
91
+ margin: 24px 0;
92
+ }
93
+
94
+ .cell {
95
+ background: #fff;
96
+ border: 1px solid var(--edge);
97
+ padding: 20px;
98
+ }
99
+
100
+ .cell b {
101
+ color: var(--accent);
102
+ }
103
+
104
+ .rule {
105
+ height: 8px;
106
+ background: var(--accent);
107
+ margin: 32px 0;
108
+ }
109
+ </style>
110
+ </head>
111
+
112
+ <body>
113
+ <h1>Duplicate stylesheet</h1>
114
+ <p>The two style elements above hold the same declarations. Everything on this page is drawn by
115
+ them, so a stylesheet that goes missing in the save takes the whole layout with it.</p>
116
+ <div class="grid">
117
+ <div class="cell"><b>One</b><br>Bordered cell drawn by the shared stylesheet.</div>
118
+ <div class="cell"><b>Two</b><br>Bordered cell drawn by the shared stylesheet.</div>
119
+ <div class="cell"><b>Three</b><br>Bordered cell drawn by the shared stylesheet.</div>
120
+ </div>
121
+ <div class="rule"></div>
122
+ <p>The grid, the rule above and the serif family all come from the custom properties declared in
123
+ the same block, so the comparison fails loudly rather than subtly.</p>
124
+ </body>
125
+
126
+ </html>
@@ -0,0 +1,53 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="utf-8">
6
+ <title>Frame fonts</title>
7
+ <!--
8
+ The frame is sandboxed, which gives it an opaque origin and puts its contentDocument out of
9
+ reach. SingleFile then re-parses it from its srcdoc with DOMParser, and that document is
10
+ never rendered: it reports no font as used at all.
11
+
12
+ An empty list of used fonts is not a short one. Every rendered element has a computed
13
+ font-family, so nothing rendered can report none — an empty list means the styles could not
14
+ be read. Read as an answer instead of as an absence, it said "this frame uses no font" and
15
+ every face the frame declared was pruned. Measured on derstandard.at, where a newsletter box
16
+ inside such a frame fell back to a system font, and on MDN, where the text in the CSS demo
17
+ reflowed.
18
+
19
+ The frame declares its own face and uses it, and the face is not declared anywhere in the
20
+ parent: nothing outside the frame can keep it alive.
21
+ -->
22
+ <style>
23
+ body {
24
+ background: #fff;
25
+ margin: 0;
26
+ padding: 40px;
27
+ font: 20px/1.5 monospace;
28
+ }
29
+
30
+ iframe {
31
+ width: 700px;
32
+ height: 220px;
33
+ border: 2px solid #333;
34
+ }
35
+ </style>
36
+ </head>
37
+
38
+ <body>
39
+ <p>The frame below declares and uses a face that the page around it never names.</p>
40
+ <iframe sandbox title="framed" srcdoc="
41
+ <!DOCTYPE html>
42
+ <meta charset=&quot;utf-8&quot;>
43
+ <style>
44
+ @font-face { font-family: &quot;Fidelity Frame&quot;; src: url(../fonts/bar.ttf) format(&quot;truetype&quot;) }
45
+ body { margin: 0; padding: 20px; background: #fff }
46
+ p { font: 48px &quot;Fidelity Frame&quot;, serif; margin: 0 0 16px }
47
+ </style>
48
+ <p>abcdefghij</p>
49
+ <p>klmnopqrst</p>
50
+ "></iframe>
51
+ </body>
52
+
53
+ </html>
@@ -0,0 +1,30 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="utf-8">
6
+ <title>Linked stylesheet</title>
7
+ <!--
8
+ An external stylesheet, linked with the attributes a page uses to find it again: an id a
9
+ theme switcher looks up, a class a selector matches, a data attribute a script reads.
10
+
11
+ A plain save has nowhere to put an external stylesheet, so the link becomes a style element
12
+ holding its content — a brand-new element, built with the media and the text and nothing
13
+ else. Everything the page used to identify that stylesheet was dropped, on every plain save
14
+ of every page with an external stylesheet. The archive path had the same defect in the other
15
+ direction and was fixed first; this is its mirror.
16
+ -->
17
+ <link rel="stylesheet" type="text/css" href="theme.css" id="theme" class="site-theme" data-role="tokens" title="Site theme">
18
+ </head>
19
+
20
+ <body>
21
+ <h1>Linked stylesheet</h1>
22
+ <p>Everything below is drawn by the linked stylesheet, so losing it in the save is not subtle.</p>
23
+ <div class="panel"><b>Panel</b> — bordered by the linked stylesheet.</div>
24
+ <div class="panel"><b>Panel</b> — bordered by the linked stylesheet.</div>
25
+ <div class="stripe"></div>
26
+ <p>The element carrying that stylesheet is looked up by id, by class and by data attribute, none
27
+ of which survives a rewrite that keeps only the text.</p>
28
+ </body>
29
+
30
+ </html>
@@ -0,0 +1,38 @@
1
+ :root {
2
+ --ink: #1b1b1f;
3
+ --paper: #fbfaf7;
4
+ --accent: #7a3b2e;
5
+ --edge: #d6d2c8;
6
+ }
7
+
8
+ body {
9
+ background: var(--paper);
10
+ color: var(--ink);
11
+ font: 16px/1.6 Georgia, "Times New Roman", serif;
12
+ margin: 0;
13
+ padding: 40px;
14
+ }
15
+
16
+ h1 {
17
+ font-size: 32px;
18
+ margin: 0 0 20px;
19
+ border-bottom: 4px solid var(--accent);
20
+ padding-bottom: 10px;
21
+ }
22
+
23
+ .panel {
24
+ border: 1px solid var(--edge);
25
+ background: #fff;
26
+ padding: 20px;
27
+ margin: 0 0 16px;
28
+ }
29
+
30
+ .panel b {
31
+ color: var(--accent);
32
+ }
33
+
34
+ .stripe {
35
+ height: 10px;
36
+ background: var(--accent);
37
+ margin: 24px 0;
38
+ }
@@ -0,0 +1,59 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="utf-8">
6
+ <title>Synthetic italic</title>
7
+ <!--
8
+ Two faces, both declared with no style of their own, both drawn. The second is asked for in
9
+ italic, and since no italic face is declared the browser slants the normal one — a synthetic
10
+ oblique. The face is used; it is simply used at an angle.
11
+
12
+ What the page reports as its used fonts is read off the computed styles, and the family is
13
+ kept only when a loaded face answers for the computed font-style. A synthesized style has no
14
+ face behind it, so the match fails and the family is not recorded as used at all — while the
15
+ first sample keeps the list non-empty, so the guard for "nothing could be read" does not
16
+ apply either. The italic family is then declared, drawn, and pruned.
17
+ -->
18
+ <style>
19
+ @font-face {
20
+ font-family: "Fidelity Block";
21
+ src: url(../fonts/block.ttf) format("truetype");
22
+ }
23
+
24
+ @font-face {
25
+ font-family: "Fidelity Bar";
26
+ src: url(../fonts/bar.ttf) format("truetype");
27
+ }
28
+
29
+ body {
30
+ background: #fff;
31
+ margin: 0;
32
+ padding: 40px;
33
+ font: 20px/1.5 monospace;
34
+ }
35
+
36
+ .sample {
37
+ font-size: 48px;
38
+ margin: 0 0 24px;
39
+ }
40
+
41
+ .upright {
42
+ font-family: "Fidelity Block";
43
+ }
44
+
45
+ .slanted {
46
+ font-family: "Fidelity Bar";
47
+ font-style: italic;
48
+ }
49
+ </style>
50
+ </head>
51
+
52
+ <body>
53
+ <p>Upright, drawn with a face declared for that style:</p>
54
+ <p class="sample upright">abcdefghij</p>
55
+ <p>Italic, drawn by slanting a face declared without one:</p>
56
+ <p class="sample slanted">abcdefghij</p>
57
+ </body>
58
+
59
+ </html>
@@ -0,0 +1,75 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="utf-8">
6
+ <title>Unresolved font property</title>
7
+ <!--
8
+ The property holds a whole font shorthand, not a family list. The browser substitutes it and
9
+ draws the text in the face it names; the minifier must not read it as a list of families,
10
+ because the words in it are a style and a size and reading them as names would invent
11
+ families the document never had. So the family is genuinely unnameable from the stylesheets,
12
+ and the only thing left that knows it is the list of fonts the rendering reported.
13
+
14
+ The shape matters. A var() in family position — even one nested in a fallback — is walked
15
+ branch by branch and usually resolves, so a fixture written that way keeps its face through
16
+ the ordinary path and proves nothing about the case it was built for. This one was.
17
+
18
+ This is the page the pruning policy for undetermined values is decided on. Keeping every
19
+ declared face renders exactly like keeping the right one, so the two halves are checked
20
+ apart: the pixels say the face that was drawn survived, and the markup says the faces that
21
+ were not drawn are gone. A build that gives up on the document passes the first and fails
22
+ the second, which is what used to happen for every page holding one unreadable value.
23
+ -->
24
+ <style>
25
+ @font-face {
26
+ font-family: "Fidelity Block";
27
+ src: url(../fonts/block.ttf) format("truetype");
28
+ }
29
+
30
+ @font-face {
31
+ font-family: "Fidelity Unused One";
32
+ src: url(../fonts/band.ttf) format("truetype");
33
+ }
34
+
35
+ @font-face {
36
+ font-family: "Fidelity Unused Two";
37
+ src: url(../fonts/bar.ttf) format("truetype");
38
+ }
39
+
40
+ body {
41
+ background: #fff;
42
+ margin: 0;
43
+ padding: 40px;
44
+ font: 20px/1.5 monospace;
45
+ }
46
+
47
+ .card {
48
+ --sample-font: 48px "Fidelity Block";
49
+ }
50
+
51
+ /* a second declaration, so that the property has no single value to substitute into the
52
+ shorthand. It names a face nothing draws with: a resolver that read these values as
53
+ family lists would keep that one, which is the other way this can go wrong */
54
+ .note {
55
+ --sample-font: 48px "Fidelity Unused One";
56
+ }
57
+
58
+ .card .sample {
59
+ font: var(--sample-font);
60
+ margin: 0;
61
+ }
62
+ </style>
63
+ </head>
64
+
65
+ <body>
66
+ <div class="card">
67
+ <p>The family below is named inside a property holding a whole font shorthand.</p>
68
+ <p class="sample">abcdefghij</p>
69
+ </div>
70
+ <div class="note">
71
+ <p>This section declares the same property with another value and draws nothing with it.</p>
72
+ </div>
73
+ </body>
74
+
75
+ </html>