[HN Gopher] Show HN: Veil - Dark mode PDFs without destroying im...
___________________________________________________________________
Show HN: Veil - Dark mode PDFs without destroying images, runs in
the browser
Hi HN! here's a tool I just deployed that renders PDFs in dark mode
without destroying the images. Internal and external links stay
intact, and I decided to implement export since I'm not a fan of
platform lock-in: you can view your dark PDF in your preferred
reader, on any device. It's a side project born from a personal
need first and foremost. When I was reading in the factory the
books that eventually helped me get out of it, I had the problem
that many study materials and books contained images and charts
that forced me, with the dark readers available at the time, to
always keep the original file in multitasking since the images
became, to put it mildly, strange. I hope it can help some of you
who have this same need. I think it could be very useful for
researchers, but only future adoption will tell. With that
premise, I'd like to share the choices that made all of this
possible. To do so, I'll walk through the three layers that veil
creates from the original PDF: - Layer 1: CSS filter. I use
invert(0.86) hue rotate(180deg) on the main canvas. I use 0.86
instead of 1.0 because I found that full inversion produces a pure
black and pure white that are too aggressive for prolonged reading.
0.86 yields a soft dark grey (around #242424, though it depends on
the document's white) and a muted white (around #DBDBDB) for the
text, which I found to be the most comfortable value for hours of
reading. - Layer 2: image protection. A second canvas is
positioned on top of the first, this time with no filters. Through
PDF.js's public API getOperatorList(), I walk the PDF's operator
list and reconstruct the CTM stack, that is the save, restore and
transform operations the PDF uses to position every object on the
page. When I encounter a paintImageXObject (opcode 85 in PDF.js
v5), the current transformation matrix gives me the exact bounds of
the image. At that point I copy those pixels from a clean render
onto the overlay. I didn't fork PDF.js because It would have become
a maintenance nightmare given the length of the codebase and the
frequent updates. Images also receive OCR treatment: text contained
in charts and images becomes selectable, just like any other text
on the page. At this point we have the text inverted and the images
intact. But what if the page is already dark? Maybe the chapter
title pages are black with white text? The next layer takes care of
that. - Layer 3: already-dark page detection. After rendering, the
background brightness is measured by sampling the edges and corners
of the page (where you're most likely to find pure background,
without text or images in the way). The BT.601 formula is used to
calculate perceived brightness by weighting the three color
channels as the human eye sees them: green at 58.7%, red at 29.9%,
blue at 11.4%. These weights reflect biology: the eye evolved in
natural environments where distinguishing shades of green
(vegetation, predators in the grass) was a matter of survival,
while blue (sky, water) was less critical. If the average luminance
falls below 40%, the page is flagged as already dark and the
inversion is skipped, returning the original page. Presentation
slides with dark backgrounds stay exactly as they are, instead of
being inverted into something blinding. Scanned documents are
detected automatically and receive OCR via Tesseract.js, making
text selectable and copyable even on PDFs that are essentially
images. Everything runs locally, no framework was used, just
vanilla JS, which is why it's an installable PWA that works offline
too. Here's the link to the app along with the repository:
https://veil.simoneamico.com | https://github.com/simoneamico-ux-
dev/veil I hope veil can make your reading more pleasant. I'm open
to any feedback. Thanks everyone
Author : simoneamico
Score : 4 points
Date : 2026-03-26 11:47 UTC (11 hours ago)
(HTM) web link (veil.simoneamico.com)
(TXT) w3m dump (veil.simoneamico.com)
___________________________________________________________________
(page generated 2026-03-26 23:00 UTC)