first commit
This commit is contained in:
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