ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
rabook_format.py
Go to the documentation of this file.
1# SPDX-License-Identifier: MIT
2# Copyright (c) 2026 Brighton Sikarskie
3"""The .rabook on-disk constants, kept in lockstep with apps/shared_libs/book/inc/book.h.
4
5Everything here is a contract with the FIRMWARE, not with this tool: a value
6changed on one side and not the other produces a blob the device parses into
7nonsense rather than a build error. Isolated in one small module so that
8contract is one file to read next to the header it mirrors.
9
10@copyright Copyright (c) 2026 Brighton Sikarskie
11SPDX-License-Identifier: MIT
12"""
13
14import struct
15import zlib
16
17# --- on-disk constants, kept in lockstep with apps/shared_libs/book/inc/book.h ------
18MAGIC = b"RABOOK1\x00"
19FORMAT_VERSION = 1
20NIL = 0xFFFFFFFF
21NODE_ELEMENT = 0
22NODE_TEXT = 1
23IMG_GRAY4 = 0
24IMG_SVG = 1
25# book_image_pixfmt_t: the raster depth stored in the image descriptor's
26# former padding byte (book_image_t.pixel_format). 0 is the default that
27# every pre-field .rabook already carried, so the firmware reads old blobs
28# unchanged; a grayscale device emits gray4 (half the bytes), a deeper panel
29# gray8 (lossless). Kept in lockstep with book.h.
30PIXFMT_GRAY4 = 0
31PIXFMT_GRAY8 = 1
32# Header feature-flag bits (book_flag_t). The firmware validator rejects any
33# bit outside its known mask, so only emit bits defined there.
34FLAG_RTL = 0x00000001
35# .rabook chunked container ("RBKC"; keep in sync with book_container_t in
36# apps/shared_libs/book/inc/book.h):
37# "RBKC" + <I chunk_bytes + <Q inflated_total + <I chunk_count + <I reserved(0)
38# + <Q offset[chunk_count + 1] (payload-relative stream offsets)
39# + chunk_count concatenated zlib streams, one per chunk_bytes slice of the
40# flat blob (last slice short).
41# Every chunk inflates independently, so the device can either inflate all of
42# them into SDRAM (resident open) or inflate single chunks on demand into
43# ra8_vmem cache frames (multi-GB books). chunk_bytes must equal the reader's
44# cache frame size; 64 KiB is the current firmware default.
45CONTAINER_MAGIC = b"RBKC"
46CONTAINER_CHUNK_BYTES = 65536
47
48
49def wrap_container(blob: bytes, chunk_bytes: int = CONTAINER_CHUNK_BYTES) -> bytes:
50 """Wrap a flat RABOOK1 blob in the chunked RBKC container."""
51 if not blob:
52 msg = "empty blob"
53 raise ValueError(msg)
54 if chunk_bytes <= 0:
55 msg = "chunk_bytes must be positive"
56 raise ValueError(msg)
57 count = (len(blob) + chunk_bytes - 1) // chunk_bytes
58 streams = [
59 zlib.compress(blob[i * chunk_bytes : (i + 1) * chunk_bytes], 9) for i in range(count)
60 ]
61 offsets = [0]
62 for stream in streams:
63 offsets.append(offsets[-1] + len(stream))
64 header = CONTAINER_MAGIC + struct.pack("<IQII", chunk_bytes, len(blob), count, 0)
65 table = b"".join(struct.pack("<Q", off) for off in offsets)
66 return header + table + b"".join(streams)
67
68
69# Downscale is OPT-IN (owner decision, issue #210): the default preserves the
70# source resolution because any compile-time pixel loss is unrecoverable at
71# zoom time (the planned press-and-hold loupe re-magnifies small manga text).
72# 0 means no clamp. Pass --max-edge N to opt into a long-edge clamp where the
73# smaller blob is worth it (e.g. TFT-class baked fixtures -- see
74# scripts/builders/books.sh). FS dithering stays off because its high-frequency