A high-performance, multi-format document reader written in Rust using the Iced 0.13 framework (Elm Architecture) and the MuPDF Fitz C engine (mupdf crate) alongside native DjVu and DOCX decoders.
PDF · EPUB · DOCX · DjVu · XPS · MOBI · CBZ · FB2
verseapp/
├── Cargo.toml # Dependencies & features
└── src/
├── main.rs # Binary entry point & Iced app launcher
├── app/ # Elm Architecture Core
│ ├── mod.rs # Facade re-exporting ReaderApp & Message
│ ├── state.rs # ReaderApp struct, new(), theme(), save_session()
│ ├── messages.rs # Exhaustive Message enum
│ ├── actions.rs # Key normalization & Action dispatcher
│ ├── tasks.rs # Tokio worker tasks & pre-fetch priority queue
│ ├── update.rs # Reducer loop, debounced scroll/zoom, session sync
│ └── view.rs # Root UI composition & modal stack
├── engine/ # Document Decoders & Renderers
│ ├── mod.rs # Load dispatcher (load_document)
│ ├── traits.rs # DocumentBackend trait, RenderQuality, PageRenderRequest
│ ├── mupdf.rs # Thread-safe MuPDF engine (PDF, EPUB, MOBI, CBZ, XPS, FB2)
│ ├── djvu.rs # Native DjVu engine
│ └── docx.rs # Native DOCX ZIP XML extractor & layout renderer
├── models/ # Domain Models & State
│ ├── mod.rs
│ ├── session.rs # SessionData, AppSettings, FileHistoryRecord, KeyBinding
│ └── workspace.rs # RuntimeTab, byte-budget LRU texture cache
└── ui/ # GUI Components & Styling
├── mod.rs
├── theme.rs # Theme definitions & transparent scrollable styles
└── components/
├── mod.rs
├── tab_bar.rs # Scrollable browser tab bar
├── viewer.rs # Canvas viewport, control tray, virtualized scrollable
├── side_panel.rs # TOC Outline tree & thumbnail preview grid
└── settings.rs # Glassmorphism keybinding modal
MuPDF C bindings require C toolchain dependencies:
# Debian / Ubuntu
sudo apt install build-essential pkg-config libclang-dev# Debug build
cargo build
# Optimized Release build
cargo build --release
# Run application
cargo run --release- Model (
ReaderApp): Holds global application settings, openRuntimeTabinstances, active tab pointers, keybinding mappings, and modal states. - Message (
messages.rs): Exhaustive enum handling UI events, keyboard shortcuts, Tokio async results, debounced scroll/zoom settling, and rendering outputs. - Update (
update.rs): Reducer modifying state, persisting session state tosession.json, and dispatching async tasks. - View (
view.rs/ui/components/): Pure, stateless UI functions assembling layout components.
-
MuPDF Thread Safety:
Documentis wrapped inThreadSafeDocument(Mutex<Document>)to allow safe cross-thread usage across background Tokio blocking threads. -
Lock-Free UI Dimension Access:
MuPdfBackendpre-caches all page dimensions (Vec<(f32, f32)>) duringopen(). The main UI thread reads page dimensions in$O(1)$ time without acquiringMutexlocks, preventing 60 FPS GUI freezes during rasterization. -
Offloaded Handle Construction:
iced::widget::image::Handle::from_rgbaruns insidetokio::task::spawn_blocking. The UI thread receives pre-built, display-ready image handles.
-
Progressive Quality Tiers:
-
Fuzzy($0.20\times$ scale) — Immediate low-res preview ($<1\text{ ms}$ ). -
Draft($0.55\times$ scale) — Fast scrolling ($60\text{ FPS}$ ). -
High($1.25\times$ scale) — Sharp text focus.
-
-
Resolution Clamping: Raster scale is bounded to max
$3840 \times 3840\text{ px}$ . High zoom factors on 8K scanned pages will never allocate gigabytes of uncompressed RGBA RAM. -
SIMD Pixel Pipeline: Fast slice-copy
copy_from_slicebuffer construction vectorizes RGBA conversion, reducing per-frame processing from$250\text{ ms}$ to$2\text{ ms}$ . -
Viewport Virtualization (
viewer.rs): Continuous mode only passes image handles to the UI for visible pages (current_page - 4tocurrent_page + 8). Distant pages render as zero-texture blank boxes with exact pixel dimensions, keeping RAM usage under$35\text{ MB}$ per tab. -
Byte-Budget LRU Cache:
RuntimeTab::enforce_memory_budgetenforces a strict$45\text{ MB}$ RAM budget per tab by evicting distant page textures when exceeded.
To implement support for a new document format (e.g., .txt or .cbr):
-
Implement
DocumentBackendinsrc/engine/your_format.rs:use crate::engine::traits::{DocumentBackend, PageRenderRequest, TocItem}; use image::RgbaImage; #[derive(Debug)] pub struct CustomBackend { ... } impl DocumentBackend for CustomBackend { fn page_count(&self) -> usize { ... } fn page_dimensions(&self, page_index: usize) -> (f32, f32) { ... } fn render_page(&self, request: &PageRenderRequest) -> Result<RgbaImage, String> { ... } fn table_of_contents(&self) -> Vec<TocItem> { ... } }
-
Register Extension in
src/engine/mod.rs:match ext.as_str() { "custom" => Ok(Arc::new(CustomBackend::open(path)?)), _ => Ok(Arc::new(MuPdfBackend::open(path)?)), }
-
Update File Dialog Filter in
src/app/update.rs: Add extension toAsyncFileDialog::new().add_filter(...).