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>
155 lines
5.1 KiB
Markdown
155 lines
5.1 KiB
Markdown
# 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 (from `pdftotext -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-utils` on `$PATH` (`pdftocairo`, `pdftotext`, `pdfinfo`):
|
|
|
|
```bash
|
|
sudo apt install poppler-utils # Debian / Ubuntu
|
|
brew install poppler # macOS
|
|
```
|
|
|
|
No third-party Python dependencies — `pyproject.toml` keeps
|
|
`dependencies = []`.
|
|
|
|
## Usage
|
|
|
|
```bash
|
|
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`):
|
|
|
|
```bash
|
|
sudo apt install inotify-tools
|
|
```
|
|
|
|
Save next to `main.py` as `watch.sh`:
|
|
|
|
```bash
|
|
#!/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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
1. In the block editor, add a **Custom HTML** block.
|
|
2. Paste:
|
|
|
|
```html
|
|
<iframe
|
|
src="https://netgazet.yourdomain.tld/"
|
|
style="width:100%; height:85vh; border:0;"
|
|
loading="lazy"
|
|
title="NETgazet archive">
|
|
</iframe>
|
|
```
|
|
|
|
3. Publish.
|
|
|
|
Notes:
|
|
|
|
- Use HTTPS for `src` if 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: 85vh` works on desktop and most phones. For a fixed value use
|
|
`height: 800px;` (or similar).
|
|
- Cross-domain iframe embeds need no special config, **unless** the static
|
|
host sends `X-Frame-Options: DENY` or
|
|
`Content-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/`:
|
|
|
|
```html
|
|
<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 serve
|
|
`https://` 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 `postMessage` auto-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 scrollbars |
|
|
| Subfolder (same origin) | `netwerk-antwerpen.be/gazet/` | ✅ — same-origin, `postMessage` auto-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.)
|