Skip to content
aJV99Public

About

make PDFs look scanned, privately in your browser

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

FauxScan

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/

Why

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.

Your files stay on your device

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).

How it works

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.

Features

  • 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 named fauxscan-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.

Command line

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 option

Files that can't be read are skipped and listed at the end (the exit code is 1 if any were skipped).

Tests

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)

For AI assistants

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.

Deploying to GitHub Pages

  1. Push these files to the main branch of a repository named FauxScan.
  2. In the repository, open Settings → Pages, set Source to "Deploy from a branch", choose main and / (root), then save.
  3. 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.

Translations

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 strings

The generated pages are plain static files, so the site still needs no build step to deploy.

Quality

  • 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

Project layout

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)

Credits

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/.

About

make PDFs look scanned, privately in your browser

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages