Use chromium to generate a PDF from a page, but as if coming from a given timezone and not ours.
  • JavaScript 67.4%
  • Shell 32.6%
Find a file
2026-10-06 20:46:26 +01:00
.gitignore first commit 2026-10-06 20:46:26 +01:00
bun.lock first commit 2026-10-06 20:46:26 +01:00
makepdf.mjs first commit 2026-10-06 20:46:26 +01:00
makepdf.sh first commit 2026-10-06 20:46:26 +01:00
package.json first commit 2026-10-06 20:46:26 +01:00
README.md first commit 2026-10-06 20:46:26 +01:00

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 the PDFGEN_CHROMIUM environment 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-header is ignored by new headless mode.
  • Backgrounds are not printed (printBackground defaults 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).