# Lumen PDF Annotator Read, search, highlight, and annotate PDFs inside Obsidian with a calm interface designed to stay out of the document's way. ![Lumen PDF Annotator showing its compact floating toolbar](assets/lumen-reader.jpeg) Lumen is a PDF reader for research-heavy vaults on desktop, phone, and tablet. It combines document themes, contextual full-PDF search, fast text markup, searchable notes, and local recovery files without modifying the source PDF or sending its contents anywhere. ## What it feels like - A compact floating toolbar with direct page entry, nearby previous/next controls, zoom, search, annotations, and light/sepia/dark themes. - An immediate selection palette—no hover step—where choosing a colour prepares the mark and choosing its type applies it. Seven mark types are included: highlight, underline, dashed underline, dotted underline, strike-through, box, and comment. - A searchable table of contents appears only when the PDF provides an outline, preserves nested hierarchy, shows muted physical page numbers, and opens exact within-page destinations. The current heading is marked with a slim accent line and centered in the list when you open it, where scrolling permits. - A floating PDF search that shows long, left-aligned excerpts so results make sense before you open them. - A virtualized annotation inspector with bright colour edges, bold quoted text above regular notes, and one-click navigation. - A full editor for each saved mark, including colour, style, note, tags, copy, and delete. - Right-click any saved highlight on desktop—or long-press it on mobile—to copy a Markdown link that opens its PDF, page, and exact annotation from another note. - Extend an existing text annotation from its editor; an opaque, content-sized preview with muted text shows the exact pending quote before confirmation, and additional selections can be on the same page, a different page, or span a page boundary while remaining one logical annotation. - Click-to-place page notes, readable exports, checksum verification, and recovery from the local PDF backup. | Contextual PDF search | Individual annotation editor | | --- | --- | | ![Search results with page numbers and surrounding text](assets/lumen-pdf-search.jpeg) | ![Expanded annotation editor with colours, styles, note, and tags](assets/lumen-annotation-editor.jpeg) | ![The virtualized annotation inspector keeps readable cards beside the page](assets/lumen-annotation-inspector.jpeg) ![The compact selection palette appears as soon as text is selected](assets/lumen-selection-toolbar.jpeg) The screenshots show the running plugin in Obsidian using an original demo document created for this repository. ## Performance by construction Lumen was built around large documents and dense annotation sets rather than optimized after the fact. - Only pages near the viewport receive a canvas and selectable text layer; distant pages remain lightweight shells. Active scrolling cancels stale work and pauses new page paints. Once motion settles, a direction-aware single-page queue produces a quick preview, then upgrades the visible page to full detail and selectable text while idle. Leaving the render window releases the canvas backing store, text layer, PDF page proxy, and task references. - Canvas rendering is capped at 12 million pixels to avoid runaway memory use at extreme zoom levels. - Annotation lookups use ID and page indexes, so drawing a page does not scan the entire collection. - Pages with unusually dense markup switch to one bounded canvas overlay with a spatial click index instead of creating thousands of DOM nodes. - The inspector mounts only the visible card window plus overscan. Its default newest/oldest views request that window directly from a recency index instead of rebuilding the full logical collection after every edit. - Full-document search yields to Obsidian every six pages, can be cancelled, bounds retained results and mounted cards, and uses a bounded least-recently-used page-text cache. - Selection capture and current-page tracking use page-range/binary lookup rather than walking every page, while hidden search and inspector panels release their result DOM. - PDF outlines load after the reader is ready; named destinations and page references are cached, resolved with bounded concurrency, and yielded in batches so even unusually large outlines do not monopolize the UI thread. - Snapshot restore, annotation journals, and compact checkpoint serialization yield in bounded batches so six-figure loads and saves do not monopolize the UI thread. Closing or switching a PDF flushes only recent journal changes rather than rebuilding the entire snapshot. - Stable PDF metadata caches the content hash so unchanged large files are not re-hashed on every open. Optional recovery copies run in the background and are disabled by default to avoid cloud-vault sync pressure. - Each open PDF receives its own bundled, version-matched PDF.js worker. - Mobile keeps the same single-page render queue and virtualized annotation architecture, while using a fit-to-width starting scale, a smaller near-page render window, a 6-million-pixel canvas ceiling, earlier dense-mark canvas promotion, and bounded 8-million-character search cache. Backgrounding the app cancels unfinished page work, flushes recent annotation journal entries, and resumes visible-page detail after return. A private synthetic benchmark used during 1.0.5 development inserted and search-indexed 100,000 annotations across 1,000 pages in 209.77 ms, traversed every page bucket in 12.80 ms, and searched all annotations in 18.13 ms on the development machine. A separate extreme run indexed 250,000 annotations across 2,000 pages in 576.63 ms, applied 10,000 edits in 72.80 ms, fetched a 12-card inspector window from the middle of the collection in 6.99 ms, traversed all page buckets in 34.76 ms, filtered the full collection in 60.07 ms, and sorted it in 10.76 ms. Those figures describe the in-memory index. A 100,000-record storage run restored the compact snapshot in 550.01 ms while returning to the host between 1,000-record batches, then produced a 28.24 MB compact checkpoint in 252.40 ms. The production bundle was also exercised inside Obsidian 1.13.7 with a synthetic 3,000-page PDF. Rapid forward scrolling and a separate forward/reverse run each recorded zero sampled frame gaps above 34 ms; the worst sampled gap was 33 ms. The first page painted in under a second; jumps to pages 1,500 and 3,000 returned to 0% idle CPU; twelve rapid distant-page jumps completed in 3.77 seconds; and the process settled between roughly 62 MB and 84 MB resident memory instead of growing with every visited page. A rare full-document search reached and highlighted its only match on page 3,000 while yielding control throughout. Results vary by machine and document, but the numbers make the intended scale concrete. ## Local, recoverable storage The source PDF is never edited. Lumen hashes its bytes with SHA-256 and stores a recoverable bundle in your vault: ```text .lumen-pdf/ bundles/ sha256/ / manifest.json annotations.snapshot.json annotations.snapshot.previous.json annotations.journal.jsonl document.pdf # only when automatic recovery copies are enabled file-index/ .json ``` The append-only JSONL journal protects recent changes between checkpoints, while the previous compact snapshot provides a recovery fallback. The export command creates a readable Markdown representation whenever you want one. Existing Markdown snapshots remain supported as migration and corruption fallbacks. Because identity comes from PDF content, annotations remain associated when the original file is renamed or moved. Everything stays in the vault: no account, telemetry, remote processing, CDN scripts, or dynamic code loading. ## Install ### Obsidian Community Plugins Once Lumen is accepted into the community directory: 1. Open **Settings → Community plugins → Browse**. 2. Search for **Lumen PDF Annotator**. 3. Select **Install**, then **Enable**. ### Manual installation 1. Download `main.js`, `manifest.json`, and `styles.css` from the latest release. 2. Create `/.obsidian/plugins/lumen-pdf-annotator/`. 3. Place all three files in that folder. 4. Reload Obsidian, then enable **Lumen PDF Annotator** under Community plugins. Lumen supports Obsidian 1.13.7 or newer on desktop, iOS, and Android. ## Use Open any PDF after enabling Lumen. The PDF search, table of contents, and annotation inspector begin closed every time a document opens. When the PDF supplies a navigable outline, the toolbar shows a table-of-contents icon; its floating panel filters heading titles, preserves the PDF hierarchy, wraps long headings beside their page numbers, marks the current heading, and jumps to the heading's exact destination. Opening the panel brings the current heading into the middle of the list unless it is too close to either end. PDFs without an outline keep the button hidden. The current theme is shown as one compact icon whose menu contains Light, Sepia, and Dark. Lumen restores each PDF's saved page and zoom after its reader is ready, even when Obsidian opens another note in a new tab during startup. On desktop, pinch the trackpad over the PDF to zoom around the pointer, or hold **Ctrl** while scrolling the mouse wheel. Ordinary two-finger scrolling still moves the document. Pinch zoom supports 50–400% with finer 1% steps and saves the final scale; the zoom buttons and hotkeys move between the usual 25% stops. The gesture applies only to the PDF reading area. Mobile keeps its native touch gestures and fit-to-width layout. Select text to open the compact markup palette. Choose a colour first, then choose a mark type to apply it immediately—there is no separate confirmation step. Choosing the comment type also opens the individual editor so you can add its note. Nothing is saved merely by choosing a swatch. Click an existing mark to open its individual editor. On desktop, an inspector card does the same; on mobile, tapping a card navigates to the mark and its pencil icon opens the editor. Use the sticky-note icon or **Place a page note** command to place a note anywhere on a page. PDF search marks every exact match on the rendered page, upgrades visible results to PDF.js's exact text-range geometry at every zoom level, and strengthens the selected result. The toolbar's theme controls affect the reading surface and editor together; the chosen theme persists and Lumen button text and icons follow your Obsidian accent colour in every PDF theme. Right-click a saved mark on desktop, or long-press it on mobile, and choose **Copy link to highlight**. Paste the resulting Markdown link into any note in the same vault; following it opens the PDF, scrolls to the linked annotation, flashes it, and opens its editor. To add more text to a saved annotation, open its editor and use the scan-text icon. Navigate if needed, select the additional text, check the opaque quote preview with muted text beneath the floating **Extend annotation** action, and confirm. New selections and their extensions retain page-text positions, so text selected before or after a mark appears in document order and overlapping characters appear once. Cross-page segments share colour, style, note, tags, link target, inspector card, and deletion as one annotation. Long quotes scroll inside the individual editor; focus the quoted text to scroll it with the keyboard. Quoted text uses the PDF's selectable text layer, restores spaces at line boundaries, and expands correctly mapped Latin ligatures such as `fi` to `fi`. For missing or NUL Unicode mappings, Lumen can recover letters from validated standard glyph names in an embedded TrueType font. This repairs selection, copy, PDF search and new quotes using the bundled PDF worker on desktop and mobile, without OCR, downloads or changes to the PDF. Recovery is limited to fonts with usable semantic names; unresolved glyphs still appear as `�` in quotes. Font names alone and surrounding words are never used to infer letters. Existing saved quotes remain unchanged when you update the plugin. Older annotations without source positions use their saved page and mark geometry when you extend them, so they support prepending and appending; exact overlap handling is more reliable for newly created annotations. Pages containing recovered glyphs have separately versioned offsets. Extending an old mark on such a page uses its saved geometry rather than combining incompatible character positions. Unaffected pages keep exact range-based overlap handling. On phones, the toolbar uses two compact touch rows. PDF search, the table of contents, and the annotation inspector open as full-size floating panels that remain raised when the keyboard closes. Tapping a search result, outline heading, or annotation card navigates to the PDF and closes its panel. The selection palette and individual editor remain safe-area-aware floating sheets. On tablets the toolbar returns to one row and the side tools remain docked to the nearest edge. Mobile PDF position and zoom are stored separately from desktop state, so a phone's fit-to-width scale never changes the same document's desktop layout. Default hotkeys: | Action | macOS | Windows/Linux | | --- | --- | --- | | Toggle PDF search (while a Lumen PDF tab is active) | `Cmd+F` | `Ctrl+F` | | Toggle annotation inspector | `Cmd+Shift+A` | `Ctrl+Shift+A` | | Zoom in | `Cmd+Shift+=` | `Ctrl+Shift+=` | | Zoom out | `Cmd+-` | `Ctrl+-` | | Reset zoom | `Cmd+0` | `Ctrl+0` | Previous page, next page, page-note placement, annotation checkpoint, export, legacy import, backup verification, recovery, and **Open current PDF in Lumen annotator** are also available in Obsidian's command palette and can be assigned custom hotkeys. ### Backup, export, and migration commands - **Export annotations for this PDF** writes a readable Markdown file under `.lumen-pdf/exports/`. In **Settings → Lumen PDF Annotator → Export annotations**, choose individual annotated PDFs or select all filtered matches and export them together. Each PDF gets a separate, uniquely named Markdown note with the source PDF path, page headings, quoted highlight text, attached notes, tags, and links back to the exact annotation. Multi-page highlights stay grouped with their one associated note. Exports do not include raw recovery JSON and do not change the source PDFs. - **Save an annotation checkpoint** immediately compacts the journal into a snapshot. - **Verify all PDF backup checksums** checks every local backup against its SHA-256 identity. - **Restore a backed-up PDF** verifies the checksum before creating a non-destructive copy under `.lumen-pdf/recovered/`. - **Import legacy annotations for this PDF** explicitly imports compatible user-owned annotation data when present and uses stable IDs to prevent duplicates. Lumen does not scan every Markdown note when a PDF opens. ## Settings **Make Lumen the default PDF viewer** is enabled initially. Disable it if you want to keep Obsidian's built-in PDF view and open selected PDFs through Lumen's command instead. Restart Obsidian after changing this setting. **PDF theme** sets the persistent light, sepia, or dark reading theme. **Create automatic PDF recovery copies** is disabled by default; enable it only if you want a background bundle copy in addition to your source PDF. ## Contributing Issues, feature requests, pull requests, forks, and independent builds are welcome. Start with [CONTRIBUTING.md](CONTRIBUTING.md) for the development workflow and project boundaries. ```bash npm ci npm run typecheck npm run build ``` The production build is written to `dist/`. For a live development build, set `LUMEN_PDF_ANNOTATOR_PLUGIN_DIR` to a dedicated test-vault plugin directory and run `npm run dev`. ## Scope and roadmap Planned work includes additional portable export formats and performance profiling across a wider range of scanned and malformed PDFs and mobile devices. Please report a reproducible document issue through the bug template. Do not upload a private PDF; use a minimal public or synthetic reproduction whenever possible. ## Clean-room note Lumen is an independent rewrite from a standalone product specification. Zotero and Alex Annotator helped identify useful interaction concepts in the broader PDF-annotation space, but no source code, assets, styles, or Git history from either project are included. The compatibility importer only reads user-owned annotation data; the interface, current storage format, performance architecture, and implementation are Lumen's own. ## License [MIT](LICENSE) © 2026 Ben Gutteridge. Bundled dependency notices are listed in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).