last changes in rendering pipeline
@@ -1,16 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build docs/GIT_FLOW.pdf from structured content and Mermaid diagrams.
|
||||
|
||||
Pipeline: Mermaid (.mmd) → SVG → high-DPI PNG (rsvg-convert) → PDF with correct aspect ratio.
|
||||
Vector sources: docs/_pdf_build/git_flow_diagram_*.svg (open in Okular for crisp zoom).
|
||||
Diagrams: Mermaid CLI renders PNG via headless Chromium (text + layout correct).
|
||||
Do NOT pipe Mermaid SVG through rsvg-convert — Mermaid uses foreignObject HTML labels
|
||||
that librsvg drops (boxes only, no text).
|
||||
|
||||
Requires: reportlab, npx (@mermaid-js/mermaid-cli), rsvg-convert (librsvg).
|
||||
Requires: reportlab, npx (@mermaid-js/mermaid-cli), Chromium (puppeteer bundled).
|
||||
Regenerate: python3 docs/_pdf_build/build_git_flow_pdf.py
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
@@ -33,40 +33,47 @@ from reportlab.platypus import (
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
BUILD = Path(__file__).resolve().parent
|
||||
CONFIG = BUILD / "mermaid-config.json"
|
||||
OUT_PDF = ROOT / "docs" / "GIT_FLOW.pdf"
|
||||
|
||||
# (mmd, svg, png, caption, mermaid w/h px, max width cm in PDF)
|
||||
# (mmd, png, caption, mermaid width px, height px, scale, max width cm in PDF)
|
||||
DIAGRAMS = [
|
||||
("git_flow_diagram_branches.mmd", "git_flow_diagram_branches.svg", "git_flow_diagram_branches.png",
|
||||
"Branch model (green master)", 1600, 720, 15.5),
|
||||
("git_flow_diagram_feature.mmd", "git_flow_diagram_feature.svg", "git_flow_diagram_feature.png",
|
||||
"Daily development", 1100, 1300, 14.0),
|
||||
("git_flow_diagram_release.mmd", "git_flow_diagram_release.svg", "git_flow_diagram_release.png",
|
||||
"Release: next → master", 1100, 1300, 14.0),
|
||||
("git_flow_diagram_hotfix.mmd", "git_flow_diagram_hotfix.svg", "git_flow_diagram_hotfix.png",
|
||||
"Hotfix decision and flow", 1200, 1000, 14.5),
|
||||
("git_flow_diagram_branches.mmd", "git_flow_diagram_branches.png",
|
||||
"Branch model (green master)", 1800, 900, 2, 15.5),
|
||||
("git_flow_diagram_feature.mmd", "git_flow_diagram_feature.png",
|
||||
"Daily development", 2400, 2000, 2, 14.0),
|
||||
("git_flow_diagram_release.mmd", "git_flow_diagram_release.png",
|
||||
"Release: next → master", 2400, 2000, 2, 14.0),
|
||||
("git_flow_diagram_hotfix.mmd", "git_flow_diagram_hotfix.png",
|
||||
"Hotfix decision and flow", 1300, 1100, 2, 14.5),
|
||||
]
|
||||
|
||||
MAX_DIAGRAM_HEIGHT_CM = 16.5
|
||||
RSVG_DPI = 200
|
||||
|
||||
DOC_VERSION = "1.1"
|
||||
DOC_DATE = "2026-05-21"
|
||||
DOC_VERSION = "1.3"
|
||||
DOC_DATE = "2026-05-23"
|
||||
DOC_AUTHOR = "Anton Afanaasyeu"
|
||||
DOC_SOURCE = "docs/GIT_FLOW.md"
|
||||
|
||||
|
||||
def render_mermaid() -> None:
|
||||
for mmd, svg_name, _, _, w_px, h_px, _ in DIAGRAMS:
|
||||
if not CONFIG.exists():
|
||||
raise FileNotFoundError(CONFIG)
|
||||
for mmd, png_name, _, w_px, h_px, scale, _ in DIAGRAMS:
|
||||
src = BUILD / mmd
|
||||
dst = BUILD / svg_name
|
||||
dst = BUILD / png_name
|
||||
if not src.exists():
|
||||
raise FileNotFoundError(src)
|
||||
subprocess.run(
|
||||
[
|
||||
"npx", "--yes", "@mermaid-js/mermaid-cli@11.4.0",
|
||||
"-i", str(src), "-o", str(dst),
|
||||
"-b", "white", "-w", str(w_px), "-H", str(h_px),
|
||||
"-i", str(src),
|
||||
"-o", str(dst),
|
||||
"-c", str(CONFIG),
|
||||
"-b", "white",
|
||||
"-w", str(w_px),
|
||||
"-H", str(h_px),
|
||||
"-s", str(scale),
|
||||
],
|
||||
cwd=str(ROOT),
|
||||
check=True,
|
||||
@@ -74,24 +81,7 @@ def render_mermaid() -> None:
|
||||
)
|
||||
|
||||
|
||||
def svg_to_png(svg_path: Path, png_path: Path) -> None:
|
||||
if not shutil.which("rsvg-convert"):
|
||||
raise RuntimeError("rsvg-convert not found (install librsvg / gnome-base/rsvg)")
|
||||
subprocess.run(
|
||||
[
|
||||
"rsvg-convert",
|
||||
"-d", str(RSVG_DPI),
|
||||
"-p", str(RSVG_DPI),
|
||||
"-o", str(png_path),
|
||||
str(svg_path),
|
||||
],
|
||||
check=True,
|
||||
timeout=60,
|
||||
)
|
||||
|
||||
|
||||
def diagram_size_cm(png_path: Path, max_width_cm: float) -> tuple[float, float]:
|
||||
"""Width/height in cm preserving aspect ratio, capped by page frame."""
|
||||
w_px, h_px = ImageReader(str(png_path)).getSize()
|
||||
max_w = max_width_cm * cm
|
||||
max_h = MAX_DIAGRAM_HEIGHT_CM * cm
|
||||
@@ -159,17 +149,15 @@ def build_pdf() -> None:
|
||||
["Published", DOC_DATE],
|
||||
["Author(s)", DOC_AUTHOR],
|
||||
["Markdown source", DOC_SOURCE],
|
||||
["Diagrams (PDF)", f"Mermaid → SVG → PNG @ {RSVG_DPI} DPI"],
|
||||
["Diagrams (vector)", "docs/_pdf_build/git_flow_diagram_*.svg"],
|
||||
["Diagrams", "Mermaid CLI → PNG (Chromium); proportional embed in PDF"],
|
||||
],
|
||||
[4.0 * cm, 12.5 * cm],
|
||||
)
|
||||
story.append(Spacer(1, 0.25 * cm))
|
||||
story.append(
|
||||
p(
|
||||
"PDF diagrams are high-resolution raster with correct aspect ratio (not stretched). "
|
||||
"For infinite zoom, open the SVG files in Okular or a browser. "
|
||||
"Okular: View → Zoom for PDF; SVG opens as native vector.",
|
||||
"Diagrams are rendered by headless Chromium (not rsvg/SVG rasterize) so labels stay visible. "
|
||||
"Regenerate after editing docs/_pdf_build/git_flow_diagram_*.mmd.",
|
||||
small,
|
||||
),
|
||||
)
|
||||
@@ -202,7 +190,7 @@ def build_pdf() -> None:
|
||||
[3 * cm, 8.5 * cm, 4 * cm],
|
||||
)
|
||||
story.append(Spacer(1, 0.3 * cm))
|
||||
add_diagram(story, DIAGRAMS[0][2], DIAGRAMS[0][3], h2, DIAGRAMS[0][6])
|
||||
add_diagram(story, DIAGRAMS[0][1], DIAGRAMS[0][2], h2, DIAGRAMS[0][6])
|
||||
story.append(PageBreak())
|
||||
|
||||
story.append(p("3. Daily development", h1))
|
||||
@@ -230,7 +218,7 @@ def build_pdf() -> None:
|
||||
body,
|
||||
),
|
||||
)
|
||||
add_diagram(story, DIAGRAMS[1][2], DIAGRAMS[1][3], h2, DIAGRAMS[1][6])
|
||||
add_diagram(story, DIAGRAMS[1][1], DIAGRAMS[1][2], h2, DIAGRAMS[1][6])
|
||||
story.append(PageBreak())
|
||||
|
||||
story.append(p("4. Release (next → master)", h1))
|
||||
@@ -243,7 +231,7 @@ def build_pdf() -> None:
|
||||
"Continue feature branches from updated next",
|
||||
]:
|
||||
story.append(p(f"• {line}", bullet))
|
||||
add_diagram(story, DIAGRAMS[2][2], DIAGRAMS[2][3], h2, DIAGRAMS[2][6])
|
||||
add_diagram(story, DIAGRAMS[2][1], DIAGRAMS[2][2], h2, DIAGRAMS[2][6])
|
||||
story.append(PageBreak())
|
||||
|
||||
story.append(p("5. Hotfixes — recommended strategy", h1))
|
||||
@@ -271,7 +259,7 @@ def build_pdf() -> None:
|
||||
body,
|
||||
),
|
||||
)
|
||||
add_diagram(story, DIAGRAMS[3][2], DIAGRAMS[3][3], h2, DIAGRAMS[3][6])
|
||||
add_diagram(story, DIAGRAMS[3][1], DIAGRAMS[3][2], h2, DIAGRAMS[3][6])
|
||||
|
||||
story.append(p("5.1 Sync cheat sheet", h2))
|
||||
add_table(
|
||||
@@ -304,9 +292,8 @@ def build_pdf() -> None:
|
||||
story.append(
|
||||
p(
|
||||
"<b>Force-push on next:</b> keep disabled by default. Allow only when you intentionally "
|
||||
"rewrite integration history (e.g. squash fixups before release) — never on master. "
|
||||
"Prefer merge/rebase on topic branches instead. If you force-push next, coordinate with "
|
||||
"anyone else using the repo and re-sync their clones.",
|
||||
"rewrite integration history — never on master. Prefer fixing on topic branches before "
|
||||
"merging to next.",
|
||||
body,
|
||||
),
|
||||
)
|
||||
@@ -322,10 +309,7 @@ def build_pdf() -> None:
|
||||
story.append(p(f"• {line}", bullet))
|
||||
story.append(Spacer(1, 0.4 * cm))
|
||||
story.append(
|
||||
p(
|
||||
"Regenerate: python3 docs/_pdf_build/build_git_flow_pdf.py",
|
||||
small,
|
||||
),
|
||||
p("Regenerate: python3 docs/_pdf_build/build_git_flow_pdf.py", small),
|
||||
)
|
||||
|
||||
doc = SimpleDocTemplate(
|
||||
@@ -343,17 +327,12 @@ def build_pdf() -> None:
|
||||
def main() -> int:
|
||||
try:
|
||||
render_mermaid()
|
||||
for _mmd, svg_name, png_name, *_ in DIAGRAMS:
|
||||
svg_to_png(BUILD / svg_name, BUILD / png_name)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print("Diagram render failed:", e, file=sys.stderr)
|
||||
print("Mermaid render failed:", e, file=sys.stderr)
|
||||
return 1
|
||||
except FileNotFoundError as e:
|
||||
print(e, file=sys.stderr)
|
||||
return 1
|
||||
except RuntimeError as e:
|
||||
print(e, file=sys.stderr)
|
||||
return 1
|
||||
build_pdf()
|
||||
return 0
|
||||
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
%% Branch model — render PNG via mermaid-cli (Chromium). Do not rasterize SVG with rsvg.
|
||||
flowchart TB
|
||||
subgraph legend[" "]
|
||||
direction LR
|
||||
L1[Long-lived]
|
||||
L2[Short-lived]
|
||||
end
|
||||
|
||||
M["master\n(always green, tagged releases)"]
|
||||
N["next\n(integration / release candidate)"]
|
||||
F["feature/* fix/*\n(fork next)"]
|
||||
|
||||
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 16 KiB |
@@ -11,4 +11,4 @@ sequenceDiagram
|
||||
Dev->>CI: run tests
|
||||
CI-->>Dev: pass
|
||||
Dev->>Next: merge topic branch
|
||||
Note over Next: server may delete merged branch on origin
|
||||
Note over Next: delete branch on merge<br/>(origin server setting)
|
||||
|
||||
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 149 KiB |
@@ -5,7 +5,7 @@ sequenceDiagram
|
||||
participant CI as Tests / CI
|
||||
participant Tag as tag vX.Y.Z
|
||||
|
||||
Note over Next: feature complete, QA ok
|
||||
Note over Next: feature complete,<br/>QA ok
|
||||
Next->>CI: full test suite
|
||||
CI-->>Next: green
|
||||
Next->>Master: merge next to master
|
||||
@@ -13,4 +13,4 @@ sequenceDiagram
|
||||
CI-->>Master: green
|
||||
Master->>Tag: annotated tag on master
|
||||
Master->>Next: merge master into next (sync)
|
||||
Note over Next: continue features from synced next
|
||||
Note over Next: continue features<br/>from synced next
|
||||
|
||||
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 28 KiB |
17
docs/_pdf_build/mermaid-config.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"theme": "neutral",
|
||||
"flowchart": {
|
||||
"htmlLabels": true,
|
||||
"curve": "basis"
|
||||
},
|
||||
"sequence": {
|
||||
"diagramMarginX": 120,
|
||||
"diagramMarginY": 28,
|
||||
"actorMargin": 90,
|
||||
"noteMargin": 24,
|
||||
"messageMargin": 48,
|
||||
"boxMargin": 14,
|
||||
"mirrorActors": false,
|
||||
"useMaxWidth": false
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
reportlab>=4.0
|
||||
|
||||
# PDF diagrams: Mermaid CLI (npx) + rsvg-convert (system package librsvg).
|
||||
# Diagrams: npx @mermaid-js/mermaid-cli (uses bundled Chromium).
|
||||
# Do NOT use rsvg-convert on Mermaid SVG — HTML labels are dropped.
|
||||
|
||||