3.7 KiB
utils/decode-string
(no submodules)
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
FontDecompressErrorwithFONT_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
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
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 < 0offset + 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.