Select text in a PDF, and find PDFs by their contents

Stage five. pdf.js draws pages as pictures, so selecting means laying
transparent spans over them exactly where each word was painted. The
layer carries the same scale as the canvas — they drift apart otherwise
and a selection lands on the wrong words. While drawing it stops taking
pointer events entirely, or a stroke would come out as a selection.

The text is also extracted and handed to the index that already holds the
notes, so one search box finds a sentence whether it was typed or
imported. Extraction runs after the document is on screen and yields
between pages: it must never stand between the reader and the first page.

The extracted text is deliberately not sent to the server. It is
derivable from a file the server already holds, so storing it again costs
space and a second thing to encrypt for nothing gained. The price, stated
plainly in the readme, is that a PDF becomes searchable on a device once
that device has opened it — the same shape as the limitation notes have.

Text runs are joined on pdf.js's hasEOL rather than concatenated. Without
it the last word of a line glues to the first of the next and matches
nothing when searched for.

The minimal text-layer CSS is written out rather than importing
pdfjs-dist/web/pdf_viewer.css, which carries the whole reference viewer's
toolbars and dialogs.

Suite now checks the layer sits over the page rather than stacked in a
corner, that the text is transparent, that dragging selects real words,
that a word from inside the file finds the note and one that is not in it
finds nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Opus 5 committed 2026-08-08 21:12:06 +02:00
1 parent 1be3a9b9ba
commit b12b0d6142
6 files changed
+303 -6

No files matched your search

+27 -2
View File
@@ -17,7 +17,7 @@ A self-hosted note-taking application with rich text editing, freehand drawing,
- **Undo/Redo** — Full history stack for drawing operations
- **Notebooks & Folders** — Create notebooks with custom gradient covers, organize notes by folder
- **Star/Favorite** — Quick access to important notes
- **PDF Reading** — Import a PDF and read it in the app; the file is cached so it stays readable offline
- **PDF Reading** — Import a PDF, read it, select its text, draw on it; the file is cached so it stays readable offline
- **Export** — Any note, notebook or the whole account as Markdown or PDF, entirely in the browser
- **Dark & Light Mode** — Full theme support with refined color schemes
@@ -330,7 +330,7 @@ docker compose -f docker-compose.dev.yml up -d --build # must be running
| `search-attachments` | 12 | body-only matches, ranking, umlauts; images as blob URLs, not inline bytes |
| `history` | 10 | a large deletion is snapshotted, restoring works, restoring is itself undoable |
| `drawing` | 7 | strokes survive the poller, and match across four viewport widths |
| `pdf` | 19 | import, page proportions, lazy loading, and reading with the network off |
| `pdf` | 26 | import, page proportions, lazy loading, and reading with the network off |
| `conflict` | 10 | a late write wins but the state it buries survives as a version |
| `export` | 21 | the downloaded bytes are real Markdown, ZIP and PNG, unpacked and read back |
@@ -442,6 +442,31 @@ storage is content-addressed, a cached copy can never go stale.
---
## Selecting and searching PDF text
pdf.js draws pages as pictures, so selecting means laying transparent spans over
them exactly where each word was painted. The layer carries the same scale as the
canvas — the two drift apart otherwise and a selection lands on the wrong words.
While drawing it stops taking pointer events entirely, or a stroke would come out
as a text selection.
The text is also pulled out and handed to the same index that holds the notes, so
one search box finds a sentence whether it was typed or imported. Extraction runs
*after* the document is on screen and yields between pages: it must never stand
between the reader and the first page.
**The extracted text is not sent to the server.** It is derivable from a file the
server already holds, so storing it again would cost space and a second thing to
encrypt for nothing gained. The price is that a PDF becomes searchable on a
device once that device has opened it — the same shape as the limitation notes
already have, and the honest trade for not duplicating data.
The minimal text-layer CSS is written out in `textLayer.css` rather than
importing `pdfjs-dist/web/pdf_viewer.css`, which carries the whole reference
viewer's toolbars and dialogs.
---
## Drawing on a PDF
Strokes are stored **per page, in that page's own coordinate space** — PDF