facett-docview — guide
Class:
guide· audience: a techie embedding or driving aDocView. The what is indocview-design.md; the why indocview-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:
- Build it —
DocView::from_svgs("Manual", svgs, toc)for a bundled/small manual, orDocView::from_source("Manual", Box::new(my_source), toc)for a large one you don't want to parse all at once. - Drop it in a deck —
DocViewimplementsfacett_core::Facet; box it like any other facet (facett-demo/src/lib.rs:633,786does exactly this). - Drive it headlessly — every gesture is a
Msg;facett_core::harnessapplies aMsgscript and returns a snapshot or aRenderReport, 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.