main.py converts every PDF in examples/ into a browseable, mobile-responsive HTML archive under output/ using poppler-utils. Includes the two NETgazet sample PDFs, project metadata, OpenWolf scaffolding, and README covering usage, a watch-folder script, and WordPress iframe embedding. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
5.1 KiB
netgazet
Tiny static-site generator for print-format PDFs (newsletters, magazines).
Drop PDFs into examples/, run main.py, and get a browseable, mobile-friendly
HTML/CSS archive in output/.
What it does
For every *.pdf in examples/ it renders each page as a JPEG with
poppler-utils (pdftocairo) and writes:
output/<pdf-stem>/page-NN.html— one wrapper per page with prev/next navigation and a collapsed text-extract panel (frompdftotext -layout).output/<pdf-stem>/index.html— thumbnail grid for the issue.output/index.html— root listing of all issues.output/styles.css— single responsive stylesheet, no JavaScript.
Layout fidelity (faithful to the print PDF) over text reflow; mobile
responsiveness is via img { max-width: 100% } and CSS grid auto-fill.
Requirements
-
Python ≥ 3.14 (see
.python-version) -
poppler-utilson$PATH(pdftocairo,pdftotext,pdfinfo):sudo apt install poppler-utils # Debian / Ubuntu brew install poppler # macOS
No third-party Python dependencies — pyproject.toml keeps
dependencies = [].
Usage
python main.py
# or:
uv run python main.py
Then open output/index.html in a browser. Each run wipes and rebuilds
output/<stem>/ for every PDF in examples/, so it's idempotent.
Auto-convert: rebuild when a PDF is dropped in examples/
Watch the folder with inotifywait (Linux; install inotify-tools):
sudo apt install inotify-tools
Save next to main.py as watch.sh:
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
WATCH_DIR="${1:-examples}"
echo "[watcher] watching $WATCH_DIR for new PDFs (Ctrl-C to stop)"
inotifywait -m -q -e close_write,moved_to --format '%f' "$WATCH_DIR" \
| while read -r filename; do
case "$filename" in
*.pdf|*.PDF)
echo "[watcher] $(date '+%H:%M:%S') new PDF: $filename — rebuilding"
python main.py
# Optional: push to your server right after rebuild
# rsync -avz --delete output/ user@server:/var/www/netgazet/
;;
esac
done
Make executable and run:
chmod +x watch.sh
./watch.sh
The script rebuilds the whole site on every new PDF (~30 s for two
16-page issues). Fine for a handful of newsletters; for dozens, extend
main.py to accept a single filename and only rebuild that issue plus
the root index.
On macOS, swap inotifywait for fswatch -o "$WATCH_DIR" | while read; do ...; done.
Embedding in a WordPress page (iframe)
Once output/ is hosted somewhere (a vhost on your server, Cloudflare Pages,
Netlify, etc.), embed it in any WordPress page or post:
-
In the block editor, add a Custom HTML block.
-
Paste:
<iframe src="https://netgazet.yourdomain.tld/" style="width:100%; height:85vh; border:0;" loading="lazy" title="NETgazet archive"> </iframe> -
Publish.
Notes:
- Use HTTPS for
srcif your WordPress site is on HTTPS — browsers block mixed content. - All navigation (issue → page → next page) stays inside the iframe, so the
WordPress page doesn't reload. Don't add
target="_top"to links in the generated HTML — that would break out of the frame. height: 85vhworks on desktop and most phones. For a fixed value useheight: 800px;(or similar).- Cross-domain iframe embeds need no special config, unless the static
host sends
X-Frame-Options: DENYorContent-Security-Policy: frame-ancestors 'none'. Nginx/Caddy don't add these by default — only an issue if you explicitly enabled them.
Subdomain vs subfolder (and auto-height)
The iframe src can point at any reachable URL, including a subdomain such
as https://gazet.netwerk-antwerpen.be/:
<iframe
src="https://gazet.netwerk-antwerpen.be/"
style="width:100%; height:90vh; border:0;"
loading="lazy"
title="NETgazet archive">
</iframe>
That works out of the box (it's your own static files, so nothing blocks framing). Two things to know:
- HTTPS must match. If WordPress is on
https://, the subdomain must servehttps://too, or the browser blocks it as mixed content. Static hosts and most shared hosting provide free Let's Encrypt certs — just enable it. - A subdomain is a different origin from the WordPress domain. That has one
practical consequence: cross-origin iframes can't auto-resize to their
content height. The browser won't let the parent page read the iframe's
height, so the
postMessageauto-height trick doesn't work. You get one of:Hosting URL example Auto-height Subdomain (different origin) gazet.netwerk-antwerpen.be❌ — use a fixed height ( 90vh/1200px), accept inner scrollbarsSubfolder (same origin) netwerk-antwerpen.be/gazet/✅ — same-origin, postMessageauto-height works
Pick a subfolder only if seamless auto-resizing matters more than the
cleaner subdomain URL. Otherwise a subdomain with a fixed height is the
simplest setup. (The Advanced iFrame Pro plugin can do cross-domain
auto-height too, but it needs a helper file dropped on the subdomain.)