- Le format du badge n'est plus fixé à CR80 : il est déduit des pixels de l'image du recto à l'import (résolution supposée 300 dpi, app/badges/pdf.py:image_size_mm), stocké sur BadgeTemplate (migration 0012). L'éditeur (BadgesPage.tsx) et la génération PDF s'alignent sur ce format réel ; CR80 reste le repère par défaut tant qu'aucune image n'est importée. Le verso est ajusté sans déformation (preserveAspectRatio). - Le champ « Postes affectés » liste désormais une ligne par créneau (triées chronologiquement) : nom du poste + horaire, et le référent du poste s'il y en a un et que ce n'est pas le bénévole badgé lui-même. Texte multi-lignes pris en charge par la génération PDF (découpe sur `\n`, hauteur de ligne calculée en mm) et par l'aperçu de l'éditeur. - Corrige au passage un bug d'unités dans le positionnement vertical du texte (police en points confondue avec des millimètres). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
100 lines
3.4 KiB
Python
100 lines
3.4 KiB
Python
"""Génération des planches de badges (module pur, sans base)."""
|
||
|
||
from app.badges.pdf import (
|
||
DEFAULT_BADGE_HEIGHT_MM,
|
||
DEFAULT_BADGE_WIDTH_MM,
|
||
BadgeFieldSpec,
|
||
BadgeSpec,
|
||
_cell_origin,
|
||
_grid_size,
|
||
generate_badge_sheets_pdf,
|
||
image_size_mm,
|
||
)
|
||
|
||
|
||
def _badge(text="Alex Martin") -> BadgeSpec:
|
||
return BadgeSpec(
|
||
fields=(
|
||
BadgeFieldSpec(
|
||
text=text,
|
||
x_mm=5,
|
||
y_mm=5,
|
||
width_mm=60,
|
||
height_mm=10,
|
||
font_size=12,
|
||
align="LEFT",
|
||
bold=True,
|
||
color="#000000",
|
||
),
|
||
)
|
||
)
|
||
|
||
|
||
def test_image_size_mm_uses_the_assumed_print_resolution():
|
||
# 300 dpi (résolution d'impression standard) : 1013 px / 300 * 25,4 mm.
|
||
width_mm, height_mm = image_size_mm(1013, 638, dpi=300)
|
||
assert width_mm == 1013 / 300 * 25.4
|
||
assert height_mm == 638 / 300 * 25.4
|
||
|
||
|
||
def test_grid_size_fits_a4_with_cr80_badges():
|
||
# A4 (210 × 297 mm), marge 10 mm, gouttière 4 mm, badge CR80 85,6 × 54 mm
|
||
# ⇒ 2 colonnes (190 mm utiles / 89,6 mm par badge+gouttière) x 4 rangées
|
||
# (277 mm utiles / 58 mm par badge+gouttière).
|
||
assert _grid_size(DEFAULT_BADGE_WIDTH_MM, DEFAULT_BADGE_HEIGHT_MM) == (2, 4)
|
||
|
||
|
||
def test_grid_size_adapts_to_a_different_badge_format():
|
||
# Badge carré 50 × 50 mm : plus de colonnes/rangées qu'un CR80.
|
||
assert _grid_size(50, 50) == (3, 5)
|
||
|
||
|
||
def test_cell_origin_places_badges_left_to_right_top_to_bottom():
|
||
cols, _ = _grid_size(DEFAULT_BADGE_WIDTH_MM, DEFAULT_BADGE_HEIGHT_MM)
|
||
x0, y0 = _cell_origin(0, cols, DEFAULT_BADGE_WIDTH_MM, DEFAULT_BADGE_HEIGHT_MM)
|
||
x1, _ = _cell_origin(1, cols, DEFAULT_BADGE_WIDTH_MM, DEFAULT_BADGE_HEIGHT_MM)
|
||
_, y_row2 = _cell_origin(cols, cols, DEFAULT_BADGE_WIDTH_MM, DEFAULT_BADGE_HEIGHT_MM)
|
||
assert x1 > x0 # la 2e colonne est à droite de la 1re
|
||
assert y_row2 < y0 # la 2e rangée est plus bas (repère PDF : Y croît vers le haut)
|
||
|
||
|
||
def test_generates_a_valid_pdf_for_a_single_badge():
|
||
pdf = generate_badge_sheets_pdf([_badge()], front_image=None, back_image=None)
|
||
assert pdf.startswith(b"%PDF")
|
||
assert pdf.rstrip().endswith(b"%%EOF")
|
||
|
||
|
||
def test_generates_a_valid_pdf_with_a_custom_badge_format():
|
||
pdf = generate_badge_sheets_pdf(
|
||
[_badge()], front_image=None, back_image=None,
|
||
badge_width_mm=50, badge_height_mm=50,
|
||
)
|
||
assert pdf.startswith(b"%PDF")
|
||
|
||
|
||
def test_multiline_field_text_does_not_crash_generation():
|
||
badge = BadgeSpec(
|
||
fields=(
|
||
BadgeFieldSpec(
|
||
text="Bar central 19:00–22:00\nAccueil 09:00–13:00 (Référent : Sam Nuit)",
|
||
x_mm=5, y_mm=5, width_mm=70, height_mm=20,
|
||
font_size=9, align="LEFT", bold=False, color="#000000",
|
||
),
|
||
)
|
||
)
|
||
pdf = generate_badge_sheets_pdf([badge], front_image=None, back_image=None)
|
||
assert pdf.startswith(b"%PDF")
|
||
|
||
|
||
def test_more_badges_than_one_sheet_produces_a_larger_pdf():
|
||
cols, rows = _grid_size(DEFAULT_BADGE_WIDTH_MM, DEFAULT_BADGE_HEIGHT_MM)
|
||
per_page = cols * rows
|
||
one_page = generate_badge_sheets_pdf(
|
||
[_badge() for _ in range(per_page)], front_image=None, back_image=None
|
||
)
|
||
# Un badge de plus déborde sur une 2e planche (recto + verso en plus).
|
||
two_pages = generate_badge_sheets_pdf(
|
||
[_badge() for _ in range(per_page + 1)], front_image=None, back_image=None
|
||
)
|
||
assert len(two_pages) > len(one_page)
|