first commit
This commit is contained in:
131
docs/media/fonts/font-decompress/README.md
Normal file
131
docs/media/fonts/font-decompress/README.md
Normal file
@@ -0,0 +1,131 @@
|
||||
# @suaveplan/font-decompress - BTF (Binary TypeFace) binary unpacking. Reverses @suaveplan/font-compress's BinaryFontPacker output back into typeface.js JSON, ready for Three.js / R3F / game-font rendering pipelines. Universal.
|
||||
|
||||
BTF (Binary TypeFace) binary unpacking. Reverses
|
||||
`@suaveplan/font-compress`'s `BinaryFontPacker` output into a
|
||||
typeface.js JSON shape ready for Three.js / R3F / game-font
|
||||
rendering. Store-agnostic, free of `node:` / `bun:`
|
||||
imports.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [error-codes](./src/error-codes/error-codes.md) — The `@suaveplan/font-decompress` package centralises every failure mode it can produce behind a single registry built w…
|
||||
- [error-codes](./src/error-codes/error-codes.md) — The `@suaveplan/font-decompress` package centralises every failure mode it can produce behind a single registry built w…
|
||||
- [binary-font-unpacker](./src/unpacker/binary-font-unpacker/binary-font-unpacker.md) — `BinaryFontUnpacker` reverses the BTF (Binary TypeFace) wire format produced by `BinaryFontPacker` from `@suaveplan/fon…
|
||||
- [binary-font-unpacker](./src/unpacker/binary-font-unpacker/binary-font-unpacker.md) — `BinaryFontUnpacker` reverses the BTF (Binary TypeFace) wire format produced by `BinaryFontPacker` from `@suaveplan/fon…
|
||||
- [utils](./src/utils/decode-string.md) — `decodeString` reads a length-prefixed UTF-8 string from a `Uint8Array` at a given offset. The BTF wire format encodes…
|
||||
- [decode-string](./src/utils/decode-string.md) — `decodeString` reads a length-prefixed UTF-8 string from a `Uint8Array` at a given offset. The BTF wire format encodes…
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Compressed BTF buffers ship from build-time tooling to the renderer
|
||||
as compact `Uint8Array` blobs — they are far smaller than the
|
||||
underlying typeface.js JSON for non-trivial fonts. The renderer needs
|
||||
the JSON back to hand to Three.js's `FontLoader.parse(...)`. This
|
||||
package owns that reverse step; the sibling `@suaveplan/font-compress`
|
||||
owns the forward step.
|
||||
|
||||
The split exists because pack and unpack are independent concerns:
|
||||
build tooling never unpacks, runtime renderers never pack. Shipping a
|
||||
unified package forces consumers to drag the other half. Splitting
|
||||
lets each side install only what it actually executes.
|
||||
|
||||
## Features
|
||||
|
||||
- `BinaryFontUnpacker` class + `createBinaryFontUnpacker()` factory
|
||||
- Strict boundary validation on every read — raises `FontDecompressError` with `FONT_DECOMPRESS.INVALID_DATA`
|
||||
- Underlying primitive exceptions (DataView, TextDecoder) wrapped as `FontDecompressError` with `FONT_DECOMPRESS.UNPACK_FAILED` and `cause` preserved
|
||||
- All five BTF glyph commands (`m`/`l`/`b`/`q`/`z`) round-trip byte-for-byte
|
||||
- Unicode glyph keys (multi-byte UTF-8) round-trip correctly
|
||||
- Negative coordinates round-trip correctly
|
||||
- 100% statement / branch / function / line Istanbul coverage
|
||||
- 5 tree-shakeable subpath exports
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { createBinaryFontUnpacker } from "@suaveplan/font-decompress";
|
||||
|
||||
const unpacker = createBinaryFontUnpacker();
|
||||
const font = unpacker.unpack(btfBuffer);
|
||||
|
||||
console.log(font.familyName); // "Inter Variable"
|
||||
console.log(font.glyphs["A"]?.ha); // glyph advance width
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import {
|
||||
BinaryFontUnpacker,
|
||||
FONT_DECOMPRESS_CODES,
|
||||
FontDecompressError,
|
||||
} from "@suaveplan/font-decompress";
|
||||
|
||||
// Reuse a single instance — stateless and safe.
|
||||
const unpacker = new BinaryFontUnpacker();
|
||||
|
||||
async function loadCdnFont(url: string) {
|
||||
const buf = new Uint8Array(
|
||||
await (await fetch(url)).arrayBuffer(),
|
||||
);
|
||||
try {
|
||||
return unpacker.unpack(buf);
|
||||
} catch (err) {
|
||||
if (
|
||||
err instanceof FontDecompressError &&
|
||||
err.code === FONT_DECOMPRESS_CODES.INVALID_DATA
|
||||
) {
|
||||
console.warn("Corrupted BTF from CDN:", err.message);
|
||||
return null;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Public exports
|
||||
|
||||
- `class BinaryFontUnpacker` — `unpack(data: Uint8Array): TypefaceFont`
|
||||
- `createBinaryFontUnpacker(): BinaryFontUnpacker`
|
||||
- `FONT_DECOMPRESS_CODES` — frozen registry with `UNPACK_FAILED`, `INVALID_DATA`
|
||||
- `class FontDecompressError extends SuaveplanError<FontDecompressErrorCode>`
|
||||
- `type FontDecompressErrorCode`
|
||||
|
||||
The `TypefaceFont` and `PathCommand` types come from
|
||||
`@suaveplan/font-convert`; consumers import them directly from there
|
||||
rather than via this package's surface.
|
||||
|
||||
### Subpath exports
|
||||
|
||||
| Subpath | What |
|
||||
| -------------------------------------------- | ----------------------------- |
|
||||
| `.` | Root barrel (above) |
|
||||
| `./error-codes` | `FONT_DECOMPRESS_CODES`, `FontDecompressError` |
|
||||
| `./constants` | BTF wire-format constants |
|
||||
| `./utils/decode-string` | `decodeString` helper |
|
||||
| `./unpacker/binary-font-unpacker` | `BinaryFontUnpacker`, factory |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The package is **universal**: zero `node:` / `bun:` imports
|
||||
anywhere in `src/`. The only platform-aware calls are `DataView`,
|
||||
`TextDecoder`, and `String.fromCharCode` — all available in every
|
||||
supported runtime (Node ≥ 18, Bun, modern browsers, Web Workers).
|
||||
|
||||
The BTF wire-format constants (`CMD_M` … `CMD_Z`, `BYTE_TO_CMD`,
|
||||
`CMD_COORD_COUNTS`, `METRICS_COUNT`) are duplicated from
|
||||
`@suaveplan/font-compress`. Duplicating 19 lines of magic numbers
|
||||
avoids a third "fonts-shared" package and keeps each package
|
||||
self-contained. If the BTF format ever changes — which is a
|
||||
coordinated breaking event — both packages get updated in the same
|
||||
commit.
|
||||
|
||||
`@suaveplan/font-compress` is a **devDependency** for round-trip
|
||||
tests only. The runtime dependency graph is `font-decompress →
|
||||
font-convert + error`; the test surface adds `font-compress` solely
|
||||
to generate known-vector fixtures.
|
||||
|
||||
115
docs/media/fonts/font-decompress/src/error-codes/error-codes.md
Normal file
115
docs/media/fonts/font-decompress/src/error-codes/error-codes.md
Normal file
@@ -0,0 +1,115 @@
|
||||
# error-codes
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
The `@suaveplan/font-decompress` package centralises every failure mode it
|
||||
can produce behind a single registry built with
|
||||
`defineCodes("FONT_DECOMPRESS", [...])` from `@suaveplan/error`. The
|
||||
registry is the stable contract callers depend on: code strings are
|
||||
documented, frozen at module load, and never renamed without an
|
||||
explicit migration. This module exports the `FONT_DECOMPRESS_CODES`
|
||||
registry, the derived `FontDecompressErrorCode` literal-union type,
|
||||
and the `FontDecompressError` class that every public failure path
|
||||
raises.
|
||||
|
||||
`FontDecompressError` extends `SuaveplanError<FontDecompressErrorCode>`
|
||||
so `instanceof FontDecompressError` narrows `code` to the union of
|
||||
`FONT_DECOMPRESS.*` strings. The class threads `cause` straight
|
||||
through `SuaveplanError`, so primitive-layer exceptions
|
||||
(`RangeError` from `DataView`, `TypeError` from `TextDecoder`,
|
||||
`@suaveplan/font-convert` errors) remain inspectable through
|
||||
`error.cause`.
|
||||
|
||||
## Features
|
||||
|
||||
- Frozen `FONT_DECOMPRESS_CODES` registry with two namespaced codes
|
||||
- `FontDecompressErrorCode` literal-union type derived from the registry
|
||||
- `FontDecompressError` class extending `SuaveplanError<FontDecompressErrorCode>`
|
||||
- Full `cause`-chain preservation via `SuaveplanError`
|
||||
- Distinct from `FONT_COMPRESS.*` codes — different namespace, different class
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import {
|
||||
FONT_DECOMPRESS_CODES,
|
||||
FontDecompressError,
|
||||
} from "@suaveplan/font-decompress";
|
||||
|
||||
if (data.byteLength < 1) {
|
||||
throw new FontDecompressError(
|
||||
FONT_DECOMPRESS_CODES.INVALID_DATA,
|
||||
"BTF buffer is empty",
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import {
|
||||
FONT_DECOMPRESS_CODES,
|
||||
FontDecompressError,
|
||||
} from "@suaveplan/font-decompress";
|
||||
|
||||
try {
|
||||
const font = unpacker.unpack(buffer);
|
||||
} catch (err) {
|
||||
if (err instanceof FontDecompressError) {
|
||||
if (err.code === FONT_DECOMPRESS_CODES.UNPACK_FAILED) {
|
||||
telemetry.increment("font.unpack.failure", {
|
||||
root: err.cause instanceof Error ? err.cause.name : "unknown",
|
||||
});
|
||||
}
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `FONT_DECOMPRESS_CODES`
|
||||
|
||||
A frozen object literal of two namespaced codes:
|
||||
|
||||
| Property | Value |
|
||||
| --------------- | -------------------------------- |
|
||||
| `UNPACK_FAILED` | `"FONT_DECOMPRESS.UNPACK_FAILED"` |
|
||||
| `INVALID_DATA` | `"FONT_DECOMPRESS.INVALID_DATA"` |
|
||||
|
||||
### `type FontDecompressErrorCode`
|
||||
|
||||
The literal union of every value in `FONT_DECOMPRESS_CODES`.
|
||||
|
||||
### `class FontDecompressError extends SuaveplanError<FontDecompressErrorCode>`
|
||||
|
||||
| Member | Type |
|
||||
| ------------------ | ---------------------------------------------------------- |
|
||||
| `code` | `FontDecompressErrorCode` (narrowed) |
|
||||
| `cause?` | `unknown` (inherited) |
|
||||
| `timestamp` | `number` (inherited; `Date.now()` at construction) |
|
||||
| `constructor(...)` | `(code, message, options?: SuaveplanErrorOptions)` |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`defineCodes` returns a `Object.freeze`d record with `null` prototype,
|
||||
so the registry cannot be mutated, reassigned, or extended at runtime.
|
||||
The literal-type machinery preserves the exact `${Namespace}.${Reason}`
|
||||
string template across every consumer, so `switch (err.code)` enjoys
|
||||
exhaustive narrowing without runtime cost.
|
||||
|
||||
The class's constructor passes `message` through to `SuaveplanError`'s
|
||||
`messageKey` parameter. The base class resolves the key via the i18n
|
||||
resolver if one has been registered with `setMessageResolver`, falling
|
||||
back to the raw string otherwise — so this package works without i18n
|
||||
wiring and lights up automatically when `@suaveplan/i18n` is present.
|
||||
|
||||
The split from `@suaveplan/font-compress` preserves namespace
|
||||
distinctness: an `INVALID_DATA` raised by the packer is
|
||||
`FONT_COMPRESS.INVALID_DATA`; an `INVALID_DATA` raised by the unpacker
|
||||
is `FONT_DECOMPRESS.INVALID_DATA`. Consumers dispatching on either
|
||||
class plus its code get fully unambiguous error handling.
|
||||
@@ -0,0 +1,134 @@
|
||||
# unpacker/binary-font-unpacker
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`BinaryFontUnpacker` reverses the BTF (Binary TypeFace) wire format
|
||||
produced by `BinaryFontPacker` from `@suaveplan/font-compress`,
|
||||
returning a `TypefaceFont` object (from `@suaveplan/font-convert`)
|
||||
ready for Three.js's `FontLoader`, R3F text rendering, or any other
|
||||
typeface.js-compatible consumer. The unpacker is the runtime side of
|
||||
the BTF pipeline: build-time tooling packs once, then ships the
|
||||
compact `Uint8Array`; renderers unpack on demand.
|
||||
|
||||
The class is intentionally self-contained — no helpers imported from
|
||||
`@suaveplan/font-compress`, no shared mutable state. Boundary
|
||||
validation runs at every read so malformed or truncated buffers raise
|
||||
`FontDecompressError` synchronously rather than corrupting the
|
||||
returned font object.
|
||||
|
||||
## Features
|
||||
|
||||
- Round-trips every field a `BinaryFontPacker` writes: header strings, reversed flag, dictionary, metrics, glyphs, glyph outlines
|
||||
- Handles all five glyph commands (`m`/`l`/`b`/`q`/`z`) per the BTF coordinate-count table
|
||||
- Validates every buffer read for truncation; raises `FontDecompressError` with `FONT_DECOMPRESS.INVALID_DATA`
|
||||
- Wraps any underlying primitive exception (`RangeError`, `TypeError` from DataView/TextDecoder) as `FontDecompressError` with `FONT_DECOMPRESS.UNPACK_FAILED` while preserving `cause`
|
||||
- Stateless — a single instance is safe to reuse across many `unpack(...)` calls
|
||||
- Universal — no `node:`/`bun:` imports
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import {
|
||||
BinaryFontUnpacker,
|
||||
createBinaryFontUnpacker,
|
||||
} from "@suaveplan/font-decompress";
|
||||
|
||||
// Load BTF bytes from anywhere — fetch, fs, memory, etc.
|
||||
const btfBuffer: Uint8Array = await fetch("/fonts/inter.btf")
|
||||
.then((r) => r.arrayBuffer())
|
||||
.then((b) => new Uint8Array(b));
|
||||
|
||||
const font = createBinaryFontUnpacker().unpack(btfBuffer);
|
||||
|
||||
// Feed `font` to Three.js's FontLoader.parse(font) or R3F's <Text3D font={font} />
|
||||
console.log(font.familyName, font.glyphs["A"]?.ha);
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import {
|
||||
BinaryFontUnpacker,
|
||||
FONT_DECOMPRESS_CODES,
|
||||
FontDecompressError,
|
||||
} from "@suaveplan/font-decompress";
|
||||
|
||||
const unpacker = new BinaryFontUnpacker(); // safe to reuse
|
||||
|
||||
async function loadFontWithFallback(
|
||||
url: string,
|
||||
fallback: Uint8Array,
|
||||
): Promise<ReturnType<BinaryFontUnpacker["unpack"]>> {
|
||||
try {
|
||||
const buf = new Uint8Array(
|
||||
await (await fetch(url)).arrayBuffer(),
|
||||
);
|
||||
return unpacker.unpack(buf);
|
||||
} catch (err) {
|
||||
if (
|
||||
err instanceof FontDecompressError &&
|
||||
(err.code === FONT_DECOMPRESS_CODES.UNPACK_FAILED ||
|
||||
err.code === FONT_DECOMPRESS_CODES.INVALID_DATA)
|
||||
) {
|
||||
// BTF corrupted in transit; fall back to the bundled font
|
||||
return unpacker.unpack(fallback);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `class BinaryFontUnpacker`
|
||||
|
||||
| Method | Signature |
|
||||
| ----------------- | -------------------------------------------------- |
|
||||
| `unpack` | `(data: Uint8Array) => TypefaceFont` |
|
||||
|
||||
### `createBinaryFontUnpacker(): BinaryFontUnpacker`
|
||||
|
||||
Convenience factory equivalent to `new BinaryFontUnpacker()`. Use it
|
||||
when you prefer functional composition over `new`.
|
||||
|
||||
### Error contract
|
||||
|
||||
| Condition | Code |
|
||||
| ------------------------------------------------------ | --------------------------------- |
|
||||
| Truncated buffer at any header / dictionary / glyph | `FONT_DECOMPRESS.INVALID_DATA` |
|
||||
| Out-of-range command byte (not 0–4) | `FONT_DECOMPRESS.INVALID_DATA` |
|
||||
| Out-of-range dictionary index | `FONT_DECOMPRESS.INVALID_DATA` |
|
||||
| `indexCount < 3` for a glyph (missing ha/x_min/x_max) | `FONT_DECOMPRESS.INVALID_DATA` |
|
||||
| Underlying `DataView` / `TextDecoder` throw | `FONT_DECOMPRESS.UNPACK_FAILED` |
|
||||
|
||||
All errors are `FontDecompressError` instances and carry `cause` when wrapping a primitive throw.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The unpacker walks `data` once, advancing a single `offset` cursor.
|
||||
Reads use a shared `DataView` constructed over `data.buffer +
|
||||
data.byteOffset` so subarray inputs work without an extra slice. Every
|
||||
read goes through one of three private helpers:
|
||||
|
||||
- `readUint32(data, offset)` — little-endian uint32 read
|
||||
- `assertRemaining(data, offset, need, field)` — throws `INVALID_DATA` if `offset + need` exceeds `data.length`, naming the field for diagnostics
|
||||
- `dictAt(dict, index, field)` — throws `INVALID_DATA` if `index` is out of range; otherwise returns `dict[index]`
|
||||
|
||||
The shared `decodeString` helper (in `utils/decode-string.ts`) handles
|
||||
the three header strings with the same boundary validation.
|
||||
|
||||
The `BYTE_TO_CMD` table (`["m", "l", "b", "q", "z"]`) and
|
||||
`CMD_COORD_COUNTS` map (`m: 2, l: 2, b: 6, q: 4, z: 0`) come from the
|
||||
package-local `constants/index.ts` module — a verbatim duplicate of
|
||||
the constants in `@suaveplan/font-compress`. Duplicating 19 lines of
|
||||
format magic keeps each package self-contained at the cost of zero
|
||||
maintenance burden (the BTF wire format is locked).
|
||||
|
||||
`original_font_information` is initialised to `{}` because the BTF
|
||||
format does not carry it — the field is preserved on the
|
||||
`TypefaceFont` shape for downstream compatibility but is always empty
|
||||
on the round-trip side.
|
||||
96
docs/media/fonts/font-decompress/src/utils/decode-string.md
Normal file
96
docs/media/fonts/font-decompress/src/utils/decode-string.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# utils/decode-string
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`decodeString` reads a length-prefixed UTF-8 string from a `Uint8Array`
|
||||
at a given offset. The BTF wire format encodes strings as
|
||||
`[uint32 length LE][utf8 bytes]`, and the unpacker calls this helper to
|
||||
read the three header strings (`familyName`, `cssFontWeight`,
|
||||
`cssFontStyle`) before walking into the dictionary section. Centralising
|
||||
the helper here keeps the unpacker focused on the structural layout
|
||||
and lets the boundary-validation logic (offset bounds, length-overrun
|
||||
detection) live in one auditable place.
|
||||
|
||||
The function is the inverse of `encodeString` from
|
||||
`@suaveplan/font-compress`. Together they form the wire-format
|
||||
agreement for length-prefixed strings used by the BTF header.
|
||||
|
||||
## Features
|
||||
|
||||
- Reads `[uint32 LE length][utf8 bytes]` and returns the decoded string + next offset
|
||||
- Rejects negative offsets via `FontDecompressError` with `FONT_DECOMPRESS.INVALID_DATA`
|
||||
- Rejects offsets where fewer than 4 bytes remain via the same code
|
||||
- Rejects declared lengths that would read past the buffer end via the same code
|
||||
- Handles empty strings (`length === 0`) correctly
|
||||
- Handles multi-byte UTF-8 (BMP and surrogate-pair) correctly via `TextDecoder`
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { decodeString } from "@suaveplan/font-decompress/utils/decode-string";
|
||||
|
||||
const bytes = new Uint8Array([5, 0, 0, 0, 104, 101, 108, 108, 111]); // length 5 + "hello"
|
||||
const { value, offset } = decodeString(bytes, 0);
|
||||
console.log(value); // "hello"
|
||||
console.log(offset); // 9
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { decodeString } from "@suaveplan/font-decompress/utils/decode-string";
|
||||
import { FONT_DECOMPRESS_CODES, FontDecompressError } from "@suaveplan/font-decompress";
|
||||
|
||||
let cursor = 0;
|
||||
try {
|
||||
const familyName = decodeString(buffer, cursor);
|
||||
cursor = familyName.offset;
|
||||
const cssFontWeight = decodeString(buffer, cursor);
|
||||
cursor = cssFontWeight.offset;
|
||||
const cssFontStyle = decodeString(buffer, cursor);
|
||||
cursor = cssFontStyle.offset;
|
||||
} catch (err) {
|
||||
if (err instanceof FontDecompressError && err.code === FONT_DECOMPRESS_CODES.INVALID_DATA) {
|
||||
// Truncated BTF buffer — log and surface to caller
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `decodeString(data: Uint8Array, offset: number): { value: string; offset: number }`
|
||||
|
||||
| Parameter | Type | Meaning |
|
||||
| --------- | ------------ | ---------------------------------------- |
|
||||
| `data` | `Uint8Array` | The buffer containing the encoded string |
|
||||
| `offset` | `number` | Byte offset to start reading from |
|
||||
|
||||
**Returns** `{ value: string; offset: number }` — the decoded string plus the next byte position. The new offset equals `offset + 4 + utf8ByteLength`.
|
||||
|
||||
**Throws** `FontDecompressError` with `code === "FONT_DECOMPRESS.INVALID_DATA"` when:
|
||||
|
||||
- `offset < 0`
|
||||
- `offset + 4 > data.length` (not enough bytes for the length prefix)
|
||||
- `offset + 4 + length > data.length` (declared length reads past buffer end)
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The helper uses a module-level `TextDecoder` rather than constructing one
|
||||
per call. `TextDecoder` is sturdy under reuse and the BTF format never
|
||||
nests its strings, so a single shared decoder is safe and faster.
|
||||
|
||||
Length is read as little-endian `uint32` via `DataView.getUint32(..., true)`,
|
||||
matching the encoder side. The DataView is constructed with the
|
||||
`byteOffset` of the input array so callers can pass subarrays without
|
||||
extra slicing.
|
||||
|
||||
Boundary validation happens up-front: the function checks the offset
|
||||
and length BEFORE reading any bytes via `TextDecoder`. This avoids any
|
||||
ambiguity about whether a partial read might emit a surrogate-half or
|
||||
similar misuse — the throw is synchronous and the wire format is
|
||||
either valid or rejected.
|
||||
Reference in New Issue
Block a user