Joueur alpha-bêta + iterative deepening pour le tournoi APP5 « IA et contraintes ». - src/escampe/ : joueur (IJoueur), moteur (alpha-bêta + DFS bitmask, make/unmake sans allocation), modèle EscampeBoard (Partie1), utilitaires de test. - Protocole arbitre vérifié (pass="E", carte des liserés identique au serveur, machine à états placement/jeu) ; 7/7 victoires vs joueur aléatoire, 0 illégal. - Vérifications : VerifMoves (int≡String, 0 divergence/142k positions), RulesTest (21/21), Branching (facteur de branchement mesuré). - Rapport : report/rapport.html + tools/make_report_pdf.py (PyMuPDF) → PDF, RAPPORT.md. - Livrables buildés inclus (dist/ : jar, mainClass, tgz, rapport PDF) + lib/escampeobf.jar. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
126 lines
4.7 KiB
Python
126 lines
4.7 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; }
|
|
|
|
/* É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; }
|
|
|
|
/* 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 présents tels quels dans report/rapport.html.
|
|
probes = ["liseré", "Présentation", "élagage", "Modélisation",
|
|
"stratégique", "approfondissement", "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).")
|