Vai al contenuto
Tutti gli articoli
iten

4 min di lettura

PDF impaginati da HTML con Chromium: interruzioni di pagina, intestazioni e indice con i numeri

Generare report e dossier in PDF partendo da HTML e CSS, con Playwright e Chromium: capitoli su pagina nuova, tabelle che si spezzano bene, numeri di pagina, font incorporati e un indice con i numeri di pagina veri.

  • Automazioni
  • PDF
  • Documenti
PDF impaginati da HTML con Chromium: interruzioni di pagina, intestazioni e indice con i numeri

Report mensili, preventivi, fogli presenze, dossier tecnici: prima o poi ogni gestionale deve produrre un PDF che sembri fatto a mano. Le librerie che disegnano il PDF riga per riga (pdfkit, ReportLab, jsPDF) danno controllo totale, ma ogni tabella diventa un esercizio di coordinate. Da tempo uso un'altra strada: scrivo il documento in HTML e CSS e lo stampo in PDF con Chromium, pilotato da Playwright. Ecco cosa ho imparato impaginando un dossier di 26 pagine con copertina, indice, tabelle e schermate.

Il motore: Playwright e page.pdf

import { chromium } from "playwright";

const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto("file://" + percorsoHtml, { waitUntil: "networkidle" });
await page.evaluate(() => document.fonts.ready); // aspetta i font prima di stampare

await page.pdf({
  path: "dossier.pdf",
  format: "A4",
  printBackground: true, // senza, sfondi e colori delle tabelle spariscono
  displayHeaderFooter: true,
  headerTemplate: "<div></div>",
  footerTemplate:
    '<div style="width:100%;font-size:8px;padding:0 18mm;display:flex;justify-content:space-between">' +
    '<span>Dossier del software</span><span>Pagina <span class="pageNumber"></span> di <span class="totalPages"></span></span></div>',
  margin: { top: "18mm", bottom: "20mm", left: "18mm", right: "18mm" },
});
await browser.close();

Le classi pageNumber e totalPages nel piè di pagina vengono riempite da Chromium. Il template è isolato dalla pagina: non vede il tuo CSS e i font vanno indicati dentro lo stile in linea.

Le regole CSS che fanno la differenza

@page { size: A4; margin: 18mm; }

h2 { break-before: page; }          /* ogni capitolo in cima a una pagina nuova */
h2, h3 { break-after: avoid; }      /* mai un titolo da solo in fondo alla pagina */
p { orphans: 3; widows: 3; }         /* niente righe orfane */

table { break-inside: auto; }       /* le tabelle lunghe possono spezzarsi... */
tr { break-inside: avoid; }          /* ...ma non a metà riga */
thead { display: table-header-group; } /* intestazione ripetuta su ogni pagina */

figure { break-inside: avoid; }
figure img { max-height: 110mm; }    /* nessuna immagine più alta di mezza pagina */

L'ultima regola è la più sottovalutata. Un'immagine che non sta nello spazio rimasto salta alla pagina dopo e lascia un buco bianco. Uno screenshot verticale del telefono a tutta larghezza occupa una pagina intera: meglio affiancare tre schermate in una tavola unica, larga e bassa, che si incastra nel testo.

Unità: millimetri, non viewport

Per la stampa usa mm e pt. Il vh in stampa non corrisponde alla pagina e 100vh per una copertina produce risultati diversi tra versioni del browser. Una copertina affidabile è un blocco alto quanto l'area utile (A4 è 297 mm, meno i margini) seguito da break-after: page.

Font: incorporali o non saranno quelli

Il PDF incorpora solo i font che Chromium ha davvero caricato. Se il font arriva da Google Fonts o da un file @font-face, la stampa deve partire dopo document.fonts.ready, altrimenti alcune pagine escono con il font di riserva. Controlla il risultato con pdffonts dossier.pdf: ogni font deve risultare incorporato (emb yes).

L'indice con i numeri di pagina veri

Qui Chromium ha un limite: non supporta target-counter(), la funzione CSS che nei motori di stampa professionali scrive «questo titolo è a pagina 14». La soluzione pratica è il doppio passaggio:

  1. Genero il PDF con l'indice senza numeri.
  2. Estraggo il testo pagina per pagina e cerco su quale pagina compare ogni titolo di capitolo.
  3. Rigenero il PDF con i numeri trovati.
import subprocess, re

def numeri_di_pagina(pdf, titoli):
    info = subprocess.run(["pdfinfo", pdf], capture_output=True, text=True).stdout
    totale = int(re.search(r"Pages:\s+(\d+)", info).group(1))
    trovati = {}
    for p in range(1, totale + 1):
        testo = subprocess.run(["pdftotext", "-f", str(p), "-l", str(p), pdf, "-"],
                               capture_output=True, text=True).stdout.strip()
        prima_riga = testo.split("\n")[0].strip() if testo else ""
        if prima_riga in titoli and prima_riga not in trovati:
            trovati[prima_riga] = p
    return trovati

Funziona perché ogni capitolo inizia in cima a una pagina: il titolo è la prima riga. I numeri hanno tutti la stessa larghezza, quindi il secondo passaggio non sposta l'impaginazione. Per sicurezza confronto i numeri del secondo giro con quelli del primo.

Controllare l'impaginazione senza aprire il PDF

Quando i documenti sono tanti non li sfogli uno per uno. Due controlli automatici che uso:

  • Pagine quasi vuote: con pdftoppm rendo ogni pagina a bassa risoluzione e misuro dove finisce il contenuto. Una pagina riempita al 15% di solito è un capitolo che «deborda» di poche righe: riduco un'immagine o un margine.
  • Capitoli al posto giusto: con pdftotext verifico che la prima riga delle pagine attese sia davvero il titolo del capitolo.

E se serve un Word?

Spesso il cliente vuole anche il .docx da modificare. Le interruzioni di pagina prima dei capitoli in Word non si fanno con il CSS: si aggiunge <w:pageBreakBefore/> allo stile del titolo dentro word/styles.xml. Così vale per tutti i capitoli, anche quelli aggiunti dopo.

In sintesi

  • HTML e CSS per il contenuto, Chromium per la stampa: le tabelle e i testi lunghi si gestiscono come in una pagina web.
  • break-before, break-inside, orphans e widows fanno l'80% dell'impaginazione.
  • Immagini con un'altezza massima, per evitare buchi bianchi.
  • Font caricati prima della stampa e verificati con pdffonts.
  • Indice con i numeri di pagina in due passaggi.

Se in azienda qualcuno passa ore a copiare dati in un modello Word per farne un PDF, quasi sempre si può automatizzare così. Scrivimi e vediamo il tuo caso.

Leggi anche