Make PDFs and images look like they came off a flatbed scanner, privately, in your browser. One file or a thousand.
Live: https://aJV99.github.io/FauxScan/
Some portals and offices only accept "scanned" documents, even when you filled the form in digitally. FauxScan gives a digital PDF the look of a real scan: the page sits slightly crooked on the scanner glass, with a grey lid edge showing, and is saved as an image-only PDF.
FauxScan has no server. PDFs are opened with pdf.js, processed on a <canvas>, and written as PDFs (and, for batches, a ZIP) by FauxScan's own small writers, all inside your browser tab. Nothing is uploaded, kept or tracked. While a save is being written it goes to your browser's private storage for this site, on your device, so that very large files fit; it is deleted a few minutes after the download starts. After the first visit it also works offline (the service worker caches only the app's own files, never your documents).
The screen is the scanner glass. Your page sits on a dark glass bed with a millimetre ruler along the top and left edges. A glowing scan head runs across the page: above it you see the scanned result, below it the original. When the page loads, the head sweeps down and the scan appears behind it. Drag the head (or press anywhere on the page, or use the arrow keys) to move it and compare.
- Bulk processing: add many PDFs and images at once, a whole folder, or drag and drop them. The file list handles thousands of files (only the visible rows are drawn, thumbnails are made as you scroll, and there is a filter box once you have more than 20 files). Click a file to preview it; arrows step through its pages.
- Files are processed one at a time. One file saves as
name_scanned.pdf; several save as a ZIP namedfauxscan-N-files.zip, built up file by file. There is no size limit of its own: a PDF or ZIP is assembled from pieces rather than one block of memory, and ZIPs switch to ZIP64 past 4 GB. Saving shows progress and can be cancelled. - Presets: Clean scan (default) and Photocopy (grainy, black and white). Changed settings are highlighted, and "Reset to …" goes back to the preset.
- Every setting has a slider and a number field, so you can drag it or type an exact value
- Placement on the glass (skew, misalignment) with a diagram of where the page lands, plus advanced settings: page shrink, lid shadow, resolution, softness, grain, black and white point, paper grey, vignette and JPEG quality
- Output: colour or black and white, original page size or A4, lossless (PNG) or smaller file (JPEG), with a size estimate
- "Shuffle placement" drops the page on the glass again at a new random tilt and offset; the preview always matches what gets saved
- Works on phones: the glass fills the screen, with Files and Adjust as their own screens
- In English, Español, Français, हिन्दी and 中文 (
/,/lang/es/,/lang/fr/,/lang/hi/,/lang/zh/); first-time visitors are sent to their browser's language, and the choice in the footer is remembered - Light and dark mode: follows your system, and the button in the header switches it (remembered on this device)
- Installable as an app on Linux, Windows, macOS, Android and iOS
Each image becomes its own one-page PDF. Password-protected or unreadable files are marked in the list and skipped when saving a batch.
cli/fauxscan.py runs the same effect without a browser, for batch jobs or scripts:
pip install pymupdf pillow numpy
python3 cli/fauxscan.py input.pdf # -> input_scanned.pdf
python3 cli/fauxscan.py *.pdf -o scanned/ # several files into a folder
python3 cli/fauxscan.py scans/ --zip scanned.zip # a whole folder (recursive) into one ZIP
python3 cli/fauxscan.py in.pdf --preset photocopy # grainy black and white
python3 cli/fauxscan.py -h # every optionFiles that can't be read are skipped and listed at the end (the exit code is 1 if any were skipped).
Development-only checks live in tests/ (Node 20+ and Chrome). The site never loads anything from there.
cd tests && npm install
npm test # end-to-end: saving one file and a batch, errors, cancel, 1000 files, every language
npm run a11y # axe-core WCAG 2.2 AA in every language, light and dark, at 1350, 390 and 320 px
npm run lighthouse # Lighthouse must score 100 in every category on mobile and desktop
npm run big # a 110-page PDF saved through the app, an 850 MB PDF and a 6 GB ZIP, read back (a few minutes)llms.txt describes the tool, its settings and the algorithm in plain text, so assistants like Claude or ChatGPT can recommend it, explain how to use it, or run the CLI for someone.
- Push these files to the
mainbranch of a repository namedFauxScan. - In the repository, open Settings → Pages, set Source to "Deploy from a branch", choose
mainand/ (root), then save. - The site appears at
https://<your-username>.github.io/FauxScan/within a minute or two.
There is no build step, bundler or npm dependency: the files are served as they are, every URL is relative, and .nojekyll stops GitHub from running Jekyll. To try it locally, serve the folder with any static server (pdf.js needs http://, not file://). When you change app.js or other cached files, bump VERSION in sw.js (and keep its file list in step) so returning visitors get the update.
index.html is the English page and the template for the others. Translatable text in it is marked with data-t (text), data-th (inline HTML) or data-ta (attributes); the strings app.js shows at runtime are in its EN table. Translations live in i18n/<lang>.json. After changing any user-facing text, regenerate the translated pages and commit them:
python3 i18n/build.py # writes lang/es/, lang/fr/, lang/hi/, lang/zh/ (standard library only)
python3 i18n/build.py --check # fails if a committed page is out of date; warns about missing stringsThe generated pages are plain static files, so the site still needs no build step to deploy.
- Lighthouse: 100 for Performance, Accessibility, Best Practices and SEO on mobile and desktop
- Built to WCAG 2.2 AA (0 axe-core violations in light and dark mode from 320 px to desktop): full keyboard support including the scan head, visible focus, labelled controls with spoken values, 24 px minimum targets (44 px on phones), AA contrast in light and dark mode, progress and errors announced, reduced-motion and forced-colours support, and the same reading order on every screen size
- The first paint is a pre-rendered example with no JavaScript work; pdf.js loads only when a file is added
index.html page markup (English; the template for the translated pages)
styles.css all styles, shared by every page
app.js all app logic
sw.js offline cache
manifest.webmanifest install metadata
llms.txt description for AI assistants
vendor/ pdf.js 3.11.174
fonts/ Bricolage Grotesque, Geist and Geist Mono (latin subsets)
i18n/ translations and the script that writes the translated pages
lang/ translated pages, one folder per language (generated; edit index.html and i18n/ instead)
icons/, assets/ icons, the pre-rendered example and preset thumbnails
cli/fauxscan.py command-line version
tests/ development-only checks (not part of the site)
Made by Abbas Alibhai.
Third-party: pdf.js (Apache-2.0), Bricolage Grotesque and Geist (SIL Open Font License 1.1). Their licences are in vendor/ and fonts/.