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.
164 lines
5.2 KiB
Bash
Executable File
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} · 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."
|