Files
skills/docs/media/fonts/font-decompress/src/utils/decode-string.md
2026-05-29 08:20:56 +01:00

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

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