I built a privacy-first PDF dark mode converter that runs entirely in your browser
Source: Dev.to
TL;DR
I shipped pdfdark.org – a browser‑side PDF dark‑mode converter. Files never leave your browser; the entire conversion happens client‑side using PDF.js, a Web Worker, and pdf‑lib. It’s open source (MIT), free, and requires no signup.
Why I Built It
Reading long PDFs at night was straining my eyes. The existing solutions had major drawbacks:
- Web‑based dark‑mode tools – required uploading the file, which felt unsafe for research papers, contracts, or medical records.
- OS‑level “invert colors” – ruined photos and charts, turning faces into X‑rays and graphs into noise.
Core Principles
- Zero data leaving the browser – PDF.js parses the file, a Web Worker applies the dark‑mode pass, and pdf‑lib stitches the result. You can verify the process in DevTools → Network.
- Preserve image colors – a saturation classifier detects photos and figures and leaves them untouched while darkening text and UI elements.
How It Works
Drop PDF
↓
PDF.js renders pages to canvas
↓
Web Worker classifies each pixel by saturation
├─ Saturated pixels (images, charts) → preserved
└─ Low‑saturation pixels (text, UI) → themed
↓
pdf‑lib assembles a new PDF
↓
User downloads the result
- The classifier runs on an OffscreenCanvas inside a Web Worker, keeping the UI thread responsive even for large PDFs.
- The output is a real PDF (image‑based, one JPEG per page), so the dark mode persists when you email, sync to an iPad, or open it on a Kindle. It’s not just a viewer toggle.
Costs
| Item | Cost |
|---|---|
| Domain (Cloudflare .org) | $7.50 |
| Hosting (Vercel free tier) | $0 |
| Email forwarding (Cloudflare) | $0 |
| Error monitoring (Sentry free tier) | $0 |
| Total | $7.50 |
Development took a few weekends.
Open Questions
- Does the “no‑upload” angle resonate beyond privacy enthusiasts?
- How to handle edge cases that break the algorithm (e.g., weird embedded fonts, scanned PDFs).
- Whether to add a vector‑preserving mode for text‑only PDFs (the current output is image‑based, so text isn’t selectable).
Links
- Live site:
- GitHub repository:
- Privacy policy: