Live Demo: https://pdf.av8r.tw | GitHub Pages
This project provides a web-based, client-side application to process PDF documents. Leveraging Pyodide (WebAssembly) and PyMuPDF, all processing occurs directly in your browser, ensuring your files never leave your device. This is also a Progressive Web App (PWA), offering offline capabilities and installability on your mobile devices.
- 100% Local & Secure: Your PDF files are processed entirely within your browser. No data is ever uploaded to a server, guaranteeing maximum privacy and security.
- Offline Capable: Once loaded, the application can work completely offline.
- Installable (PWA): Add it to your home screen on iOS or Android for an app-like experience.
- Python Power in the Browser: Utilizes your original Python
pdf-watermark-removelogic (PyMuPDF) directly in the browser via WebAssembly. - Real-time Progress: Visual feedback with a progress bar during PDF processing.
- Multi-file Processing: Process multiple PDFs sequentially, with each result appearing in a dedicated download list.
- Dark Mode: Supports light and dark themes based on system preference or manual toggle.
- Node.js (for
npmandVite) - Docker (for building the PyMuPDF WASM wheel on Linux, if you don't have one)
- Git (for cloning submodules)
Clone this repository and initialize the Python submodule:
# Clone this web project
git clone https://github.com/jonatw/pdf-processor.git
cd pdf-watermark-remove-js
# Initialize the Python core as a submodule
git submodule update --init --recursive(Note: public/python_core now contains the Python source from jonatw/pdf-watermark-remove.)
PyMuPDF does not provide official Pyodide wheels on PyPI. You need to build it yourself (recommended with Docker) or find a community-provided one.
Steps to Build the Wheel using Docker (Recommended):
-
Ensure Docker is Running on a Linux machine (or a Linux VM/WSL on Windows/macOS).
-
Navigate to the root of this web project (
pdf-watermark-remove-js/). -
Execute the following Docker command. This will clone PyMuPDF, set up the build environment, and build the WASM wheel. This process can take 5-20 minutes depending on your machine and internet speed. It may seem stuck, but be patient.
docker run -it --rm -v "$(pwd)/public/python_core:/src/PyMuPDF" -w /src/PyMuPDF ghcr.io/pyodide/pyodide-env:20240928-chrome127-firefox128 /bin/bash -c "pip install pyodide-build && python3 scripts/test.py pyodide"
(Note: If the above tag is outdated, check Pyodide-env Container Versions for a newer
py312compatible image. If none is explicitlypy312, you might need to try apy313image and uncomment thesedcommands inmain.js's Python script to bypass the Python version assertion in PyMuPDF's build script.) -
Locate the Built Wheel: After the Docker command successfully completes, the
.whlfile (e.g.,pymupdf-1.26.7-cp312-abi3-pyodide_2024_0_wasm32.whl) will be found in your localpublic/python_core/wheelhouse/directory. -
Move the Wheel: Move this
.whlfile frompublic/python_core/wheelhouse/topublic/wheels/.mv public/python_core/wheelhouse/pymupdf-1.26.7-cp312-abi3-pyodide_2024_0_wasm32.whl public/wheels/
(Ensure the filename in
public/worker.js(PYMUPDF_WHEEL_PATH) matches this filename.)
npm installnpm run devThe application will be accessible at http://localhost:5173 (or another port specified by Vite).
- Open the web application in your browser.
- If you want to use it offline, "Add to Home Screen" on your mobile device (iOS/Android).
- Select a PDF file using the "Select Document" input.
- Click "Process PDF".
- Observe the progress bar.
- Once completed, a download link for the processed PDF will appear in the "Processed Files" list.
- On iOS (Safari): Tap the "Share" button (), then select "Add to Home Screen".
- On Android (Chrome): Tap the menu icon (), then select "Add to Home screen".
cd public/python_core
git pull origin main # or your main branch name
cd ../../ # Back to web project root.
├── public/
│ ├── python_core/ # Git Submodule: original Python project (pdf-watermark-remove)
│ ├── wheels/ # PyMuPDF WASM wheel (.whl file)
│ ├── worker.js # Web Worker: Pyodide bridge, PDF processing off main thread
│ ├── sw.js # Service Worker for PWA caching and offline support
│ └── manifest.json # PWA manifest file
├── main.js # UI logic: file handling, progress, theme, Worker communication
├── style.css # Custom CSS styles
├── index.html # Entry point (preload hints for WASM assets, no blocking scripts)
├── vite.config.js # Vite configuration
├── package.json # Project dependencies and scripts
├── CLAUDE.md # Claude Code development guide
└── README.md # This file
The Python backend (pdf-watermark-remove) is integrated as a Git Submodule at public/python_core.
- To update the submodule:
This will pull the latest changes from the remote Python repository.
git submodule update --remote --merge
The harness at tests/wasm/run.mjs verifies the Python core and the end-to-end
removal pipeline actually work inside Pyodide — the same runtime users run in
their browser — not just under CPython.
Prerequisites: npm install (pulls pyodide devDependency at the pinned version)
and a built wheel in public/wheels/ (committed by the build-wheel workflow).
npm run test:wasmWhat it tests:
| Phase | What | How |
|---|---|---|
| 1 | python_core unittest suite |
python -m unittest tests inside Pyodide via pyodide.runPython() |
| 2 | End-to-end removal | Synthetic watermarked PDF built with fitz → WatermarkRemover.remove_watermark() → assert watermark absent from output |
Known WASM limitation (Phase 1):
asyncio.run() requires WebAssembly stack switching (Asyncify/JSPI), which is
not available in the Node.js 22 runtime bundled with Pyodide 0.29.x. Tests that
call asyncio.run() directly are patched to unittest.SkipTest in the harness
so they appear as skipped (not errored). Phase 2 avoids this by using
pyodide.runPythonAsync() — the correct approach for async Python in WASM.
Pyodide version is read from pyodide-versions.json (auto-generated by the
build-wheel workflow; never hardcoded in the harness). The npm pyodide
devDependency must match this version.
The public/wheels/ directory contains the pre-built PyMuPDF WASM wheel. It is automatically rebuilt monthly by the build-wheel.yml GitHub Actions workflow.
Automated rebuild via GitHub Actions:
- Actions tab → "Build PyMuPDF WASM Wheel" → Run workflow
- Inputs:
pyodide_version(e.g.0.29.3),pymupdf_version(e.g.1.27.1) - Workflow: builds wheel → smoke tests → auto-commits to repo → triggers deploy
Version tracking: successful build versions are recorded in pyodide-versions.json and auto-updated as workflow defaults for the monthly cron.
Local build (fallback):
pip install cibuildwheel
git clone --depth 1 https://github.com/pymupdf/PyMuPDF.git /tmp/PyMuPDF
cd /tmp/PyMuPDF
HAVE_LIBCRYPTO=no HAVE_TESSERACT=0 CIBW_BUILD="cp313-*" \
CIBW_PYODIDE_VERSION=0.29.3 \
cibuildwheel --platform pyodide --output-dir /tmp/wheelhouse
cp /tmp/wheelhouse/*.whl public/wheels/Ensure PYMUPDF_WHEEL_PATH in public/worker.js matches the wheel filename.