boox-note-parser 0.2.0

A Rust library for parsing .note files from Onyx Boox e-ink devices.
Documentation

boox-note-parser

WARNING: This crate is still work in progress.

A Rust library for parsing .note files from Onyx Boox e-ink devices.

Crates.io Docs.rs 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 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

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:

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:

cargo run --bin render -- <path-to-note-file.note> [output_dir]

Try It Out

CLI Track (Inspector Example)

Run the built-in inspector:

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:

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:

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(&note_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:

cargo new boox-note-playground --bin
cd boox-note-playground

Add this dependency in Cargo.toml:

[dependencies]
boox-note-parser = { path = "../boox-note-parser" }

Then run:

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:

cargo fmt -- --check
cargo check --quiet
cargo test --quiet

Optional local corpus validation (requires local sample .note files):

BOOX_NOTE_PARSER_RUN_CORPUS_TESTS=1 cargo test --test p1_format_coverage

Optional stricter static analysis (non-gating for now):

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.