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.cjs CHANGED
@@ -31,6 +31,7 @@ brotli_decompress_js = __toESM(brotli_decompress_js);
31
31
 
32
32
  //#region src/brotli.ts
33
33
  let nativeBrotli = null;
34
+ let browserBrotli = null;
34
35
  function tryLoadNative() {
35
36
  try {
36
37
  if (typeof process !== "undefined" && process.versions?.node) {
@@ -43,9 +44,24 @@ function tryLoadNative() {
43
44
  } catch {}
44
45
  return null;
45
46
  }
47
+ function tryLoadBrowserBrotli() {
48
+ try {
49
+ if (typeof DecompressionStream !== "undefined") {
50
+ new DecompressionStream("brotli");
51
+ return async (buf) => {
52
+ const arrayBuf = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
53
+ const decompressed = new Blob([arrayBuf]).stream().pipeThrough(new DecompressionStream("brotli"));
54
+ return new Uint8Array(await new Response(decompressed).arrayBuffer());
55
+ };
56
+ }
57
+ } catch {}
58
+ return null;
59
+ }
46
60
  nativeBrotli = tryLoadNative();
47
- function decompress(data) {
61
+ browserBrotli = tryLoadBrowserBrotli();
62
+ async function decompress(data) {
48
63
  if (nativeBrotli) return nativeBrotli(data);
64
+ if (browserBrotli) return browserBrotli(data);
49
65
  return (0, brotli_decompress_js.default)(data);
50
66
  }
51
67
  function decompressPure(data) {
@@ -60,9 +76,11 @@ var Buffer$1 = class {
60
76
  this.pos = 0;
61
77
  if (data instanceof Uint8Array) {
62
78
  const len = length ?? data.byteLength - offset;
79
+ this.u8 = data.subarray(offset, offset + len);
63
80
  this.view = new DataView(data.buffer, data.byteOffset + offset, len);
64
81
  } else {
65
82
  const len = length ?? data.byteLength - offset;
83
+ this.u8 = new Uint8Array(data, offset, len);
66
84
  this.view = new DataView(data, offset, len);
67
85
  }
68
86
  }
@@ -70,66 +88,67 @@ var Buffer$1 = class {
70
88
  return this.pos;
71
89
  }
72
90
  get length() {
73
- return this.view.byteLength;
91
+ return this.u8.byteLength;
74
92
  }
75
93
  get remaining() {
76
- return this.view.byteLength - this.pos;
94
+ return this.u8.byteLength - this.pos;
77
95
  }
78
96
  get buffer() {
79
97
  return this.view;
80
98
  }
81
99
  skip(n) {
82
- if (this.pos + n > this.view.byteLength || this.pos + n < this.pos) return false;
100
+ if (this.pos + n > this.u8.byteLength || this.pos + n < this.pos) return false;
83
101
  this.pos += n;
84
102
  return true;
85
103
  }
86
104
  seek(offset) {
87
- if (offset > this.view.byteLength || offset < 0) return false;
105
+ if (offset > this.u8.byteLength || offset < 0) return false;
88
106
  this.pos = offset;
89
107
  return true;
90
108
  }
91
109
  readU8() {
92
- if (this.pos + 1 > this.view.byteLength) return null;
93
- return this.view.getUint8(this.pos++);
110
+ if (this.pos + 1 > this.u8.byteLength) return null;
111
+ return this.u8[this.pos++];
94
112
  }
95
113
  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;
114
+ if (this.pos + 2 > this.u8.byteLength) return null;
115
+ const idx = this.pos;
116
+ this.pos = idx + 2;
117
+ return this.u8[idx] << 8 | this.u8[idx + 1];
100
118
  }
101
119
  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;
120
+ if (this.pos + 2 > this.u8.byteLength) return null;
121
+ const idx = this.pos;
122
+ this.pos = idx + 2;
123
+ const val = this.u8[idx] << 8 | this.u8[idx + 1];
124
+ return (val & 32768) !== 0 ? val - 65536 : val;
106
125
  }
107
126
  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;
127
+ if (this.pos + 4 > this.u8.byteLength) return null;
128
+ const idx = this.pos;
129
+ this.pos = idx + 4;
130
+ return this.u8[idx] * 16777216 + (this.u8[idx + 1] << 16 | this.u8[idx + 2] << 8 | this.u8[idx + 3]) >>> 0;
112
131
  }
113
132
  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;
133
+ if (this.pos + 4 > this.u8.byteLength) return null;
134
+ const idx = this.pos;
135
+ this.pos = idx + 4;
136
+ return this.u8[idx] << 24 | this.u8[idx + 1] << 16 | this.u8[idx + 2] << 8 | this.u8[idx + 3];
118
137
  }
