Initial commit: PDF→static HTML/CSS site generator
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>
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
# 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.)
|
||||
Reference in New Issue
Block a user