- Sommaire, exemple tactique Q4 (figure 6) et note « pas de bibliothèque d'ouvertures » repris du commit d'Antonin et portés dans report/rapport.html (source du PDF), jusque-là seulement dans RAPPORT.md. - Exemple Q4 vérifié contre TILE_MAP : liserés D4/F6=2, E5=1, A2/C2=3 et chemin de capture C2→D2→D1→C1 (3 pas = liseré de C2) tous corrects. - Relecture du style sur tout le rapport ; correction de deux coquilles (« énnoncé », ancre de sommaire). HTML et RAPPORT.md tenus en miroir. - PDF régénéré (9 pages, sommaire inclus) ; chiffres mesurés inchangés. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
132 lines
4.9 KiB
Python
132 lines
4.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Génère le rapport PDF à partir de report/rapport.html, avec PyMuPDF (fitz).
|
|
|
|
Aucune dépendance externe : ni pandoc, ni LaTeX, ni navigateur. On utilise
|
|
l'API fitz.Story (rendu HTML/CSS -> PDF multi-pages) puis une seconde passe
|
|
pour le pied de page et les numéros de page.
|
|
|
|
python tools/make_report_pdf.py
|
|
"""
|
|
import os
|
|
import sys
|
|
import fitz # PyMuPDF
|
|
|
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
HTML = os.path.join(ROOT, "report", "rapport.html")
|
|
OUT = os.path.join(ROOT, "dist", "Puyaubreau_Russac_rapport.pdf")
|
|
|
|
# Feuille de style : mise en page A4 sobre, titres colorés, tables et blocs <pre>
|
|
# en Courier pour aligner diagrammes et carte des liserés.
|
|
CSS = """
|
|
* { font-family: serif; }
|
|
body { font-size: 10.5pt; line-height: 1.45; color: #1a1a1a; }
|
|
|
|
h1, h2, h3, .cover-title, .cover-course, .cover-sub, th { font-family: sans-serif; }
|
|
|
|
h2 { font-size: 15pt; color: #1c3d5a; margin: 18pt 0 6pt 0;
|
|
border-bottom: 1.5px solid #1c3d5a; padding-bottom: 2pt; }
|
|
h3 { font-size: 12pt; color: #2a6f97; margin: 12pt 0 3pt 0; }
|
|
p { margin: 5pt 0; text-align: justify; }
|
|
ul, ol { margin: 4pt 0 4pt 0; }
|
|
li { margin: 2pt 0; }
|
|
|
|
code { font-family: monospace; font-size: 9.5pt; color: #8a2846; }
|
|
|
|
pre.grid { font-family: monospace; font-size: 9pt; line-height: 1.25;
|
|
background: #f4f6f8; border: 1px solid #d3dae0; border-radius: 3px;
|
|
padding: 7pt; margin: 6pt 0; color: #14213d; white-space: pre; }
|
|
|
|
table { border-collapse: collapse; width: 100%; margin: 7pt 0; font-size: 9.5pt; }
|
|
th { background: #1c3d5a; color: #ffffff; text-align: left; padding: 4pt 6pt; }
|
|
td { border: 1px solid #c5ccd3; padding: 4pt 6pt; vertical-align: top; }
|
|
tr:nth-child(even) td { background: #f4f6f8; }
|
|
|
|
p.note { background: #fff8e6; border-left: 3px solid #e0a526;
|
|
padding: 5pt 8pt; margin: 7pt 0; font-size: 9.8pt; }
|
|
|
|
/* Sommaire */
|
|
.toc-title { border: none; color: #1c3d5a; margin-bottom: 4pt; }
|
|
ol.toc { font-size: 11pt; line-height: 1.7; color: #1a1a1a; }
|
|
ol.toc li { margin: 1pt 0; }
|
|
|
|
/* Éviter qu'un bloc préformaté ou une table soit coupé entre deux pages. */
|
|
pre.grid, table, tr { page-break-inside: avoid; }
|
|
h2, h3 { page-break-after: avoid; }
|
|
.cover { page-break-after: always; }
|
|
ol.toc { page-break-after: always; }
|
|
|
|
/* Page de titre */
|
|
.cover { text-align: center; padding-top: 40pt; }
|
|
.cover-univ { font-size: 10pt; color: #555; margin-bottom: 30pt; }
|
|
.cover-course { font-size: 13pt; color: #2a6f97; letter-spacing: 1pt; }
|
|
.cover-title { font-size: 30pt; color: #1c3d5a; margin: 8pt 0 0 0; }
|
|
.cover-sub { font-size: 12pt; color: #333; margin: 4pt 0; }
|
|
.cover-authors{ font-size: 15pt; color: #14213d; margin-top: 34pt; font-family: sans-serif; }
|
|
.cover-date { font-size: 11pt; color: #555; margin-top: 6pt; }
|
|
.cover-meta { font-size: 9.5pt; color: #555; margin-top: 34pt; line-height: 1.6; }
|
|
"""
|
|
|
|
MARGIN = 48 # marge en points (1pt = 1/72")
|
|
FOOTER = "Escampe — Puyaubreau / Russac — version finale"
|
|
|
|
|
|
def build():
|
|
with open(HTML, "r", encoding="utf-8") as f:
|
|
html = f.read()
|
|
|
|
os.makedirs(os.path.dirname(OUT), exist_ok=True)
|
|
|
|
mediabox = fitz.paper_rect("a4")
|
|
where = mediabox + (MARGIN, MARGIN, -MARGIN, -MARGIN)
|
|
|
|
# 1) Rendu du flux HTML en pages PDF via Story.
|
|
writer = fitz.DocumentWriter(OUT)
|
|
story = fitz.Story(html=html, user_css=CSS)
|
|
more = 1
|
|
pages = 0
|
|
while more:
|
|
dev = writer.begin_page(mediabox)
|
|
more, _ = story.place(where)
|
|
story.draw(dev)
|
|
writer.end_page()
|
|
pages += 1
|
|
writer.close()
|
|
|
|
# 2) Seconde passe : pied de page + numéros « page X / N ».
|
|
doc = fitz.open(OUT)
|
|
n = doc.page_count
|
|
for i, page in enumerate(doc, start=1):
|
|
y = page.rect.height - 26
|
|
page.insert_text((MARGIN, y), FOOTER, fontname="helv",
|
|
fontsize=7.5, color=(0.45, 0.45, 0.45))
|
|
label = f"page {i} / {n}"
|
|
w = fitz.get_text_length(label, fontname="helv", fontsize=7.5)
|
|
page.insert_text((page.rect.width - MARGIN - w, y), label,
|
|
fontname="helv", fontsize=7.5, color=(0.45, 0.45, 0.45))
|
|
doc.saveIncr()
|
|
doc.close()
|
|
return pages, n
|
|
|
|
|
|
def verify():
|
|
"""Contrôle que les accents survivent au rendu (round-trip texte)."""
|
|
doc = fitz.open(OUT)
|
|
full = "".join(p.get_text() for p in doc)
|
|
doc.close()
|
|
# Mots accentués stables, présents dans report/rapport.html (titres de section).
|
|
probes = ["liseré", "Présentation", "Modélisation", "Intégration",
|
|
"Heuristique", "Puyaubreau"]
|
|
missing = [s for s in probes if s not in full]
|
|
return missing, len(full)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
pages, n = build()
|
|
missing, chars = verify()
|
|
print(f"PDF écrit : {OUT}")
|
|
print(f"Pages : {n} | texte extrait : {chars} caractères")
|
|
if missing:
|
|
print("ATTENTION, chaînes accentuées introuvables après rendu :", missing)
|
|
sys.exit(1)
|
|
print("Accents vérifiés (round-trip OK).")
|