@flighthq/font-formats 0.3.1-next.209.43ef1bc

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.
Files changed (83) hide show
  1. package/dist/cffCharstring.d.ts +13 -0
  2. package/dist/cffCharstring.d.ts.map +1 -0
  3. package/dist/cffCharstring.js +336 -0
  4. package/dist/cffCharstring.js.map +1 -0
  5. package/dist/cffDict.d.ts +8 -0
  6. package/dist/cffDict.d.ts.map +1 -0
  7. package/dist/cffDict.js +118 -0
  8. package/dist/cffDict.js.map +1 -0
  9. package/dist/cffFdSelect.d.ts +2 -0
  10. package/dist/cffFdSelect.d.ts.map +1 -0
  11. package/dist/cffFdSelect.js +60 -0
  12. package/dist/cffFdSelect.js.map +1 -0
  13. package/dist/cffIndex.d.ts +3 -0
  14. package/dist/cffIndex.d.ts.map +1 -0
  15. package/dist/cffIndex.js +52 -0
  16. package/dist/cffIndex.js.map +1 -0
  17. package/dist/cffTable.d.ts +3 -0
  18. package/dist/cffTable.d.ts.map +1 -0
  19. package/dist/cffTable.js +134 -0
  20. package/dist/cffTable.js.map +1 -0
  21. package/dist/contract.d.ts +15 -0
  22. package/dist/contract.d.ts.map +1 -0
  23. package/dist/contract.js +15 -0
  24. package/dist/contract.js.map +1 -0
  25. package/dist/index.d.ts +2 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +2 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/openTypeCmap.d.ts +5 -0
  30. package/dist/openTypeCmap.d.ts.map +1 -0
  31. package/dist/openTypeCmap.js +161 -0
  32. package/dist/openTypeCmap.js.map +1 -0
  33. package/dist/openTypeGlyf.d.ts +4 -0
  34. package/dist/openTypeGlyf.d.ts.map +1 -0
  35. package/dist/openTypeGlyf.js +229 -0
  36. package/dist/openTypeGlyf.js.map +1 -0
  37. package/dist/openTypeGlyphOutlineSource.d.ts +4 -0
  38. package/dist/openTypeGlyphOutlineSource.d.ts.map +1 -0
  39. package/dist/openTypeGlyphOutlineSource.js +168 -0
  40. package/dist/openTypeGlyphOutlineSource.js.map +1 -0
  41. package/dist/openTypeMetrics.d.ts +6 -0
  42. package/dist/openTypeMetrics.d.ts.map +1 -0
  43. package/dist/openTypeMetrics.js +74 -0
  44. package/dist/openTypeMetrics.js.map +1 -0
  45. package/dist/openTypeTestHelper.d.ts +26 -0
  46. package/dist/openTypeTestHelper.d.ts.map +1 -0
  47. package/dist/openTypeTestHelper.js +494 -0
  48. package/dist/openTypeTestHelper.js.map +1 -0
  49. package/dist/sfntAssembly.d.ts +7 -0
  50. package/dist/sfntAssembly.d.ts.map +1 -0
  51. package/dist/sfntAssembly.js +77 -0
  52. package/dist/sfntAssembly.js.map +1 -0
  53. package/dist/sfntTableDirectory.d.ts +4 -0
  54. package/dist/sfntTableDirectory.d.ts.map +1 -0
  55. package/dist/sfntTableDirectory.js +39 -0
  56. package/dist/sfntTableDirectory.js.map +1 -0
  57. package/dist/woff2Font.d.ts +6 -0
  58. package/dist/woff2Font.d.ts.map +1 -0
  59. package/dist/woff2Font.js +210 -0
  60. package/dist/woff2Font.js.map +1 -0
  61. package/dist/woff2GlyfTransform.d.ts +6 -0
  62. package/dist/woff2GlyfTransform.d.ts.map +1 -0
  63. package/dist/woff2GlyfTransform.js +104 -0
  64. package/dist/woff2GlyfTransform.js.map +1 -0
  65. package/dist/woffFont.d.ts +6 -0
  66. package/dist/woffFont.d.ts.map +1 -0
  67. package/dist/woffFont.js +112 -0
  68. package/dist/woffFont.js.map +1 -0
  69. package/package.json +50 -0
  70. package/src/cffCharstring.test.ts +177 -0
  71. package/src/cffDict.test.ts +67 -0
  72. package/src/cffFdSelect.test.ts +84 -0
  73. package/src/cffIndex.test.ts +97 -0
  74. package/src/cffTable.test.ts +84 -0
  75. package/src/openTypeCmap.test.ts +124 -0
  76. package/src/openTypeGlyf.test.ts +168 -0
  77. package/src/openTypeGlyphOutlineSource.test.ts +298 -0
  78. package/src/openTypeMetrics.test.ts +103 -0
  79. package/src/sfntAssembly.test.ts +92 -0
  80. package/src/sfntTableDirectory.test.ts +64 -0
  81. package/src/woff2Font.test.ts +125 -0
  82. package/src/woff2GlyfTransform.test.ts +88 -0
  83. package/src/woffFont.test.ts +167 -0
