One window, one document, nothing else. No editing, no annotation, no cloud, no plugins. Under 20 MB, cold start under 300 ms, 1000-page PDFs that scroll at refresh rate, shell integration on all three desktops, tray icon optional, tabs that can be pinned. This is the feasibility read, the engine decision that drives everything, the full ecosystem sweep, the crate-by-crate shopping list, and what your own repositories already supply.
“Lightweight, cross-platform, many formats, superfast” is not four features; it is one decision made four times. If you embed a browser engine you lose small and fast. If you write your own PDF parser you lose two years. The realistic answer is a native UI shell wrapped around one battle-tested C rendering library, and the entire shape of the product follows from which library you pick.
MuPDF gives you PDF, EPUB, MOBI, FB2, CBZ, XPS, SVG, HTML, TXT and raster images from a single dependency, at ~4–6 MB compiled in, and it is the fastest of the mainstream engines — but it is AGPL-3.0. PDFium (Chrome's engine, BSD-3) is battle-tested on dirty real-world files but does only PDF. hayro is the new third answer: a pure-Rust, Apache/MIT PDF rasteriser with no C toolchain at all. Pick your licence posture first, then the engine is decided for you.
| Engine | Licence | Formats | Footprint | Reality check |
|---|---|---|---|---|
| MuPDF via mupdf / mupdf-rs |
AGPL-3.0 | PDF, EPUB, MOBI, FB2, CBZ, XPS, SVG, HTML, images | 4–6 MB static |
Fastest of the three in independent benchmarks, best render quality after PDFium. This is what SumatraPDF is built on, which is why it is 8 MB and instant. One dependency replaces six. |
PDFiumpdfium-render |
BSD-3 | PDF only | ~10 MB shared lib |
Best correctness on messy real-world PDFs, no licence strings attached. But you still need separate libraries for EPUB, comics, DjVu, images — each with its own licence and its own bugs. |
| Poppler | GPL-2+ | PDF only | 20 MB+ + deps |
Slowest of the three and the worst render quality in head-to-head tests. Effectively Linux-first. Not worth it for this project. |
| hayro pure Rust · a resvg maintainer |
Apache/MIT | PDF only | pure Rust no C toolchain |
A PDF rasteriser built for exactly this job: it never builds an editable model, so parsing is faster and lighter than lopdf-style crates. 1,600+ test PDFs, plus hayro-svg and standalone JBIG2 / JPEG2000 / CCITT decoders you would otherwise have to source. Young: no encrypted PDFs yet, and performance work is explicitly unfinished. |
Every requirement you stated, with a verdict: proven something shipping today already does it, constrained doable but the OS will push back somewhere, not free it costs you size, time or a licence.
| # | Requirement | How it is actually done | Verdict |
|---|---|---|---|
| R1 | Read-only, no extra functions | Delete the feature list. No save, no annotate, no print dialog, no share. The renderer is opened in read-only mode and never writes to the file — enforced at the engine API level, not the UI. | Proven |
| R2 | Small binary, portable | Single statically-linked binary. Windows: one .exe in a folder, no install, no registry writes unless you ask. Linux: AppImage. macOS: .app bundle (portable is not an option there). | Proven — 8 MB is the reference |
| R3 | Superfast launch | Native toolkit, no webview, no runtime. Splash-less: create window → mmap file → render page 1 → show. Target ≤300 ms cold on an SSD, ≤100 ms warm. | Proven |
| R4 | Large format list | Seven format families in v1.0 — PDF, images, comics, EPUB, XPS, SVG, text — about twenty extensions in all, from one engine plus a few small decoders. Office documents land in a separate, deliberately reduced mode — see §4. | Constrained |
| R5 | Scroll + zoom, smooth on huge files | Virtualised scroll with a tile cache: only visible pages exist as bitmaps, evicted LRU. Draft render at low DPI during motion, crisp render on settle. This is the single biggest engineering item. | Proven pattern |
| R6 | Shell right-click integration | Windows: file association + static verb (registry, installer-time). macOS: CFBundleDocumentTypes in Info.plist. Linux: .desktop file with MimeType= plus a KDE service menu. | Easy on 2, fiddly on Windows 11 |
| R7 | Global shortcut to open the selected file | Win32 RegisterHotKey; macOS Carbon RegisterEventHotKey (needs no Accessibility grant); Linux X11 XGrabKey, Wayland only via the GlobalShortcuts portal. | X11 yes, Wayland depends on DE |
| R8 | Tray icon, optional | tray-icon crate (Windows/macOS/StatusNotifier). On Linux prefer the ksni backend — the GTK/AppIndicator path drags in GTK and can panic. | Doable, Linux is the tax |
| R9 | Multiple documents, multiple windows | Each window owns its own document + render thread. A second instance forwards paths to the first over a local socket, or spawns a window — your choice. | Proven |
| R10 | Tab bar with pin | Pinned tabs sort left, cannot be closed by middle-click, and persist across sessions. Cheap to build; easy to get right in one pass. | Proven |
| R11 | Elegant, no chrome | Content is the interface: no toolbar by default, a floating page indicator, auto-hiding scrollbar. Keyboard-first. | Proven (qView) |
| R12 | Cross-platform from one codebase | Rust + a native toolkit. One repo, three CI targets. macOS adds notarisation; Windows adds signing if you want SmartScreen to stay quiet. | Yes, with packaging work |
| R13 | Search inside document | Text extraction is available from the engine for PDF/EPUB/TXT. Not free: incremental search across a 3000-page PDF needs a background index and cancellation. | v1.1, not v1 |
| R14 | Open Office documents (.docx/.xlsx/.pptx) | No honest path without shipping LibreOffice (~300 MB) or a commercial SDK. Every project that claims this either converts headlessly or lies about fidelity. | Out of scope |
| R15 | Select and copy text | A read-only viewer that cannot copy is a screenshot. Text extraction per page, hit-testing per character box, and a clipboard write — all three engines give you the boxes. | v1, mandatory |
| R16 | Hindi, Gujarati, Devanagari, Arabic in documents and in the UI | Documents render through embedded fonts, so PDF is fine. Your own chrome is the risk: complex-script shaping needs a real shaper (cosmic-text / parley + rustybuzz), not a bitmap-font stack. | Easy to get wrong |
| R17 | Screen-reader access | AccessKit is the cross-platform answer and the Rust toolkits already speak it. A read-only viewer is exactly the app a screen-reader user needs, so this is not optional polish. | v1.1 |
Fourteen formats is achievable and impressive. Forty is a maintenance liability. The line to draw is: formats where the engine already rasterises the page (free) versus formats that need a layout engine you would have to write (expensive) versus formats that need an office suite (not this app).
| Format | Engine / library | Effort | Notes | v1.0 |
|---|---|---|---|---|
| PDF .pdf, .ai | MuPDF | free | The whole reason the app exists. Encrypted PDFs → password prompt, that is the one dialog you keep. | Yes |
| Images PNG, JPEG, WebP, AVIF, JXL, TIFF, GIF, BMP, ICO, HEIC | MuPDF + image / libheif / jxl-oxide | free | HEIC and JXL are the only ones needing extra crates. All are pure decode-and-draw. | Yes |
| Comics .cbz .cbr .cb7 .cbt | MuPDF (CBZ) + unrar/7z | free | A zip full of images. Natural page-flip mode, no work beyond archive reading. | Yes |
| EPUB .epub | MuPDF (reflow) | medium | MuPDF reflows EPUB, but its typography is plain. Expect to spend time on font fallback and CSS you choose to ignore. | Yes |
| XPS / OXPS | MuPDF | free | Fixed-layout, identical code path to PDF. Free win. | Yes |
| SVG | MuPDF / resvg | free | Render to a page. Zoom must re-rasterise, never scale the bitmap. | Yes |
| Plain text & code .txt .log .md .json .csv … | own renderer + tree-sitter or a tiny highlighter | free | This is a text widget, not a document engine. Cheap, and the highest-perceived-value format after PDF. | Yes |
| MOBI / AZW3 / FB2 | MuPDF | low | Works, but DRM-free only. Say so in the README before someone files a bug. | v1.1 |
| DjVu | DjVuLibre | medium | GPL — fine if the app is already AGPL. Adds a second C dependency and its own build pain. | v1.1 |
| CHM | libchm (MIT) | medium | Archive + HTML. Small library, ugly edge cases. | v1.2 |
| HTML .html .htm | MuPDF's HTML path | medium | Renders, but is not a browser. Local files only, no scripts, no remote content — that is a security decision, not a limitation. | v1.1 |
| Long tail fonts, certificates, SQLite, archives, .eml, .ics/.vcf | small Rust parsers + a table view | cheap | None of these are documents, and each one is roughly a weekend: parse, then draw a table. They are also what makes a viewer feel universal — see §16, where a 1-binary Rust tool already ships all of them. | v1.2 |
| Office / ODF .docx .xlsx .pptx .odt .ods .odp | Rust parsers (own layout) | medium | Readable, not page-accurate — the full answer is §4. Roughly +2–4 MB, lazily loaded so it costs nothing at startup. | Read mode |
| PostScript / DVI | Ghostscript / TeX | huge | Only via an external converter. SumatraPDF does it by shelling out and caching — optional, at most. | No |
| DRM-locked EPUB / Kindle | — | illegal | Out of scope by policy, not by difficulty. | Never |
.docx, .xlsx, .pptx, .odt, .ods and .odp are not opaque binary blobs — they are zip archives of XML. That moves them from “impossible” to “possible, with a caveat you have to put in the UI”. The caveat is layout: a Word page is the output of a layout engine, and you would be writing a second one. So the question is not can you render them, it is what do you promise when you do.
| Tier | What the user sees | How it is built | Cost | Verdict |
|---|---|---|---|---|
| T1 · Read mode docx, odt, rtf, and the rest of the text formats |
Text, headings, lists, tables, images, bold/italic/underline, hyperlinks — in a single reflowed column that you can resize and re-zoom like an EPUB. Page breaks, headers, footers, columns, floats and exact font metrics are approximated or dropped. | Parse the package to a small document IR, emit HTML, lay it out with MuPDF's HTML path. This is precisely what VerseApp and Paperback already do in Rust. | ~1–2 MB crates |
Ship in v1.1 |
| T2 · Grid mode xlsx, ods, xls |
The sheet as a real virtualised grid: cell values, dates, cached formula results, basic fills and fonts, frozen panes. Column widths approximate. This is how people actually read a spreadsheet. | calamine reads xlsx/xlsb/xls/ods — including styles — in pure Rust. Your own grid renderer, reusing the same tile/virtualisation machinery as PDF pages. |
~0.5 MB | Ship in v1.1 |
| T3 · Slide mode pptx, odp |
Each slide as a canvas: shape positions, text boxes, images, notes underneath. Positioning is close but not exact — a stray text box may wrap differently. | Same IR, different renderer: a positioned shape list instead of a reflowed column. | +1 MB | v1.2 |
| T4 · Precise mode any office format, when soffice exists |
A page-accurate rendering, because it is a PDF by then. This is the only honest WYSIWYG path. | Shell out to LibreOffice headless, convert once, cache the PDF next to the document, then hand it to your own PDF engine. SumatraPDF uses exactly this pattern for DVI and CAJ files. | 0 MB but 300 MB dependency |
Opt-in only |
| T5 · Borrow the OS Windows, macOS |
Whatever the platform's own previewer draws — usually perfect, because it is Office or iWork doing the drawing. | Windows: host the registered IPreviewHandler out-of-process through prevhost.exe (QuickLook's Office plugin does this). macOS: QLPreviewView. Both give you a foreign window inside your tab — no zoom consistency, no cross-platform behaviour. |
0 MB but needs Office installed |
Escape hatch |
Never draw a reflowed DOCX inside a page-shaped white rectangle with a shadow and call it a document preview. It looks like Word, so people will trust the line breaks, the page count and the table widths — and then discover the PDF they export is a different document. Give read mode its own chrome: full-width text, a resize handle, and one honest line at the top — “Reflowed view · layout approximated · Ctrl+Shift+O for exact rendering”.
| Crate | Covers | What it gives you | Caveat |
|---|---|---|---|
office_oxide | DOCX, XLSX, PPTX + legacy DOC, XLS, PPT | One unified Document handle with to_html(), plain_text() and a format-agnostic IR. Zero C dependencies, WASM-capable, and it has an explicit resource-limits module for untrusted input. | Young project. Published parse times are sub-millisecond on a large corpus, but verify licence and edge cases before committing. |
calamine | XLSX, XLSB, XLS, ODS | The de-facto Rust spreadsheet reader: values, formulas, and now cell styles, fills and fonts — everything a grid view needs. | Spreadsheets only, and it reads rather than lays out. You draw the grid. |
ferrodoc | DOCX, ODT, EPUB, HTML, Markdown | A Pandoc-compatible AST in pure Rust with a DOCX reader and an HTML writer — the cleanest route from ODF/OOXML to something you can lay out, with conformance tests against pandoc. | Its focus is conversion fidelity, not visual fidelity. Same trade-off as T1. |
docx-rs | DOCX | Mature, widely used reader/writer for WordprocessingML. Fine if you only want the document body and tables. | Reads structure, not styling. Tables of contents and fields need your own handling. |
libreoffice-pure | DOCX, XLSX, PPTX, ODT, ODS, ODP → PDF/HTML/SVG | A pure-Rust office toolkit that mirrors the soffice CLI without the install, and claims 10–187× faster conversions than real LibreOffice. | Its own README says plainly it is not feature-parity with LibreOffice. Treat as a fast T4, not a replacement for T1. |
LibreOffice headless costs roughly 2–3 seconds per document, and a cold start can be far worse;
batching amortises it to about 100 ms per file, and a resident listener removes the startup
entirely. Two traps: two soffice processes cannot share a user profile (the second
exits silently with no output), and if the fonts used by the document are not installed, the
layout silently shifts. Cache the converted PDF and invalidate on mtime, or you will pay that
cost on every open.
Size figures below are hello-world-class release builds from published benchmarks and project releases; treat them as ±30%, not gospel. What matters is the ordering, which is stable.
| Option | UI | Binary | Cold start | Effort | Verdict |
|---|---|---|---|---|---|
| A · Rust + a PDF engine MuPDF or hayro, behind winit/wgpu |
egui or custom | ~12–16 MB | 150–250 ms | high | Recommended Best balance: memory-safe, one toolchain for all three OSes, MuPDF does the heavy lifting. egui is ~5 MB on its own and 175 ms to first frame in benchmarks — acceptable, not luxurious. |
| B · C++ + MuPDF + FLTK/wx the SumatraPDF path |
FLTK / wx | 7–10 MB | 40–100 ms | very high | Fastest possible FLTK is 40 ms / 1.8 MB RSS in benchmarks. But you hand-write text rendering, HiDPI, font fallback and every widget. Two years of weekends. |
| C · Qt 6 + Qt PDF PDFium inside |
Qt Widgets | 45–70 MB | ~300 ms | low | Fastest to ship Everything is already built: text layout, PDF, images, print, accessibility. But “small and portable” dies at the deploy step — you are shipping a toolkit, not an app. |
| D · Rust + Slint | Slint DSL | ~20 MB | ~150 ms | medium | Viable Nicest-looking native Rust UI and a live preview designer. Licence needs care (GPL/commercial/royalty-free tiers) and it is heavier than egui. |
| E · Tauri / Electron | webview | ~5 MB + runtime | 470–700 ms | low | Reject pdf.js on a 1000-page file is the exact experience you are trying to escape, and a webview is 100 MB of RSS before you draw anything. |
Option A, with the engine chosen in §12. Rust workspace, a thin winit +
wgpu shell (or egui for the chrome only, with the page drawn as a
textured quad), and the engine behind your own Document trait. Which engine you put
behind it is a licence decision, not an architecture one — and for this estate the answer changed.
See §12.
Speed is not an optimisation phase at the end; it is this pipeline, built once, correctly. The rule: the UI thread never blocks on decode, and the renderer never draws more than it can see.
page_count, page_size, render(page, rect, dpi), text(page). Every format implements it.Space/PgDn page, +/- zoom, 1 fit width, 2 fit page, F11 fullscreen, Ctrl+W close, Ctrl+P pin.RegisterHotKey, Shell_NotifyIcon.NSStatusItem..desktop + MimeType, X11 grab / Wayland portal, ksni tray.platform module with three implementations and graceful no-ops.This is the part of the plan that looks trivial and is not. Each row below is a real API with a real failure mode.
| Capability | Windows | macOS | Linux |
|---|---|---|---|
| Right-click → Open with | Registry: HKCR\Applications\app.exe with shell\open\command and SupportedTypes. No DLL needed. Appears in the Windows 11 “Open with” list automatically. |
CFBundleDocumentTypes + CFBundleTypeRole = Viewer in Info.plist, then lsregister. |
.desktop file with MimeType= and Exec=… %F into /usr/share/applications. |
| A custom verb on the context menu | trap Classic IContextMenu only shows under “Show more options”. A first-level entry on the Windows 11 menu needs IExplorerCommand in a COM DLL plus package identity (MSIX or a sparse package) — a day of work and a signing certificate. |
Finder Quick Action / Services, or a Finder Sync extension for a real menu item. | KDE service menu (.desktop in kio/servicemenus). Nautilus scripts or nautilus-python for GNOME. |
| Global hotkey | RegisterHotKey. Watch for OS-reserved chords — they fail silently-ish; report the failure in the UI. |
Carbon RegisterEventHotKey: no Accessibility permission, and it consumes the chord. The modern NSEvent monitor needs TCC and cannot consume. Use Carbon. |
X11: XGrabKey, works. Wayland: only the org.freedesktop.portal.GlobalShortcuts portal — KDE 5.27+, GNOME 48+, Hyprland 0.20+. Older GNOME: document it, do not fake it. |
| Tray icon | Straightforward. | Straightforward, must be created on the main thread after the loop starts. | Needs a StatusNotifier host. GTK/AppIndicator path can panic; use ksni. On a bare WM there may be no tray at all — degrade to nothing. |
| “Open selected file in viewer” | Explorer selection is not exposed to a hotkey; the practical version is: register as default handler, or copy the path and paste. | Finder selection via AppleScript / Services — workable. | Depends on the file manager; KDE and Nautilus expose it, others do not. |
A truly global “open whatever file is selected in Explorer right now” hotkey is not something the shell offers. What you can build, and what people actually mean, is: press the hotkey → a minimal command palette opens → the path is already in the clipboard or you type three letters → the document opens in 300 ms. Design for that and it will feel like the feature you wanted.
“Superfast and lightweight” only survives if it is a CI check, not a vibe. These are the numbers to defend; every release measures them on a fixed laptop.
| Metric | Target | Fail if | How it is enforced |
|---|---|---|---|
| Cold start to visible window | ≤ 300 ms | > 600 ms | Timestamp in main() vs first frame; printed in debug builds, asserted in CI. |
| First page of a 50 MB PDF | ≤ 150 ms | > 400 ms | Benchmark corpus in the repo, run on every PR. |
| Scroll, 1000-page PDF | 60 fps | < 45 fps | Draft-DPI rendering during motion; a frame budget of 16 ms. |
| Idle RSS, one small PDF | ≤ 60 MB | > 120 MB | Tile cache cap + no thumbnails until asked. |
| Binary, stripped | ≤ 20 MB | > 30 MB | MuPDF features trimmed at build time; lto = "thin", strip = true. |
| Installer / portable zip | ≤ 25 MB | > 40 MB | No bundled fonts beyond one UI face; system fonts for content. |
| Dependencies (direct crates) | ≤ 25 | > 40 | cargo deny in CI — this is how “no bloat” stays true in year two. |
v2 of this plan listed ten projects. That was thin. This is the wider sweep — what each one is built on, its licence, and the single idea worth copying. Read the four marked study before writing anything: between them they contain most of your architecture, already debugged.
| Project | Built on | Licence | The idea to take |
|---|---|---|---|
| KOReader Lua + MuPDF + CREngine + DjVuLibre |
Fixed-layout and reflowable formats in one app, on e-ink hardware | AGPL-3 | Study The reference implementation of your §6: a DocumentRegistry maps extension → provider → engine, every engine inherits one abstract Document class (renderPage, getPageTextBoxes, getNativePageDimensions), and results land in a shared TileCacheItem cache. A decade of devices have hardened it. |
| SumatraPDF C++ + MuPDF |
7.9 MB portable, 14 formats, Windows | (A)GPLv3 | Study How to keep it this small: a thin engine abstraction, aggressive format sniffing, and a vendored ext/ tree instead of a package manager. The size discipline is the product. |
| MuPDF's own viewer C + OpenGL |
PDF, XPS, EPUB, CBZ — the engine's reference client | AGPL-3 | Study The lower bound. A complete, usable viewer in a few thousand lines. Read it to calibrate how little UI a document actually needs. |
| Sioyek C++ / Qt |
Research papers and technical books | GPL | The interaction model: a searchable command palette, browser-like location history across documents, single-key marks, and “portals” — a second window pinned to the figure you are reading about. Proof a viewer can be entirely keyboard. |
| qpdfview C++ / Qt + Poppler |
Tabbed document viewer, reported around 15 MB RSS | GPL | Tabs with almost no chrome, and a reminder that Poppler's licence ceiling is real. Best existing answer to “tabs, but quiet”. |
| llpp OCaml + MuPDF |
PDF, EPUB, XPS, CBZ | GPL | A MuPDF integration that stays small: keyboard grammar, no toolbar, fast incremental search. Useful if you want the MuPDF path without Qt. |
| Zathura C + one plugin per backend |
PDF, DjVu, PostScript via separate .so plugins |
GPL | How to split format backends into separate libraries without inventing a plugin framework — the boundary is a C ABI and nothing more. |
| Papers / Evince C / GTK4 |
GNOME's default document viewer | GPL | Linux desktop citizenship: MIME registration, recent-files integration, the small behaviours users notice when they are missing. |
| PreviewQt Qt |
Quick-look style preview of “all kinds of files” on Linux | GPL | The spacebar-preview interaction outside GNOME and KDE — closest existing thing to the summon behaviour you described. |
| GNOME Sushi GTK4 + Glycin |
Spacebar preview inside Nautilus | GPL | How a previewer registers itself with a file manager, and why sandboxed image decoding (Glycin) is worth the indirection. |
| KDE Maki Qt6 / Kirigami |
Dolphin service-menu previewer | GPL | Dolphin registration, and the honest LibreOffice-based answer for Office documents — see §4, tier T4. |
| QuickLook C# / WPF |
macOS-style spacebar preview on Windows, 100+ formats | GPL-3 | The plugin-host model — and its bill: ~100 MB installer for what is conceptually a viewer. Read it as a warning about extensibility. |
| PowerToys Peek C# / WinUI |
Microsoft's own spacebar preview | MIT | Study The cleanest reference for hosting an existing IPreviewHandler out-of-process — §4, tier T5 — including the class-factory caching that keeps prevhost.exe warm. |
| VerseApp Rust / Iced + MuPDF |
PDF, EPUB, DOCX, DjVu, XPS, MOBI, CBZ, FB2, multi-tab | check | Closest Rust prior art: DocumentBackend trait, virtualised continuous scroll, and the page-offset arithmetic that keeps mixed-height pages aligned. |
| Torto Rust + egui + Parley + Vello |
Native ebook reader, no webview | MIT | What native reflowable text layout actually costs when you own the whole stack — and how good it looks when you do. |
| Paperback Rust + wxWidgets |
Accessible multi-format reader | MIT | Accessibility done properly in a native reader — screen-reader navigation by heading, figure and table. The bar for R17. |
| qView C++ / Qt |
Minimal image viewer | GPLv3 | Restraint as a product decision: no toolbars, just the image and a title bar. |
| nsxiv C + X11 |
A whole image viewer in a fraction of a megabyte | GPL-2+ | The discipline check. Every feature you add should have a reason that survives being compared to this. |
| ImageGlass C# / .NET |
90+ image formats, cross-platform | GPLv3 | A ready-made decoder matrix — the checklist for the image half of your format table, including HEIC, JXL and RAW. |
This is the shopping list. It is deliberately short — roughly 25 direct dependencies — and every entry was chosen for licence, size or the fact that it has no C dependency at all.
| Job | Crate | Why this one |
|---|---|---|
| Window, input, HiDPI | winit | The only serious cross-platform windowing layer in Rust. Handles Wayland, X11, Win32 and Cocoa; gives you the device pixel ratio you need for crisp re-rasterisation. |
| Presenting pixels | wgpu or softbuffer | wgpu for GPU blits and scaling; softbuffer if you want zero graphics-stack risk and are happy on the CPU. A document viewer blits textures — it does not need a scene graph. |
| Chrome widgets | egui or iced | Use one for the tab strip, menus and dialogs only; draw the page yourself as a textured quad. This keeps the UI layer replaceable and the document path pure. |
| Text shaping, including Indic | cosmic-text or parley + swash + fontdb | R16 depends on this. Both do real shaping via rustybuzz; egui's built-in text stack does not. Also needed for Arabic, Thai and anything with ligatures. |
| PDF — permissive | hayro (+ hayro-svg) | Pure Rust, Apache/MIT, read-only by design, with JBIG2 / JPEG2000 / CCITT decoders bundled. No C toolchain means trivial Windows, Linux and ARM64 cross-compiles. |
| PDF — fastest, most formats | mupdf | One dependency for PDF, EPUB, XPS, CBZ, SVG, HTML and images. The AGPL is the price. Builds the C library for you; disable default features to shed OCR and formats you do not ship. |
| PDF — maximum correctness | pdfium-render + pdfium-binaries | When a 1998 PDF has to render exactly like Chrome renders it. Runtime-bound shared library, so it can be swapped without a rebuild. |
| EPUB parsing | rbook or epub | If you go hayro-first you need your own reflow. Parse the container, read the spine, lay out the XHTML yourself — this is the single largest piece of work in the permissive path. |
| Images | image, zune-jpeg, image-webp, jxl-oxide, libheif-rs, rawler | Core formats from image; add JXL, HEIC and RAW only if you actually promise them. Each is a separate licence and a separate CVE surface. |
| SVG | resvg / usvg | The best SVG renderer in any language, permissive, and the same author as hayro. Parses separately from rendering, so you can rasterise at any zoom. |
| Spreadsheets | calamine | XLSX, XLSB, XLS and ODS with values, formulas and cell styles. The whole of §4 tier T2. |
| Office text | office_oxide / docx-rs / ferrodoc | §4 tier T1. Start with office_oxide for breadth, fall back to ferrodoc if you want a Pandoc-compatible AST you can reason about. |
| Markdown & code | comrak, syntect | GFM to HTML, then your own layout; syntect for highlighting with Sublime grammars. |
| Mapping the file | memmap2 | Never read a 400 MB PDF into a Vec. Map it and let the engine page it in. |
| Archives (CBZ/CBR) | zip, unrar | Comics are archives of images; CBR needs a RAR decoder, which is the only awkward licence in this list. |
| Tray icon | tray-icon with the ksni backend on Linux | The GTK/AppIndicator path can panic on a machine without GTK initialised; the StatusNotifier path cannot. |
| Menus | muda | Native menu bar and tray context menus. Optional — a keyboard-first viewer can skip it entirely. |
| Global hotkey | global-hotkey + ashpd for Wayland | The crate is X11-only on Linux; the portal half you write yourself with ashpd, or accept a documented gap. |
| Single instance & summon | interprocess or plain loopback TCP | A second launch must hand its path to the running window, not start a rival. Your CLDR already does this over loopback TCP — copy it. |
| Dialogs | rfd | Native open/save panels through XDG portals on Linux. Only used for “open another file” and the PDF password prompt. |
| Clipboard | arboard | Text and image clipboard for R15. |
| File watching | notify | Auto-reload when the file changes underneath you — cheap, and it makes the viewer feel alive. |
| Config & state | serde + toml | One config.toml next to the binary. Portable means no registry and no dotfiles. |
| Packaging | cargo-bundle, cargo-bloat, cargo-deny, cross | Bundle the .app, measure what got big, keep the licence tree clean, and cross-compile ARM64 without a second machine. |
None of these are features in the “bloat” sense. They are the difference between a demo that opens a PDF and a viewer someone keeps on their taskbar.
| Gap | Why it matters | What it costs |
|---|---|---|
| Selection and copy | Read-only does not mean inert. The first thing anyone does with a document is copy a line out of it. Without this, the app is a picture frame. | Per-character boxes from the engine, hit-testing, drag selection, a clipboard write. One focused week. |
| Complex-script shaping | Your corpus is Hindi, Gujarati and Sanskrit; your users read Devanagari daily. PDF is safe (embedded fonts), but your own chrome, search results and reflowed EPUB are not. | A real shaper and bundled Noto faces — or a UI stack that already has one. |
| Screen-reader access | A read-only viewer is precisely the app a blind user needs, and the one most Rust apps fail to serve. | AccessKit wiring in the toolkit; more if you build custom widgets. |
| Per-document memory | Last page, zoom, scroll offset, rotation — restored on reopen. Reopening a 400-page manual at page 1 is the fastest way to be uninstalled. | A tiny sidecar keyed by path + mtime. Half a day. |
| Dark documents, not just dark chrome | Night reading means dimming or inverting the page, and doing it without destroying images. | A shader or a colour transform in the blit path; a per-document toggle. |
| Auto-reload | You edit a document, the viewer follows. Anyone who reads what they write will expect it. | notify plus cache invalidation — an hour, if the cache is keyed on mtime. |
| Fractional HiDPI | 150% scaling on Windows is the default on most laptops. Scaling a bitmap looks like 2008. | Render at scale_factor, never stretch. Free if the tile cache is keyed by DPI. |
| Password-protected PDFs | One dialog, kept. Every other feature can be cut; this one is a wall people hit weekly. | Engine API, one modal, no storage of the password. hayro does not support it yet — check before committing to that path. |
| Thumbnail sidebar | Navigation in a 600-page document. And it is nearly free: the same tile renderer at 1/10 scale. | A virtualised strip and a second, smaller LRU cache. |
| Crash isolation | A malformed PDF must not take the window with it. One hostile file should cost you one tab, not the session. | Engine in a supervised worker thread, or a subprocess per document with a restart. |
| ARM64 builds | Snapdragon Windows laptops and ARM Linux boards are real now, and a 20 MB viewer is exactly what people install on them. | A second CI target. Nearly free with pure Rust, painful with a C engine. |
| A stated “no” for printing | Printing drags in a page-imposition model, a print dialog and platform print APIs. Saying no is defensible — saying nothing is not. | One line in the README and one disabled menu item. |
I went through your repositories. This is not a courtesy note — CLDR is the same program with a different payload, and several other repos supply the pieces this plan was describing as work to be done. Reading your own code is the fastest route to the finished app.
| Repo | What is already in it | What to lift for the viewer |
|---|---|---|
| CLDR Rust · MIT + attribution |
A tray-resident binary that pops up on a keystroke: daemon.rs, summon.rs (global hotkey), ipc.rs (loopback-TCP single instance — no clipboard mailbox), engine.rs, plus scripts/install-shortcut.ps1 and install-shortcut.sh. Release profile: fat LTO, one codegen unit, panic = "abort", stripped → ~600 KB. |
Reuse wholesale The daemon + summon + IPC trio is your viewer's shell. Swap the TUI for a document surface and the shortcut installer scripts give you shell integration on day one. The release profile is the one to copy into the new workspace. |
| ziptastic C++ · CMake |
“The CHCE native engine — a single binary with the codecs embedded”, with a vendor/ tree, a plugins/ directory and a PROJECT_OVERVIEW.md. |
The single-binary-with-embedded-codecs philosophy, and the vendored-dependency layout SumatraPDF also uses. This is the answer to “how do I ship C code without a package manager”. |
| offline-pdf-toolkit single-file HTML |
PDF merge/split/rotate/watermark entirely in the browser, with a service worker, a vendor/ tree and a proper THIRD-PARTY-NOTICES.md. |
Your PDF-domain knowledge in one place, and the notices discipline that an AGPL or BSD dependency will require. Also the honest fallback: a browser page that does what the desktop app does not. |
| repertory-reader · kent-repertory-explorer · boericke-repertory · open-materia-medica · app-organon-reader | Reading interfaces over very large structured texts — 271,421 rubrics, 74,480 entries — with search, no build step, offline. | Your real test corpus. A 2 MB JSON of graded rubrics and a long repertory page are harder to scroll than most PDFs. Load these into the viewer on day three and let them set the performance budget. |
| homoeopathic-materia-medica-repertory-json Python |
13 books, ~108,000 rubrics, app-ready JSON, plus the omm-json-builder skill. |
The content that makes a viewer worth building for you specifically — and a second format worth supporting natively: your own JSON reader, which costs almost nothing on top of the text renderer. |
| lumen Dart / Flutter |
An offline photo library: indexing, a local database, caching, favourites. | The recents/library model in §11, already designed — how to index a folder, cache thumbnails, and remember what was opened. |
| app-builder · All4ONE · TTB | Static web apps compiled into single-file portable Windows EXEs; 403 tools in one portable binary; 591 utilities in one extension. | Your house rule, stated three ways: one file, no installer, no runtime. The viewer is the same promise with a harder payload. |
| drsamonline.github.io · profile-kit · dashboard · skills · audit · backup | A self-updating project index, theme-aware profile kit, live fleet status, an extracted-skills catalogue, a changelog with evidence, and pre-merge snapshots. | The shipping apparatus. A new repo appears on your index automatically; the release workflow, landing page and support block already have a house style (see the repo-presentation skill) — build the viewer to it from the first commit, not after. |
| The_Browser C++ · Ladybird-derived |
An experimental browser engine with its own layout and rendering. | Relevant only if you ever attempt a real EPUB/Office layout engine. It is the difference between reading about that problem and having started it. |
Your estate is uniformly MIT-with-attribution, zero-dependency, offline-first, single binary. MuPDF would be the first AGPL dependency in the fleet, and AGPL's source obligations sit awkwardly next to a licence whose whole point is a retained attribution watermark. That is the strongest argument in this document for the hayro path: pure Rust, Apache/MIT, no C toolchain, cross-compiles to Windows ARM64 in one command — and it costs you a hand-written EPUB reflow. If you do take MuPDF anyway, the obligation is concrete: the combined work becomes AGPL, and every binary you publish must come with an offer of the complete corresponding source, including your own modifications. Decide that trade deliberately in week one, not in month four.
Estimates assume one experienced developer part-time. Every milestone ends with something you can run, not a branch that half-compiles.
Window + one engine + one page of one PDF, timed. If first frame is over 500 ms or the binary is over 30 MB, stop and rethink the stack before writing anything else. Build it on Windows, Linux and ARM64 — cross-compilation pain shows up here or never. Run this spike twice: once with hayro, once with MuPDF, and let the licence argument be settled by a real build rather than by this document.
Document trait, virtualised scroll, tile cache with cancellation, zoom ladder, keyboard navigation, password prompt. Benchmark corpus + the §8 numbers in CI. This is the hardest milestone and it is 60% of the value.
Tab strip, pinned tabs persisted to a small config, multi-window, drag-and-drop, single-instance forwarding, session restore. Deliberately no toolbar.
Images and comics first (cheapest, highest delight), then EPUB, XPS, SVG, then plain text with a small highlighter. Each one is a new implementation of the same trait — if any of them touches the UI code, the abstraction is wrong.
File associations, right-click verbs, global hotkey (with an honest fallback on Wayland), tray
icon, remember-and-restore. Most of this is already written: lift daemon.rs,
summon.rs, ipc.rs and the two install-shortcut scripts out
of CLDR (§12). Test on a clean VM for each OS — developer machines lie about what is registered.
Portable zip, MSI, DMG (signed + notarised, or accept the Gatekeeper warning), AppImage + deb.
cargo deny in CI, a size budget check on every tag, and a README that states the
formats, the non-goals and the licence.
In-document search, MOBI/AZW3, DjVu, CHM, HTML — plus the Office read mode and spreadsheet grid (§4) that ship as v1.1 alongside them. Each one is opt-in and each one must not move the startup or size numbers. The moment a feature request starts with “could it also…”, the answer is the app store of your choice.
Not “is it open source” — everything in your estate is. The question is whether this app may carry the only AGPL dependency in the fleet. MuPDF buys you a dozen formats in one binary and the fastest renderer, and costs you a licence that will not sit comfortably beside MIT-with-attribution. hayro keeps the licence story clean and the build pure Rust, and costs you PDF-only plus an EPUB path you write yourself. Both fit behind the same trait — decide with your eyes open, not by accident.
A plan that has been revised three times accumulates contradictions faster than it accumulates insight. This is the pass that went looking for them. Every row below was a real defect in the previous version, not a stylistic preference.
| Defect | Why it mattered | Fixed |
|---|---|---|
| Recommended MuPDF to an MIT estate | v1 and v2 called AGPL “fine, even elegant” without checking what the rest of your work is licensed under. That was advice shaped for a generic open-source project, not for yours. | hayro added as the licence-clean path; §12 states the trade and the concrete AGPL obligations. |
| “Option A, revised in §9” | A stale cross-reference left over from renumbering — §9 is the ecosystem map, not the licence decision. A reader following it would land nowhere useful. | Points at §12. |
| “the §7 numbers” in M1 | The performance budget moved to §8 when the Office section was inserted. The roadmap still cited the old number. | Points at §8. |
| “14 formats in v1.0” | The KPI and requirement R4 both claimed 14, while the format matrix ships seven families in v1.0 and defers the rest. A number you cannot check is a number nobody trusts. | Restated as seven families, about twenty extensions. |
| Office contradiction | §4 shipped Office read mode in v1.1; the roadmap filed it under post-1.0; the summary said Office fidelity was “not happening” while §4 tiers T4 and T5 deliver exactly that. | All three now say v1.1, and the summary distinguishes fidelity without a converter. |
| Warm vs cold start | R3 asked for “≤300 ms warm”, the budget table asked for “≤300 ms cold”. Two different products. | Cold ≤300 ms, warm ≤100 ms, stated once and consistently. |
| Unverified third-party numbers | “nsxiv under 100 KB” came from a forum comment, not the project; “qpdfview ~15 MB RSS” from a blog review. Presented as fact, they invite a correction you cannot defend. | Softened to “a fraction of a megabyte” and “reported around 15 MB”. |
| hayro attributed to “the resvg author” | Accurate-ish but imprecise: the same person is one of several resvg maintainers. | “a resvg maintainer”. |
| No licence mechanics | The plan said “AGPL is a decision” without saying what the decision obliges you to do on release day. | Source-offer requirement stated in §12. |
| Selection, shaping, accessibility missing | Three requirements absent from v1 and v2 that decide whether the app is usable at all — especially for a Devanagari corpus and for screen-reader users. | Added as R15–R17 and expanded in §11. |
Two things this document cannot settle for you. One: whether the EPUB reflow is worth writing yourself — that is the hidden cost of the hayro path and it is measured in months, not weeks. Two: whether “open the file currently selected in Explorer” is a requirement or a nice-to-have; no desktop exposes it, so the honest substitute in §7 is what you would actually ship. Both are decisions, not research gaps.
Most of what you sent is a different species of software: browser components and server-side converters. That is not a criticism — it is the most useful finding in the batch, because it shows exactly which territory is crowded and which is empty.
| Link | What it actually is | Verdict | The idea worth keeping |
|---|---|---|---|
| thaapasa/peek MIT · Rust · terminal |
A single-binary file viewer that previews PDF, DOCX, ODT, RTF, EPUB, CBZ, PPTX, ODP, XLSX, ODS, SQLite, fonts, X.509 certificates, ELF/PE/Mach-O, email, iCalendar, vCards, archives, .DS_Store — with a hex fallback. | Best find | The scope model, and the licence fit: MIT, pure Rust, one binary, one file at a time. Two techniques to steal — rendering a PDF page as an image with a reconstructed-text overlay at matching zoom, and treating every non-document format as “parse, then draw a table”. |
| flyfish-dev/file-viewer Apache-2.0 · 1.3k ★ · web |
Browser-native Office/PDF/CAD/archive viewer: 206 extension mappings, 24 preview pipelines, no server conversion, heavy renderers lazy-loaded per format. | Steal the router | Format count is a routing problem, not an engine problem: extension → pipeline registry, with the expensive pipelines loaded only when a file hits them. This is your §6 DocumentRegistry, productised. |
| docmentis/udoc-viewer MIT wrapper · Rust/WASM engine |
Client-side PDF/DOCX/PPTX/image viewer on a from-scratch Rust engine compiled to WebAssembly — explicitly not PDF.js. Repo has moved to a larger platform. | Validation | Someone is already building a Rust office+PDF rendering engine and shipping it. Confirms the thesis in §5, and confirms the difficulty: they wrapped it, then grew into a platform to fund it. |
| Okular GPLv2+ · KDE |
“The Universal Document Viewer” — PDF, EPUB, DjVu, Markdown, images, CBZ/CBR — via a Generator plugin per format. | The mature design | Your Document trait, twenty years older: one generator per format behind one interface, with selection, thumbnails and search implemented once. Why not just use it — KDE dependencies, no portable single binary, no tray, no summon. It is also the first software to carry the Blue Angel ecolabel for resource efficiency, which is the right thing to be measured against. |
| UniversalViewer MIT · IIIF + OpenSeadragon |
Web viewer for IIIF manifests: images, 3D, PDF, audio, video. | One idea | The tile pyramid. A 20,000-pixel scan needs pre-computed levels, not one bitmap — the correct extension of §6's tile cache to oversized images. |
| kkfileview Java / Spring Boot |
Server that converts Office, PDF, media and archives to browser-viewable output. | Anti-pattern | Proves §4 tier T4 at scale — and shows the bill: a JVM service in the middle. Everything it does, a local viewer does without a server. |
| ngx-universal-viewer Angular |
Component wrapping PDF.js + Mammoth.js + SheetJS for PDF/Word/Excel/PowerPoint. | Baseline | That trio is the web definition of “universal viewer”. Useful as a feature checklist, useless as a base for something fast. |
| mendixlabs/FileDocumentViewer Apache-2.0 · Mendix widget |
Embeds a file document inside a form; falls back to PDF.js when the browser cannot render. | One sentence | Its README is the argument for native: “Word or Excel are usually not viewable in the browser… the browser will download it.” |
| myworkside/UniversalFileEditorViewer MIT · Android/Kotlin |
Viewer + editor + file manager + vault + storage analyser + APK analyser + wireless sharing. | Scope trap | This is the app your plan exists not to be. Useful only as a reminder of what a feature list looks like when nobody says no. |
| Universal-File-Reader · Universal-File-Explorer 2016 and 2021 · both dead |
C# and Flutter attempts at a tabbed universal viewer; last commits 2017 and 2021. | Evidence, not code | The tabbed universal viewer is a recurring idea that dies when it runs out of maintainers. Scope discipline is survival, not taste. |
| The topic pages document-viewer, file-preview-viewer, react/angular-file-viewer, presentation-viewer, binary-viewer, kkfileview |
A census of the category, not a project. | Market read | Nearly every entry is a web component, a server, or a dead repo. The native, portable, summonable viewer is close to unoccupied. |
SumatraPDF owns Windows fixed-layout. Okular owns Linux all-formats, at KDE's weight. QuickLook owns the Windows spacebar, at 100 MB. Sushi and Maki own their own desktops' preview. peek owns the terminal. Nobody owns small, portable, cross-platform, summoned by a keystroke, read-only. That is not a crowded market with a hard incumbent — it is a category with five regional champions and no one in the middle.
Two edits, both from evidence rather than taste. One: §3 gained a “long tail” row — fonts, certificates, SQLite, archives, email, calendars — because peek demonstrates each is a weekend of parsing plus a table, and together they are what makes a viewer feel universal rather than specialised. Two: §6 gained an explicit router in front of the document trait, with lazy pipeline loading, because flyfish shows how one binary can honestly claim 206 extensions without paying for them at startup. Neither change touches the size or startup budgets in §8 — both are the kind of thing you add because the budgets held.