Volevo trasformare in PDF alcuni file Markdown che contengono tabelle larghe e diagrammi Mermaid. Ho provato convertitori online, la stampa su PDF di CUPS e l'esportazione di VS Code, e ogni volta c'era qualcosa che non andava: le tabelle uscivano dalla pagina, o i diagrammi erano illeggibili o rotti. La combinazione che alla fine funziona è pandoc con il filtro mermaid-filter e weasyprint come motore del PDF, senza LaTeX.
Installazione
sudo apt install pandoc weasyprint
npm install -g mermaid-filter
mermaid-filter disegna i diagrammi con Mermaid dentro un Chromium lanciato da puppeteer. Se hai già Chrome, si può farglielo usare con la variabile CHROME, come vedi nello script qui sotto.
Un comando solo
Il comando di pandoc da solo non basta, perché servono anche un foglio di stile e un file di configurazione per Mermaid, e dopo un po' non ricordavo più dove fossero. Ho messo tutto in uno script che si porta dietro il CSS e la configurazione e li scrive in una cartella temporanea. Si salva come md2pdf in una cartella del PATH e si rende eseguibile con chmod +x.
#!/usr/bin/env bash
# uso: md2pdf file.md [file.pdf]
# CHROME=/percorso/chrome per far usare a mermaid-filter un Chrome già installato
set -euo pipefail
in="$(realpath "$1")"
out="$(realpath -m "${2:-${1%.md}.pdf}")"
work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT
cat > "$work/pandoc.css" <<'CSS'
@page { size: A4; margin: 1cm; }
body {
font-size: 9pt;
font-family: sans-serif;
line-height: 1.5;
}
img {
display: block;
margin: 0 auto;
max-width: 100%;
height: auto;
}
table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
overflow-wrap: break-word;
}
td, th {
border: 1px solid #ccc;
padding: 6px;
text-align: left;
font-size: 8pt;
overflow: hidden;
}
CSS
# Con htmlLabels false il testo dei nodi resta testo SVG: weasyprint non
# disegna le etichette HTML (foreignObject) che Mermaid usa di default.
echo '{"flowchart":{"htmlLabels":false}}' > "$work/.mermaid-config.json"
if [ -n "${CHROME:-}" ]; then
printf '{"executablePath":"%s","args":["--no-sandbox"]}' "$CHROME" > "$work/.puppeteer.json"
fi
# mermaid-filter legge i due file di configurazione dalla cartella corrente
cd "$work"
MERMAID_FILTER_FORMAT=svg pandoc -F mermaid-filter \
-t html5 --pdf-engine=weasyprint --css=pandoc.css \
-o "$out" "$in"
Si usa così:
md2pdf articolo.md # crea articolo.pdf
md2pdf articolo.md uscita.pdf
Se il Markdown ha un front-matter YAML in testa, va tolto prima, altrimenti finisce nel testo del PDF.
Perché la configurazione di Mermaid
Con MERMAID_FILTER_FORMAT=svg i diagrammi restano vettoriali e si leggono bene anche ingranditi. Ma la prima volta che ho provato, i riquadri dei diagrammi erano vuoti: le forme c'erano, il testo no. Mermaid di default scrive le etichette dei nodi dentro un elemento HTML incorporato nell'SVG (foreignObject), e per quanto ho capito weasyprint non lo disegna. La riga htmlLabels: false nel file .mermaid-config.json fa scrivere le etichette come normale testo SVG, e il problema sparisce. mermaid-filter legge quel file dalla cartella corrente, per questo lo script entra nella cartella temporanea prima di lanciare pandoc.
L'alternativa è lasciare il formato png (il predefinito del filtro), ma i diagrammi non sono più vettoriali.
Perché il CSS
Le parti del foglio di stile che contano sono poche:
-
@pagecon margini di 1 cm, per usare bene l'A4. -
table-layout: fixedeoverflow-wrap: break-word, che sono quello che tiene le tabelle larghe dentro la pagina: le colonne si dividono lo spazio in parti uguali e il testo lungo va a capo, anche nel mezzo di una parola se serve (un nome comedevto_synced_hashin una colonna stretta si spezza in due righe). -
text-align: leftnelle celle: senza, il testo giustificato in colonne strette lascia spazi enormi tra le parole. - il font a 9pt nel testo e 8pt nelle tabelle. Sono valori che ho scelto a occhio: con i 7pt con cui avevo cominciato il PDF si leggeva a fatica. Se hai tabelle con molte colonne puoi scendere ancora.
Ho provato lo script su una tabella con otto colonne e su un'altra con percorsi e comandi lunghi, e in entrambi i casi sono rimaste dentro la pagina.
Lanciarlo da VS Code
Per non aprire il terminale ogni volta ho aggiunto un task di VS Code, cioè un comando salvato che parte dal menu. Va messo in .vscode/tasks.json nel progetto, oppure in tasks.json nella cartella di configurazione utente (~/.config/Code/User/ su Linux) per averlo in tutti i progetti:
{
"version": "2.0.0",
"tasks": [
{
"label": "Markdown → PDF",
"type": "shell",
"command": "${env:HOME}/bin/md2pdf \"${file}\"",
"problemMatcher": [],
"presentation": { "reveal": "silent", "panel": "shared" }
}
]
}
Con un file Markdown aperto si lancia da Ctrl+Shift+P, scegliendo «Tasks: Run Task» e poi «Markdown → PDF». Il PDF compare nella stessa cartella del file. ${file} è il file aperto nell'editor e ${env:HOME} evita di dipendere dal PATH, perché VS Code potrebbe non conoscere la cartella dove hai messo lo script. Per una scorciatoia da tastiera si aggiunge in keybindings.json:
{
"key": "ctrl+alt+p",
"command": "workbench.action.tasks.runTask",
"args": "Markdown → PDF",
"when": "editorLangId == markdown"
}
Un limite dei diagrammi piccoli
Un diagramma molto piccolo, per esempio con due soli nodi, viene ingrandito fino a occupare tutta la larghezza della pagina. mermaid-filter lo salva con una larghezza fissa di 800 e weasyprint lo adatta alla pagina, mentre max-height sulle immagini, nelle mie prove, non ha cambiato nulla. I diagrammi di un articolo vero hanno di solito più nodi e vengono della misura giusta, quindi per me non è un problema, ma è una cosa da sapere.
Top comments (0)