first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View 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.