Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

.nornir/docview-guide.md

facett-docview — guide

Class: guide · audience: a techie embedding or driving a DocView. The what is in docview-design.md; the why in docview-reasoning.md. This file is the workflow.

f0 — zero to a driven viewer (the whole workflow)

Three moves get a manual on screen and under headless test:

  1. Build it — DocView::from_svgs("Manual", svgs, toc) for a bundled/small manual, or DocView::from_source("Manual", Box::new(my_source), toc) for a large one you don't want to parse all at once.
  2. Drop it in a deck — DocView implements facett_core::Facet; box it like any other facet (facett-demo/src/lib.rs:633,786 does exactly this).
  3. Drive it headlessly — every gesture is a Msg; facett_core::harness applies a Msg script and returns a snapshot or a RenderReport, so a test never opens a window.

f1 — constructing a DocView

use facett_docview::{DocView, DocPage, PageSource};

// Eager: parse every page up front (rayon, 1 page/core) — good for small/medium manuals.
let view = DocView::from_svgs(
    "Manual",
    [page1_svg_bytes, page2_svg_bytes /* , .. */],
    vec![("Introduction".into(), 0), ("Chapter 2".into(), 1)],   // (toc title, 0-based page index)
);

// Or pre-parse pages yourself:
let pages = vec![DocPage::from_svg_bytes(bytes)?, /* .. */];
let view = DocView::new("Manual", pages, toc);

Failed parses are skipped, not fatal: from_svgs/new drop unparseable pages, so view.page_count() can be less than the input length.

f2 — lazy loading a huge manual via PageSource

Implement PageSource over wherever your pages live (an archive, a compressed blob, a network store) and hand it to from_source. The viewer never sees your storage — it only asks for page i's bytes when i scrolls near the viewport:

struct ArchiveSource { /* .. */ }

impl PageSource for ArchiveSource {
    fn page_count(&self) -> usize { /* total pages */ }
    fn page_svg(&self, i: usize) -> Option<Vec<u8>> { /* fetch + return page i's SVG bytes */ }
    fn page_size_hint(&self) -> (f32, f32) { (595.0, 842.0) } // A4 default; refine if you know better
}

let view = DocView::from_source("Manual", Box::new(ArchiveSource::open(path)?), toc);

page_count() returns instantly (no parsing); the viewer fetches + parses only the pages your current scroll position and its prefetch lead touch, and drops them again once they scroll far enough away.

f3 — controls

All controls route through the same Msg enum a headless driver uses, so live and scripted behavior are identical:

method effect
zoom_in() / zoom_out() stepped zoom (× / ÷ ZOOM_STEP), clamped to [MIN_ZOOM, MAX_ZOOM]
set_zoom(z) zoom to z, clamped
fit_width(panel_px) zoom = panel_px / first_page_width_pt, clamped
jump_to(page) scroll to 0-based page (clamped to the last page) next render
toggle_sidebar() show/hide the TOC panel

Read-backs: page_count(), current_page(), zoom(), toc(), visible_pages(), cache_len(), state_json().

f4 — the headless harness seam

use facett_core::harness;

let mut v = DocView::from_svgs("Manual", svgs, toc);

// Drive a Msg script and read the resulting Model — no window, no GPU:
let model = harness::snapshot(&mut v, [Msg::ZoomIn, Msg::JumpTo(1), Msg::ToggleSidebar]);
assert_eq!(model.current_page, 1);

// Confirm it actually paints (rasterizes + tessellates) offscreen:
let report = harness::headless_render(&mut v);
assert!(report.drew());
assert_eq!(report.state["page_count"], model_page_count);

harness::drive returns the (always-empty, for docview) Vec<Effect>; harness::snapshot_json gives a golden-file-friendly serde_json::Value of the DocState.

f5 — typed copy

DocView implements facett_core::clip::CopySource, so a host's generic "copy" action gets the table of contents as text ("title\tpage" per line, one page per row):

use facett_core::clip::CopySource;
if let Some(payload) = view.copy_payload() {
    let text = payload.as_text(); // "Introduction\t1\nChapter 2\t2"
}

f6 — running the demo

facett-demo wires a docview tab over a bundled sample manual (facett-demo/src/data.rs:427 sample_manual() — synthetic Typst-style SVG pages + a TOC). Run it:

CARGO_TARGET_DIR=/home/rickard/.cargo-target-facett cargo run -p facett-demo

and pick the docview tab. The mega-matrix, self-check, and robot-UI live-drive rows in facett-demo (facett-demo/src/lib.rs:1573-1577) all exercise it, including caps and jump-to-page.