Le besoin

Les élèves veulent des compilations PDF d'annales : toutes les épreuves d'une matière sur 5 ans dans un seul document propre et paginé, généré à la volée selon leur sélection.

Pourquoi ReportLab

Pour un document structuré et répétitif, ReportLab (via Platypus) donne un contrôle au pixel près, sans navigateur headless lourd sur le VPS.

from reportlab.lib.pagesizes import A4
from reportlab.lib.units import cm
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak
from reportlab.lib.styles import getSampleStyleSheet

def build_annales_pdf(output_path, epreuves):
    doc = SimpleDocTemplate(output_path, pagesize=A4,
        topMargin=2*cm, bottomMargin=2*cm, title="Annales - baccalaureat.sn")
    styles = getSampleStyleSheet()
    story = [Paragraph("Compilation d'annales", styles['Title']), Spacer(1, 0.5*cm)]

    for ep in epreuves:
        story.append(Paragraph(
            f"{ep['matiere']} — {ep['serie']} — {ep['annee']}", styles['Heading2']))
        story.append(Paragraph(ep['enonce'], styles['BodyText']))
        story.append(PageBreak())

    doc.build(story, onFirstPage=_header_footer, onLaterPages=_header_footer)

En-têtes paginés

def _header_footer(canvas, doc):
    canvas.saveState()
    canvas.setFont('Helvetica', 8)
    canvas.drawString(2*cm, 1*cm, "baccalaureat.sn")
    canvas.drawRightString(A4[0] - 2*cm, 1*cm, f"Page {doc.page}")
    canvas.restoreState()

Le pont avec Symfony

Le PDF est généré par un service Python appelé depuis Symfony (via Process), et chaque téléchargement est loggé pour les stats et la détection d'abus.

$log = new DownloadLog();
$log->setUser($user);
$log->setSelection($selection);
$em->persist($log);
$em->flush();

Ce que je retiens

  • Pour des documents structurés : ReportLab + Platypus > HTML-to-PDF.
  • Platypus gère la pagination tout seul ; on décrit un flux de contenu.
  • onLaterPages pour les en-têtes récurrents.
  • Logguer chaque génération : utile pour les stats et la sécurité.