- JavaScript 67.4%
- Shell 32.6%
| .gitignore | ||
| bun.lock | ||
| makepdf.mjs | ||
| makepdf.sh | ||
| package.json | ||
| README.md | ||
pdfgen
Render a web page to a clean, print-ready PDF with headless Chromium.
Built to capture flight-status pages (e.g. Málaga airport departures) with the
browser running in mainland Spain time (Europe/Madrid) regardless of the
machine's system timezone — the default here is Atlantic/Canary.
Files
| File | What it is |
|---|---|
makepdf.mjs |
The tool: bun + puppeteer-core driving the system Chromium |
makepdf.sh |
Bash wrapper meant to live in ~/bin (on PATH) |
package.json / bun.lock |
Dependencies (puppeteer-core — no browser download) |
Installation
Requirements:
- Bun on
PATH(~/.bun/bin/bun) - Chromium at
/usr/bin/chromium(override with thePDFGEN_CHROMIUMenvironment variable if it lives elsewhere)
Install:
git clone https://forgejo.ijuanes.ovh/ijuanes/pdfgen.git
cd pdfgen
bun install # installs puppeteer-core into node_modules/
# install into ~/bin: wrapper on PATH, real tool in a pdfgen/ subdir
mkdir -p ~/bin/pdfgen
cp makepdf.mjs README.md package.json bun.lock ~/bin/pdfgen/
cp makepdf.sh ~/bin/
chmod +x ~/bin/makepdf.sh ~/bin/pdfgen/makepdf.mjs
makepdf.sh https://example.com # try it
You can also run it straight from the checkout — the wrapper finds
makepdf.mjs next to itself when there is no pdfgen/ subdir:
./makepdf.sh https://example.com
node_modules/ is gitignored; bun install recreates it from bun.lock.
Usage
~/bin/pdfgen/makepdf.mjs <url> [output.pdf] [options]
The script is executable and ~/bin is on PATH, so this works too:
pdfgen/makepdf.mjs https://www.aeropuertodemalaga-costadelsol.com/salida-vuelo-FR5124
Or use the wrapper, which is directly on PATH:
makepdf.sh https://www.aeropuertodemalaga-costadelsol.com/salida-vuelo-FR5124
If no output path is given, the file is named after the last URL path segment
(salida-vuelo-FR5124.pdf in the example above) and written to the current
directory.
Options
| Option | Default | Description |
|---|---|---|
--tz=<zone> |
Europe/Madrid |
IANA timezone for the browser process |
--lang=<locale> |
es-ES |
Browser locale (--lang Chromium flag) |
--wait=<ms> |
3000 |
Extra settle time after networkidle |
--timeout=<ms> |
90000 |
Navigation and PDF generation timeout |
--help |
Print usage |
Examples
# Flight page, mainland-Spain timezone, default output name
makepdf.sh https://www.aeropuertodemalaga-costadelsol.com/salida-vuelo-FR5124
# Explicit output file
makepdf.sh https://example.com/report ~/Downloads/report.pdf
# Different timezone and locale
makepdf.sh https://example.com --tz=Atlantic/Canary --lang=en-GB
Why not chromium --print-to-pdf?
The plain CLI route produces an unusable result for this purpose:
- Paper size is fixed to US Letter (no flag for A4).
- It stamps Chromium's own header/footer (date, time, URL, page numbers)
on every page —
--print-to-pdf-no-headeris ignored by new headless mode. - Backgrounds are not printed (
printBackgrounddefaults to off), so dark footers render as white text on white.
So this tool drives Chromium through the DevTools protocol with
puppeteer-core and calls Page.printToPDF explicitly:
A4, printBackground: true, no header/footer, 8 mm margins, selectable text.
How the timezone forcing works
TZ=<zone> is set on the browser process only (the same trick
Puppeteer/Playwright use on Linux). Chromium's Intl/Date then resolve to that
zone, and any page that renders times client-side picks it up. The system
timezone is never modified. The script verifies the effective zone in-page and
warns if it doesn't match what you asked for.
Note: some sites always display their own local time regardless of the
visitor's timezone (the Málaga airport site does — its disclaimer says times
are "hora local en el Aeropuerto de Málaga-Costa del Sol"). For those, the PDF
shows the site's times either way; forcing --tz just guarantees the browser
context matches what a mainland-Spain visitor would see.
Tip: screenshots instead of PDF
For a quick full-page screenshot, the plain CLI is enough (window height may need adjusting to the page height):
TZ=Europe/Madrid chromium --headless --disable-gpu --hide-scrollbars \
--window-size=1280,2428 --virtual-time-budget=15000 \
--screenshot=page.png 'https://example.com'
Full-page height can be measured by shooting taller than needed and trimming the blank tail (e.g. with PIL).