woff2-decode 0.1.0
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.
- package/LICENSE +21 -0
- package/LICENSE_THIRD_PARTY +121 -0
- package/README.md +53 -0
- package/dist/brotli.d.ts +5 -0
- package/dist/brotli.d.ts.map +1 -0
- package/dist/buffer.d.ts +20 -0
- package/dist/buffer.d.ts.map +1 -0
- package/dist/decode.d.ts +5 -0
- package/dist/decode.d.ts.map +1 -0
- package/dist/index.cjs +970 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +947 -0
- package/dist/index.js.map +1 -0
- package/dist/known-tags.d.ts +13 -0
- package/dist/known-tags.d.ts.map +1 -0
- package/dist/variable-length.d.ts +6 -0
- package/dist/variable-length.d.ts.map +1 -0
- package/package.json +63 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,970 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
12
|
+
key = keys[i];
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
14
|
+
__defProp(to, key, {
|
|
15
|
+
get: ((k) => from[k]).bind(null, key),
|
|
16
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
24
|
+
value: mod,
|
|
25
|
+
enumerable: true
|
|
26
|
+
}) : target, mod));
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
let brotli_decompress = require("brotli/decompress");
|
|
30
|
+
brotli_decompress = __toESM(brotli_decompress);
|
|
31
|
+
|
|
32
|
+
//#region src/brotli.ts
|
|
33
|
+
let nativeBrotli = null;
|
|
34
|
+
function tryLoadNative() {
|
|
35
|
+
try {
|
|
36
|
+
if (typeof process !== "undefined" && process.versions?.node) {
|
|
37
|
+
const zlib = require("node:zlib");
|
|
38
|
+
if (typeof zlib.brotliDecompressSync === "function") return (buf) => {
|
|
39
|
+
const result = zlib.brotliDecompressSync(buf);
|
|
40
|
+
return new Uint8Array(result.buffer, result.byteOffset, result.byteLength);
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
} catch {}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
nativeBrotli = tryLoadNative();
|
|
47
|
+
function decompress(data) {
|
|
48
|
+
if (nativeBrotli) return nativeBrotli(data);
|
|
49
|
+
return (0, brotli_decompress.default)(data);
|
|
50
|
+
}
|
|
51
|
+
function decompressPure(data) {
|
|
52
|
+
return (0, brotli_decompress.default)(data);
|
|
53
|
+
}
|
|
54
|
+
const hasNative = nativeBrotli !== null;
|
|
55
|
+
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/buffer.ts
|
|
58
|
+
var Buffer$1 = class {
|
|
59
|
+
constructor(data, offset = 0, length) {
|
|
60
|
+
this.pos = 0;
|
|
61
|
+
if (data instanceof Uint8Array) {
|
|
62
|
+
const len = length ?? data.byteLength - offset;
|
|
63
|
+
this.view = new DataView(data.buffer, data.byteOffset + offset, len);
|
|
64
|
+
} else {
|
|
65
|
+
const len = length ?? data.byteLength - offset;
|
|
66
|
+
this.view = new DataView(data, offset, len);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
get offset() {
|
|
70
|
+
return this.pos;
|
|
71
|
+
}
|
|
72
|
+
get length() {
|
|
73
|
+
return this.view.byteLength;
|
|
74
|
+
}
|
|
75
|
+
get remaining() {
|
|
76
|
+
return this.view.byteLength - this.pos;
|
|
77
|
+
}
|
|
78
|
+
get buffer() {
|
|
79
|
+
return this.view;
|
|
80
|
+
}
|
|
81
|
+
skip(n) {
|
|
82
|
+
if (this.pos + n > this.view.byteLength || this.pos + n < this.pos) return false;
|
|
83
|
+
this.pos += n;
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
seek(offset) {
|
|
87
|
+
if (offset > this.view.byteLength || offset < 0) return false;
|
|
88
|
+
this.pos = offset;
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
readU8() {
|
|
92
|
+
if (this.pos + 1 > this.view.byteLength) return null;
|
|
93
|
+
return this.view.getUint8(this.pos++);
|
|
94
|
+
}
|
|
95
|
+
readU16() {
|
|
96
|
+
if (this.pos + 2 > this.view.byteLength) return null;
|
|
97
|
+
const val = this.view.getUint16(this.pos, false);
|
|
98
|
+
this.pos += 2;
|
|
99
|
+
return val;
|
|
100
|
+
}
|
|
101
|
+
readS16() {
|
|
102
|
+
if (this.pos + 2 > this.view.byteLength) return null;
|
|
103
|
+
const val = this.view.getInt16(this.pos, false);
|
|
104
|
+
this.pos += 2;
|
|
105
|
+
return val;
|
|
106
|
+
}
|
|
107
|
+
readU32() {
|
|
108
|
+
if (this.pos + 4 > this.view.byteLength) return null;
|
|
109
|
+
const val = this.view.getUint32(this.pos, false);
|
|
110
|
+
this.pos += 4;
|
|
111
|
+
return val;
|
|
112
|
+
}
|
|
113
|
+
readS32() {
|
|
114
|
+
if (this.pos + 4 > this.view.byteLength) return null;
|
|
115
|
+
const val = this.view.getInt32(this.pos, false);
|
|
116
|
+
this.pos += 4;
|
|
117
|
+
return val;
|
|
118
|
+
}
|
|
119
|
+
readBytes(n) {
|
|
120
|
+
if (this.pos + n > this.view.byteLength || n < 0) return null;
|
|
121
|
+
const result = new Uint8Array(this.view.buffer, this.view.byteOffset + this.pos, n);
|
|
122
|
+
this.pos += n;
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
peekU8(offset = 0) {
|
|
126
|
+
const idx = this.pos + offset;
|
|
127
|
+
if (idx >= this.view.byteLength || idx < 0) return null;
|
|
128
|
+
return this.view.getUint8(idx);
|
|
129
|
+
}
|
|
130
|
+
subarray(offset, length) {
|
|
131
|
+
if (offset + length > this.view.byteLength || offset < 0 || length < 0) return null;
|
|
132
|
+
return new Uint8Array(this.view.buffer, this.view.byteOffset + offset, length);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
//#endregion
|
|
137
|
+
//#region src/known-tags.ts
|
|
138
|
+
const KNOWN_TAGS = [
|
|
139
|
+
1668112752,
|
|
140
|
+
1751474532,
|
|
141
|
+
1751672161,
|
|
142
|
+
1752003704,
|
|
143
|
+
1835104368,
|
|
144
|
+
1851878757,
|
|
145
|
+
1330851634,
|
|
146
|
+
1886352244,
|
|
147
|
+
1668707360,
|
|
148
|
+
1718642541,
|
|
149
|
+
1735162214,
|
|
150
|
+
1819239265,
|
|
151
|
+
1886545264,
|
|
152
|
+
1128678944,
|
|
153
|
+
1448038983,
|
|
154
|
+
1161970772,
|
|
155
|
+
1161972803,
|
|
156
|
+
1734439792,
|
|
157
|
+
1751412088,
|
|
158
|
+
1801810542,
|
|
159
|
+
1280594760,
|
|
160
|
+
1346587732,
|
|
161
|
+
1447316824,
|
|
162
|
+
1986553185,
|
|
163
|
+
1986884728,
|
|
164
|
+
1111577413,
|
|
165
|
+
1195656518,
|
|
166
|
+
1196445523,
|
|
167
|
+
1196643650,
|
|
168
|
+
1161974595,
|
|
169
|
+
1246975046,
|
|
170
|
+
1296127048,
|
|
171
|
+
1128416340,
|
|
172
|
+
1128418371,
|
|
173
|
+
1129270354,
|
|
174
|
+
1129333068,
|
|
175
|
+
1398163232,
|
|
176
|
+
1935829368,
|
|
177
|
+
1633906292,
|
|
178
|
+
1635148146,
|
|
179
|
+
1650745716,
|
|
180
|
+
1651273571,
|
|
181
|
+
1651731566,
|
|
182
|
+
1668702578,
|
|
183
|
+
1717859171,
|
|
184
|
+
1717920116,
|
|
185
|
+
1718449272,
|
|
186
|
+
1719034226,
|
|
187
|
+
1735811442,
|
|
188
|
+
1752396921,
|
|
189
|
+
1786082164,
|
|
190
|
+
1818452338,
|
|
191
|
+
1836020340,
|
|
192
|
+
1836020344,
|
|
193
|
+
1869636196,
|
|
194
|
+
1886547824,
|
|
195
|
+
1953653099,
|
|
196
|
+
1516335206,
|
|
197
|
+
1399417958,
|
|
198
|
+
1198285172,
|
|
199
|
+
1198288739,
|
|
200
|
+
1181049204,
|
|
201
|
+
1399417964
|
|
202
|
+
];
|
|
203
|
+
const TAG_GLYF = 1735162214;
|
|
204
|
+
const TAG_LOCA = 1819239265;
|
|
205
|
+
const TAG_HMTX = 1752003704;
|
|
206
|
+
const TAG_HHEA = 1751672161;
|
|
207
|
+
const TAG_HEAD = 1751474532;
|
|
208
|
+
const TTC_FLAVOR = 1953784678;
|
|
209
|
+
const WOFF2_SIGNATURE = 2001684018;
|
|
210
|
+
const WOFF2_FLAGS_TRANSFORM = 32;
|
|
211
|
+
function tagToString(tag) {
|
|
212
|
+
return String.fromCharCode(tag >> 24 & 255, tag >> 16 & 255, tag >> 8 & 255, tag & 255);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
//#endregion
|
|
216
|
+
//#region src/variable-length.ts
|
|
217
|
+
function read255UShort(buf) {
|
|
218
|
+
const code = buf.readU8();
|
|
219
|
+
if (code === null) return null;
|
|
220
|
+
if (code === 253) return buf.readU16();
|
|
221
|
+
else if (code === 255) {
|
|
222
|
+
const next = buf.readU8();
|
|
223
|
+
if (next === null) return null;
|
|
224
|
+
return 253 + next;
|
|
225
|
+
} else if (code === 254) {
|
|
226
|
+
const next = buf.readU8();
|
|
227
|
+
if (next === null) return null;
|
|
228
|
+
return 506 + next;
|
|
229
|
+
} else return code;
|
|
230
|
+
}
|
|
231
|
+
function readBase128(buf) {
|
|
232
|
+
let result = 0;
|
|
233
|
+
for (let i = 0; i < 5; i++) {
|
|
234
|
+
const code = buf.readU8();
|
|
235
|
+
if (code === null) return null;
|
|
236
|
+
if (i === 0 && code === 128) return null;
|
|
237
|
+
if ((result & 4261412864) !== 0) return null;
|
|
238
|
+
result = result << 7 | code & 127;
|
|
239
|
+
if ((code & 128) === 0) return result;
|
|
240
|
+
}
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
//#endregion
|
|
245
|
+
//#region src/decode.ts
|
|
246
|
+
const SFNT_HEADER_SIZE = 12;
|
|
247
|
+
const SFNT_ENTRY_SIZE = 16;
|
|
248
|
+
function decode(data, options) {
|
|
249
|
+
const input = data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
250
|
+
const header = readHeader(new Buffer$1(input), input.byteLength);
|
|
251
|
+
if (!header) throw new Error("Failed to read WOFF2 header");
|
|
252
|
+
const compressedData = input.subarray(header.compressedOffset, header.compressedOffset + header.compressedLength);
|
|
253
|
+
const decompressed = (options?.forcePureBrotli ? decompressPure : decompress)(compressedData);
|
|
254
|
+
if (!decompressed || decompressed.byteLength !== header.uncompressedSize) throw new Error(`Brotli decompression failed: expected ${header.uncompressedSize} bytes, got ${decompressed?.byteLength ?? 0}`);
|
|
255
|
+
let outputSize = computeOffsetToFirstTable(header);
|
|
256
|
+
for (const table of header.tables) {
|
|
257
|
+
outputSize += table.origLength;
|
|
258
|
+
outputSize += (4 - table.origLength % 4) % 4;
|
|
259
|
+
}
|
|
260
|
+
const output = new Uint8Array(outputSize);
|
|
261
|
+
const outView = new DataView(output.buffer);
|
|
262
|
+
const fontInfos = writeHeaders(header, output, outView);
|
|
263
|
+
const writtenTables = /* @__PURE__ */ new Map();
|
|
264
|
+
let nextTableOffset = computeOffsetToFirstTable(header);
|
|
265
|
+
if (header.ttcFonts.length > 0) for (let i = 0; i < header.ttcFonts.length; i++) nextTableOffset = reconstructFont(decompressed, header, i, fontInfos[i], output, outView, writtenTables, nextTableOffset);
|
|
266
|
+
else reconstructFont(decompressed, header, 0, fontInfos[0], output, outView, writtenTables, nextTableOffset);
|
|
267
|
+
return output;
|
|
268
|
+
}
|
|
269
|
+
function readHeader(buf, totalLength) {
|
|
270
|
+
if (buf.readU32() !== WOFF2_SIGNATURE) return null;
|
|
271
|
+
const flavor = buf.readU32();
|
|
272
|
+
if (flavor === null) return null;
|
|
273
|
+
const length = buf.readU32();
|
|
274
|
+
if (length === null || length !== totalLength) return null;
|
|
275
|
+
const numTables = buf.readU16();
|
|
276
|
+
if (numTables === null || numTables === 0) return null;
|
|
277
|
+
if (!buf.skip(2)) return null;
|
|
278
|
+
if (!buf.skip(4)) return null;
|
|
279
|
+
const compressedLength = buf.readU32();
|
|
280
|
+
if (compressedLength === null) return null;
|
|
281
|
+
if (!buf.skip(4)) return null;
|
|
282
|
+
const metaOffset = buf.readU32();
|
|
283
|
+
const metaLength = buf.readU32();
|
|
284
|
+
const metaOrigLength = buf.readU32();
|
|
285
|
+
if (metaOffset === null || metaLength === null || metaOrigLength === null) return null;
|
|
286
|
+
if (metaOffset !== 0) {
|
|
287
|
+
if (metaOffset >= totalLength || totalLength - metaOffset < metaLength) return null;
|
|
288
|
+
}
|
|
289
|
+
const privOffset = buf.readU32();
|
|
290
|
+
const privLength = buf.readU32();
|
|
291
|
+
if (privOffset === null || privLength === null) return null;
|
|
292
|
+
if (privOffset !== 0) {
|
|
293
|
+
if (privOffset >= totalLength || totalLength - privOffset < privLength) return null;
|
|
294
|
+
}
|
|
295
|
+
const tables = readTableDirectory(buf, numTables);
|
|
296
|
+
if (!tables) return null;
|
|
297
|
+
const lastTable = tables[tables.length - 1];
|
|
298
|
+
const uncompressedSize = lastTable.srcOffset + lastTable.srcLength;
|
|
299
|
+
let headerVersion = 0;
|
|
300
|
+
const ttcFonts = [];
|
|
301
|
+
if (flavor === TTC_FLAVOR) {
|
|
302
|
+
headerVersion = buf.readU32() ?? 0;
|
|
303
|
+
if (headerVersion !== 65536 && headerVersion !== 131072) return null;
|
|
304
|
+
const numFonts = read255UShort(buf);
|
|
305
|
+
if (numFonts === null || numFonts === 0) return null;
|
|
306
|
+
for (let i = 0; i < numFonts; i++) {
|
|
307
|
+
const fontNumTables = read255UShort(buf);
|
|
308
|
+
if (fontNumTables === null || fontNumTables === 0) return null;
|
|
309
|
+
const fontFlavor = buf.readU32();
|
|
310
|
+
if (fontFlavor === null) return null;
|
|
311
|
+
const tableIndices = [];
|
|
312
|
+
for (let j = 0; j < fontNumTables; j++) {
|
|
313
|
+
const idx = read255UShort(buf);
|
|
314
|
+
if (idx === null || idx >= tables.length) return null;
|
|
315
|
+
tableIndices.push(idx);
|
|
316
|
+
}
|
|
317
|
+
ttcFonts.push({
|
|
318
|
+
flavor: fontFlavor,
|
|
319
|
+
dstOffset: 0,
|
|
320
|
+
headerChecksum: 0,
|
|
321
|
+
tableIndices
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return {
|
|
326
|
+
flavor,
|
|
327
|
+
headerVersion,
|
|
328
|
+
numTables,
|
|
329
|
+
compressedOffset: buf.offset,
|
|
330
|
+
compressedLength,
|
|
331
|
+
uncompressedSize,
|
|
332
|
+
tables,
|
|
333
|
+
ttcFonts
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
function readTableDirectory(buf, numTables) {
|
|
337
|
+
const tables = [];
|
|
338
|
+
let srcOffset = 0;
|
|
339
|
+
for (let i = 0; i < numTables; i++) {
|
|
340
|
+
const flagByte = buf.readU8();
|
|
341
|
+
if (flagByte === null) return null;
|
|
342
|
+
let tag;
|
|
343
|
+
if ((flagByte & 63) === 63) {
|
|
344
|
+
tag = buf.readU32() ?? 0;
|
|
345
|
+
if (tag === 0) return null;
|
|
346
|
+
} else tag = KNOWN_TAGS[flagByte & 63];
|
|
347
|
+
const xformVersion = flagByte >> 6 & 3;
|
|
348
|
+
let flags = 0;
|
|
349
|
+
if (tag === TAG_GLYF || tag === TAG_LOCA) {
|
|
350
|
+
if (xformVersion === 0) flags |= WOFF2_FLAGS_TRANSFORM;
|
|
351
|
+
} else if (xformVersion !== 0) flags |= WOFF2_FLAGS_TRANSFORM;
|
|
352
|
+
flags |= xformVersion;
|
|
353
|
+
const origLength = readBase128(buf);
|
|
354
|
+
if (origLength === null) return null;
|
|
355
|
+
let transformLength = origLength;
|
|
356
|
+
if ((flags & WOFF2_FLAGS_TRANSFORM) !== 0) {
|
|
357
|
+
transformLength = readBase128(buf) ?? 0;
|
|
358
|
+
if (transformLength === 0 && tag !== TAG_LOCA) return null;
|
|
359
|
+
if (tag === TAG_LOCA && transformLength !== 0) return null;
|
|
360
|
+
}
|
|
361
|
+
tables.push({
|
|
362
|
+
tag,
|
|
363
|
+
flags,
|
|
364
|
+
origLength,
|
|
365
|
+
transformLength,
|
|
366
|
+
srcOffset,
|
|
367
|
+
srcLength: transformLength,
|
|
368
|
+
dstOffset: 0,
|
|
369
|
+
dstLength: origLength
|
|
370
|
+
});
|
|
371
|
+
srcOffset += transformLength;
|
|
372
|
+
}
|
|
373
|
+
return tables;
|
|
374
|
+
}
|
|
375
|
+
function computeOffsetToFirstTable(header) {
|
|
376
|
+
if (header.ttcFonts.length === 0) return SFNT_HEADER_SIZE + SFNT_ENTRY_SIZE * header.numTables;
|
|
377
|
+
let offset = 12;
|
|
378
|
+
offset += 4 * header.ttcFonts.length;
|
|
379
|
+
if (header.headerVersion === 131072) offset += 12;
|
|
380
|
+
for (const ttcFont of header.ttcFonts) {
|
|
381
|
+
offset += SFNT_HEADER_SIZE;
|
|
382
|
+
offset += SFNT_ENTRY_SIZE * ttcFont.tableIndices.length;
|
|
383
|
+
}
|
|
384
|
+
return offset;
|
|
385
|
+
}
|
|
386
|
+
function writeHeaders(header, output, outView) {
|
|
387
|
+
const fontInfos = [];
|
|
388
|
+
let offset = 0;
|
|
389
|
+
if (header.ttcFonts.length > 0) {
|
|
390
|
+
outView.setUint32(offset, header.flavor);
|
|
391
|
+
offset += 4;
|
|
392
|
+
outView.setUint32(offset, header.headerVersion);
|
|
393
|
+
offset += 4;
|
|
394
|
+
outView.setUint32(offset, header.ttcFonts.length);
|
|
395
|
+
offset += 4;
|
|
396
|
+
const offsetTableStart = offset;
|
|
397
|
+
offset += 4 * header.ttcFonts.length;
|
|
398
|
+
if (header.headerVersion === 131072) offset += 12;
|
|
399
|
+
for (let i = 0; i < header.ttcFonts.length; i++) {
|
|
400
|
+
const ttcFont = header.ttcFonts[i];
|
|
401
|
+
outView.setUint32(offsetTableStart + i * 4, offset);
|
|
402
|
+
ttcFont.dstOffset = offset;
|
|
403
|
+
const numTables = ttcFont.tableIndices.length;
|
|
404
|
+
offset = writeOffsetTable(outView, offset, ttcFont.flavor, numTables);
|
|
405
|
+
const sortedIndices = [...ttcFont.tableIndices].sort((a, b) => header.tables[a].tag - header.tables[b].tag);
|
|
406
|
+
const tableEntryByTag = /* @__PURE__ */ new Map();
|
|
407
|
+
for (const tableIdx of sortedIndices) {
|
|
408
|
+
const table = header.tables[tableIdx];
|
|
409
|
+
tableEntryByTag.set(table.tag, offset);
|
|
410
|
+
offset = writeTableEntry(outView, offset, table.tag);
|
|
411
|
+
}
|
|
412
|
+
ttcFont.tableIndices = sortedIndices;
|
|
413
|
+
ttcFont.headerChecksum = computeChecksum(output, ttcFont.dstOffset, offset - ttcFont.dstOffset);
|
|
414
|
+
fontInfos.push({
|
|
415
|
+
numGlyphs: 0,
|
|
416
|
+
indexFormat: 0,
|
|
417
|
+
numHMetrics: 0,
|
|
418
|
+
xMins: new Int16Array(0),
|
|
419
|
+
tableEntryByTag
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
} else {
|
|
423
|
+
offset = writeOffsetTable(outView, offset, header.flavor, header.numTables);
|
|
424
|
+
const sortedTables = [...header.tables].sort((a, b) => a.tag - b.tag);
|
|
425
|
+
const tableEntryByTag = /* @__PURE__ */ new Map();
|
|
426
|
+
for (const table of sortedTables) {
|
|
427
|
+
tableEntryByTag.set(table.tag, offset);
|
|
428
|
+
offset = writeTableEntry(outView, offset, table.tag);
|
|
429
|
+
}
|
|
430
|
+
fontInfos.push({
|
|
431
|
+
numGlyphs: 0,
|
|
432
|
+
indexFormat: 0,
|
|
433
|
+
numHMetrics: 0,
|
|
434
|
+
xMins: new Int16Array(0),
|
|
435
|
+
tableEntryByTag
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
return fontInfos;
|
|
439
|
+
}
|
|
440
|
+
function writeOffsetTable(view, offset, flavor, numTables) {
|
|
441
|
+
view.setUint32(offset, flavor);
|
|
442
|
+
view.setUint16(offset + 4, numTables);
|
|
443
|
+
let maxPow2 = 0;
|
|
444
|
+
while (1 << maxPow2 + 1 <= numTables) maxPow2++;
|
|
445
|
+
const searchRange = (1 << maxPow2) * 16;
|
|
446
|
+
view.setUint16(offset + 6, searchRange);
|
|
447
|
+
view.setUint16(offset + 8, maxPow2);
|
|
448
|
+
view.setUint16(offset + 10, numTables * 16 - searchRange);
|
|
449
|
+
return offset + SFNT_HEADER_SIZE;
|
|
450
|
+
}
|
|
451
|
+
function writeTableEntry(view, offset, tag) {
|
|
452
|
+
view.setUint32(offset, tag);
|
|
453
|
+
view.setUint32(offset + 4, 0);
|
|
454
|
+
view.setUint32(offset + 8, 0);
|
|
455
|
+
view.setUint32(offset + 12, 0);
|
|
456
|
+
return offset + SFNT_ENTRY_SIZE;
|
|
457
|
+
}
|
|
458
|
+
function reconstructFont(decompressed, header, fontIndex, fontInfo, output, outView, writtenTables, dstOffset) {
|
|
459
|
+
const sortedTables = [...header.ttcFonts.length > 0 ? header.ttcFonts[fontIndex].tableIndices.map((i) => header.tables[i]) : header.tables].sort((a, b) => a.tag - b.tag);
|
|
460
|
+
const glyfTable = sortedTables.find((t) => t.tag === TAG_GLYF);
|
|
461
|
+
const locaTable = sortedTables.find((t) => t.tag === TAG_LOCA);
|
|
462
|
+
const hheaTable = sortedTables.find((t) => t.tag === TAG_HHEA);
|
|
463
|
+
if (hheaTable) {
|
|
464
|
+
const hheaData = decompressed.subarray(hheaTable.srcOffset, hheaTable.srcOffset + hheaTable.srcLength);
|
|
465
|
+
if (hheaData.byteLength >= 36) fontInfo.numHMetrics = new DataView(hheaData.buffer, hheaData.byteOffset).getUint16(34);
|
|
466
|
+
}
|
|
467
|
+
let fontChecksum = header.ttcFonts.length > 0 ? header.ttcFonts[fontIndex].headerChecksum : 0;
|
|
468
|
+
const isTTC = header.ttcFonts.length > 0;
|
|
469
|
+
for (const table of sortedTables) {
|
|
470
|
+
const entryOffset = fontInfo.tableEntryByTag.get(table.tag);
|
|
471
|
+
if (entryOffset === void 0) continue;
|
|
472
|
+
const tableKey = `${table.tag}:${table.srcOffset}`;
|
|
473
|
+
const existing = writtenTables.get(tableKey);
|
|
474
|
+
if (existing) {
|
|
475
|
+
updateTableEntry(outView, entryOffset, existing.checksum, existing.dstOffset, existing.dstLength);
|
|
476
|
+
if (isTTC) {
|
|
477
|
+
fontChecksum = fontChecksum + existing.checksum >>> 0;
|
|
478
|
+
fontChecksum = fontChecksum + computeTableEntryChecksum(existing.checksum, existing.dstOffset, existing.dstLength) >>> 0;
|
|
479
|
+
}
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
table.dstOffset = dstOffset;
|
|
483
|
+
let tableData;
|
|
484
|
+
let checksum;
|
|
485
|
+
if ((table.flags & WOFF2_FLAGS_TRANSFORM) !== 0) if (table.tag === TAG_GLYF && glyfTable && locaTable) {
|
|
486
|
+
const result = reconstructGlyf(decompressed, glyfTable, locaTable, fontInfo);
|
|
487
|
+
tableData = result.glyfData;
|
|
488
|
+
glyfTable.dstLength = result.glyfData.byteLength;
|
|
489
|
+
locaTable.dstOffset = dstOffset + pad4(result.glyfData.byteLength);
|
|
490
|
+
locaTable.dstLength = result.locaData.byteLength;
|
|
491
|
+
output.set(tableData, dstOffset);
|
|
492
|
+
checksum = computeChecksum(output, dstOffset, tableData.byteLength);
|
|
493
|
+
updateTableEntry(outView, entryOffset, checksum, dstOffset, tableData.byteLength);
|
|
494
|
+
if (isTTC) {
|
|
495
|
+
fontChecksum = fontChecksum + checksum >>> 0;
|
|
496
|
+
fontChecksum = fontChecksum + computeTableEntryChecksum(checksum, dstOffset, tableData.byteLength) >>> 0;
|
|
497
|
+
}
|
|
498
|
+
writtenTables.set(tableKey, {
|
|
499
|
+
dstOffset,
|
|
500
|
+
dstLength: tableData.byteLength,
|
|
501
|
+
checksum
|
|
502
|
+
});
|
|
503
|
+
dstOffset += pad4(tableData.byteLength);
|
|
504
|
+
const locaEntryOffset = fontInfo.tableEntryByTag.get(TAG_LOCA);
|
|
505
|
+
if (locaEntryOffset !== void 0) {
|
|
506
|
+
output.set(result.locaData, dstOffset);
|
|
507
|
+
const locaChecksum = computeChecksum(output, dstOffset, result.locaData.byteLength);
|
|
508
|
+
updateTableEntry(outView, locaEntryOffset, locaChecksum, dstOffset, result.locaData.byteLength);
|
|
509
|
+
if (isTTC) {
|
|
510
|
+
fontChecksum = fontChecksum + locaChecksum >>> 0;
|
|
511
|
+
fontChecksum = fontChecksum + computeTableEntryChecksum(locaChecksum, dstOffset, result.locaData.byteLength) >>> 0;
|
|
512
|
+
}
|
|
513
|
+
const locaKey = `${TAG_LOCA}:${locaTable.srcOffset}`;
|
|
514
|
+
writtenTables.set(locaKey, {
|
|
515
|
+
dstOffset,
|
|
516
|
+
dstLength: result.locaData.byteLength,
|
|
517
|
+
checksum: locaChecksum
|
|
518
|
+
});
|
|
519
|
+
dstOffset += pad4(result.locaData.byteLength);
|
|
520
|
+
}
|
|
521
|
+
continue;
|
|
522
|
+
} else if (table.tag === TAG_LOCA) continue;
|
|
523
|
+
else if (table.tag === TAG_HMTX) tableData = reconstructHmtx(decompressed, table, fontInfo.numGlyphs, fontInfo.numHMetrics, fontInfo.xMins);
|
|
524
|
+
else throw new Error(`Unknown transform for table ${tagToString(table.tag)}`);
|
|
525
|
+
else {
|
|
526
|
+
tableData = decompressed.subarray(table.srcOffset, table.srcOffset + table.srcLength);
|
|
527
|
+
if (table.tag === TAG_HEAD && tableData.byteLength >= 12) {
|
|
528
|
+
tableData = new Uint8Array(tableData);
|
|
529
|
+
new DataView(tableData.buffer, tableData.byteOffset).setUint32(8, 0);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
output.set(tableData, dstOffset);
|
|
533
|
+
checksum = computeChecksum(output, dstOffset, tableData.byteLength);
|
|
534
|
+
table.dstLength = tableData.byteLength;
|
|
535
|
+
updateTableEntry(outView, entryOffset, checksum, dstOffset, tableData.byteLength);
|
|
536
|
+
if (isTTC) {
|
|
537
|
+
fontChecksum = fontChecksum + checksum >>> 0;
|
|
538
|
+
fontChecksum = fontChecksum + computeTableEntryChecksum(checksum, dstOffset, tableData.byteLength) >>> 0;
|
|
539
|
+
}
|
|
540
|
+
writtenTables.set(tableKey, {
|
|
541
|
+
dstOffset,
|
|
542
|
+
dstLength: tableData.byteLength,
|
|
543
|
+
checksum
|
|
544
|
+
});
|
|
545
|
+
dstOffset += pad4(tableData.byteLength);
|
|
546
|
+
}
|
|
547
|
+
const headTable = sortedTables.find((t) => t.tag === TAG_HEAD);
|
|
548
|
+
if (headTable) {
|
|
549
|
+
const headEntry = writtenTables.get(`${TAG_HEAD}:${headTable.srcOffset}`);
|
|
550
|
+
if (headEntry && headEntry.dstLength >= 12) {
|
|
551
|
+
const finalChecksum = isTTC ? fontChecksum : computeChecksum(output, 0, dstOffset);
|
|
552
|
+
outView.setUint32(headEntry.dstOffset + 8, 2981146554 - finalChecksum >>> 0);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
return dstOffset;
|
|
556
|
+
}
|
|
557
|
+
function computeTableEntryChecksum(checksum, offset, length) {
|
|
558
|
+
return checksum + offset + length >>> 0;
|
|
559
|
+
}
|
|
560
|
+
function reconstructGlyf(data, glyfTable, locaTable, fontInfo) {
|
|
561
|
+
const buf = new Buffer$1(data, glyfTable.srcOffset, glyfTable.transformLength);
|
|
562
|
+
const version = buf.readU16();
|
|
563
|
+
const optionFlags = buf.readU16();
|
|
564
|
+
const numGlyphs = buf.readU16();
|
|
565
|
+
const indexFormat = buf.readU16();
|
|
566
|
+
if (version === null || optionFlags === null || numGlyphs === null || indexFormat === null) throw new Error("Invalid glyf transform header");
|
|
567
|
+
fontInfo.numGlyphs = numGlyphs;
|
|
568
|
+
fontInfo.indexFormat = indexFormat;
|
|
569
|
+
const nContourStreamSize = buf.readU32();
|
|
570
|
+
const nPointsStreamSize = buf.readU32();
|
|
571
|
+
const flagStreamSize = buf.readU32();
|
|
572
|
+
const glyphStreamSize = buf.readU32();
|
|
573
|
+
const compositeStreamSize = buf.readU32();
|
|
574
|
+
const bboxStreamSize = buf.readU32();
|
|
575
|
+
const instructionStreamSize = buf.readU32();
|
|
576
|
+
if (nContourStreamSize === null || nPointsStreamSize === null || flagStreamSize === null || glyphStreamSize === null || compositeStreamSize === null || bboxStreamSize === null || instructionStreamSize === null) throw new Error("Invalid glyf substream sizes");
|
|
577
|
+
let offset = buf.offset;
|
|
578
|
+
const nContourStream = new Buffer$1(data, glyfTable.srcOffset + offset, nContourStreamSize);
|
|
579
|
+
offset += nContourStreamSize;
|
|
580
|
+
const nPointsStream = new Buffer$1(data, glyfTable.srcOffset + offset, nPointsStreamSize);
|
|
581
|
+
offset += nPointsStreamSize;
|
|
582
|
+
const flagStream = new Buffer$1(data, glyfTable.srcOffset + offset, flagStreamSize);
|
|
583
|
+
offset += flagStreamSize;
|
|
584
|
+
const glyphStream = new Buffer$1(data, glyfTable.srcOffset + offset, glyphStreamSize);
|
|
585
|
+
offset += glyphStreamSize;
|
|
586
|
+
const compositeStream = new Buffer$1(data, glyfTable.srcOffset + offset, compositeStreamSize);
|
|
587
|
+
offset += compositeStreamSize;
|
|
588
|
+
const bboxStream = new Buffer$1(data, glyfTable.srcOffset + offset, bboxStreamSize);
|
|
589
|
+
offset += bboxStreamSize;
|
|
590
|
+
const instructionStream = new Buffer$1(data, glyfTable.srcOffset + offset, instructionStreamSize);
|
|
591
|
+
const hasOverlapBitmap = (optionFlags & 1) !== 0;
|
|
592
|
+
let overlapBitmap = null;
|
|
593
|
+
if (hasOverlapBitmap) {
|
|
594
|
+
const overlapBitmapLength = numGlyphs + 7 >> 3;
|
|
595
|
+
overlapBitmap = data.subarray(glyfTable.srcOffset + offset + instructionStreamSize, glyfTable.srcOffset + offset + instructionStreamSize + overlapBitmapLength);
|
|
596
|
+
}
|
|
597
|
+
const bboxBitmapLength = numGlyphs + 31 >> 5 << 2;
|
|
598
|
+
const bboxBitmap = bboxStream.readBytes(bboxBitmapLength);
|
|
599
|
+
if (!bboxBitmap) throw new Error("Failed to read bbox bitmap");
|
|
600
|
+
let glyfOutput = new Uint8Array(glyfTable.origLength * 2);
|
|
601
|
+
let glyfOffset = 0;
|
|
602
|
+
const locaValues = [];
|
|
603
|
+
fontInfo.xMins = new Int16Array(numGlyphs);
|
|
604
|
+
for (let glyphId = 0; glyphId < numGlyphs; glyphId++) {
|
|
605
|
+
locaValues.push(glyfOffset);
|
|
606
|
+
const nContours = nContourStream.readS16();
|
|
607
|
+
if (nContours === null) throw new Error(`Failed to read nContours for glyph ${glyphId}`);
|
|
608
|
+
const haveBbox = (bboxBitmap[glyphId >> 3] & 128 >> (glyphId & 7)) !== 0;
|
|
609
|
+
if (nContours === 0) {
|
|
610
|
+
if (haveBbox) throw new Error(`Empty glyph ${glyphId} has bbox`);
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
if (nContours === -1) {
|
|
614
|
+
if (!haveBbox) throw new Error(`Composite glyph ${glyphId} missing bbox`);
|
|
615
|
+
const { compositeData, haveInstructions } = readCompositeGlyph(compositeStream);
|
|
616
|
+
let instructionSize = 0;
|
|
617
|
+
if (haveInstructions) instructionSize = read255UShort(glyphStream) ?? 0;
|
|
618
|
+
const glyphSize = 10 + compositeData.byteLength + (haveInstructions ? 2 + instructionSize : 0);
|
|
619
|
+
ensureCapacity(glyphSize);
|
|
620
|
+
new DataView(glyfOutput.buffer, glyfOffset).setInt16(0, -1);
|
|
621
|
+
const bbox = bboxStream.readBytes(8);
|
|
622
|
+
if (!bbox) throw new Error("Failed to read bbox");
|
|
623
|
+
glyfOutput.set(bbox, glyfOffset + 2);
|
|
624
|
+
fontInfo.xMins[glyphId] = new DataView(bbox.buffer, bbox.byteOffset).getInt16(0);
|
|
625
|
+
glyfOutput.set(compositeData, glyfOffset + 10);
|
|
626
|
+
if (haveInstructions) {
|
|
627
|
+
const instrOffset = glyfOffset + 10 + compositeData.byteLength;
|
|
628
|
+
new DataView(glyfOutput.buffer, instrOffset).setUint16(0, instructionSize);
|
|
629
|
+
const instructions = instructionStream.readBytes(instructionSize);
|
|
630
|
+
if (!instructions) throw new Error("Failed to read instructions");
|
|
631
|
+
glyfOutput.set(instructions, instrOffset + 2);
|
|
632
|
+
}
|
|
633
|
+
glyfOffset += glyphSize;
|
|
634
|
+
glyfOffset = pad4(glyfOffset);
|
|
635
|
+
} else {
|
|
636
|
+
const result = reconstructSimpleGlyph(nContours, haveBbox, (overlapBitmap?.[glyphId >> 3] ?? 0) & 128 >> (glyphId & 7), nPointsStream, flagStream, glyphStream, bboxStream, instructionStream);
|
|
637
|
+
ensureCapacity(result.byteLength);
|
|
638
|
+
glyfOutput.set(result, glyfOffset);
|
|
639
|
+
if (result.byteLength >= 4) fontInfo.xMins[glyphId] = new DataView(result.buffer, result.byteOffset).getInt16(2);
|
|
640
|
+
glyfOffset += result.byteLength;
|
|
641
|
+
glyfOffset = pad4(glyfOffset);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
locaValues.push(glyfOffset);
|
|
645
|
+
const locaSize = indexFormat ? (numGlyphs + 1) * 4 : (numGlyphs + 1) * 2;
|
|
646
|
+
const locaData = new Uint8Array(locaSize);
|
|
647
|
+
const locaView = new DataView(locaData.buffer);
|
|
648
|
+
for (let i = 0; i <= numGlyphs; i++) if (indexFormat) locaView.setUint32(i * 4, locaValues[i]);
|
|
649
|
+
else locaView.setUint16(i * 2, locaValues[i] >> 1);
|
|
650
|
+
return {
|
|
651
|
+
glyfData: glyfOutput.subarray(0, glyfOffset),
|
|
652
|
+
locaData
|
|
653
|
+
};
|
|
654
|
+
function ensureCapacity(needed) {
|
|
655
|
+
if (glyfOffset + needed > glyfOutput.byteLength) {
|
|
656
|
+
const newOutput = new Uint8Array((glyfOffset + needed) * 2);
|
|
657
|
+
newOutput.set(glyfOutput);
|
|
658
|
+
glyfOutput = newOutput;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
function readCompositeGlyph(stream) {
|
|
663
|
+
const FLAG_ARG_1_AND_2_ARE_WORDS = 1;
|
|
664
|
+
const FLAG_WE_HAVE_A_SCALE = 8;
|
|
665
|
+
const FLAG_MORE_COMPONENTS = 32;
|
|
666
|
+
const FLAG_WE_HAVE_AN_X_AND_Y_SCALE = 64;
|
|
667
|
+
const FLAG_WE_HAVE_A_TWO_BY_TWO = 128;
|
|
668
|
+
const FLAG_WE_HAVE_INSTRUCTIONS = 256;
|
|
669
|
+
const startOffset = stream.offset;
|
|
670
|
+
let haveInstructions = false;
|
|
671
|
+
let flags = FLAG_MORE_COMPONENTS;
|
|
672
|
+
while (flags & FLAG_MORE_COMPONENTS) {
|
|
673
|
+
flags = stream.readU16() ?? 0;
|
|
674
|
+
haveInstructions = haveInstructions || (flags & FLAG_WE_HAVE_INSTRUCTIONS) !== 0;
|
|
675
|
+
let argSize = 2;
|
|
676
|
+
if (flags & FLAG_ARG_1_AND_2_ARE_WORDS) argSize += 4;
|
|
677
|
+
else argSize += 2;
|
|
678
|
+
if (flags & FLAG_WE_HAVE_A_SCALE) argSize += 2;
|
|
679
|
+
else if (flags & FLAG_WE_HAVE_AN_X_AND_Y_SCALE) argSize += 4;
|
|
680
|
+
else if (flags & FLAG_WE_HAVE_A_TWO_BY_TWO) argSize += 8;
|
|
681
|
+
stream.skip(argSize);
|
|
682
|
+
}
|
|
683
|
+
const compositeData = stream.subarray(startOffset, stream.offset - startOffset);
|
|
684
|
+
if (!compositeData) throw new Error("Failed to read composite glyph data");
|
|
685
|
+
return {
|
|
686
|
+
compositeData,
|
|
687
|
+
haveInstructions
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
function reconstructSimpleGlyph(nContours, haveBbox, hasOverlapBit, nPointsStream, flagStream, glyphStream, bboxStream, instructionStream) {
|
|
691
|
+
const pointsPerContour = [];
|
|
692
|
+
let totalPoints = 0;
|
|
693
|
+
for (let i = 0; i < nContours; i++) {
|
|
694
|
+
const n = read255UShort(nPointsStream);
|
|
695
|
+
if (n === null) throw new Error("Failed to read points count");
|
|
696
|
+
pointsPerContour.push(n);
|
|
697
|
+
totalPoints += n;
|
|
698
|
+
}
|
|
699
|
+
const points = decodeTriplets(flagStream, glyphStream, totalPoints);
|
|
700
|
+
const instructionSize = read255UShort(glyphStream) ?? 0;
|
|
701
|
+
const flagsAndCoords = encodePointsToGlyf(points, hasOverlapBit !== 0);
|
|
702
|
+
const glyphSize = 10 + 2 * nContours + 2 + instructionSize + flagsAndCoords.byteLength;
|
|
703
|
+
const output = new Uint8Array(glyphSize);
|
|
704
|
+
const view = new DataView(output.buffer);
|
|
705
|
+
let offset = 0;
|
|
706
|
+
view.setInt16(offset, nContours);
|
|
707
|
+
offset += 2;
|
|
708
|
+
if (haveBbox) {
|
|
709
|
+
const bbox = bboxStream.readBytes(8);
|
|
710
|
+
if (!bbox) throw new Error("Failed to read bbox");
|
|
711
|
+
output.set(bbox, offset);
|
|
712
|
+
} else {
|
|
713
|
+
let xMin = 0, yMin = 0, xMax = 0, yMax = 0;
|
|
714
|
+
if (points.length > 0) {
|
|
715
|
+
xMin = xMax = points[0].x;
|
|
716
|
+
yMin = yMax = points[0].y;
|
|
717
|
+
for (const p of points) {
|
|
718
|
+
xMin = Math.min(xMin, p.x);
|
|
719
|
+
xMax = Math.max(xMax, p.x);
|
|
720
|
+
yMin = Math.min(yMin, p.y);
|
|
721
|
+
yMax = Math.max(yMax, p.y);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
view.setInt16(offset, xMin);
|
|
725
|
+
view.setInt16(offset + 2, yMin);
|
|
726
|
+
view.setInt16(offset + 4, xMax);
|
|
727
|
+
view.setInt16(offset + 6, yMax);
|
|
728
|
+
}
|
|
729
|
+
offset += 8;
|
|
730
|
+
let endPoint = -1;
|
|
731
|
+
for (let i = 0; i < nContours; i++) {
|
|
732
|
+
endPoint += pointsPerContour[i];
|
|
733
|
+
view.setUint16(offset, endPoint);
|
|
734
|
+
offset += 2;
|
|
735
|
+
}
|
|
736
|
+
view.setUint16(offset, instructionSize);
|
|
737
|
+
offset += 2;
|
|
738
|
+
if (instructionSize > 0) {
|
|
739
|
+
const instructions = instructionStream.readBytes(instructionSize);
|
|
740
|
+
if (!instructions) throw new Error("Failed to read instructions");
|
|
741
|
+
output.set(instructions, offset);
|
|
742
|
+
offset += instructionSize;
|
|
743
|
+
}
|
|
744
|
+
output.set(flagsAndCoords, offset);
|
|
745
|
+
return output;
|
|
746
|
+
}
|
|
747
|
+
function decodeTriplets(flagStream, glyphStream, nPoints) {
|
|
748
|
+
const points = [];
|
|
749
|
+
let x = 0;
|
|
750
|
+
let y = 0;
|
|
751
|
+
for (let i = 0; i < nPoints; i++) {
|
|
752
|
+
const flag = flagStream.readU8();
|
|
753
|
+
if (flag === null) throw new Error("Failed to read triplet flag");
|
|
754
|
+
const onCurve = (flag & 128) === 0;
|
|
755
|
+
const flagLow = flag & 127;
|
|
756
|
+
let dx, dy;
|
|
757
|
+
if (flagLow < 10) {
|
|
758
|
+
dx = 0;
|
|
759
|
+
const b = glyphStream.readU8();
|
|
760
|
+
if (b === null) throw new Error("Failed to read triplet data");
|
|
761
|
+
dy = ((flagLow & 14) << 7) + b;
|
|
762
|
+
if ((flagLow & 1) === 0) dy = -dy;
|
|
763
|
+
} else if (flagLow < 20) {
|
|
764
|
+
const b = glyphStream.readU8();
|
|
765
|
+
if (b === null) throw new Error("Failed to read triplet data");
|
|
766
|
+
dx = ((flagLow - 10 & 14) << 7) + b;
|
|
767
|
+
if ((flagLow & 1) === 0) dx = -dx;
|
|
768
|
+
dy = 0;
|
|
769
|
+
} else if (flagLow < 84) {
|
|
770
|
+
const b = glyphStream.readU8();
|
|
771
|
+
if (b === null) throw new Error("Failed to read triplet data");
|
|
772
|
+
const b0 = flagLow - 20;
|
|
773
|
+
dx = 1 + (b0 & 48) + (b >> 4);
|
|
774
|
+
dy = 1 + ((b0 & 12) << 2) + (b & 15);
|
|
775
|
+
if ((flagLow & 1) === 0) dx = -dx;
|
|
776
|
+
if ((flagLow & 2) === 0) dy = -dy;
|
|
777
|
+
} else if (flagLow < 120) {
|
|
778
|
+
const b0 = glyphStream.readU8();
|
|
779
|
+
const b1 = glyphStream.readU8();
|
|
780
|
+
if (b0 === null || b1 === null) throw new Error("Failed to read triplet data");
|
|
781
|
+
const idx = flagLow - 84;
|
|
782
|
+
dx = 1 + (Math.floor(idx / 12) << 8) + b0;
|
|
783
|
+
dy = 1 + (idx % 12 >> 2 << 8) + b1;
|
|
784
|
+
if ((flagLow & 1) === 0) dx = -dx;
|
|
785
|
+
if ((flagLow & 2) === 0) dy = -dy;
|
|
786
|
+
} else if (flagLow < 124) {
|
|
787
|
+
const b0 = glyphStream.readU8();
|
|
788
|
+
const b1 = glyphStream.readU8();
|
|
789
|
+
const b2 = glyphStream.readU8();
|
|
790
|
+
if (b0 === null || b1 === null || b2 === null) throw new Error("Failed to read triplet data");
|
|
791
|
+
dx = (b0 << 4) + (b1 >> 4);
|
|
792
|
+
dy = ((b1 & 15) << 8) + b2;
|
|
793
|
+
if ((flagLow & 1) === 0) dx = -dx;
|
|
794
|
+
if ((flagLow & 2) === 0) dy = -dy;
|
|
795
|
+
} else {
|
|
796
|
+
const b0 = glyphStream.readU8();
|
|
797
|
+
const b1 = glyphStream.readU8();
|
|
798
|
+
const b2 = glyphStream.readU8();
|
|
799
|
+
const b3 = glyphStream.readU8();
|
|
800
|
+
if (b0 === null || b1 === null || b2 === null || b3 === null) throw new Error("Failed to read triplet data");
|
|
801
|
+
dx = (b0 << 8) + b1;
|
|
802
|
+
dy = (b2 << 8) + b3;
|
|
803
|
+
if ((flagLow & 1) === 0) dx = -dx;
|
|
804
|
+
if ((flagLow & 2) === 0) dy = -dy;
|
|
805
|
+
}
|
|
806
|
+
x += dx;
|
|
807
|
+
y += dy;
|
|
808
|
+
points.push({
|
|
809
|
+
x,
|
|
810
|
+
y,
|
|
811
|
+
onCurve
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
return points;
|
|
815
|
+
}
|
|
816
|
+
function encodePointsToGlyf(points, hasOverlapBit) {
|
|
817
|
+
const FLAG_ON_CURVE = 1;
|
|
818
|
+
const FLAG_X_SHORT = 2;
|
|
819
|
+
const FLAG_Y_SHORT = 4;
|
|
820
|
+
const FLAG_REPEAT = 8;
|
|
821
|
+
const FLAG_X_SAME = 16;
|
|
822
|
+
const FLAG_Y_SAME = 32;
|
|
823
|
+
const FLAG_OVERLAP_SIMPLE = 64;
|
|
824
|
+
if (points.length === 0) return new Uint8Array(0);
|
|
825
|
+
const flags = [];
|
|
826
|
+
const xCoords = [];
|
|
827
|
+
const yCoords = [];
|
|
828
|
+
let lastX = 0;
|
|
829
|
+
let lastY = 0;
|
|
830
|
+
for (let i = 0; i < points.length; i++) {
|
|
831
|
+
const p = points[i];
|
|
832
|
+
let flag = p.onCurve ? FLAG_ON_CURVE : 0;
|
|
833
|
+
if (hasOverlapBit && i === 0) flag |= FLAG_OVERLAP_SIMPLE;
|
|
834
|
+
const dx = p.x - lastX;
|
|
835
|
+
const dy = p.y - lastY;
|
|
836
|
+
if (dx === 0) flag |= FLAG_X_SAME;
|
|
837
|
+
else if (dx >= -255 && dx <= 255) {
|
|
838
|
+
flag |= FLAG_X_SHORT;
|
|
839
|
+
if (dx > 0) flag |= FLAG_X_SAME;
|
|
840
|
+
xCoords.push(Math.abs(dx));
|
|
841
|
+
} else xCoords.push(dx);
|
|
842
|
+
if (dy === 0) flag |= FLAG_Y_SAME;
|
|
843
|
+
else if (dy >= -255 && dy <= 255) {
|
|
844
|
+
flag |= FLAG_Y_SHORT;
|
|
845
|
+
if (dy > 0) flag |= FLAG_Y_SAME;
|
|
846
|
+
yCoords.push(Math.abs(dy));
|
|
847
|
+
} else yCoords.push(dy);
|
|
848
|
+
flags.push(flag);
|
|
849
|
+
lastX = p.x;
|
|
850
|
+
lastY = p.y;
|
|
851
|
+
}
|
|
852
|
+
const encodedFlags = [];
|
|
853
|
+
let lastFlag = -1;
|
|
854
|
+
let repeatCount = 0;
|
|
855
|
+
for (let i = 0; i < flags.length; i++) {
|
|
856
|
+
const flag = flags[i];
|
|
857
|
+
if (flag === lastFlag && repeatCount < 255) {
|
|
858
|
+
encodedFlags[encodedFlags.length - 1] |= FLAG_REPEAT;
|
|
859
|
+
repeatCount++;
|
|
860
|
+
} else {
|
|
861
|
+
if (repeatCount > 0) encodedFlags.push(repeatCount);
|
|
862
|
+
encodedFlags.push(flag);
|
|
863
|
+
repeatCount = 0;
|
|
864
|
+
}
|
|
865
|
+
lastFlag = flag;
|
|
866
|
+
}
|
|
867
|
+
if (repeatCount > 0) encodedFlags.push(repeatCount);
|
|
868
|
+
let xSize = 0;
|
|
869
|
+
let ySize = 0;
|
|
870
|
+
let xIdx = 0;
|
|
871
|
+
let yIdx = 0;
|
|
872
|
+
for (const flag of flags) {
|
|
873
|
+
if ((flag & FLAG_X_SHORT) !== 0) {
|
|
874
|
+
xSize += 1;
|
|
875
|
+
xIdx++;
|
|
876
|
+
} else if ((flag & FLAG_X_SAME) === 0) {
|
|
877
|
+
xSize += 2;
|
|
878
|
+
xIdx++;
|
|
879
|
+
}
|
|
880
|
+
if ((flag & FLAG_Y_SHORT) !== 0) {
|
|
881
|
+
ySize += 1;
|
|
882
|
+
yIdx++;
|
|
883
|
+
} else if ((flag & FLAG_Y_SAME) === 0) {
|
|
884
|
+
ySize += 2;
|
|
885
|
+
yIdx++;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
const output = new Uint8Array(encodedFlags.length + xSize + ySize);
|
|
889
|
+
let offset = 0;
|
|
890
|
+
for (const f of encodedFlags) output[offset++] = f;
|
|
891
|
+
xIdx = 0;
|
|
892
|
+
for (const flag of flags) if ((flag & FLAG_X_SHORT) !== 0) output[offset++] = xCoords[xIdx++];
|
|
893
|
+
else if ((flag & FLAG_X_SAME) === 0) {
|
|
894
|
+
const val = xCoords[xIdx++];
|
|
895
|
+
output[offset++] = val >> 8 & 255;
|
|
896
|
+
output[offset++] = val & 255;
|
|
897
|
+
}
|
|
898
|
+
yIdx = 0;
|
|
899
|
+
for (const flag of flags) if ((flag & FLAG_Y_SHORT) !== 0) output[offset++] = yCoords[yIdx++];
|
|
900
|
+
else if ((flag & FLAG_Y_SAME) === 0) {
|
|
901
|
+
const val = yCoords[yIdx++];
|
|
902
|
+
output[offset++] = val >> 8 & 255;
|
|
903
|
+
output[offset++] = val & 255;
|
|
904
|
+
}
|
|
905
|
+
return output;
|
|
906
|
+
}
|
|
907
|
+
function reconstructHmtx(data, table, numGlyphs, numHMetrics, xMins) {
|
|
908
|
+
const buf = new Buffer$1(data, table.srcOffset, table.srcLength);
|
|
909
|
+
const hmtxFlags = buf.readU8();
|
|
910
|
+
if (hmtxFlags === null) throw new Error("Failed to read hmtx flags");
|
|
911
|
+
const hasProportionalLsbs = (hmtxFlags & 1) === 0;
|
|
912
|
+
const hasMonospaceLsbs = (hmtxFlags & 2) === 0;
|
|
913
|
+
const advanceWidths = [];
|
|
914
|
+
for (let i = 0; i < numHMetrics; i++) {
|
|
915
|
+
const w = buf.readU16();
|
|
916
|
+
if (w === null) throw new Error("Failed to read advance width");
|
|
917
|
+
advanceWidths.push(w);
|
|
918
|
+
}
|
|
919
|
+
const lsbs = [];
|
|
920
|
+
for (let i = 0; i < numHMetrics; i++) if (hasProportionalLsbs) {
|
|
921
|
+
const lsb = buf.readS16();
|
|
922
|
+
if (lsb === null) throw new Error("Failed to read proportional LSB");
|
|
923
|
+
lsbs.push(lsb);
|
|
924
|
+
} else lsbs.push(xMins[i]);
|
|
925
|
+
for (let i = numHMetrics; i < numGlyphs; i++) if (hasMonospaceLsbs) {
|
|
926
|
+
const lsb = buf.readS16();
|
|
927
|
+
if (lsb === null) throw new Error("Failed to read monospace LSB");
|
|
928
|
+
lsbs.push(lsb);
|
|
929
|
+
} else lsbs.push(xMins[i]);
|
|
930
|
+
const outputSize = numHMetrics * 4 + (numGlyphs - numHMetrics) * 2;
|
|
931
|
+
const output = new Uint8Array(outputSize);
|
|
932
|
+
const view = new DataView(output.buffer);
|
|
933
|
+
let offset = 0;
|
|
934
|
+
for (let i = 0; i < numGlyphs; i++) {
|
|
935
|
+
if (i < numHMetrics) {
|
|
936
|
+
view.setUint16(offset, advanceWidths[i]);
|
|
937
|
+
offset += 2;
|
|
938
|
+
}
|
|
939
|
+
view.setInt16(offset, lsbs[i]);
|
|
940
|
+
offset += 2;
|
|
941
|
+
}
|
|
942
|
+
return output;
|
|
943
|
+
}
|
|
944
|
+
function updateTableEntry(view, entryOffset, checksum, offset, length) {
|
|
945
|
+
view.setUint32(entryOffset + 4, checksum);
|
|
946
|
+
view.setUint32(entryOffset + 8, offset);
|
|
947
|
+
view.setUint32(entryOffset + 12, length);
|
|
948
|
+
}
|
|
949
|
+
function computeChecksum(data, offset, length) {
|
|
950
|
+
let sum = 0;
|
|
951
|
+
const end = offset + length;
|
|
952
|
+
const view = new DataView(data.buffer, data.byteOffset);
|
|
953
|
+
const alignedEnd = offset + (length & -4);
|
|
954
|
+
for (let i = offset; i < alignedEnd; i += 4) sum = sum + view.getUint32(i) >>> 0;
|
|
955
|
+
if (end > alignedEnd) {
|
|
956
|
+
let last = 0;
|
|
957
|
+
for (let i = alignedEnd; i < end; i++) last = last << 8 | data[i];
|
|
958
|
+
last <<= (4 - (end - alignedEnd)) * 8;
|
|
959
|
+
sum = sum + last >>> 0;
|
|
960
|
+
}
|
|
961
|
+
return sum;
|
|
962
|
+
}
|
|
963
|
+
function pad4(n) {
|
|
964
|
+
return n + 3 & -4;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
//#endregion
|
|
968
|
+
exports.decode = decode;
|
|
969
|
+
exports.hasNativeBrotli = hasNative;
|
|
970
|
+
//# sourceMappingURL=index.cjs.map
|