119
138
  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);
139
+ if (this.pos + n > this.u8.byteLength || n < 0) return null;
140
+ const result = this.u8.subarray(this.pos, this.pos + n);
122
141
  this.pos += n;
123
142
  return result;
124
143
  }
125
144
  peekU8(offset = 0) {
126
145
  const idx = this.pos + offset;
127
- if (idx >= this.view.byteLength || idx < 0) return null;
128
- return this.view.getUint8(idx);
146
+ if (idx >= this.u8.byteLength || idx < 0) return null;
147
+ return this.u8[idx];
129
148
  }
130
149
  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);
150
+ if (offset + length > this.u8.byteLength || offset < 0 || length < 0) return null;
151
+ return this.u8.subarray(offset, offset + length);
133
152
  }
134
153
  };
135
154
 
@@ -245,12 +264,19 @@ function readBase128(buf) {
245
264
  //#region src/decode.ts
246
265
  const SFNT_HEADER_SIZE = 12;
247
266
  const SFNT_ENTRY_SIZE = 16;
248
- function decode(data, options) {
267
+ const FLAG_ON_CURVE = 1;
268
+ const FLAG_X_SHORT = 2;
269
+ const FLAG_Y_SHORT = 4;
270
+ const FLAG_REPEAT = 8;
271
+ const FLAG_X_SAME = 16;
272
+ const FLAG_Y_SAME = 32;
273
+ const FLAG_OVERLAP_SIMPLE = 64;
274
+ async function decode(data, options) {
249
275
  const input = data instanceof Uint8Array ? data : new Uint8Array(data);
250
276
  const header = readHeader(new Buffer$1(input), input.byteLength);
251
277
  if (!header) throw new Error("Failed to read WOFF2 header");
252
278
  const compressedData = input.subarray(header.compressedOffset, header.compressedOffset + header.compressedLength);
253
- const decompressed = (options?.forcePureBrotli ? decompressPure : decompress)(compressedData);
279
+ const decompressed = options?.forcePureBrotli ? decompressPure(compressedData) : await decompress(compressedData);
254
280
  if (!decompressed || decompressed.byteLength !== header.uncompressedSize) throw new Error(`Brotli decompression failed: expected ${header.uncompressedSize} bytes, got ${decompressed?.byteLength ?? 0}`);
255
281
  let outputSize = computeOffsetToFirstTable(header);
256
282
  for (const table of header.tables) {
@@ -366,7 +392,8 @@ function readTableDirectory(buf, numTables) {
366
392
  srcOffset,
367
393
  srcLength: transformLength,
368
394
  dstOffset: 0,
369
- dstLength: origLength
395
+ dstLength: origLength,
396
+ key: `${tag}:${srcOffset}`
370
397
  });
371
398
  srcOffset += transformLength;
372
399
  }
@@ -469,8 +496,8 @@ function reconstructFont(decompressed, header, fontIndex, fontInfo, output, outV
469
496
  for (const table of sortedTables) {
470
497
  const entryOffset = fontInfo.tableEntryByTag.get(table.tag);
471
498
  if (entryOffset === void 0) continue;
472
- const tableKey = `${table.tag}:${table.srcOffset}`;
473
- const existing = writtenTables.get(tableKey);
499
+ const tKey = table.key;
500
+ const existing = writtenTables.get(tKey);
474
501
  if (existing) {
475
502
  updateTableEntry(outView, entryOffset, existing.checksum, existing.dstOffset, existing.dstLength);
476
503
  if (isTTC) {
@@ -495,7 +522,7 @@ function reconstructFont(decompressed, header, fontIndex, fontInfo, output, outV
495
522
  fontChecksum = fontChecksum + checksum >>> 0;
496
523
  fontChecksum = fontChecksum + computeTableEntryChecksum(checksum, dstOffset, tableData.byteLength) >>> 0;
497
524
  }
498
- writtenTables.set(tableKey, {
525
+ writtenTables.set(tKey, {
499
526
  dstOffset,
500
527
  dstLength: tableData.byteLength,
501
528
  checksum
@@ -510,8 +537,7 @@ function reconstructFont(decompressed, header, fontIndex, fontInfo, output, outV
510
537
  fontChecksum = fontChecksum + locaChecksum >>> 0;
511
538
  fontChecksum = fontChecksum + computeTableEntryChecksum(locaChecksum, dstOffset, result.locaData.byteLength) >>> 0;
512
539
  }
513
- const locaKey = `${TAG_LOCA}:${locaTable.srcOffset}`;
514
- writtenTables.set(locaKey, {
540
+ writtenTables.set(locaTable.key, {
515
541
  dstOffset,
516
542
  dstLength: result.locaData.byteLength,
517
543
  checksum: locaChecksum
@@ -537,7 +563,7 @@ function reconstructFont(decompressed, header, fontIndex, fontInfo, output, outV
537
563
  fontChecksum = fontChecksum + checksum >>> 0;
538
564
  fontChecksum = fontChecksum + computeTableEntryChecksum(checksum, dstOffset, tableData.byteLength) >>> 0;
539
565
  }
540
- writtenTables.set(tableKey, {
566
+ writtenTables.set(tKey, {
541
567
  dstOffset,
542
568
  dstLength: tableData.byteLength,
543
569
  checksum
@@ -546,7 +572,7 @@ function reconstructFont(decompressed, header, fontIndex, fontInfo, output, outV
546
572
  }
547
573
  const headTable = sortedTables.find((t) => t.tag === TAG_HEAD);
548
574
  if (headTable) {
549
- const headEntry = writtenTables.get(`${TAG_HEAD}:${headTable.srcOffset}`);
575
+ const headEntry = writtenTables.get(headTable.key);
550
576
  if (headEntry && headEntry.dstLength >= 12) {
551
577
  const finalChecksum = isTTC ? fontChecksum : computeChecksum(output, 0, dstOffset);
552
578
  outView.setUint32(headEntry.dstOffset + 8, 2981146554 - finalChecksum >>> 0);
@@ -557,54 +583,100 @@ function reconstructFont(decompressed, header, fontIndex, fontInfo, output, outV
557
583
  function computeTableEntryChecksum(checksum, offset, length) {
558
584
  return checksum + offset + length >>> 0;
559
585
  }
586
+ function makeByteStream(data, start, length) {
587
+ return {
588
+ data,
589
+ pos: start,
590
+ end: start + length
591
+ };
592
+ }
593
+ function bsReadU8(stream) {
594
+ if (stream.pos >= stream.end) throw new Error("Stream overflow");
595
+ return stream.data[stream.pos++];
596
+ }
597
+ function bsReadU16(stream) {
598
+ if (stream.pos + 2 > stream.end) throw new Error("Stream overflow");
599
+ const idx = stream.pos;
600
+ stream.pos = idx + 2;
601
+ return stream.data[idx] << 8 | stream.data[idx + 1];
602
+ }
603
+ function bsReadS16(stream) {
604
+ if (stream.pos + 2 > stream.end) throw new Error("Stream overflow");
605
+ const idx = stream.pos;
606
+ stream.pos = idx + 2;
607
+ const val = stream.data[idx] << 8 | stream.data[idx + 1];
608
+ return (val & 32768) !== 0 ? val - 65536 : val;
609
+ }
610
+ function fsReadU32(stream) {
611
+ if (stream.pos + 4 > stream.end) throw new Error("Stream overflow");
612
+ const idx = stream.pos;
613
+ stream.pos = idx + 4;
614
+ return stream.data[idx] * 16777216 + (stream.data[idx + 1] << 16 | stream.data[idx + 2] << 8 | stream.data[idx + 3]) >>> 0;
615
+ }
616
+ function bsSkip(stream, n) {
617
+ if (stream.pos + n > stream.end || n < 0) throw new Error("Stream overflow");
618
+ stream.pos += n;
619
+ }
620
+ function fsReadBytes(stream, n) {
621
+ if (stream.pos + n > stream.end || n < 0) throw new Error("Stream overflow");
622
+ const start = stream.pos;
623
+ stream.pos += n;
624
+ return stream.data.subarray(start, start + n);
625
+ }
626
+ function bsRead255UShort(stream) {
627
+ const code = bsReadU8(stream);
628
+ if (code === 253) return bsReadU16(stream);
629
+ else if (code === 255) return 253 + bsReadU8(stream);
630
+ else if (code === 254) return 506 + bsReadU8(stream);
631
+ return code;
632
+ }
560
633
  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");
634
+ const headerStream = makeByteStream(data, glyfTable.srcOffset, glyfTable.transformLength);
635
+ bsReadU16(headerStream);
636
+ const optionFlags = bsReadU16(headerStream);
637
+ const numGlyphs = bsReadU16(headerStream);
638
+ const indexFormat = bsReadU16(headerStream);
567
639
  fontInfo.numGlyphs = numGlyphs;
568
640
  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);
641
+ const nContourStreamSize = fsReadU32(headerStream);
642
+ const nPointsStreamSize = fsReadU32(headerStream);
643
+ const flagStreamSize = fsReadU32(headerStream);
644
+ const glyphStreamSize = fsReadU32(headerStream);
645
+ const compositeStreamSize = fsReadU32(headerStream);
646
+ const bboxStreamSize = fsReadU32(headerStream);
647
+ const instructionStreamSize = fsReadU32(headerStream);
648
+ let offset = headerStream.pos;
649
+ const nContourStream = makeByteStream(data, offset, nContourStreamSize);
579
650
  offset += nContourStreamSize;
580
- const nPointsStream = new Buffer$1(data, glyfTable.srcOffset + offset, nPointsStreamSize);
651
+ const nPointsStream = makeByteStream(data, offset, nPointsStreamSize);
581
652
  offset += nPointsStreamSize;
582
- const flagStream = new Buffer$1(data, glyfTable.srcOffset + offset, flagStreamSize);
653
+ const flagStream = makeByteStream(data, offset, flagStreamSize);
583
654
  offset += flagStreamSize;
584
- const glyphStream = new Buffer$1(data, glyfTable.srcOffset + offset, glyphStreamSize);
655
+ const glyphStream = makeByteStream(data, offset, glyphStreamSize);
585
656
  offset += glyphStreamSize;
586
- const compositeStream = new Buffer$1(data, glyfTable.srcOffset + offset, compositeStreamSize);
657
+ const compositeStream = makeByteStream(data, offset, compositeStreamSize);
587
658
  offset += compositeStreamSize;
588
- const bboxStream = new Buffer$1(data, glyfTable.srcOffset + offset, bboxStreamSize);
659
+ const bboxStream = makeByteStream(data, offset, bboxStreamSize);
589
660
  offset += bboxStreamSize;
590
- const instructionStream = new Buffer$1(data, glyfTable.srcOffset + offset, instructionStreamSize);
661
+ const instructionStream = makeByteStream(data, offset, instructionStreamSize);
591
662
  const hasOverlapBitmap = (optionFlags & 1) !== 0;
592
663
  let overlapBitmap = null;
593
664
  if (hasOverlapBitmap) {
594
665
  const overlapBitmapLength = numGlyphs + 7 >> 3;
595
- overlapBitmap = data.subarray(glyfTable.srcOffset + offset + instructionStreamSize, glyfTable.srcOffset + offset + instructionStreamSize + overlapBitmapLength);
666
+ overlapBitmap = data.subarray(offset + instructionStreamSize, offset + instructionStreamSize + overlapBitmapLength);
596
667
  }
597
- const bboxBitmapLength = numGlyphs + 31 >> 5 << 2;
598
- const bboxBitmap = bboxStream.readBytes(bboxBitmapLength);
599
- if (!bboxBitmap) throw new Error("Failed to read bbox bitmap");
668
+ const bboxBitmap = fsReadBytes(bboxStream, numGlyphs + 31 >> 5 << 2);
600
669
  let glyfOutput = new Uint8Array(glyfTable.origLength * 2);
601
670
  let glyfOffset = 0;
602
- const locaValues = [];
671
+ const locaValues = new Uint32Array(numGlyphs + 1);
603
672
  fontInfo.xMins = new Int16Array(numGlyphs);
673
+ let contourEndsScratch = new Uint16Array(128);
674
+ let flagsScratch = new Uint8Array(512);
675
+ let xScratch = new Uint8Array(512);
676
+ let yScratch = new Uint8Array(512);
604
677
  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}`);
678
+ locaValues[glyphId] = glyfOffset;
679
+ const nContours = bsReadS16(nContourStream);
608
680
  const haveBbox = (bboxBitmap[glyphId >> 3] & 128 >> (glyphId & 7)) !== 0;
609
681
  if (nContours === 0) {
610
682
  if (haveBbox) throw new Error(`Empty glyph ${glyphId} has bbox`);
@@ -614,34 +686,76 @@ function reconstructGlyf(data, glyfTable, locaTable, fontInfo) {
614
686
  if (!haveBbox) throw new Error(`Composite glyph ${glyphId} missing bbox`);
615
687
  const { compositeData, haveInstructions } = readCompositeGlyph(compositeStream);
616
688
  let instructionSize = 0;
617
- if (haveInstructions) instructionSize = read255UShort(glyphStream) ?? 0;
689
+ if (haveInstructions) instructionSize = bsRead255UShort(glyphStream);
618
690
  const glyphSize = 10 + compositeData.byteLength + (haveInstructions ? 2 + instructionSize : 0);
619
691
  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");
692
+ writeInt16BE(glyfOutput, glyfOffset, -1);
693
+ const bbox = fsReadBytes(bboxStream, 8);
623
694
  glyfOutput.set(bbox, glyfOffset + 2);
624
- fontInfo.xMins[glyphId] = new DataView(bbox.buffer, bbox.byteOffset).getInt16(0);
695
+ fontInfo.xMins[glyphId] = readInt16BE(bbox, 0);
625
696
  glyfOutput.set(compositeData, glyfOffset + 10);
626
697
  if (haveInstructions) {
627
698
  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");
699
+ writeUint16BE(glyfOutput, instrOffset, instructionSize);
700
+ const instructions = fsReadBytes(instructionStream, instructionSize);
631
701
  glyfOutput.set(instructions, instrOffset + 2);
632
702
  }
633
703
  glyfOffset += glyphSize;
634
704
  glyfOffset = pad4(glyfOffset);
635
705
  } 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;
706
+ if (nContours > contourEndsScratch.length) contourEndsScratch = new Uint16Array(nContours * 2);
707
+ let totalPoints = 0;
708
+ let endPoint = -1;
709
+ for (let i = 0; i < nContours; i++) {
710
+ const n = bsRead255UShort(nPointsStream);
711
+ totalPoints += n;
712
+ endPoint += n;
713
+ contourEndsScratch[i] = endPoint;
714
+ }
715
+ const scratchSize = totalPoints * 2;
716
+ if (scratchSize > flagsScratch.length) flagsScratch = new Uint8Array(scratchSize);
717
+ if (scratchSize > xScratch.length) xScratch = new Uint8Array(scratchSize);
718
+ if (scratchSize > yScratch.length) yScratch = new Uint8Array(scratchSize);
719
+ const encoded = encodeTripletsToScratch(flagStream, glyphStream, totalPoints, ((overlapBitmap?.[glyphId >> 3] ?? 0) & 128 >> (glyphId & 7)) !== 0, flagsScratch, xScratch, yScratch);
720
+ const instructionSize = bsRead255UShort(glyphStream);
721
+ const glyphSize = 10 + 2 * nContours + 2 + instructionSize + encoded.flagsLen + encoded.xLen + encoded.yLen;
722
+ ensureCapacity(glyphSize);
723
+ writeInt16BE(glyfOutput, glyfOffset, nContours);
724
+ let xMin = 0;
725
+ if (haveBbox) {
726
+ const bbox = fsReadBytes(bboxStream, 8);
727
+ glyfOutput.set(bbox, glyfOffset + 2);
728
+ xMin = readInt16BE(bbox, 0);
729
+ } else {
730
+ writeInt16BE(glyfOutput, glyfOffset + 2, encoded.xMin);
731
+ writeInt16BE(glyfOutput, glyfOffset + 4, encoded.yMin);
732
+ writeInt16BE(glyfOutput, glyfOffset + 6, encoded.xMax);
733
+ writeInt16BE(glyfOutput, glyfOffset + 8, encoded.yMax);
734
+ xMin = encoded.xMin;
735
+ }
736
+ let writeOffset = glyfOffset + 10;
737
+ for (let i = 0; i < nContours; i++) {
738
+ writeUint16BE(glyfOutput, writeOffset, contourEndsScratch[i]);
739
+ writeOffset += 2;
740
+ }
741
+ writeUint16BE(glyfOutput, writeOffset, instructionSize);
742
+ writeOffset += 2;
743
+ if (instructionSize > 0) {
744
+ const instructions = fsReadBytes(instructionStream, instructionSize);
745
+ glyfOutput.set(instructions, writeOffset);
746
+ writeOffset += instructionSize;
747
+ }
748
+ glyfOutput.set(flagsScratch.subarray(0, encoded.flagsLen), writeOffset);
749
+ writeOffset += encoded.flagsLen;
750
+ glyfOutput.set(xScratch.subarray(0, encoded.xLen), writeOffset);
751
+ writeOffset += encoded.xLen;
752
+ glyfOutput.set(yScratch.subarray(0, encoded.yLen), writeOffset);
753
+ fontInfo.xMins[glyphId] = xMin;
754
+ glyfOffset += glyphSize;
641
755
  glyfOffset = pad4(glyfOffset);
642
756
  }
643
757
  }
644
- locaValues.push(glyfOffset);
758
+ locaValues[numGlyphs] = glyfOffset;
645
759
  const locaSize = indexFormat ? (numGlyphs + 1) * 4 : (numGlyphs + 1) * 2;
646
760
  const locaData = new Uint8Array(locaSize);
647
761
  const locaView = new DataView(locaData.buffer);
@@ -666,11 +780,11 @@ function readCompositeGlyph(stream) {
666
780
  const FLAG_WE_HAVE_AN_X_AND_Y_SCALE = 64;
667
781
  const FLAG_WE_HAVE_A_TWO_BY_TWO = 128;
668
782
  const FLAG_WE_HAVE_INSTRUCTIONS = 256;
669
- const startOffset = stream.offset;
783
+ const startOffset = stream.pos;
670
784
  let haveInstructions = false;
671
785
  let flags = FLAG_MORE_COMPONENTS;
672
786
  while (flags & FLAG_MORE_COMPONENTS) {
673
- flags = stream.readU16() ?? 0;
787
+ flags = bsReadU16(stream);
674
788
  haveInstructions = haveInstructions || (flags & FLAG_WE_HAVE_INSTRUCTIONS) !== 0;
675
789
  let argSize = 2;
676
790
  if (flags & FLAG_ARG_1_AND_2_ARE_WORDS) argSize += 4;
@@ -678,126 +792,91 @@ function readCompositeGlyph(stream) {
678
792
  if (flags & FLAG_WE_HAVE_A_SCALE) argSize += 2;
679
793
  else if (flags & FLAG_WE_HAVE_AN_X_AND_Y_SCALE) argSize += 4;
680
794
  else if (flags & FLAG_WE_HAVE_A_TWO_BY_TWO) argSize += 8;
681
- stream.skip(argSize);
795
+ bsSkip(stream, argSize);
682
796
  }
683
- const compositeData = stream.subarray(startOffset, stream.offset - startOffset);
684
- if (!compositeData) throw new Error("Failed to read composite glyph data");
685
797
  return {
686
- compositeData,
798
+ compositeData: stream.data.subarray(startOffset, stream.pos),
687
799
  haveInstructions
688
800
  };
689
801
  }
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 = [];
802
+ function encodeTripletsToScratch(flagStream, glyphStream, nPoints, hasOverlapBit, flagsOut, xOut, yOut) {
803
+ if (nPoints === 0) return {
804
+ flagsLen: 0,
805
+ xLen: 0,
806
+ yLen: 0,
807
+ xMin: 0,
808
+ yMin: 0,
809
+ xMax: 0,
810
+ yMax: 0
811
+ };
812
+ let flagsLen = 0;
813
+ let xLen = 0;
814
+ let yLen = 0;
749
815
  let x = 0;
750
816
  let y = 0;
817
+ let xMin = 0;
818
+ let yMin = 0;
819
+ let xMax = 0;
820
+ let yMax = 0;
821
+ let lastFlag = -1;
822
+ let repeatCount = 0;
823
+ const flagData = flagStream.data;
824
+ let flagPos = flagStream.pos;
825
+ const flagEnd = flagStream.end;
826
+ const glyphData = glyphStream.data;
827
+ let glyphPos = glyphStream.pos;
828
+ const glyphEnd = glyphStream.end;
751
829
  for (let i = 0; i < nPoints; i++) {
752
- const flag = flagStream.readU8();
753
- if (flag === null) throw new Error("Failed to read triplet flag");
830
+ if (flagPos >= flagEnd) throw new Error("Stream overflow");
831
+ const flag = flagData[flagPos++];
754
832
  const onCurve = (flag & 128) === 0;
755
833
  const flagLow = flag & 127;
756
- let dx, dy;
834
+ let dx;
835
+ let dy;
757
836
  if (flagLow < 10) {
758
837
  dx = 0;
759
- const b = glyphStream.readU8();
760
- if (b === null) throw new Error("Failed to read triplet data");
838
+ if (glyphPos >= glyphEnd) throw new Error("Stream overflow");
839
+ const b = glyphData[glyphPos++];
761
840
  dy = ((flagLow & 14) << 7) + b;
762
841
  if ((flagLow & 1) === 0) dy = -dy;
763
842
  } else if (flagLow < 20) {
764
- const b = glyphStream.readU8();
765
- if (b === null) throw new Error("Failed to read triplet data");
843
+ if (glyphPos >= glyphEnd) throw new Error("Stream overflow");
844
+ const b = glyphData[glyphPos++];
766
845
  dx = ((flagLow - 10 & 14) << 7) + b;
767
846
  if ((flagLow & 1) === 0) dx = -dx;
768
847
  dy = 0;
769
848
  } else if (flagLow < 84) {
770
- const b = glyphStream.readU8();
771
- if (b === null) throw new Error("Failed to read triplet data");
849
+ if (glyphPos >= glyphEnd) throw new Error("Stream overflow");
850
+ const b = glyphData[glyphPos++];
772
851
  const b0 = flagLow - 20;
773
852
  dx = 1 + (b0 & 48) + (b >> 4);
774
853
  dy = 1 + ((b0 & 12) << 2) + (b & 15);
775
854
  if ((flagLow & 1) === 0) dx = -dx;
776
855
  if ((flagLow & 2) === 0) dy = -dy;
777
856
  } 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");
857
+ if (glyphPos + 1 >= glyphEnd) throw new Error("Stream overflow");
858
+ const b0 = glyphData[glyphPos++];
859
+ const b1 = glyphData[glyphPos++];
781
860
  const idx = flagLow - 84;
782
- dx = 1 + (Math.floor(idx / 12) << 8) + b0;
861
+ dx = 1 + ((idx / 12 | 0) << 8) + b0;
783
862
  dy = 1 + (idx % 12 >> 2 << 8) + b1;
784
863
  if ((flagLow & 1) === 0) dx = -dx;
785
864
  if ((flagLow & 2) === 0) dy = -dy;
786
865
  } 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");
866
+ if (glyphPos + 2 >= glyphEnd) throw new Error("Stream overflow");
867
+ const b0 = glyphData[glyphPos++];
868
+ const b1 = glyphData[glyphPos++];
869
+ const b2 = glyphData[glyphPos++];
791
870
  dx = (b0 << 4) + (b1 >> 4);
792
871
  dy = ((b1 & 15) << 8) + b2;
793
872
  if ((flagLow & 1) === 0) dx = -dx;
794
873
  if ((flagLow & 2) === 0) dy = -dy;
795
874
  } 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");
875
+ if (glyphPos + 3 >= glyphEnd) throw new Error("Stream overflow");
876
+ const b0 = glyphData[glyphPos++];
877
+ const b1 = glyphData[glyphPos++];
878
+ const b2 = glyphData[glyphPos++];
879
+ const b3 = glyphData[glyphPos++];
801
880
  dx = (b0 << 8) + b1;
802
881
  dy = (b2 << 8) + b3;
803
882
  if ((flagLow & 1) === 0) dx = -dx;
@@ -805,138 +884,81 @@ function decodeTriplets(flagStream, glyphStream, nPoints) {
805
884
  }
806
885
  x += dx;
807
886
  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;
887
+ if (i === 0) {
888
+ xMin = xMax = x;
889
+ yMin = yMax = y;
890
+ } else {
891
+ if (x < xMin) xMin = x;
892
+ if (x > xMax) xMax = x;
893
+ if (y < yMin) yMin = y;
894
+ if (y > yMax) yMax = y;
895
+ }
896
+ let outFlag = onCurve ? FLAG_ON_CURVE : 0;
897
+ if (hasOverlapBit && i === 0) outFlag |= FLAG_OVERLAP_SIMPLE;
898
+ if (dx === 0) outFlag |= FLAG_X_SAME;
837
899
  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++;
900
+ outFlag |= FLAG_X_SHORT;
901
+ if (dx > 0) outFlag |= FLAG_X_SAME;
902
+ xOut[xLen++] = dx > 0 ? dx : -dx;
860
903
  } else {
861
- if (repeatCount > 0) encodedFlags.push(repeatCount);
862
- encodedFlags.push(flag);
863
- repeatCount = 0;
904
+ xOut[xLen++] = dx >> 8 & 255;
905
+ xOut[xLen++] = dx & 255;
864
906
  }
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++;
907
+ if (dy === 0) outFlag |= FLAG_Y_SAME;
908
+ else if (dy >= -255 && dy <= 255) {
909
+ outFlag |= FLAG_Y_SHORT;
910
+ if (dy > 0) outFlag |= FLAG_Y_SAME;
911
+ yOut[yLen++] = dy > 0 ? dy : -dy;
912
+ } else {
913
+ yOut[yLen++] = dy >> 8 & 255;
914
+ yOut[yLen++] = dy & 255;
879
915
  }
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++;
916
+ if (outFlag === lastFlag && repeatCount < 255) {
917
+ flagsOut[flagsLen - 1] |= FLAG_REPEAT;
918
+ repeatCount++;
919
+ } else {
920
+ if (repeatCount > 0) {
921
+ flagsOut[flagsLen++] = repeatCount;
922
+ repeatCount = 0;
923
+ }
924
+ flagsOut[flagsLen++] = outFlag;
925
+ lastFlag = outFlag;
886
926
  }
887
927
  }
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;
928
+ if (repeatCount > 0) flagsOut[flagsLen++] = repeatCount;
929
+ flagStream.pos = flagPos;
930
+ glyphStream.pos = glyphPos;
931
+ return {
932
+ flagsLen,
933
+ xLen,
934
+ yLen,
935
+ xMin,
936
+ yMin,
937
+ xMax,
938
+ yMax
939
+ };
906
940
  }
907
941
  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");
942
+ const hmtxStream = makeByteStream(data, table.srcOffset, table.srcLength);
943
+ const hmtxFlags = bsReadU8(hmtxStream);
911
944
  const hasProportionalLsbs = (hmtxFlags & 1) === 0;
912
945
  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]);
946
+ const advanceWidths = new Uint16Array(numHMetrics);
947
+ for (let i = 0; i < numHMetrics; i++) advanceWidths[i] = bsReadU16(hmtxStream);
948
+ const lsbs = new Int16Array(numGlyphs);
949
+ for (let i = 0; i < numHMetrics; i++) if (hasProportionalLsbs) lsbs[i] = bsReadS16(hmtxStream);
950
+ else lsbs[i] = xMins[i];
951
+ for (let i = numHMetrics; i < numGlyphs; i++) if (hasMonospaceLsbs) lsbs[i] = bsReadS16(hmtxStream);
952
+ else lsbs[i] = xMins[i];
930
953
  const outputSize = numHMetrics * 4 + (numGlyphs - numHMetrics) * 2;
931
954
  const output = new Uint8Array(outputSize);
932
- const view = new DataView(output.buffer);
933
955
  let offset = 0;
934
956
  for (let i = 0; i < numGlyphs; i++) {
935
957
  if (i < numHMetrics) {
936
- view.setUint16(offset, advanceWidths[i]);
958
+ writeUint16BE(output, offset, advanceWidths[i]);
937
959
  offset += 2;
938
960
  }
939
- view.setInt16(offset, lsbs[i]);
961
+ writeInt16BE(output, offset, lsbs[i]);
940
962
  offset += 2;
941
963
  }
942
964
  return output;
@@ -946,12 +968,24 @@ function updateTableEntry(view, entryOffset, checksum, offset, length) {
946
968
  view.setUint32(entryOffset + 8, offset);
947
969
  view.setUint32(entryOffset + 12, length);
948
970
  }
971
+ function readInt16BE(data, offset) {
972
+ const val = data[offset] << 8 | data[offset + 1];
973
+ return (val & 32768) !== 0 ? val - 65536 : val;
974
+ }
975
+ function writeInt16BE(data, offset, value) {
976
+ data[offset] = value >> 8 & 255;
977
+ data[offset + 1] = value & 255;
978
+ }
979
+ function writeUint16BE(data, offset, value) {
980
+ data[offset] = value >> 8 & 255;
981
+ data[offset + 1] = value & 255;
982
+ }
949
983
  function computeChecksum(data, offset, length) {
950
984
  let sum = 0;
951
985
  const end = offset + length;
952
- const view = new DataView(data.buffer, data.byteOffset);
986
+ const dataView = new DataView(data.buffer, data.byteOffset);
953
987
  const alignedEnd = offset + (length & -4);
954
- for (let i = offset; i < alignedEnd; i += 4) sum = sum + view.getUint32(i) >>> 0;
988
+ for (let i = offset; i < alignedEnd; i += 4) sum = sum + dataView.getUint32(i) >>> 0;
955
989
  if (end > alignedEnd) {
956
990
  let last = 0;
957
991
  for (let i = alignedEnd; i < end; i++) last = last << 8 | data[i];