Export a PDF with the drawings written into it

Stage seven, and the last of the PDF work. A PDF note now offers the
document with your marks stamped in, the untouched original, or just the
notes written beside it.

pdf.js only reads, so writing needs pdf-lib — behind a dynamic import
like pdf.js, so the bundle everyone downloads carries no PDF writer for
the sake of people who never export one. Main bundle grew 6 kB; the
180 kB writer arrives only when used.

The coordinate decision from stage four is what makes this a translation
rather than a reconstruction: strokes were stored in page points from the
start, the only space a PDF understands, so nothing has to be guessed
back from screen pixels. Only the origin moves, PDF putting it at the
bottom-left.

The stored file is never modified — pdf-lib builds a new document — so a
clean copy stays available however often it has been drawn on. The suite
re-fetches the stored bytes after exporting and compares them, and checks
the plain export is byte-identical to what was imported.

Eraser strokes are dropped. On screen they remove ink by drawing in
destination-out; a PDF content stream has no equivalent, and stamping
them would put grey scribbles exactly where something had been rubbed
out.

The suite renders both files with pdf.js and counts dark pixels, 1633
against 2557. A larger file only proves bytes were added; this proves ink
can be seen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Opus 5 committed 2026-08-08 21:49:01 +02:00
1 parent 7f9f39790f
commit a32d6eba99
10 files changed
+407 -48

No files matched your search

+32 -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, read it, select its text, draw on it and write notes beside it; the file is cached so it stays readable offline
- **PDF Reading & Annotation** — Import a PDF, read it, select its text, draw on it, write notes beside it, and export it with or without your marks
- **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` | 32 | import, page proportions, lazy loading, and reading with the network off |
| `pdf` | 41 | 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,36 @@ storage is content-addressed, a cached copy can never go stale.
---
## Exporting an annotated PDF
A PDF note offers three things rather than the note formats: the document with
your drawings written into it, the untouched original, or just the notes you
wrote beside it as Markdown.
pdf.js can only read, so producing a file needs a second library — `pdf-lib`,
loaded the same way, behind a dynamic import, so the bundle everyone downloads
carries no PDF writer for the sake of the people who never export one.
This is where the coordinate decision pays off. Strokes were stored in page
points from the start, which is the only space a PDF understands: nothing has to
be guessed back from screen pixels, and the stamping is a direct translation
apart from flipping the origin, which PDF puts at the bottom-left.
**The stored file is never modified.** `pdf-lib` builds a new document, so a
clean copy of the original stays available however often it has been drawn on —
checked by the suite, which re-fetches the stored bytes after exporting and
compares them.
Erased areas cannot come along. On screen an eraser removes ink by drawing in
`destination-out`; a PDF content stream has no equivalent, so those strokes are
dropped rather than stamped as visible grey scribbles where something was rubbed
out.
The suite renders both files and counts dark pixels — 1633 against 2557 — because
a larger file proves bytes were added, not that any ink can be seen.
---
## Notes beside a PDF
A PDF note is a note that happens to have a document attached, not a separate