60 lines
2.1 KiB
TypeScript
60 lines
2.1 KiB
TypeScript
/**
|
|
* Minimal `.tbl` writer, shared by the fixture generator and its checker.
|
|
*
|
|
* Tooling only: the engine reads TBL files, it never writes them. It lives here so
|
|
* the fixture generator and the construction check encode a table the same way,
|
|
* instead of two copies drifting apart.
|
|
*/
|
|
|
|
/** One entry: a string, or null for an unused index. */
|
|
export type TblEntry = string | null
|
|
|
|
/**
|
|
* Encode a classic `.tbl`: crc, entry count, index table, then
|
|
* `u16 characterCount` + UTF-16LE strings.
|
|
*
|
|
* @param entries - the entries, in index order.
|
|
* @returns the encoded bytes.
|
|
*/
|
|
export function encodeTbl(entries: readonly TblEntry[]): Uint8Array {
|
|
const headerBytes = 4
|
|
const indexBytes = entries.length * 2
|
|
const blocks: { index: number; bytes: Uint8Array }[] = []
|
|
let cursor = headerBytes + indexBytes
|
|
for (const [index, entry] of entries.entries()) {
|
|
if (entry === null) continue
|
|
// The count is in characters; an astral character is two UTF-16 units.
|
|
let characters = 0
|
|
for (const character of entry) characters += character.codePointAt(0)! > 0xffff ? 2 : 1
|
|
const body = new Uint8Array(characters * 2)
|
|
const view = new DataView(body.buffer)
|
|
let units = 0
|
|
for (const character of entry) {
|
|
const code = character.codePointAt(0)!
|
|
if (code > 0xffff) {
|
|
const adjusted = code - 0x10000
|
|
view.setUint16(units * 2, 0xd800 + (adjusted >> 10), true)
|
|
view.setUint16((units + 1) * 2, 0xdc00 + (adjusted & 0x3ff), true)
|
|
units += 2
|
|
} else {
|
|
view.setUint16(units * 2, code, true)
|
|
units += 1
|
|
}
|
|
}
|
|
const bytes = new Uint8Array([characters & 0xff, (characters >> 8) & 0xff, ...body])
|
|
blocks.push({ index, bytes })
|
|
cursor += bytes.byteLength
|
|
}
|
|
const out = new Uint8Array(cursor)
|
|
const view = new DataView(out.buffer)
|
|
view.setUint16(0, 0x1234, true)
|
|
view.setUint16(2, entries.length, true)
|
|
let at = headerBytes + indexBytes
|
|
for (const block of blocks) {
|
|
view.setUint16(headerBytes + block.index * 2, at, true)
|
|
out.set(block.bytes, at)
|
|
at += block.bytes.byteLength
|
|
}
|
|
return out
|
|
}
|