woff2-decode 0.1.3 → 0.2.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/dist/index.js CHANGED
@@ -9,6 +9,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
9
9
  //#endregion
10
10
  //#region src/brotli.ts
11
11
  let nativeBrotli = null;
12
+ let browserBrotli = null;
12
13
  function tryLoadNative() {
13
14
  try {
14
15
  if (typeof process !== "undefined" && process.versions?.node) {
@@ -21,9 +22,24 @@ function tryLoadNative() {
21
22
  } catch {}
22
23
  return null;
23
24
  }
25
+ function tryLoadBrowserBrotli() {
26
+ try {
27
+ if (typeof DecompressionStream !== "undefined") {
28
+ new DecompressionStream("brotli");
29
+ return async (buf) => {
30
+ const arrayBuf = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
31
+ const decompressed = new Blob([arrayBuf]).stream().pipeThrough(new DecompressionStream("brotli"));
32
+ return new Uint8Array(await new Response(decompressed).arrayBuffer());
33
+ };
34
+ }
35
+ } catch {}
36
+ return null;
37
+ }
24
38
  nativeBrotli = tryLoadNative();
25
- function decompress(data) {
39
+ browserBrotli = tryLoadBrowserBrotli();
40
+ async function decompress(data) {
26
41
  if (nativeBrotli) return nativeBrotli(data);
42
+ if (browserBrotli) return browserBrotli(data);
27
43
  return brotliDecompress(data);
28
44
  }
29
45
  function decompressPure(data) {
@@ -38,9 +54,11 @@ var Buffer$1 = class {
38
54
  this.pos = 0;
39
55
  if (data instanceof Uint8Array) {
40
56
  const len = length ?? data.byteLength - offset;
57
+ this.u8 = data.subarray(offset, offset + len);
41
58
  this.view = new DataView(data.buffer, data.byteOffset + offset, len);
42
59
  } else {
43
60
  const len = length ?? data.byteLength - offset;
61
+ this.u8 = new Uint8Array(data, offset, len);
44
62
  this.view = new DataView(data, offset, len);
45
63
  }
46
64
  }
@@ -48,66 +66,67 @@ var Buffer$1 = class {
48
66
  return this.pos;
49
67
  }
50
68
  get length() {
51
- return this.view.byteLength;
69
+ return this.u8.byteLength;
52
70
  }
53
71
  get remaining() {
54
- return this.view.byteLength - this.pos;
72
+ return this.u8.byteLength - this.pos;
55
73
  }
56
74
  get buffer() {
57
75
  return this.view;
58
76
  }
59
77
  skip(n) {
60
- if (this.pos + n > this.view.byteLength || this.pos + n < this.pos) return false;
78
+ if (this.pos + n > this.u8.byteLength || this.pos + n < this.pos) return false;
61
79
  this.pos += n;
62
80
  return true;
63
81
  }
64
82
  seek(offset) {
65
- if (offset > this.view.byteLength || offset < 0) return false;
83
+ if (offset > this.u8.byteLength || offset < 0) return false;
66
84
  this.pos = offset;
67
85
  return true;
68
86
  }
69
87
  readU8() {
70
- if (this.pos + 1 > this.view.byteLength) return null;
71
- return this.view.getUint8(this.pos++);
88
+ if (this.pos + 1 > this.u8.byteLength) return null;
89
+ return this.u8[this.pos++];
72
90
  }
73
91
  readU16() {
74
- if (this.pos + 2 > this.view.byteLength) return null;
75
- const val = this.view.getUint16(this.pos, false);
76
- this.pos += 2;
77
- return val;
92
+ if (this.pos + 2 > this.u8.byteLength) return null;
93
+ const idx = this.pos;
94
+ this.pos = idx + 2;
95
+ return this.u8[idx] << 8 | this.u8[idx + 1];
78
96
  }
79
97
  readS16() {
80
- if (this.pos + 2 > this.view.byteLength) return null;
81
- const val = this.view.getInt16(this.pos, false);
82
- this.pos += 2;
83
- return val;
98
+ if (this.pos + 2 > this.u8.byteLength) return null;
99
+ const idx = this.pos;
100
+ this.pos = idx + 2;
101
+ const val = this.u8[idx] << 8 | this.u8[idx + 1];
102
+ return (val & 32768) !== 0 ? val - 65536 : val;
84
103
  }
85
104
  readU32() {
86
- if (this.pos + 4 > this.view.byteLength) return null;
87
- const val = this.view.getUint32(this.pos, false);
88
- this.pos += 4;
89
- return val;
105
+ if (this.pos + 4 > this.u8.byteLength) return null;
106
+ const idx = this.pos;
107
+ this.pos = idx + 4;
108
+ return this.u8[idx] * 16777216 + (this.u8[idx + 1] << 16 | this.u8[idx + 2] << 8 | this.u8[idx + 3]) >>> 0;
90
109
  }
91
110
  readS32() {
92
- if (this.pos + 4 > this.view.byteLength) return null;
93
- const val = this.view.getInt32(this.pos, false);
94
- this.pos += 4;
95
- return val;
111
+ if (this.pos + 4 > this.u8.byteLength) return null;
112
+ const idx = this.pos;
113
+ this.pos = idx + 4;
114
+ return this.u8[idx] << 24 | this.u8[idx + 1] << 16 | this.u8[idx + 2] << 8 | this.u8[idx + 3];
96
115
  }
97
116
  readBytes(n) {
98
- if (this.pos + n > this.view.byteLength || n < 0) return null;
99
- const result = new Uint8Array(this.view.buffer, this.view.byteOffset + this.pos, n);
117
+ if (this.pos + n > this.u8.byteLength || n < 0) return null;
118
+ const result = this.u8.subarray(this.pos, this.pos + n);
100
119
  this.pos += n;
101
120
  return result;
102
121
  }
103
122
  peekU8(offset = 0) {
104
123
  const idx = this.pos + offset;
105
- if (idx >= this.view.byteLength || idx < 0) return null;
106
- return this.view.getUint8(idx);
124
+ if (idx >= this.u8.byteLength || idx < 0) return null;
125
+ return this.u8[idx];
107
126
  }
108
127
  subarray(offset, length) {
109
- if (offset + length > this.view.byteLength || offset < 0 || length < 0) return null;
110
- return new Uint8Array(this.view.buffer, this.view.byteOffset + offset, length);
128
+ if (offset + length > this.u8.byteLength || offset < 0 || length < 0) return null;
129
+ return this.u8.subarray(offset, offset + length);
111
130
  }
112
131
  };
113
132
 
@@ -223,12 +242,19 @@ function readBase128(buf) {
223
242
  //#region src/decode.ts
224
243
  const SFNT_HEADER_SIZE = 12;
225
244
  const SFNT_ENTRY_SIZE = 16;
226
- function decode(data, options) {
245
+ const FLAG_ON_CURVE = 1;
246
+ const FLAG_X_SHORT = 2;
247
+ const FLAG_Y_SHORT = 4;
248
+ const FLAG_REPEAT = 8;
249
+ const FLAG_X_SAME = 16;
250
+ const FLAG_Y_SAME = 32;
251
+ const FLAG_OVERLAP_SIMPLE = 64;
252
+ async function decode(data, options) {
227
253
  const input = data instanceof Uint8Array ? data : new Uint8Array(data);
228
254
  const header = readHeader(new Buffer$1(input), input.byteLength);
229
255
  if (!header) throw new Error("Failed to read WOFF2 header");
230
256
  const compressedData = input.subarray(header.compressedOffset, header.compressedOffset + header.compressedLength);
231
- const decompressed = (options?.forcePureBrotli ? decompressPure : decompress)(compressedData);
257
+ const decompressed = options?.forcePureBrotli ? decompressPure(compressedData) : await decompress(compressedData);
232
258
  if (!decompressed || decompressed.byteLength !== header.uncompressedSize) throw new Error(`Brotli decompression failed: expected ${header.uncompressedSize} bytes, got ${decompressed?.byteLength ?? 0}`);
233
259
  let outputSize = computeOffsetToFirstTable(header);
234
260
  for (const table of header.tables) {
@@ -344,7 +370,8 @@ function readTableDirectory(buf, numTables) {
344
370
  srcOffset,
345
371
  srcLength: transformLength,
346
372
  dstOffset: 0,
347
- dstLength: origLength
373
+ dstLength: origLength,
374
+ key: `${tag}:${srcOffset}`
348
375
  });
349
376
  srcOffset += transformLength;
350
377
  }
@@ -447,8 +474,8 @@ function reconstructFont(decompressed, header, fontIndex, fontInfo, output, outV
447
474
  for (const table of sortedTables) {
448
475
  const entryOffset = fontInfo.tableEntryByTag.get(table.tag);
449
476
  if (entryOffset === void 0) continue;
450
- const tableKey = `${table.tag}:${table.srcOffset}`;
451
- const existing = writtenTables.get(tableKey);
477
+ const tKey = table.key;
478
+ const existing = writtenTables.get(tKey);
452
479
  if (existing) {
453
480
  updateTableEntry(outView, entryOffset, existing.checksum, existing.dstOffset, existing.dstLength);
454
481
  if (isTTC) {
@@ -473,7 +500,7 @@ function reconstructFont(decompressed, header, fontIndex, fontInfo, output, outV
473
500
  fontChecksum = fontChecksum + checksum >>> 0;
474
501
  fontChecksum = fontChecksum + computeTableEntryChecksum(checksum, dstOffset, tableData.byteLength) >>> 0;
475
502
  }
476
- writtenTables.set(tableKey, {
503
+ writtenTables.set(tKey, {
477
504
  dstOffset,
478
505
  dstLength: tableData.byteLength,
479
506
  checksum
@@ -488,8 +515,7 @@ function reconstructFont(decompressed, header, fontIndex, fontInfo, output, outV
488
515
  fontChecksum = fontChecksum + locaChecksum >>> 0;
489
516
  fontChecksum = fontChecksum + computeTableEntryChecksum(locaChecksum, dstOffset, result.locaData.byteLength) >>> 0;
490
517
  }
491
- const locaKey = `${TAG_LOCA}:${locaTable.srcOffset}`;
492
- writtenTables.set(locaKey, {
518
+ writtenTables.set(locaTable.key, {
493
519
  dstOffset,
494
520
  dstLength: result.locaData.byteLength,
495
521
  checksum: locaChecksum
@@ -515,7 +541,7 @@ function reconstructFont(decompressed, header, fontIndex, fontInfo, output, outV
515
541
  fontChecksum = fontChecksum + checksum >>> 0;
516
542
  fontChecksum = fontChecksum + computeTableEntryChecksum(checksum, dstOffset, tableData.byteLength) >>> 0;
517
543
  }
518
- writtenTables.set(tableKey, {
544
+ writtenTables.set(tKey, {
519
545
  dstOffset,
520
546
  dstLength: tableData.byteLength,
521
547
  checksum
@@ -524,7 +550,7 @@ function reconstructFont(decompressed, header, fontIndex, fontInfo, output, outV
524
550
  }
525
551
  const headTable = sortedTables.find((t) => t.tag === TAG_HEAD);
526
552
  if (headTable) {
527
- const headEntry = writtenTables.get(`${TAG_HEAD}:${headTable.srcOffset}`);
553
+ const headEntry = writtenTables.get(headTable.key);
528
554
  if (headEntry && headEntry.dstLength >= 12) {
529
555
  const finalChecksum = isTTC ? fontChecksum : computeChecksum(output, 0, dstOffset);
530
556
  outView.setUint32(headEntry.dstOffset + 8, 2981146554 - finalChecksum >>> 0);
@@ -535,54 +561,100 @@ function reconstructFont(decompressed, header, fontIndex, fontInfo, output, outV
535
561
  function computeTableEntryChecksum(checksum, offset, length) {
536
562
  return checksum + offset + length >>> 0;
537
563
  }
564
+ function makeByteStream(data, start, length) {
565
+ return {
566
+ data,
567
+ pos: start,
568
+ end: start + length
569
+ };
570
+ }
571
+ function bsReadU8(stream) {
572
+ if (stream.pos >= stream.end) throw new Error("Stream overflow");
573
+ return stream.data[stream.pos++];
574
+ }
575
+ function bsReadU16(stream) {
576
+ if (stream.pos + 2 > stream.end) throw new Error("Stream overflow");
577
+ const idx = stream.pos;
578
+ stream.pos = idx + 2;
579
+ return stream.data[idx] << 8 | stream.data[idx + 1];
580
+ }
581
+ function bsReadS16(stream) {
582
+ if (stream.pos + 2 > stream.end) throw new Error("Stream overflow");
583
+ const idx = stream.pos;
584
+ stream.pos = idx + 2;
585
+ const val = stream.data[idx] << 8 | stream.data[idx + 1];
586
+ return (val & 32768) !== 0 ? val - 65536 : val;
587
+ }
588
+ function fsReadU32(stream) {
589
+ if (stream.pos + 4 > stream.end) throw new Error("Stream overflow");
590
+ const idx = stream.pos;
591
+ stream.pos = idx + 4;
592
+ return stream.data[idx] * 16777216 + (stream.data[idx + 1] << 16 | stream.data[idx + 2] << 8 | stream.data[idx + 3]) >>> 0;
593
+ }
594
+ function bsSkip(stream, n) {
595
+ if (stream.pos + n > stream.end || n < 0) throw new Error("Stream overflow");
596
+ stream.pos += n;
597
+ }
598
+ function fsReadBytes(stream, n) {
599
+ if (stream.pos + n > stream.end || n < 0) throw new Error("Stream overflow");
600
+ const start = stream.pos;
601
+ stream.pos += n;
602
+ return stream.data.subarray(start, start + n);
603
+ }
604
+ function bsRead255UShort(stream) {
605
+ const code = bsReadU8(stream);
606
+ if (code === 253) return bsReadU16(stream);
607
+ else if (code === 255) return 253 + bsReadU8(stream);
608
+ else if (code === 254) return 506 + bsReadU8(stream);
609
+ return code;
610
+ }
538
611
  function reconstructGlyf(data, glyfTable, locaTable, fontInfo) {
539
- const buf = new Buffer$1(data, glyfTable.srcOffset, glyfTable.transformLength);
540
- const version = buf.readU16();
541
- const optionFlags = buf.readU16();
542
- const numGlyphs = buf.readU16();
543
- const indexFormat = buf.readU16();
544
- if (version === null || optionFlags === null || numGlyphs === null || indexFormat === null) throw new Error("Invalid glyf transform header");
612
+ const headerStream = makeByteStream(data, glyfTable.srcOffset, glyfTable.transformLength);
613
+ bsReadU16(headerStream);
614
+ const optionFlags = bsReadU16(headerStream);
615
+ const numGlyphs = bsReadU16(headerStream);
616
+ const indexFormat = bsReadU16(headerStream);
545
617
  fontInfo.numGlyphs = numGlyphs;
546
618
  fontInfo.indexFormat = indexFormat;
547
- const nContourStreamSize = buf.readU32();
548
- const nPointsStreamSize = buf.readU32();
549
- const flagStreamSize = buf.readU32();
550
- const glyphStreamSize = buf.readU32();
551
- const compositeStreamSize = buf.readU32();
552
- const bboxStreamSize = buf.readU32();
553
- const instructionStreamSize = buf.readU32();
554
- if (nContourStreamSize === null || nPointsStreamSize === null || flagStreamSize === null || glyphStreamSize === null || compositeStreamSize === null || bboxStreamSize === null || instructionStreamSize === null) throw new Error("Invalid glyf substream sizes");
555
- let offset = buf.offset;
556
- const nContourStream = new Buffer$1(data, glyfTable.srcOffset + offset, nContourStreamSize);
619
+ const nContourStreamSize = fsReadU32(headerStream);
620
+ const nPointsStreamSize = fsReadU32(headerStream);
621
+ const flagStreamSize = fsReadU32(headerStream);
622
+ const glyphStreamSize = fsReadU32(headerStream);
623
+ const compositeStreamSize = fsReadU32(headerStream);
624
+ const bboxStreamSize = fsReadU32(headerStream);
625
+ const instructionStreamSize = fsReadU32(headerStream);
626
+ let offset = headerStream.pos;
627
+ const nContourStream = makeByteStream(data, offset, nContourStreamSize);
557
628
  offset += nContourStreamSize;
558
- const nPointsStream = new Buffer$1(data, glyfTable.srcOffset + offset, nPointsStreamSize);
629
+ const nPointsStream = makeByteStream(data, offset, nPointsStreamSize);
559
630
  offset += nPointsStreamSize;
560
- const flagStream = new Buffer$1(data, glyfTable.srcOffset + offset, flagStreamSize);
631
+ const flagStream = makeByteStream(data, offset, flagStreamSize);
561
632
  offset += flagStreamSize;
562
- const glyphStream = new Buffer$1(data, glyfTable.srcOffset + offset, glyphStreamSize);
633
+ const glyphStream = makeByteStream(data, offset, glyphStreamSize);
563
634
  offset += glyphStreamSize;
564
- const compositeStream = new Buffer$1(data, glyfTable.srcOffset + offset, compositeStreamSize);
635
+ const compositeStream = makeByteStream(data, offset, compositeStreamSize);
565
636
  offset += compositeStreamSize;
566
- const bboxStream = new Buffer$1(data, glyfTable.srcOffset + offset, bboxStreamSize);
637
+ const bboxStream = makeByteStream(data, offset, bboxStreamSize);
567
638
  offset += bboxStreamSize;
568
- const instructionStream = new Buffer$1(data, glyfTable.srcOffset + offset, instructionStreamSize);
639
+ const instructionStream = makeByteStream(data, offset, instructionStreamSize);
569
640
  const hasOverlapBitmap = (optionFlags & 1) !== 0;
570
641
  let overlapBitmap = null;
571
642
  if (hasOverlapBitmap) {
572
643
  const overlapBitmapLength = numGlyphs + 7 >> 3;
573
- overlapBitmap = data.subarray(glyfTable.srcOffset + offset + instructionStreamSize, glyfTable.srcOffset + offset + instructionStreamSize + overlapBitmapLength);
644
+ overlapBitmap = data.subarray(offset + instructionStreamSize, offset + instructionStreamSize + overlapBitmapLength);
574
645
  }
575
- const bboxBitmapLength = numGlyphs + 31 >> 5 << 2;
576
- const bboxBitmap = bboxStream.readBytes(bboxBitmapLength);
577
- if (!bboxBitmap) throw new Error("Failed to read bbox bitmap");
646
+ const bboxBitmap = fsReadBytes(bboxStream, numGlyphs + 31 >> 5 << 2);
578
647
  let glyfOutput = new Uint8Array(glyfTable.origLength * 2);
579
648
  let glyfOffset = 0;
580
- const locaValues = [];
649
+ const locaValues = new Uint32Array(numGlyphs + 1);
581
650
  fontInfo.xMins = new Int16Array(numGlyphs);
651
+ let contourEndsScratch = new Uint16Array(128);
652
+ let flagsScratch = new Uint8Array(512);
653
+ let xScratch = new Uint8Array(512);
654
+ let yScratch = new Uint8Array(512);
582
655
  for (let glyphId = 0; glyphId < numGlyphs; glyphId++) {
583
- locaValues.push(glyfOffset);
584
- const nContours = nContourStream.readS16();
585
- if (nContours === null) throw new Error(`Failed to read nContours for glyph ${glyphId}`);
656
+ locaValues[glyphId] = glyfOffset;
657
+ const nContours = bsReadS16(nContourStream);
586
658
  const haveBbox = (bboxBitmap[glyphId >> 3] & 128 >> (glyphId & 7)) !== 0;
587
659
  if (nContours === 0) {
588
660
  if (haveBbox) throw new Error(`Empty glyph ${glyphId} has bbox`);
@@ -592,34 +664,76 @@ function reconstructGlyf(data, glyfTable, locaTable, fontInfo) {
592
664
  if (!haveBbox) throw new Error(`Composite glyph ${glyphId} missing bbox`);
593
665
  const { compositeData, haveInstructions } = readCompositeGlyph(compositeStream);
594
666
  let instructionSize = 0;
595
- if (haveInstructions) instructionSize = read255UShort(glyphStream) ?? 0;
667
+ if (haveInstructions) instructionSize = bsRead255UShort(glyphStream);
596
668
  const glyphSize = 10 + compositeData.byteLength + (haveInstructions ? 2 + instructionSize : 0);
597
669
  ensureCapacity(glyphSize);
598
- new DataView(glyfOutput.buffer, glyfOffset).setInt16(0, -1);
599
- const bbox = bboxStream.readBytes(8);
600
- if (!bbox) throw new Error("Failed to read bbox");
670
+ writeInt16BE(glyfOutput, glyfOffset, -1);
671
+ const bbox = fsReadBytes(bboxStream, 8);
601
672
  glyfOutput.set(bbox, glyfOffset + 2);
602
- fontInfo.xMins[glyphId] = new DataView(bbox.buffer, bbox.byteOffset).getInt16(0);
673
+ fontInfo.xMins[glyphId] = readInt16BE(bbox, 0);
603
674
  glyfOutput.set(compositeData, glyfOffset + 10);
604
675
  if (haveInstructions) {
605
676
  const instrOffset = glyfOffset + 10 + compositeData.byteLength;
606
- new DataView(glyfOutput.buffer, instrOffset).setUint16(0, instructionSize);
607
- const instructions = instructionStream.readBytes(instructionSize);
608
- if (!instructions) throw new Error("Failed to read instructions");
677
+ writeUint16BE(glyfOutput, instrOffset, instructionSize);
678
+ const instructions = fsReadBytes(instructionStream, instructionSize);
609
679
  glyfOutput.set(instructions, instrOffset + 2);
610
680
  }
611
681
  glyfOffset += glyphSize;
612
682
  glyfOffset = pad4(glyfOffset);
613
683
  } else {
614
- const result = reconstructSimpleGlyph(nContours, haveBbox, (overlapBitmap?.[glyphId >> 3] ?? 0) & 128 >> (glyphId & 7), nPointsStream, flagStream, glyphStream, bboxStream, instructionStream);
615
- ensureCapacity(result.byteLength);
616
- glyfOutput.set(result, glyfOffset);
617
- if (result.byteLength >= 4) fontInfo.xMins[glyphId] = new DataView(result.buffer, result.byteOffset).getInt16(2);
618
- glyfOffset += result.byteLength;
684
+ if (nContours > contourEndsScratch.length) contourEndsScratch = new Uint16Array(nContours * 2);
685
+ let totalPoints = 0;
686
+ let endPoint = -1;
687
+ for (let i = 0; i < nContours; i++) {
688
+ const n = bsRead255UShort(nPointsStream);
689
+ totalPoints += n;
690
+ endPoint += n;
691
+ contourEndsScratch[i] = endPoint;
692
+ }
693
+ const scratchSize = totalPoints * 2;
694
+ if (scratchSize > flagsScratch.length) flagsScratch = new Uint8Array(scratchSize);
695
+ if (scratchSize > xScratch.length) xScratch = new Uint8Array(scratchSize);
696
+ if (scratchSize > yScratch.length) yScratch = new Uint8Array(scratchSize);
697
+ const encoded = encodeTripletsToScratch(flagStream, glyphStream, totalPoints, ((overlapBitmap?.[glyphId >> 3] ?? 0) & 128 >> (glyphId & 7)) !== 0, flagsScratch, xScratch, yScratch);
698
+ const instructionSize = bsRead255UShort(glyphStream);
699
+ const glyphSize = 10 + 2 * nContours + 2 + instructionSize + encoded.flagsLen + encoded.xLen + encoded.yLen;
700
+ ensureCapacity(glyphSize);
701
+ writeInt16BE(glyfOutput, glyfOffset, nContours);
702
+ let xMin = 0;
703
+ if (haveBbox) {
704
+ const bbox = fsReadBytes(bboxStream, 8);
705
+ glyfOutput.set(bbox, glyfOffset + 2);
706
+ xMin = readInt16BE(bbox, 0);
707
+ } else {
708
+ writeInt16BE(glyfOutput, glyfOffset + 2, encoded.xMin);
709
+ writeInt16BE(glyfOutput, glyfOffset + 4, encoded.yMin);
710
+ writeInt16BE(glyfOutput, glyfOffset + 6, encoded.xMax);
711
+ writeInt16BE(glyfOutput, glyfOffset + 8, encoded.yMax);
712
+ xMin = encoded.xMin;
713
+ }
714
+ let writeOffset = glyfOffset + 10;
715
+ for (let i = 0; i < nContours; i++) {
716
+ writeUint16BE(glyfOutput, writeOffset, contourEndsScratch[i]);
717
+ writeOffset += 2;
718
+ }
719
+ writeUint16BE(glyfOutput, writeOffset, instructionSize);
720
+ writeOffset += 2;
721
+ if (instructionSize > 0) {
722
+ const instructions = fsReadBytes(instructionStream, instructionSize);
723
+ glyfOutput.set(instructions, writeOffset);
724
+ writeOffset += instructionSize;
725
+ }
726
+ glyfOutput.set(flagsScratch.subarray(0, encoded.flagsLen), writeOffset);
727
+ writeOffset += encoded.flagsLen;
728
+ glyfOutput.set(xScratch.subarray(0, encoded.xLen), writeOffset);
729
+ writeOffset += encoded.xLen;
730
+ glyfOutput.set(yScratch.subarray(0, encoded.yLen), writeOffset);
731
+ fontInfo.xMins[glyphId] = xMin;
732
+ glyfOffset += glyphSize;
619
733
  glyfOffset = pad4(glyfOffset);
620
734
  }
621
735
  }
622
- locaValues.push(glyfOffset);
736
+ locaValues[numGlyphs] = glyfOffset;
623
737
  const locaSize = indexFormat ? (numGlyphs + 1) * 4 : (numGlyphs + 1) * 2;
624
738
  const locaData = new Uint8Array(locaSize);
625
739
  const locaView = new DataView(locaData.buffer);
@@ -644,11 +758,11 @@ function readCompositeGlyph(stream) {
644
758
  const FLAG_WE_HAVE_AN_X_AND_Y_SCALE = 64;
645
759
  const FLAG_WE_HAVE_A_TWO_BY_TWO = 128;
646
760
  const FLAG_WE_HAVE_INSTRUCTIONS = 256;
647
- const startOffset = stream.offset;
761
+ const startOffset = stream.pos;
648
762
  let haveInstructions = false;
649
763
  let flags = FLAG_MORE_COMPONENTS;
650
764
  while (flags & FLAG_MORE_COMPONENTS) {
651
- flags = stream.readU16() ?? 0;
765
+ flags = bsReadU16(stream);
652
766
  haveInstructions = haveInstructions || (flags & FLAG_WE_HAVE_INSTRUCTIONS) !== 0;
653
767
  let argSize = 2;
654
768
  if (flags & FLAG_ARG_1_AND_2_ARE_WORDS) argSize += 4;
@@ -656,126 +770,91 @@ function readCompositeGlyph(stream) {
656
770
  if (flags & FLAG_WE_HAVE_A_SCALE) argSize += 2;
657
771
  else if (flags & FLAG_WE_HAVE_AN_X_AND_Y_SCALE) argSize += 4;
658
772
  else if (flags & FLAG_WE_HAVE_A_TWO_BY_TWO) argSize += 8;
659
- stream.skip(argSize);
773
+ bsSkip(stream, argSize);
660
774
  }
661
- const compositeData = stream.subarray(startOffset, stream.offset - startOffset);
662
- if (!compositeData) throw new Error("Failed to read composite glyph data");
663
775
  return {
664
- compositeData,
776
+ compositeData: stream.data.subarray(startOffset, stream.pos),
665
777
  haveInstructions
666
778
  };
667
779
  }
668
- function reconstructSimpleGlyph(nContours, haveBbox, hasOverlapBit, nPointsStream, flagStream, glyphStream, bboxStream, instructionStream) {
669
- const pointsPerContour = [];
670
- let totalPoints = 0;
671
- for (let i = 0; i < nContours; i++) {
672
- const n = read255UShort(nPointsStream);
673
- if (n === null) throw new Error("Failed to read points count");
674
- pointsPerContour.push(n);
675
- totalPoints += n;
676
- }
677
- const points = decodeTriplets(flagStream, glyphStream, totalPoints);
678
- const instructionSize = read255UShort(glyphStream) ?? 0;
679
- const flagsAndCoords = encodePointsToGlyf(points, hasOverlapBit !== 0);
680
- const glyphSize = 10 + 2 * nContours + 2 + instructionSize + flagsAndCoords.byteLength;
681
- const output = new Uint8Array(glyphSize);
682
- const view = new DataView(output.buffer);
683
- let offset = 0;
684
- view.setInt16(offset, nContours);
685
- offset += 2;
686
- if (haveBbox) {
687
- const bbox = bboxStream.readBytes(8);
688
- if (!bbox) throw new Error("Failed to read bbox");
689
- output.set(bbox, offset);
690
- } else {
691
- let xMin = 0, yMin = 0, xMax = 0, yMax = 0;
692
- if (points.length > 0) {
693
- xMin = xMax = points[0].x;
694
- yMin = yMax = points[0].y;
695
- for (const p of points) {
696
- xMin = Math.min(xMin, p.x);
697
- xMax = Math.max(xMax, p.x);
698
- yMin = Math.min(yMin, p.y);
699
- yMax = Math.max(yMax, p.y);
700
- }
701
- }
702
- view.setInt16(offset, xMin);
703
- view.setInt16(offset + 2, yMin);
704
- view.setInt16(offset + 4, xMax);
705
- view.setInt16(offset + 6, yMax);
706
- }
707
- offset += 8;
708
- let endPoint = -1;
709
- for (let i = 0; i < nContours; i++) {
710
- endPoint += pointsPerContour[i];
711
- view.setUint16(offset, endPoint);
712
- offset += 2;
713
- }
714
- view.setUint16(offset, instructionSize);
715
- offset += 2;
716
- if (instructionSize > 0) {
717
- const instructions = instructionStream.readBytes(instructionSize);
718
- if (!instructions) throw new Error("Failed to read instructions");
719
- output.set(instructions, offset);
720
- offset += instructionSize;
721
- }
722
- output.set(flagsAndCoords, offset);
723
- return output;
724
- }
725
- function decodeTriplets(flagStream, glyphStream, nPoints) {
726
- const points = [];
780
+ function encodeTripletsToScratch(flagStream, glyphStream, nPoints, hasOverlapBit, flagsOut, xOut, yOut) {
781
+ if (nPoints === 0) return {
782
+ flagsLen: 0,
783
+ xLen: 0,
784
+ yLen: 0,
785
+ xMin: 0,
786
+ yMin: 0,
787
+ xMax: 0,
788
+ yMax: 0
789
+ };
790
+ let flagsLen = 0;
791
+ let xLen = 0;
792
+ let yLen = 0;
727
793
  let x = 0;
728
794
  let y = 0;
795
+ let xMin = 0;
796
+ let yMin = 0;
797
+ let xMax = 0;
798
+ let yMax = 0;
799
+ let lastFlag = -1;
800
+ let repeatCount = 0;
801
+ const flagData = flagStream.data;
802
+ let flagPos = flagStream.pos;
803
+ const flagEnd = flagStream.end;
804
+ const glyphData = glyphStream.data;
805
+ let glyphPos = glyphStream.pos;
806
+ const glyphEnd = glyphStream.end;
729
807
  for (let i = 0; i < nPoints; i++) {
730
- const flag = flagStream.readU8();
731
- if (flag === null) throw new Error("Failed to read triplet flag");
808
+ if (flagPos >= flagEnd) throw new Error("Stream overflow");
809
+ const flag = flagData[flagPos++];
732
810
  const onCurve = (flag & 128) === 0;
733
811
  const flagLow = flag & 127;
734
- let dx, dy;
812
+ let dx;
813
+ let dy;
735
814
  if (flagLow < 10) {
736
815
  dx = 0;
737
- const b = glyphStream.readU8();
738
- if (b === null) throw new Error("Failed to read triplet data");
816
+ if (glyphPos >= glyphEnd) throw new Error("Stream overflow");
817
+ const b = glyphData[glyphPos++];
739
818
  dy = ((flagLow & 14) << 7) + b;
740
819
  if ((flagLow & 1) === 0) dy = -dy;
741
820
  } else if (flagLow < 20) {
742
- const b = glyphStream.readU8();
743
- if (b === null) throw new Error("Failed to read triplet data");
821
+ if (glyphPos >= glyphEnd) throw new Error("Stream overflow");
822
+ const b = glyphData[glyphPos++];
744
823
  dx = ((flagLow - 10 & 14) << 7) + b;
745
824
  if ((flagLow & 1) === 0) dx = -dx;
746
825
  dy = 0;
747
826
  } else if (flagLow < 84) {
748
- const b = glyphStream.readU8();
749
- if (b === null) throw new Error("Failed to read triplet data");
827
+ if (glyphPos >= glyphEnd) throw new Error("Stream overflow");
828
+ const b = glyphData[glyphPos++];
750
829
  const b0 = flagLow - 20;
751
830
  dx = 1 + (b0 & 48) + (b >> 4);
752
831
  dy = 1 + ((b0 & 12) << 2) + (b & 15);
753
832
  if ((flagLow & 1) === 0) dx = -dx;
754
833
  if ((flagLow & 2) === 0) dy = -dy;
755
834
  } else if (flagLow < 120) {
756
- const b0 = glyphStream.readU8();
757
- const b1 = glyphStream.readU8();
758
- if (b0 === null || b1 === null) throw new Error("Failed to read triplet data");
835
+ if (glyphPos + 1 >= glyphEnd) throw new Error("Stream overflow");
836
+ const b0 = glyphData[glyphPos++];
837
+ const b1 = glyphData[glyphPos++];
759
838
  const idx = flagLow - 84;
760
- dx = 1 + (Math.floor(idx / 12) << 8) + b0;
839
+ dx = 1 + ((idx / 12 | 0) << 8) + b0;
761
840
  dy = 1 + (idx % 12 >> 2 << 8) + b1;
762
841
  if ((flagLow & 1) === 0) dx = -dx;
763
842
  if ((flagLow & 2) === 0) dy = -dy;
764
843
  } else if (flagLow < 124) {
765
- const b0 = glyphStream.readU8();
766
- const b1 = glyphStream.readU8();
767
- const b2 = glyphStream.readU8();
768
- if (b0 === null || b1 === null || b2 === null) throw new Error("Failed to read triplet data");
844
+ if (glyphPos + 2 >= glyphEnd) throw new Error("Stream overflow");
845
+ const b0 = glyphData[glyphPos++];
846
+ const b1 = glyphData[glyphPos++];
847
+ const b2 = glyphData[glyphPos++];
769
848
  dx = (b0 << 4) + (b1 >> 4);
770
849
  dy = ((b1 & 15) << 8) + b2;
771
850
  if ((flagLow & 1) === 0) dx = -dx;
772
851
  if ((flagLow & 2) === 0) dy = -dy;
773
852
  } else {
774
- const b0 = glyphStream.readU8();
775
- const b1 = glyphStream.readU8();
776
- const b2 = glyphStream.readU8();
777
- const b3 = glyphStream.readU8();
778
- if (b0 === null || b1 === null || b2 === null || b3 === null) throw new Error("Failed to read triplet data");
853
+ if (glyphPos + 3 >= glyphEnd) throw new Error("Stream overflow");
854
+ const b0 = glyphData[glyphPos++];
855
+ const b1 = glyphData[glyphPos++];
856
+ const b2 = glyphData[glyphPos++];
857
+ const b3 = glyphData[glyphPos++];
779
858
  dx = (b0 << 8) + b1;
780
859
  dy = (b2 << 8) + b3;
781
860
  if ((flagLow & 1) === 0) dx = -dx;
@@ -783,138 +862,81 @@ function decodeTriplets(flagStream, glyphStream, nPoints) {
783
862
  }
784
863
  x += dx;
785
864
  y += dy;
786
- points.push({
787
- x,
788
- y,
789
- onCurve
790
- });
791
- }
792
- return points;
793
- }
794
- function encodePointsToGlyf(points, hasOverlapBit) {
795
- const FLAG_ON_CURVE = 1;
796
- const FLAG_X_SHORT = 2;
797
- const FLAG_Y_SHORT = 4;
798
- const FLAG_REPEAT = 8;
799
- const FLAG_X_SAME = 16;
800
- const FLAG_Y_SAME = 32;
801
- const FLAG_OVERLAP_SIMPLE = 64;
802
- if (points.length === 0) return new Uint8Array(0);
803
- const flags = [];
804
- const xCoords = [];
805
- const yCoords = [];
806
- let lastX = 0;
807
- let lastY = 0;
808
- for (let i = 0; i < points.length; i++) {
809
- const p = points[i];
810
- let flag = p.onCurve ? FLAG_ON_CURVE : 0;
811
- if (hasOverlapBit && i === 0) flag |= FLAG_OVERLAP_SIMPLE;
812
- const dx = p.x - lastX;
813
- const dy = p.y - lastY;
814
- if (dx === 0) flag |= FLAG_X_SAME;
865
+ if (i === 0) {
866
+ xMin = xMax = x;
867
+ yMin = yMax = y;
868
+ } else {
869
+ if (x < xMin) xMin = x;
870
+ if (x > xMax) xMax = x;
871
+ if (y < yMin) yMin = y;
872
+ if (y > yMax) yMax = y;
873
+ }
874
+ let outFlag = onCurve ? FLAG_ON_CURVE : 0;
875
+ if (hasOverlapBit && i === 0) outFlag |= FLAG_OVERLAP_SIMPLE;
876
+ if (dx === 0) outFlag |= FLAG_X_SAME;
815
877
  else if (dx >= -255 && dx <= 255) {
816
- flag |= FLAG_X_SHORT;
817
- if (dx > 0) flag |= FLAG_X_SAME;
818
- xCoords.push(Math.abs(dx));
819
- } else xCoords.push(dx);
820
- if (dy === 0) flag |= FLAG_Y_SAME;
821
- else if (dy >= -255 && dy <= 255) {
822
- flag |= FLAG_Y_SHORT;
823
- if (dy > 0) flag |= FLAG_Y_SAME;
824
- yCoords.push(Math.abs(dy));
825
- } else yCoords.push(dy);
826
- flags.push(flag);
827
- lastX = p.x;
828
- lastY = p.y;
829
- }
830
- const encodedFlags = [];
831
- let lastFlag = -1;
832
- let repeatCount = 0;
833
- for (let i = 0; i < flags.length; i++) {
834
- const flag = flags[i];
835
- if (flag === lastFlag && repeatCount < 255) {
836
- encodedFlags[encodedFlags.length - 1] |= FLAG_REPEAT;
837
- repeatCount++;
878
+ outFlag |= FLAG_X_SHORT;
879
+ if (dx > 0) outFlag |= FLAG_X_SAME;
880
+ xOut[xLen++] = dx > 0 ? dx : -dx;
838
881
  } else {
839
- if (repeatCount > 0) encodedFlags.push(repeatCount);
840
- encodedFlags.push(flag);
841
- repeatCount = 0;
882
+ xOut[xLen++] = dx >> 8 & 255;
883
+ xOut[xLen++] = dx & 255;
842
884
  }
843
- lastFlag = flag;
844
- }
845
- if (repeatCount > 0) encodedFlags.push(repeatCount);
846
- let xSize = 0;
847
- let ySize = 0;
848
- let xIdx = 0;
849
- let yIdx = 0;
850
- for (const flag of flags) {
851
- if ((flag & FLAG_X_SHORT) !== 0) {
852
- xSize += 1;
853
- xIdx++;
854
- } else if ((flag & FLAG_X_SAME) === 0) {
855
- xSize += 2;
856
- xIdx++;
885
+ if (dy === 0) outFlag |= FLAG_Y_SAME;
886
+ else if (dy >= -255 && dy <= 255) {
887
+ outFlag |= FLAG_Y_SHORT;
888
+ if (dy > 0) outFlag |= FLAG_Y_SAME;
889
+ yOut[yLen++] = dy > 0 ? dy : -dy;
890
+ } else {
891
+ yOut[yLen++] = dy >> 8 & 255;
892
+ yOut[yLen++] = dy & 255;
857
893
  }
858
- if ((flag & FLAG_Y_SHORT) !== 0) {
859
- ySize += 1;
860
- yIdx++;
861
- } else if ((flag & FLAG_Y_SAME) === 0) {
862
- ySize += 2;
863
- yIdx++;
894
+ if (outFlag === lastFlag && repeatCount < 255) {
895
+ flagsOut[flagsLen - 1] |= FLAG_REPEAT;
896
+ repeatCount++;
897
+ } else {
898
+ if (repeatCount > 0) {
899
+ flagsOut[flagsLen++] = repeatCount;
900
+ repeatCount = 0;
901
+ }
902
+ flagsOut[flagsLen++] = outFlag;
903
+ lastFlag = outFlag;
864
904
  }
865
905
  }
866
- const output = new Uint8Array(encodedFlags.length + xSize + ySize);
867
- let offset = 0;
868
- for (const f of encodedFlags) output[offset++] = f;
869
- xIdx = 0;
870
- for (const flag of flags) if ((flag & FLAG_X_SHORT) !== 0) output[offset++] = xCoords[xIdx++];
871
- else if ((flag & FLAG_X_SAME) === 0) {
872
- const val = xCoords[xIdx++];
873
- output[offset++] = val >> 8 & 255;
874
- output[offset++] = val & 255;
875
- }
876
- yIdx = 0;
877
- for (const flag of flags) if ((flag & FLAG_Y_SHORT) !== 0) output[offset++] = yCoords[yIdx++];
878
- else if ((flag & FLAG_Y_SAME) === 0) {
879
- const val = yCoords[yIdx++];
880
- output[offset++] = val >> 8 & 255;
881
- output[offset++] = val & 255;
882
- }
883
- return output;
906
+ if (repeatCount > 0) flagsOut[flagsLen++] = repeatCount;
907
+ flagStream.pos = flagPos;
908
+ glyphStream.pos = glyphPos;
909
+ return {
910
+ flagsLen,
911
+ xLen,
912
+ yLen,
913
+ xMin,
914
+ yMin,
915
+ xMax,
916
+ yMax
917
+ };
884
918
  }
885
919
  function reconstructHmtx(data, table, numGlyphs, numHMetrics, xMins) {
886
- const buf = new Buffer$1(data, table.srcOffset, table.srcLength);
887
- const hmtxFlags = buf.readU8();
888
- if (hmtxFlags === null) throw new Error("Failed to read hmtx flags");
920
+ const hmtxStream = makeByteStream(data, table.srcOffset, table.srcLength);
921
+ const hmtxFlags = bsReadU8(hmtxStream);
889
922
  const hasProportionalLsbs = (hmtxFlags & 1) === 0;
890
923
  const hasMonospaceLsbs = (hmtxFlags & 2) === 0;
891
- const advanceWidths = [];
892
- for (let i = 0; i < numHMetrics; i++) {
893
- const w = buf.readU16();
894
- if (w === null) throw new Error("Failed to read advance width");
895
- advanceWidths.push(w);
896
- }
897
- const lsbs = [];
898
- for (let i = 0; i < numHMetrics; i++) if (hasProportionalLsbs) {
899
- const lsb = buf.readS16();
900
- if (lsb === null) throw new Error("Failed to read proportional LSB");
901
- lsbs.push(lsb);
902
- } else lsbs.push(xMins[i]);
903
- for (let i = numHMetrics; i < numGlyphs; i++) if (hasMonospaceLsbs) {
904
- const lsb = buf.readS16();
905
- if (lsb === null) throw new Error("Failed to read monospace LSB");
906
- lsbs.push(lsb);
907
- } else lsbs.push(xMins[i]);
924
+ const advanceWidths = new Uint16Array(numHMetrics);
925
+ for (let i = 0; i < numHMetrics; i++) advanceWidths[i] = bsReadU16(hmtxStream);
926
+ const lsbs = new Int16Array(numGlyphs);
927
+ for (let i = 0; i < numHMetrics; i++) if (hasProportionalLsbs) lsbs[i] = bsReadS16(hmtxStream);
928
+ else lsbs[i] = xMins[i];
929
+ for (let i = numHMetrics; i < numGlyphs; i++) if (hasMonospaceLsbs) lsbs[i] = bsReadS16(hmtxStream);
930
+ else lsbs[i] = xMins[i];
908
931
  const outputSize = numHMetrics * 4 + (numGlyphs - numHMetrics) * 2;
909
932
  const output = new Uint8Array(outputSize);
910
- const view = new DataView(output.buffer);
911
933
  let offset = 0;
912
934
  for (let i = 0; i < numGlyphs; i++) {
913
935
  if (i < numHMetrics) {
914
- view.setUint16(offset, advanceWidths[i]);
936
+ writeUint16BE(output, offset, advanceWidths[i]);
915
937
  offset += 2;
916
938
  }
917
- view.setInt16(offset, lsbs[i]);
939
+ writeInt16BE(output, offset, lsbs[i]);
918
940
  offset += 2;
919
941
  }
920
942
  return output;
@@ -924,12 +946,24 @@ function updateTableEntry(view, entryOffset, checksum, offset, length) {
924
946
  view.setUint32(entryOffset + 8, offset);
925
947
  view.setUint32(entryOffset + 12, length);
926
948
  }
949
+ function readInt16BE(data, offset) {
950
+ const val = data[offset] << 8 | data[offset + 1];
951
+ return (val & 32768) !== 0 ? val - 65536 : val;
952
+ }
953
+ function writeInt16BE(data, offset, value) {
954
+ data[offset] = value >> 8 & 255;
955
+ data[offset + 1] = value & 255;
956
+ }
957
+ function writeUint16BE(data, offset, value) {
958
+ data[offset] = value >> 8 & 255;
959
+ data[offset + 1] = value & 255;
960
+ }
927
961
  function computeChecksum(data, offset, length) {
928
962
  let sum = 0;
929
963
  const end = offset + length;
930
- const view = new DataView(data.buffer, data.byteOffset);
964
+ const dataView = new DataView(data.buffer, data.byteOffset);
931
965
  const alignedEnd = offset + (length & -4);
932
- for (let i = offset; i < alignedEnd; i += 4) sum = sum + view.getUint32(i) >>> 0;
966
+ for (let i = offset; i < alignedEnd; i += 4) sum = sum + dataView.getUint32(i) >>> 0;
933
967
  if (end > alignedEnd) {
934
968
  let last = 0;
935
969
  for (let i = alignedEnd; i < end; i++) last = last << 8 | data[i];