Files
plm-lottery/flowchart/render-pdf.sh
davide 8a3dfd4592 Replace flowchart.mmd with per-topic diagrams and a print pipeline
Split the single flowchart.mmd into flowchart/platform-overview.mmd
(all 5 phases) and flowchart/round-lifecycle.mmd (round/draw detail),
plus render-pdf.sh to generate print-ready A4/A3 PDFs with a consistent
theme, header/footer, and legible contrast against the page background.

Also flip the operational policy in CLAUDE.md: the app now always runs
via Docker (dev and prod alike), with the venv reserved for tests,
migration authoring, and one-time secret/key-generation scripts.
2026-07-23 16:24:28 +02:00

164 lines
5.2 KiB
Bash
Executable File

#!/usr/bin/env bash
# Regenerates professional-looking A4 and A3 landscape PDFs from a Mermaid
# .mmd flowchart: consistent color theme, legible fonts, a title (read from
# the .mmd's own YAML frontmatter) and a footer with the generation date.
#
# Usage:
# ./render-pdf.sh [path/to/file.mmd]
#
# Defaults to round-lifecycle.mmd in this same directory.
# Produces <name>-A4.pdf and <name>-A3.pdf next to the .mmd file.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MMD_FILE="${1:-$SCRIPT_DIR/round-lifecycle.mmd}"
if [[ ! -f "$MMD_FILE" ]]; then
echo "Errore: file non trovato: $MMD_FILE" >&2
exit 1
fi
OUT_DIR="$(cd "$(dirname "$MMD_FILE")" && pwd)"
BASE="$(basename "$MMD_FILE" .mmd)"
SVG_TMP="$OUT_DIR/.${BASE}.tmp.svg"
CONFIG_TMP="$OUT_DIR/.${BASE}.tmp-config.json"
cleanup() {
rm -f "$SVG_TMP" "$CONFIG_TMP" "$OUT_DIR/.${BASE}.tmp-A4.html" "$OUT_DIR/.${BASE}.tmp-A3.html"
}
trap cleanup EXIT
# Consistent, print-friendly color theme (indigo nodes/edges, warm amber phase
# clusters, generous font size) instead of mermaid's flat default palette.
cat > "$CONFIG_TMP" <<'EOF'
{
"theme": "base",
"themeVariables": {
"fontFamily": "\"Segoe UI\", Helvetica, Arial, sans-serif",
"fontSize": "17px",
"primaryColor": "#c9d6f7",
"primaryBorderColor": "#3949ab",
"primaryTextColor": "#1a1a2e",
"lineColor": "#3949ab",
"secondaryColor": "#fff8e1",
"tertiaryColor": "#ffffff",
"clusterBkg": "#fff8e1",
"clusterBorder": "#c9a227",
"edgeLabelBackground": "#c9d6f7",
"titleColor": "#1a1a2e"
},
"flowchart": {
"curve": "basis",
"padding": 16,
"htmlLabels": true,
"nodeSpacing": 100,
"rankSpacing": 25
}
}
EOF
echo "-> Rendering diagram to SVG..."
npx -y @mermaid-js/mermaid-cli -i "$MMD_FILE" -o "$SVG_TMP" -b white -c "$CONFIG_TMP"
echo "-> Building print-ready A4/A3 PDFs..."
# mermaid-cli pulls in puppeteer as a transitive dependency; reuse that install
# instead of adding a separate one just for this script.
PUPPETEER_DIR="$(dirname "$(find "$HOME/.npm/_npx" -maxdepth 3 -type d -name puppeteer 2>/dev/null | head -n1)")"
if [[ -z "$PUPPETEER_DIR" || ! -d "$PUPPETEER_DIR" ]]; then
echo "Errore: modulo puppeteer non trovato (serve mermaid-cli gia' eseguito almeno una volta)." >&2
exit 1
fi
export NODE_PATH="$PUPPETEER_DIR"
GENERATED_AT="$(date '+%d/%m/%Y %H:%M')"
# Human title for the header banner; falls back to a prettified filename for
# any .mmd this script hasn't been told about explicitly.
case "$BASE" in
round-lifecycle) TITLE="Ciclo di vita di un round" ;;
platform-overview) TITLE="Flusso completo della piattaforma" ;;
*) TITLE="$(echo "$BASE" | tr '-' ' ' | sed 's/\b\(.\)/\u\1/g')" ;;
esac
node -e '
const fs = require("fs");
const path = require("path");
const puppeteer = require("puppeteer");
const outDir = process.argv[1];
const base = process.argv[2];
const svgPath = process.argv[3];
const generatedAt = process.argv[4];
const title = process.argv[5];
const svg = fs.readFileSync(svgPath, "utf-8");
function htmlFor(size) {
return `<!doctype html>
<html><head><meta charset="utf-8">
<style>
html, body { margin:0; padding:0; height:100%; font-family: "Segoe UI", Helvetica, Arial, sans-serif; }
body { display:flex; flex-direction:column; height:100%; box-sizing:border-box; padding:4mm 6mm; }
.header {
flex:0 0 auto;
display:flex;
align-items:baseline;
gap:3mm;
border-bottom:1.5pt solid #3949ab;
padding-bottom:1.5mm;
margin-bottom:2mm;
}
.header .brand { font-size:12pt; font-weight:700; color:#3949ab; }
.header .title { font-size:10pt; font-weight:400; color:#1a1a2e; }
.diagram { flex:1 1 auto; min-height:0; display:flex; align-items:flex-start; justify-content:center; }
.diagram svg { width:100%; height:auto; max-width:100%; max-height:100%; }
.footer {
flex:0 0 auto;
display:flex;
justify-content:space-between;
align-items:center;
border-top:0.5pt solid #c9c9d6;
padding-top:2mm;
margin-top:2mm;
font-size:8pt;
color:#6b6b7a;
}
</style>
</head><body>
<div class="header">
<span class="brand">PLM Lottery</span>
<span class="title">${title}</span>
</div>
<div class="diagram">${svg}</div>
<div class="footer">
<span>Diagramma di flusso</span>
<span>Generato il ${generatedAt} &middot; formato ${size} orizzontale</span>
</div>
</body></html>`;
}
(async () => {
const browser = await puppeteer.launch({ args: ["--no-sandbox"] });
const page = await browser.newPage();
for (const fmt of ["A4", "A3"]) {
const htmlPath = path.join(outDir, `.${base}.tmp-${fmt}.html`);
fs.writeFileSync(htmlPath, htmlFor(fmt));
await page.goto("file://" + htmlPath, { waitUntil: "networkidle0" });
const pdfPath = path.join(outDir, `${base}-${fmt}.pdf`);
await page.pdf({
path: pdfPath,
format: fmt,
landscape: true,
printBackground: true,
margin: { top: "6mm", bottom: "6mm", left: "6mm", right: "6mm" },
});
console.log(" " + pdfPath);
}
await browser.close();
})();
' "$OUT_DIR" "$BASE" "$SVG_TMP" "$GENERATED_AT" "$TITLE"
echo "Fatto."