Feuilles de poste compactées : une page par poste et par jour
Bénévoles deux par ligne (vignette 10 mm, nom et téléphone superposés), titres, en-têtes de créneau et marges resserrés — 6 feuilles réalistes : 11 pages ⇒ 6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
749c7e0917
commit
5c2cc0a6c3
+66
-30
@@ -30,9 +30,12 @@ from reportlab.platypus import (
|
||||
TableStyle,
|
||||
)
|
||||
|
||||
_PHOTO_BOX_MM = 14 * mm
|
||||
# Côté de la vignette en pixels : 14 mm à 300 dpi.
|
||||
_PHOTO_BOX_PX = round(14 / 25.4 * 300)
|
||||
# Mise en page compacte (une feuille = une page autant que possible) :
|
||||
# vignette de 10 mm, deux bénévoles par ligne.
|
||||
_PHOTO_BOX_MM = 10 * mm
|
||||
# Côté de la vignette en pixels : 10 mm à 300 dpi.
|
||||
_PHOTO_BOX_PX = round(10 / 25.4 * 300)
|
||||
_PER_ROW = 2
|
||||
|
||||
_WEEKDAYS_FR = ["Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche"]
|
||||
_MONTHS_FR = [
|
||||
@@ -94,26 +97,36 @@ def _styles() -> dict[str, ParagraphStyle]:
|
||||
base = getSampleStyleSheet()
|
||||
return {
|
||||
"title": ParagraphStyle(
|
||||
"SheetTitle", parent=base["Title"], alignment=0, fontSize=22, spaceAfter=2
|
||||
"SheetTitle", parent=base["Title"], alignment=0,
|
||||
fontSize=18, leading=21, spaceBefore=0, spaceAfter=1,
|
||||
),
|
||||
"subtitle": ParagraphStyle(
|
||||
"SheetSubtitle", parent=base["Normal"],
|
||||
fontSize=12, textColor=colors.grey, spaceAfter=4,
|
||||
fontSize=10.5, leading=13, textColor=colors.grey, spaceAfter=2,
|
||||
),
|
||||
"referent": ParagraphStyle(
|
||||
"SheetReferent", parent=base["Normal"],
|
||||
fontSize=11.5, fontName="Helvetica-Bold", spaceAfter=6,
|
||||
fontSize=10.5, leading=13, fontName="Helvetica-Bold", spaceAfter=3,
|
||||
),
|
||||
"artists": ParagraphStyle(
|
||||
"SheetArtists", parent=base["Normal"],
|
||||
fontSize=9.5, textColor=colors.HexColor("#4a4a4a"), spaceAfter=16,
|
||||
fontSize=8.5, leading=10.5, textColor=colors.HexColor("#4a4a4a"), spaceAfter=4,
|
||||
),
|
||||
"shiftHeader": ParagraphStyle(
|
||||
"ShiftHeader", parent=base["Heading3"], fontSize=13, spaceBefore=12, spaceAfter=4
|
||||
"ShiftHeader", parent=base["Heading3"],
|
||||
fontSize=11, leading=13, spaceBefore=7, spaceAfter=3,
|
||||
),
|
||||
"shiftHeaderWarning": ParagraphStyle(
|
||||
"ShiftHeaderWarning", parent=base["Heading3"],
|
||||
fontSize=13, spaceBefore=12, spaceAfter=4, textColor=colors.HexColor("#b23a1f"),
|
||||
fontSize=11, leading=13, spaceBefore=7, spaceAfter=3,
|
||||
textColor=colors.HexColor("#b23a1f"),
|
||||
),
|
||||
"volunteerName": ParagraphStyle(
|
||||
"SheetVolunteerName", parent=base["Normal"],
|
||||
fontName="Helvetica-Bold", fontSize=9.5, leading=11.5,
|
||||
),
|
||||
"volunteerPhone": ParagraphStyle(
|
||||
"SheetVolunteerPhone", parent=base["Normal"], fontSize=9, leading=11,
|
||||
),
|
||||
"footerTitle": ParagraphStyle(
|
||||
"SheetFooterTitle", parent=base["Normal"],
|
||||
@@ -136,8 +149,9 @@ class SheetFooter:
|
||||
message: str
|
||||
|
||||
|
||||
_MARGIN_X = 18 * mm
|
||||
_MARGIN_BOTTOM = 16 * mm
|
||||
_MARGIN_X = 15 * mm
|
||||
_MARGIN_TOP = 12 * mm
|
||||
_MARGIN_BOTTOM = 12 * mm
|
||||
# Écart entre le bas du contenu et le filet du pied de page.
|
||||
_FOOTER_GAP = 5 * mm
|
||||
|
||||
@@ -186,25 +200,47 @@ def _photo_cell(photo: bytes | None, cache: dict[bytes, tuple[bytes, int, int] |
|
||||
return Image(io.BytesIO(data), width=width_px * scale, height=height_px * scale)
|
||||
|
||||
|
||||
def _shift_table(shift: SheetShift, cache: dict[bytes, tuple[bytes, int, int] | None]) -> Table:
|
||||
rows = [["", "Bénévole", "Téléphone"]] + [
|
||||
[_photo_cell(v.photo, cache), v.name, v.phone or "—"]
|
||||
def _shift_table(
|
||||
shift: SheetShift,
|
||||
cache: dict[bytes, tuple[bytes, int, int] | None],
|
||||
styles: dict[str, ParagraphStyle],
|
||||
width: float,
|
||||
) -> Table:
|
||||
"""Bénévoles du créneau, deux par ligne : vignette, puis nom (gras) et
|
||||
téléphone l'un sous l'autre. Deux fois moins de lignes qu'une liste à
|
||||
une personne par ligne (avant l'étape 65, souvent deux pages par
|
||||
feuille)."""
|
||||
photo_w = _PHOTO_BOX_MM + 3 * mm
|
||||
text_w = width / _PER_ROW - photo_w
|
||||
cells = [
|
||||
[
|
||||
_photo_cell(v.photo, cache),
|
||||
[
|
||||
Paragraph(escape(v.name), styles["volunteerName"]),
|
||||
Paragraph(escape(v.phone or "—"), styles["volunteerPhone"]),
|
||||
],
|
||||
]
|
||||
for v in shift.volunteers
|
||||
]
|
||||
table = Table(rows, colWidths=[18 * mm, 92 * mm, 55 * mm])
|
||||
table.setStyle(
|
||||
TableStyle(
|
||||
[
|
||||
("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
|
||||
("FONTSIZE", (0, 0), (-1, -1), 10.5),
|
||||
("TOPPADDING", (0, 0), (-1, -1), 5),
|
||||
("BOTTOMPADDING", (0, 0), (-1, -1), 5),
|
||||
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
|
||||
("LINEBELOW", (0, 0), (-1, 0), 0.75, colors.black),
|
||||
("GRID", (0, 1), (-1, -1), 0.25, colors.lightgrey),
|
||||
]
|
||||
)
|
||||
)
|
||||
rows = []
|
||||
for i in range(0, len(cells), _PER_ROW):
|
||||
chunk = cells[i : i + _PER_ROW]
|
||||
chunk += [["", ""]] * (_PER_ROW - len(chunk))
|
||||
rows.append([cell for pair in chunk for cell in pair])
|
||||
table = Table(rows, colWidths=[photo_w, text_w] * _PER_ROW)
|
||||
style = [
|
||||
("TOPPADDING", (0, 0), (-1, -1), 2),
|
||||
("BOTTOMPADDING", (0, 0), (-1, -1), 2),
|
||||
("LEFTPADDING", (0, 0), (-1, -1), 3),
|
||||
("RIGHTPADDING", (0, 0), (-1, -1), 3),
|
||||
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
|
||||
("LINEBELOW", (0, 0), (-1, -1), 0.25, colors.lightgrey),
|
||||
("LINEABOVE", (0, 0), (-1, 0), 0.25, colors.lightgrey),
|
||||
]
|
||||
# Filet vertical entre les deux bénévoles d'une même ligne.
|
||||
for col in range(2, 2 * _PER_ROW, 2):
|
||||
style.append(("LINEBEFORE", (col, 0), (col, -1), 0.25, colors.lightgrey))
|
||||
table.setStyle(TableStyle(style))
|
||||
return table
|
||||
|
||||
|
||||
@@ -221,7 +257,7 @@ def generate_position_sheets_pdf(
|
||||
footer_height = sum(p.wrap(width, A4[1])[1] for p in paragraphs)
|
||||
doc = SimpleDocTemplate(
|
||||
buffer, pagesize=A4,
|
||||
leftMargin=_MARGIN_X, rightMargin=_MARGIN_X, topMargin=16 * mm,
|
||||
leftMargin=_MARGIN_X, rightMargin=_MARGIN_X, topMargin=_MARGIN_TOP,
|
||||
bottomMargin=_MARGIN_BOTTOM + (footer_height + _FOOTER_GAP if paragraphs else 0),
|
||||
)
|
||||
|
||||
@@ -279,7 +315,7 @@ def generate_position_sheets_pdf(
|
||||
style = styles["shiftHeaderWarning"] if shift.understaffed else styles["shiftHeader"]
|
||||
story.append(Paragraph(header, style))
|
||||
if shift.volunteers:
|
||||
story.append(_shift_table(shift, thumbnails))
|
||||
story.append(_shift_table(shift, thumbnails, styles, width))
|
||||
else:
|
||||
story.append(Paragraph("Personne affecté pour l'instant.", styles["normal"]))
|
||||
story.append(Spacer(1, 0))
|
||||
|
||||
Reference in New Issue
Block a user