# boox-note-parser
**WARNING:** This crate is still work in progress.
> A Rust library for parsing `.note` files from Onyx Boox e-ink devices.
[](https://crates.io/crates/boox-note-parser)
[](https://docs.rs/boox-note-parser)
[](https://github.com/hhornbacher/boox-note-parser/blob/main/LICENSE)
`boox-note-parser` provides a pure Rust implementation for reading and interpreting handwritten note data stored in `.note` files on Boox devices.
The format is undocumented and [reverse-engineered](docs/format.md) from real note exports.
## What Works Today
- Open single-note and multi-note `.note` archives.
- Read note metadata (`note_tree` / `note_info` protobuf payloads).
- Parse page models and virtual page/document metadata.
- Parse shape groups and custom points stroke files.
- Parse `extra/pb/extra` metadata.
- Parse templates from `template/json/*.template_json` and `.template_json` (including mirrored `document/<note_id>/...` paths).
- Surface `resource/pb/*` records, including explicit handling for empty payload files.
- Surface TOC and preview PNG asset metadata.
- Render page strokes to PNG using style-aware color/width/line-style mapping.
## Quick Start
```rust
use std::{fs::File, path::Path};
use boox_note_parser::NoteFile;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let path = Path::new("MyBackup_1753137342560.note");
let file = File::open(path)?;
let note_file = NoteFile::read(file)?;
for (note_id, name) in note_file.list_notes() {
println!("{} -> {}", note_id.to_hyphenated_string(), name);
}
Ok(())
}
```
You can also run the example inspector:
```bash
cargo run --example inspector -- <path-to-note-file>
```
Or the bundled `render` CLI binary, which writes one PNG per page without
the inspector's metadata dump:
```bash
cargo run --bin render -- <path-to-note-file.note> [output_dir]
```
## Try It Out
### CLI Track (Inspector Example)
Run the built-in inspector:
```bash
cargo run --example inspector -- <path-to-note-file.note>
```
Expected behavior:
- Prints note metadata (IDs, names, timestamps, pen settings).
- Prints template/resource/extra/asset metadata (`Templates:`, `Resources:`, `TOC exists:`, `Preview:`).
- Renders pages and writes PNG files to the current directory.
- Skips pages with no shapes (logs a `WARN` and prints `(empty page — skipped render)`).
- Skips detached page entries that have no page model (prints `(no page model — detached/orphan)`).
Quick verification checks:
- Confirm output includes lines for template/resource/assets.
- Confirm generated PNGs are present in your working directory.
### CLI Track (`render` Binary)
For batch rendering without inspection output:
```bash
cargo run --bin render -- <path-to-note-file.note> [output_dir]
```
Writes `<note_name>_<note_id>_<page_id>.png` for every active, reserved, and
detached page. The output directory defaults to the current working directory
and is created if missing.
### Library Track (Use the Crate in Your Own Binary)
Minimal example:
```rust
use std::{env, fs::File, path::PathBuf};
use boox_note_parser::NoteFile;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let note_path = env::args().nth(1).expect("Usage: app <file.note>");
let file = File::open(PathBuf::from(note_path))?;
let note_file = NoteFile::read(file)?;
for (note_id, name) in note_file.list_notes() {
println!("{} -> {}", note_id.to_hyphenated_string(), name);
let mut note = note_file.get_note(¬e_id).expect("note should exist");
if let Some(page_id) = note.active_pages().first().copied() {
let mut page = note.get_page(&page_id).expect("page should exist");
let rendered = page.render()?;
rendered.write_png(format!("{}_{}.png", name, page_id.to_simple_string()))?;
println!("Rendered first active page for {}", name);
}
}
Ok(())
}
```
Compile/run guidance:
```bash
cargo new boox-note-playground --bin
cd boox-note-playground
```
Add this dependency in `Cargo.toml`:
```toml
[dependencies]
boox-note-parser = { path = "../boox-note-parser" }
```
Then run:
```bash
cargo run -- <path-to-note-file.note>
```
### Troubleshooting
- If you forget the note-file path, inspector exits with a usage line.
- If a note is unsupported/corrupt, parsing returns an error instead of panicking.
- Corpus integration tests are skipped unless enabled with:
- `BOOX_NOTE_PARSER_RUN_CORPUS_TESTS=1`
## Fixture Contribution Guide
This project prefers extracted-minimal fixtures over full `.note` files.
Guidelines:
- Do not commit personal/raw full exports by default.
- Contribute only the smallest extracted blobs needed to prove parser behavior under `tests/fixtures/<topic>/`.
- Remove or anonymize sensitive names/text/identifiers in fixture content before submission.
- Add a short note in the test describing which behavior the fixture validates.
Expected wiring:
- Fixture files live under `tests/fixtures/<topic>/`.
- In-memory archive assembly and reuse helpers live in `tests/common/mod.rs`.
- Behavior-focused integration tests follow the pattern in `tests/p2_fixture_coverage.rs`.
## Quality Checks
Baseline required checks:
```bash
cargo fmt -- --check
cargo check --quiet
cargo test --quiet
```
Optional local corpus validation (requires local sample `.note` files):
```bash
BOOX_NOTE_PARSER_RUN_CORPUS_TESTS=1 cargo test --test p1_format_coverage
```
Optional stricter static analysis (non-gating for now):
```bash
cargo clippy --all-targets --all-features
```
## Corpus Notes (Current Reverse Engineering Basis)
The code and format notes are currently validated against Boox Notes App exports from a Boox Note Air 4 C (Notes app build `42842 - 0760e1b1dad`).
Observed in that corpus:
- Both single-note and multi-note layouts are present.
- UUIDs appear in both simple (32 hex chars) and hyphenated forms.
- `template/json/.template_json` appears as a per-note default template/background descriptor.
- `resource/pb` files may exist but can be empty (0 bytes).
- `toc/` directories may exist and be empty.
- Multi-note exports include note preview PNG thumbnails (`499x666`) at note-root level.
## Current Gaps
- Unknown protobuf fields and some metadata semantics still need broader cross-device validation.
- `resource/pb/*` and `toc/pb/*` payload internals are still treated as opaque/partially modeled.
- More cross-device corpus coverage is needed to confirm behavior outside current observed exports.