@@ -0,0 +1,39 @@
1
+ // The sfnt container's table directory: a fixed 12-byte header followed by one 16-byte record per
2
+ // table, each record naming a four-character tag and the table's byte range within the file. Every
3
+ // OpenType flavor shares this layout — what differs is only which tables are present — so this is the
4
+ // one reader every table parser starts from.
5
+ //
6
+ // Offsets and tags are facts about the format. Nothing here interprets a table's contents.
7
+ const SFNT_HEADER_BYTES = 12;
8
+ const SFNT_TABLE_RECORD_BYTES = 16;
9
+ // Reads the directory and clamps every entry to the bytes actually present. A record pointing past the
10
+ // end of the buffer is dropped rather than trusted or thrown on: a truncated download is an ordinary
11
+ // thing to be handed, and the caller distinguishes "table absent" from "file damaged" by comparing
12
+ // `declaredTableCount` against the size of `tables` — which is exactly what `explainOpenTypeFont`
13
+ // reports. Returns the null sentinel only when the header itself cannot be read.
14
+ export function readSfntTableDirectory(bytes) {
15
+ if (bytes.byteLength < SFNT_HEADER_BYTES)
16
+ return null;
17
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
18
+ const declaredTableCount = view.getUint16(4);
19
+ const tables = new Map();
20
+ for (let index = 0; index < declaredTableCount; index += 1) {
21
+ const record = SFNT_HEADER_BYTES + index * SFNT_TABLE_RECORD_BYTES;
22
+ // The record itself must be inside the buffer before any field of it can be read.
23
+ if (record + SFNT_TABLE_RECORD_BYTES > bytes.byteLength)
24
+ break;
25
+ const offset = view.getUint32(record + 8);
26
+ const length = view.getUint32(record + 12);
27
+ if (offset + length > bytes.byteLength)
28
+ continue;
29
+ tables.set(readSfntTag(bytes, record), { length, offset });
30
+ }
31
+ return { declaredTableCount, sfntVersion: view.getUint32(0), tables };
32
+ }
33
+ // A table tag is four bytes read as characters rather than as a number, because that is how every
34
+ // specification, every font tool, and every error message spells it — `glyf`, `cmap`, `CFF ` with its
35
+ // significant trailing space. Keeping the string form means a tag never has to be decoded by a reader.
36
+ export function readSfntTag(bytes, offset) {
37
+ return String.fromCharCode(bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3]);
38
+ }
39
+ //# sourceMappingURL=sfntTableDirectory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sfntTableDirectory.js","sourceRoot":"","sources":["../src/sfntTableDirectory.ts"],"names":[],"mappings":"AAEA,kGAAkG;AAClG,mGAAmG;AACnG,sGAAsG;AACtG,6CAA6C;AAC7C,EAAE;AACF,2FAA2F;AAE3F,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAC7B,MAAM,uBAAuB,GAAG,EAAE,CAAC;AAEnC,uGAAuG;AACvG,qGAAqG;AACrG,mGAAmG;AACnG,kGAAkG;AAClG,iFAAiF;AACjF,MAAM,UAAU,sBAAsB,CAAC,KAA2B;IAChE,IAAI,KAAK,CAAC,UAAU,GAAG,iBAAiB;QAAE,OAAO,IAAI,CAAC;IAEtD,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IAC5E,MAAM,kBAAkB,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IAC7C,MAAM,MAAM,GAAG,IAAI,GAAG,EAA8C,CAAC;IAErE,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,kBAAkB,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QAC3D,MAAM,MAAM,GAAG,iBAAiB,GAAG,KAAK,GAAG,uBAAuB,CAAC;QACnE,kFAAkF;QAClF,IAAI,MAAM,GAAG,uBAAuB,GAAG,KAAK,CAAC,UAAU;YAAE,MAAM;QAE/D,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;QAC3C,IAAI,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC,UAAU;YAAE,SAAS;QAEjD,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC7D,CAAC;IAED,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;AACxE,CAAC;AAED,kGAAkG;AAClG,sGAAsG;AACtG,uGAAuG;AACvG,MAAM,UAAU,WAAW,CAAC,KAA2B,EAAE,MAAc;IACrE,OAAO,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,MAAM,CAAE,EAAE,KAAK,CAAC,MAAM,GAAG,CAAC,CAAE,EAAE,KAAK,CAAC,MAAM,GAAG,CAAC,CAAE,EAAE,KAAK,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC,CAAC;AACzG,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { Woff2TableDirectory } from '@flighthq/types/contract';
2
+ import { Compression } from '@flighthq/types/contract';
3
+ export declare const WOFF2_COMPRESSION: Compression;
4
+ export declare function readWoff2Font(bytes: Readonly<Uint8Array>, decompress: ((compressed: Readonly<Uint8Array>, uncompressedLength: number) => Uint8Array | null) | null, reverseTransform: ((tag: string, transformed: Readonly<Uint8Array>, tables: ReadonlyMap<string, Uint8Array>) => Uint8Array | null) | null): Uint8Array | null;
5
+ export declare function readWoff2TableDirectory(bytes: Readonly<Uint8Array>): Woff2TableDirectory | null;
6
+ //# sourceMappingURL=woff2Font.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"woff2Font.d.ts","sourceRoot":"","sources":["../src/woff2Font.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAmB,MAAM,0BAA0B,CAAC;AACrF,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AA4FvD,eAAO,MAAM,iBAAiB,EAAE,WAAgC,CAAC;AAMjE,wBAAgB,aAAa,CAC3B,KAAK,EAAE,QAAQ,CAAC,UAAU,CAAC,EAC3B,UAAU,EAAE,CAAC,CAAC,UAAU,EAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,kBAAkB,EAAE,MAAM,KAAK,UAAU,GAAG,IAAI,CAAC,GAAG,IAAI,EACxG,gBAAgB,EACZ,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,KAAK,UAAU,GAAG,IAAI,CAAC,GAChH,IAAI,GACP,UAAU,GAAG,IAAI,CA6CnB;AAMD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,mBAAmB,GAAG,IAAI,CAqD/F"}
@@ -0,0 +1,210 @@
1
+ import { Compression } from '@flighthq/types/contract';
2
+ import { assembleSfntFont, packSfntTag } from './sfntAssembly';
3
+ // WOFF2 is WOFF's wrapper idea taken two steps further, and both steps are why it needs its own reader
4
+ // rather than a flag on the WOFF one.
5
+ //
6
+ // 1. ONE Brotli STREAM FOR THE WHOLE FONT, not one deflate stream per table. Tables are concatenated
7
+ // before compression, so nothing can be read until the entire stream is decompressed, and the
8
+ // directory carries lengths rather than offsets — there are no offsets to carry.
9
+ // 2. TABLES MAY BE *TRANSFORMED*, not merely compressed. A transformed table is not the sfnt table in
10
+ // compressed form; it is a different byte layout that must be reversed to get the table back.
11
+ //
12
+ // The container layout is an interface fact about the format. The reading is Flight's own.
13
+ //
14
+ // ★ NO DECOMPRESSOR AND NO DICTIONARY SHIP WITH THIS. Brotli's decoder needs a large static dictionary
15
+ // that is data rather than rules, so Flight registers nothing and the caller registers a decompressor
16
+ // they already have — `zlib` on Node, an ordinary package in a browser. That is the same registry seam
17
+ // WOFF already uses, and it keeps a `.ttf` reader from carrying a codec it never calls.
18
+ const WOFF2_HEADER_BYTES = 48;
19
+ // The 63 tags a directory entry can name by index instead of spelling out. The order is fixed by the
20
+ // format — index 63 means an arbitrary tag follows as four bytes instead.
21
+ const WOFF2_KNOWN_TAGS = [
22
+ 'cmap',
23
+ 'head',
24
+ 'hhea',
25
+ 'hmtx',
26
+ 'maxp',
27
+ 'name',
28
+ 'OS/2',
29
+ 'post',
30
+ 'cvt ',
31
+ 'fpgm',
32
+ 'glyf',
33
+ 'loca',
34
+ 'prep',
35
+ 'CFF ',
36
+ 'VORG',
37
+ 'EBDT',
38
+ 'EBLC',
39
+ 'gasp',
40
+ 'hdmx',
41
+ 'kern',
42
+ 'LTSH',
43
+ 'PCLT',
44
+ 'VDMX',
45
+ 'vhea',
46
+ 'vmtx',
47
+ 'BASE',
48
+ 'GDEF',
49
+ 'GPOS',
50
+ 'GSUB',
51
+ 'EBSC',
52
+ 'JSTF',
53
+ 'MATH',
54
+ 'CBDT',
55
+ 'CBLC',
56
+ 'COLR',
57
+ 'CPAL',
58
+ 'SVG ',
59
+ 'sbix',
60
+ 'acnt',
61
+ 'avar',
62
+ 'bdat',
63
+ 'bloc',
64
+ 'bsln',
65
+ 'cvar',
66
+ 'fdsc',
67
+ 'feat',
68
+ 'fmtx',
69
+ 'fvar',
70
+ 'gvar',
71
+ 'hsty',
72
+ 'just',
73
+ 'lcar',
74
+ 'mort',
75
+ 'morx',
76
+ 'opbd',
77
+ 'prop',
78
+ 'trak',
79
+ 'Zapf',
80
+ 'Silf',
81
+ 'Glat',
82
+ 'Gloc',
83
+ 'Feat',
84
+ 'Sill',
85
+ ];
86
+ // The compression this container uses. Named through the shared vocabulary rather than a local constant
87
+ // so a caller registering a codec and this module asking for one cannot drift apart.
88
+ export const WOFF2_COMPRESSION = Compression.Brotli;
89
+ // Rebuilds the plain sfnt a WOFF2 wraps. Returns the null sentinel for a malformed container, for a
90
+ // missing decompressor, and for a transform this package cannot reverse — the caller distinguishes those
91
+ // through `explainOpenTypeFont`, because the remedies are a repaired file, one line of registration, and
92
+ // a different producer respectively.
93
+ export function readWoff2Font(bytes, decompress, reverseTransform) {
94
+ const directory = readWoff2TableDirectory(bytes);
95
+ if (directory === null)
96
+ return null;
97
+ if (decompress === null)
98
+ return null;
99
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
100
+ const flavor = view.getUint32(4);
101
+ const totalCompressedSize = view.getUint32(20);
102
+ // The compressed stream begins immediately after the directory, whose length is only known once it has
103
+ // been walked. Deriving the start from the end of the file instead lands inside the format's trailing
104
+ // padding, which decodes as a valid but empty stream rather than failing.
105
+ const streamEnd = directory.streamStart + totalCompressedSize;
106
+ if (streamEnd > bytes.byteLength)
107
+ return null;
108
+ const stream = decompress(bytes.subarray(directory.streamStart, streamEnd), directory.totalUncompressedLength);
109
+ if (stream === null || stream.byteLength < directory.totalUncompressedLength)
110
+ return null;
111
+ // Tables sit end to end in the decompressed stream, in directory order, each occupying its transformed
112
+ // length where it was transformed and its original length where it was not.
113
+ const raw = new Map();
114
+ let at = 0;
115
+ for (const entry of directory.entries) {
116
+ const length = entry.transformed ? entry.transformLength : entry.originalLength;
117
+ if (at + length > stream.byteLength)
118
+ return null;
119
+ raw.set(entry.tag, stream.subarray(at, at + length));
120
+ at += length;
121
+ }
122
+ const tables = [];
123
+ for (const entry of directory.entries) {
124
+ const stored = raw.get(entry.tag);
125
+ if (!entry.transformed) {
126
+ tables.push({ data: stored, tag: packSfntTag(entry.tag) });
127
+ continue;
128
+ }
129
+ // A transform this package cannot reverse is refused rather than passed through. Emitting the
130
+ // transformed bytes under the table's own tag would produce a font whose directory says `glyf` over
131
+ // something that is not a `glyf` — readable, wrong, and silent.
132
+ if (reverseTransform === null)
133
+ return null;
134
+ const restored = reverseTransform(entry.tag, stored, raw);
135
+ if (restored === null)
136
+ return null;
137
+ tables.push({ data: restored, tag: packSfntTag(entry.tag) });
138
+ }
139
+ return assembleSfntFont(flavor, tables);
140
+ }
141
+ // The directory a WOFF2 carries in place of sfnt's offset table: one variable-length record per table,
142
+ // naming a tag, whether it was transformed, and the lengths on both sides of that transform. Returns the
143
+ // null sentinel rather than a partial directory, since a desynchronised walk yields entries whose tags
144
+ // and lengths are real values read at the wrong offsets.
145
+ export function readWoff2TableDirectory(bytes) {
146
+ if (bytes.byteLength < WOFF2_HEADER_BYTES)
147
+ return null;
148
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
149
+ const tableCount = view.getUint16(12);
150
+ if (tableCount === 0)
151
+ return null;
152
+ const cursor = { at: WOFF2_HEADER_BYTES };
153
+ const entries = [];
154
+ let totalUncompressedLength = 0;
155
+ for (let index = 0; index < tableCount; index += 1) {
156
+ if (cursor.at >= bytes.byteLength)
157
+ return null;
158
+ const flags = view.getUint8(cursor.at);
159
+ cursor.at += 1;
160
+ const tagIndex = flags & 0x3f;
161
+ const transformVersion = (flags >> 6) & 0x03;
162
+ let tag;
163
+ if (tagIndex === 0x3f) {
164
+ if (cursor.at + 4 > bytes.byteLength)
165
+ return null;
166
+ tag = String.fromCharCode(view.getUint8(cursor.at), view.getUint8(cursor.at + 1), view.getUint8(cursor.at + 2), view.getUint8(cursor.at + 3));
167
+ cursor.at += 4;
168
+ }
169
+ else {
170
+ tag = WOFF2_KNOWN_TAGS[tagIndex];
171
+ }
172
+ const originalLength = readWoff2Base128(view, cursor, bytes.byteLength);
173
+ if (originalLength < 0)
174
+ return null;
175
+ // The sense of `transformVersion` is INVERTED for the glyph pair, and getting this backwards is the
176
+ // mistake that reads a length field that is not there: for `glyf` and `loca`, version 3 means the
177
+ // null transform, while for every other table it is version 0 that means untransformed.
178
+ const isGlyphPair = tag === 'glyf' || tag === 'loca';
179
+ const transformed = isGlyphPair ? transformVersion !== 3 : transformVersion !== 0;
180
+ let transformLength = originalLength;
181
+ if (transformed) {
182
+ transformLength = readWoff2Base128(view, cursor, bytes.byteLength);
183
+ if (transformLength < 0)
184
+ return null;
185
+ }
186
+ entries.push({ originalLength, tag, transformLength, transformVersion, transformed });
187
+ totalUncompressedLength += transformed ? transformLength : originalLength;
188
+ }
189
+ return { entries, streamStart: cursor.at, totalUncompressedLength };
190
+ }
191
+ // UIntBase128: seven bits per byte, most significant first, high bit set on every byte but the last.
192
+ // Returns -1 for an unterminated or over-long value rather than a truncated number, so a caller cannot
193
+ // mistake a malformed length for a small one. Five bytes is the most a 32-bit value can occupy, and a
194
+ // leading zero byte is disallowed because it makes one value spellable two ways.
195
+ function readWoff2Base128(view, cursor, end) {
196
+ let value = 0;
197
+ for (let byte = 0; byte < 5; byte += 1) {
198
+ if (cursor.at >= end)
199
+ return -1;
200
+ const next = view.getUint8(cursor.at);
201
+ cursor.at += 1;
202
+ if (byte === 0 && next === 0x80)
203
+ return -1;
204
+ value = value * 128 + (next & 0x7f);
205
+ if ((next & 0x80) === 0)
206
+ return value > 0xffffffff ? -1 : value;
207
+ }
208
+ return -1;
209
+ }
210
+ //# sourceMappingURL=woff2Font.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"woff2Font.js","sourceRoot":"","sources":["../src/woff2Font.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAEvD,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE/D,uGAAuG;AACvG,sCAAsC;AACtC,EAAE;AACF,uGAAuG;AACvG,mGAAmG;AACnG,sFAAsF;AACtF,wGAAwG;AACxG,mGAAmG;AACnG,EAAE;AACF,2FAA2F;AAC3F,EAAE;AACF,uGAAuG;AACvG,sGAAsG;AACtG,uGAAuG;AACvG,wFAAwF;AAExF,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAE9B,qGAAqG;AACrG,0EAA0E;AAC1E,MAAM,gBAAgB,GAAsB;IAC1C,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;IACN,MAAM;CACP,CAAC;AAEF,wGAAwG;AACxG,qFAAqF;AACrF,MAAM,CAAC,MAAM,iBAAiB,GAAgB,WAAW,CAAC,MAAM,CAAC;AAEjE,oGAAoG;AACpG,yGAAyG;AACzG,yGAAyG;AACzG,qCAAqC;AACrC,MAAM,UAAU,aAAa,CAC3B,KAA2B,EAC3B,UAAwG,EACxG,gBAEQ;IAER,MAAM,SAAS,GAAG,uBAAuB,CAAC,KAAK,CAAC,CAAC;IACjD,IAAI,SAAS,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACpC,IAAI,UAAU,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAErC,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IAC5E,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IACjC,MAAM,mBAAmB,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IAC/C,uGAAuG;IACvG,sGAAsG;IACtG,0EAA0E;IAC1E,MAAM,SAAS,GAAG,SAAS,CAAC,WAAW,GAAG,mBAAmB,CAAC;IAC9D,IAAI,SAAS,GAAG,KAAK,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC;IAE9C,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,WAAW,EAAE,SAAS,CAAC,EAAE,SAAS,CAAC,uBAAuB,CAAC,CAAC;IAC/G,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC,UAAU,GAAG,SAAS,CAAC,uBAAuB;QAAE,OAAO,IAAI,CAAC;IAE1F,uGAAuG;IACvG,4EAA4E;IAC5E,MAAM,GAAG,GAAG,IAAI,GAAG,EAAsB,CAAC;IAC1C,IAAI,EAAE,GAAG,CAAC,CAAC;IACX,KAAK,MAAM,KAAK,IAAI,SAAS,CAAC,OAAO,EAAE,CAAC;QACtC,MAAM,MAAM,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,cAAc,CAAC;QAChF,IAAI,EAAE,GAAG,MAAM,GAAG,MAAM,CAAC,UAAU;YAAE,OAAO,IAAI,CAAC;QACjD,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC;QACrD,EAAE,IAAI,MAAM,CAAC;IACf,CAAC;IAED,MAAM,MAAM,GAAkD,EAAE,CAAC;IACjE,KAAK,MAAM,KAAK,IAAI,SAAS,CAAC,OAAO,EAAE,CAAC;QACtC,MAAM,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAE,CAAC;QACnC,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;YACvB,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC3D,SAAS;QACX,CAAC;QACD,8FAA8F;QAC9F,oGAAoG;QACpG,gEAAgE;QAChE,IAAI,gBAAgB,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QAC3C,MAAM,QAAQ,GAAG,gBAAgB,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC;QAC1D,IAAI,QAAQ,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QACnC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC/D,CAAC;IAED,OAAO,gBAAgB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAC1C,CAAC;AAED,uGAAuG;AACvG,yGAAyG;AACzG,uGAAuG;AACvG,yDAAyD;AACzD,MAAM,UAAU,uBAAuB,CAAC,KAA2B;IACjE,IAAI,KAAK,CAAC,UAAU,GAAG,kBAAkB;QAAE,OAAO,IAAI,CAAC;IACvD,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IAE5E,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IACtC,IAAI,UAAU,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAElC,MAAM,MAAM,GAAG,EAAE,EAAE,EAAE,kBAAkB,EAAE,CAAC;IAC1C,MAAM,OAAO,GAAsB,EAAE,CAAC;IACtC,IAAI,uBAAuB,GAAG,CAAC,CAAC;IAEhC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,UAAU,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACnD,IAAI,MAAM,CAAC,EAAE,IAAI,KAAK,CAAC,UAAU;YAAE,OAAO,IAAI,CAAC;QAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACvC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC;QAEf,MAAM,QAAQ,GAAG,KAAK,GAAG,IAAI,CAAC;QAC9B,MAAM,gBAAgB,GAAG,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC;QAE7C,IAAI,GAAW,CAAC;QAChB,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;YACtB,IAAI,MAAM,CAAC,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC,UAAU;gBAAE,OAAO,IAAI,CAAC;YAClD,GAAG,GAAG,MAAM,CAAC,YAAY,CACvB,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,EACxB,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC,EAC5B,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC,EAC5B,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC,CAC7B,CAAC;YACF,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC;QACjB,CAAC;aAAM,CAAC;YACN,GAAG,GAAG,gBAAgB,CAAC,QAAQ,CAAE,CAAC;QACpC,CAAC;QAED,MAAM,cAAc,GAAG,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;QACxE,IAAI,cAAc,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QAEpC,oGAAoG;QACpG,kGAAkG;QAClG,wFAAwF;QACxF,MAAM,WAAW,GAAG,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,MAAM,CAAC;QACrD,MAAM,WAAW,GAAG,WAAW,CAAC,CAAC,CAAC,gBAAgB,KAAK,CAAC,CAAC,CAAC,CAAC,gBAAgB,KAAK,CAAC,CAAC;QAElF,IAAI,eAAe,GAAG,cAAc,CAAC;QACrC,IAAI,WAAW,EAAE,CAAC;YAChB,eAAe,GAAG,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;YACnE,IAAI,eAAe,GAAG,CAAC;gBAAE,OAAO,IAAI,CAAC;QACvC,CAAC;QAED,OAAO,CAAC,IAAI,CAAC,EAAE,cAAc,EAAE,GAAG,EAAE,eAAe,EAAE,gBAAgB,EAAE,WAAW,EAAE,CAAC,CAAC;QACtF,uBAAuB,IAAI,WAAW,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,cAAc,CAAC;IAC5E,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,CAAC,EAAE,EAAE,uBAAuB,EAAE,CAAC;AACtE,CAAC;AAED,qGAAqG;AACrG,uGAAuG;AACvG,sGAAsG;AACtG,iFAAiF;AACjF,SAAS,gBAAgB,CAAC,IAAwB,EAAE,MAAsB,EAAE,GAAW;IACrF,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,EAAE,IAAI,IAAI,CAAC,EAAE,CAAC;QACvC,IAAI,MAAM,CAAC,EAAE,IAAI,GAAG;YAAE,OAAO,CAAC,CAAC,CAAC;QAChC,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACtC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC;QACf,IAAI,IAAI,KAAK,CAAC,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,CAAC,CAAC,CAAC;QAC3C,KAAK,GAAG,KAAK,GAAG,GAAG,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAClE,CAAC;IACD,OAAO,CAAC,CAAC,CAAC;AACZ,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { Woff2GlyfStreams } from '@flighthq/types/contract';
2
+ export declare function readWoff2GlyfStreams(transformed: Readonly<Uint8Array>): Woff2GlyfStreams | null;
3
+ export declare function readWoff2Short(bytes: Readonly<Uint8Array>, cursor: {
4
+ at: number;
5
+ }, end: number): number;
6
+ //# sourceMappingURL=woff2GlyfTransform.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"woff2GlyfTransform.d.ts","sourceRoot":"","sources":["../src/woff2GlyfTransform.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAwBjE,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,gBAAgB,GAAG,IAAI,CA8C/F;AAQD,wBAAgB,cAAc,CAAC,KAAK,EAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAwBvG"}
@@ -0,0 +1,104 @@
1
+ // The transformed `glyf` table: seven parallel sub-streams instead of one array of glyphs.
2
+ //
3
+ // WHY THE TRANSFORM EXISTS, WHICH IS ALSO WHY IT MUST BE REVERSED RATHER THAN DECOMPRESSED: a `glyf`
4
+ // table interleaves contour counts, flags, coordinates and hinting bytecode per glyph, so similar values
5
+ // end up far apart and compress badly. The transform SEPARATES them — every glyph's contour count in one
6
+ // stream, every flag in another, every coordinate in a third — so like sits beside like. It is a
7
+ // reordering, not a compression, and Brotli is applied over the result.
8
+ //
9
+ // So the reversal is a re-interleave: walk the streams in step and rebuild each glyph.
10
+ //
11
+ // The stream layout and header fields are interface facts about the format. The reading is Flight's own.
12
+ const WOFF2_GLYF_HEADER_BYTES = 36;
13
+ // Carves the seven sub-streams out of a transformed `glyf` table. Returns the null sentinel when the
14
+ // declared sizes do not fit the table, rather than clamped streams: a stream that is short by a few bytes
15
+ // still decodes into real-looking contour counts and coordinates, so a partial carve is the silent-
16
+ // wrongness case rather than a visible one.
17
+ //
18
+ // Kept separate from the re-interleave so the split is checkable on its own — the seven sizes plus the
19
+ // header must account for the table exactly, which is a property a real font can be tested against
20
+ // without reconstructing a single glyph.
21
+ export function readWoff2GlyfStreams(transformed) {
22
+ if (transformed.byteLength < WOFF2_GLYF_HEADER_BYTES)
23
+ return null;
24
+ const view = new DataView(transformed.buffer, transformed.byteOffset, transformed.byteLength);
25
+ const glyphCount = view.getUint16(4);
26
+ const indexFormat = view.getUint16(6);
27
+ const sizes = [];
28
+ for (let index = 0; index < 7; index += 1)
29
+ sizes.push(view.getUint32(8 + index * 4));
30
+ let total = WOFF2_GLYF_HEADER_BYTES;
31
+ for (const size of sizes)
32
+ total += size;
33
+ // The sizes must account for the table exactly. A table longer than its streams claim carries bytes
34
+ // nothing will read; one shorter means at least one stream is truncated.
35
+ if (total !== transformed.byteLength)
36
+ return null;
37
+ let at = WOFF2_GLYF_HEADER_BYTES;
38
+ const slice = (size) => {
39
+ const part = transformed.subarray(at, at + size);
40
+ at += size;
41
+ return part;
42
+ };
43
+ // Carved into locals in STREAM order before the object is built. The returned fields are alphabetized
44
+ // to match the rest of the package, and object literal properties evaluate in source order — so
45
+ // slicing inside the literal would consume the streams alphabetically and hand every field the right
46
+ // LENGTH with the wrong BYTES. Lengths alone cannot see that, which is why the order lives here.
47
+ const nContourStream = slice(sizes[0]);
48
+ const nPointsStream = slice(sizes[1]);
49
+ const flagStream = slice(sizes[2]);
50
+ const glyphStream = slice(sizes[3]);
51
+ const compositeStream = slice(sizes[4]);
52
+ const bboxStream = slice(sizes[5]);
53
+ const instructionStream = slice(sizes[6]);
54
+ return {
55
+ bboxStream,
56
+ compositeStream,
57
+ flagStream,
58
+ glyphCount,
59
+ glyphStream,
60
+ indexFormat,
61
+ instructionStream,
62
+ nContourStream,
63
+ nPointsStream,
64
+ };
65
+ }
66
+ // 255UInt16: a variable-length count used for point-per-contour and instruction lengths. Three escape
67
+ // codes extend a single byte's range, and reading one with the wrong escape yields a plausible small
68
+ // number rather than an error — which is why the codes are named rather than inlined.
69
+ //
70
+ // Advances `cursor.at`. Returns -1 when the value runs past `end`, so a caller can refuse rather than
71
+ // treat a truncated stream as a short glyph.
72
+ export function readWoff2Short(bytes, cursor, end) {
73
+ if (cursor.at >= end)
74
+ return -1;
75
+ const code = bytes[cursor.at];
76
+ cursor.at += 1;
77
+ if (code === WOFF2_SHORT_WORD) {
78
+ if (cursor.at + 2 > end)
79
+ return -1;
80
+ const value = (bytes[cursor.at] << 8) | bytes[cursor.at + 1];
81
+ cursor.at += 2;
82
+ return value;
83
+ }
84
+ if (code === WOFF2_SHORT_ONE_MORE_BYTE_2) {
85
+ if (cursor.at >= end)
86
+ return -1;
87
+ const value = bytes[cursor.at] + WOFF2_SHORT_LOWEST * 2;
88
+ cursor.at += 1;
89
+ return value;
90
+ }
91
+ if (code === WOFF2_SHORT_ONE_MORE_BYTE_1) {
92
+ if (cursor.at >= end)
93
+ return -1;
94
+ const value = bytes[cursor.at] + WOFF2_SHORT_LOWEST;
95
+ cursor.at += 1;
96
+ return value;
97
+ }
98
+ return code;
99
+ }
100
+ const WOFF2_SHORT_WORD = 253;
101
+ const WOFF2_SHORT_ONE_MORE_BYTE_2 = 254;
102
+ const WOFF2_SHORT_ONE_MORE_BYTE_1 = 255;
103
+ const WOFF2_SHORT_LOWEST = 253;
104
+ //# sourceMappingURL=woff2GlyfTransform.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"woff2GlyfTransform.js","sourceRoot":"","sources":["../src/woff2GlyfTransform.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { WoffChecksumMismatch } from '@flighthq/types/contract';
2
+ import { Compression } from '@flighthq/types/contract';
3
+ export declare function readWoffChecksumMismatches(bytes: Readonly<Uint8Array>, decompress: ((compressed: Readonly<Uint8Array>, uncompressedLength: number) => Uint8Array | null) | null): readonly WoffChecksumMismatch[];
4
+ export declare const WOFF_COMPRESSION: Compression;
5
+ export declare function readWoffFont(bytes: Readonly<Uint8Array>, decompress: ((compressed: Readonly<Uint8Array>, uncompressedLength: number) => Uint8Array | null) | null): Uint8Array | null;
6
+ //# sourceMappingURL=woffFont.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"woffFont.d.ts","sourceRoot":"","sources":["../src/woffFont.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AACrE,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AA8BvD,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,QAAQ,CAAC,UAAU,CAAC,EAC3B,UAAU,EAAE,CAAC,CAAC,UAAU,EAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,kBAAkB,EAAE,MAAM,KAAK,UAAU,GAAG,IAAI,CAAC,GAAG,IAAI,GACvG,SAAS,oBAAoB,EAAE,CAgCjC;AAID,eAAO,MAAM,gBAAgB,EAAE,WAAiC,CAAC;AAKjE,wBAAgB,YAAY,CAC1B,KAAK,EAAE,QAAQ,CAAC,UAAU,CAAC,EAC3B,UAAU,EAAE,CAAC,CAAC,UAAU,EAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,kBAAkB,EAAE,MAAM,KAAK,UAAU,GAAG,IAAI,CAAC,GAAG,IAAI,GACvG,UAAU,GAAG,IAAI,CA4CnB"}
@@ -0,0 +1,112 @@
1
+ import { Compression } from '@flighthq/types/contract';
2
+ import { assembleSfntFont, computeSfntTableChecksum } from './sfntAssembly';
3
+ // WOFF is a wrapper, not a font format: the same sfnt tables, each optionally deflated, behind a header
4
+ // that says where they went. So the whole job is to REBUILD THE SFNT and hand it to the reader that
5
+ // already exists — no new outline code, no new seam, and `glyf`, `CFF ` and CID all work through it
6
+ // unchanged because they never learn the bytes arrived wrapped.
7
+ //
8
+ // The container layout is an interface fact about the format. The reconstruction is Flight's own.
9
+ //
10
+ // ★ THE DECOMPRESSOR IS NOT BUNDLED HERE, DELIBERATELY. It is fetched from `@flighthq/compression`'s
11
+ // registry, which a caller opts into with `registerDeflateDecompressor()`. Importing the codec directly
12
+ // would drag DEFLATE into every bundle that reads a `.ttf`, which is the bundle invariant this repository
13
+ // enforces — an assembly never inflates the bundle cost of a primitive. The cost of that choice is that
14
+ // an unregistered decompressor is a real outcome, so it gets its own reported reason rather than being
15
+ // folded into "unsupported container".
16
+ const WOFF_HEADER_BYTES = 44;
17
+ const WOFF_DIRECTORY_ENTRY_BYTES = 20;
18
+ // Which tables' stored checksums disagree with their own bytes. Returns an empty array when every
19
+ // table agrees, and for a container this reader cannot open at all — a caller distinguishes those by
20
+ // whether `readWoffFont` returned a font.
21
+ //
22
+ // ★ REPORTED, NEVER ENFORCED. A mismatch is bad data rather than API misuse, so it takes a sentinel
23
+ // and not a throw, and the font still loads: deciding that a font is unacceptable belongs to the
24
+ // caller, and a reader that refused one which would otherwise work would have taken that decision
25
+ // away with no way to opt out. Kept as a separate query so a caller who never asks does not link the
26
+ // arithmetic — the same shape as `explainOpenTypeFont`, for the same reason.
27
+ export function readWoffChecksumMismatches(bytes, decompress) {
28
+ if (bytes.byteLength < WOFF_HEADER_BYTES)
29
+ return [];
30
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
31
+ const tableCount = view.getUint16(12);
32
+ const directoryEnd = WOFF_HEADER_BYTES + tableCount * WOFF_DIRECTORY_ENTRY_BYTES;
33
+ if (tableCount === 0 || directoryEnd > bytes.byteLength)
34
+ return [];
35
+ const mismatches = [];
36
+ for (let index = 0; index < tableCount; index += 1) {
37
+ const record = WOFF_HEADER_BYTES + index * WOFF_DIRECTORY_ENTRY_BYTES;
38
+ const tag = view.getUint32(record);
39
+ const offset = view.getUint32(record + 4);
40
+ const compressedLength = view.getUint32(record + 8);
41
+ const originalLength = view.getUint32(record + 12);
42
+ const stored = view.getUint32(record + 16);
43
+ if (offset + compressedLength > bytes.byteLength)
44
+ continue;
45
+ const raw = bytes.subarray(offset, offset + compressedLength);
46
+ let data = raw;
47
+ if (compressedLength !== originalLength) {
48
+ if (decompress === null)
49
+ continue;
50
+ data = decompress(raw, originalLength);
51
+ if (data === null || data.byteLength !== originalLength)
52
+ continue;
53
+ }
54
+ const computed = computeSfntTableChecksum(data, tag === HEAD_TAG);
55
+ if (computed !== stored) {
56
+ mismatches.push({ computed, stored, tag: readWoffTagText(view, record) });
57
+ }
58
+ }
59
+ return mismatches;
60
+ }
61
+ // The compression this container uses. Named through the shared vocabulary rather than a local constant so
62
+ // a caller registering a codec and this module asking for one cannot drift apart.
63
+ export const WOFF_COMPRESSION = Compression.Deflate;
64
+ // Rebuilds the plain sfnt a WOFF wraps. Returns the null sentinel for a malformed container, and for a
65
+ // deflated table when no decompressor is registered — the caller distinguishes those through
66
+ // `explainOpenTypeFont`, since one wants a repaired file and the other wants one line of registration.
67
+ export function readWoffFont(bytes, decompress) {
68
+ if (bytes.byteLength < WOFF_HEADER_BYTES)
69
+ return null;
70
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
71
+ const tableCount = view.getUint16(12);
72
+ if (tableCount === 0)
73
+ return null;
74
+ const directoryEnd = WOFF_HEADER_BYTES + tableCount * WOFF_DIRECTORY_ENTRY_BYTES;
75
+ if (directoryEnd > bytes.byteLength)
76
+ return null;
77
+ // The wrapper carries the sfnt version the tables belonged to, so the rebuilt font declares the flavor
78
+ // it actually is rather than one inferred from which tables happen to be present.
79
+ const flavor = view.getUint32(4);
80
+ const tables = [];
81
+ for (let index = 0; index < tableCount; index += 1) {
82
+ const record = WOFF_HEADER_BYTES + index * WOFF_DIRECTORY_ENTRY_BYTES;
83
+ const tag = view.getUint32(record);
84
+ const offset = view.getUint32(record + 4);
85
+ const compressedLength = view.getUint32(record + 8);
86
+ const originalLength = view.getUint32(record + 12);
87
+ if (offset + compressedLength > bytes.byteLength)
88
+ return null;
89
+ const stored = bytes.subarray(offset, offset + compressedLength);
90
+ // A table is stored uncompressed when the two lengths match — the format's own way of saying
91
+ // "compressing this one did not pay", so it is a normal case rather than an edge one.
92
+ if (compressedLength === originalLength) {
93
+ tables.push({ data: stored, tag });
94
+ continue;
95
+ }
96
+ if (decompress === null)
97
+ return null;
98
+ const inflated = decompress(stored, originalLength);
99
+ // A decompressor that returns the wrong length has produced something that is not this table, and
100
+ // reassembling it would yield a font whose directory lengths lie about its own contents.
101
+ if (inflated === null || inflated.byteLength !== originalLength)
102
+ return null;
103
+ tables.push({ data: inflated, tag });
104
+ }
105
+ return assembleSfntFont(flavor, tables);
106
+ }
107
+ // The four-character tag as text, for a caller that will show it or key on it.
108
+ function readWoffTagText(view, record) {
109
+ return String.fromCharCode(view.getUint8(record), view.getUint8(record + 1), view.getUint8(record + 2), view.getUint8(record + 3));
110
+ }
111
+ const HEAD_TAG = 0x68656164;
112
+ //# sourceMappingURL=woffFont.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"woffFont.js","sourceRoot":"","sources":["../src/woffFont.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@flighthq/font-formats",
3
+ "version": "0.3.1-next.209.43ef1bc",
4
+ "author": "Joshua Granick and other contributors",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/flighthq/flight.git",
9
+ "directory": "packages/font-formats"
10
+ },
11
+ "type": "module",
12
+ "main": "dist/index.js",
13
+ "types": "dist/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "default": "./dist/index.js"
18
+ },
19
+ "./contract": {
20
+ "types": "./dist/contract.d.ts",
21
+ "default": "./dist/contract.js"
22
+ }
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "src/**/*.test.ts",
27
+ "!dist/**/*.test.js",
28
+ "!dist/**/*.test.d.ts",
29
+ "!dist/**/*.test.js.map",
30
+ "!dist/**/*.test.d.ts.map"
31
+ ],
32
+ "scripts": {
33
+ "build": "tsc -b",
34
+ "clean": "tsc -b --clean",
35
+ "test": "vitest run --config vitest.config.ts",
36
+ "test:watch": "vitest --watch --config vitest.config.ts",
37
+ "prepack": "npm run clean && npm run clean:dist && npm run build",
38
+ "clean:dist": "tsx ../../scripts/clean-package-dist.ts"
39
+ },
40
+ "dependencies": {
41
+ "@flighthq/compression": "0.3.1-next.209.43ef1bc",
42
+ "@flighthq/font": "0.3.1-next.209.43ef1bc",
43
+ "@flighthq/types": "0.3.1-next.209.43ef1bc"
44
+ },
45
+ "devDependencies": {
46
+ "typescript": "^5.3.0"
47
+ },
48
+ "description": "OpenType/TrueType codec neighbor of @flighthq/font — parse font bytes into a GlyphOutlineSource (outlines, advances, codepoint mapping)",
49
+ "sideEffects": false
50
+ }