= encadré d'intro (lead). + if ($strTag === 'p' && !$blnLeadDone) { + $blnLeadDone = true; + $strLead = fxBibDistPrintDomPlainText($el); + $fltInnerW = $fltContentW - 10; + $pdf->SetFont('Arial', 'I', 10); + $intLines = max(2, (int)ceil($pdf->GetStringWidth(fxBibDistPrintPdfText($strLead)) / max(1, $fltInnerW)) + 1); + $fltBoxH = max(12, 4 + ($intLines * 4.5) + 3); + if ($pdf->GetY() + $fltBoxH > $pdf->GetPageHeight() - $fltBottom) { + $pdf->AddPage(); + } + $fltY0 = $pdf->GetY(); + $pdf->SetFillColor(232, 240, 254); + $pdf->Rect($fltLeft, $fltY0, $fltContentW, $fltBoxH, 'F'); + $pdf->SetFillColor(47, 95, 208); + $pdf->Rect($fltLeft, $fltY0, 2.5, $fltBoxH, 'F'); + $pdf->SetXY($fltLeft + 6, $fltY0 + 3.5); + $pdf->SetFont('Arial', 'I', 10); + $pdf->SetTextColor(40, 55, 90); + $pdf->MultiCell($fltInnerW, 4.5, fxBibDistPrintPdfText($strLead), 0, 'L'); + $pdf->SetY($fltY0 + $fltBoxH + 5); + $pdf->SetTextColor(0, 0, 0); + $i++; + continue; + } + + if ($strTag === 'h3' || $strTag === 'h2' || $strTag === 'h4') { + $intTip++; + $strTitleRaw = fxBibDistPrintDomPlainText($el); + $strTitle = $strTitleRaw; + $strNum = (string)$intTip; + if (preg_match('/^(\d+)\s*[\.\)\—\-–]\s*(.+)$/u', $strTitleRaw, $m)) { + $strNum = $m[1]; + $strTitle = $m[2]; + } + + $bodyNode = null; + if (($i + 1) < $intN && strtolower($tabBlocks[$i + 1]->nodeName) === 'p') { + $bodyNode = $tabBlocks[$i + 1]; + $i += 2; + } else { + $i++; + } + $strBodyPlain = $bodyNode ? fxBibDistPrintDomPlainText($bodyNode) : ''; + + $fltBadge = 12; + $fltPad = 4.5; + $fltInnerLeft = $fltLeft + 6 + $fltBadge + 5; + $fltInnerW = ($fltLeft + $fltContentW) - $fltPad - $fltInnerLeft; + + $pdf->SetFont('Arial', 'B', 11); + $intTitleLines = max(1, (int)ceil($pdf->GetStringWidth(fxBibDistPrintPdfText($strTitle)) / max(1, $fltInnerW))); + $fltTitleH = $intTitleLines * 5.5; + $pdf->SetFont('Arial', '', 10); + $fltBodyH = 0; + if ($strBodyPlain !== '') { + $intBodyLines = max(1, (int)ceil($pdf->GetStringWidth(fxBibDistPrintPdfText($strBodyPlain)) / max(1, $fltInnerW))); + $fltBodyH = $intBodyLines * 4.8; + } + $fltCardH = max(24, $fltPad + $fltTitleH + 2 + $fltBodyH + $fltPad + 1); + + if ($pdf->GetY() + $fltCardH > $pdf->GetPageHeight() - $fltBottom) { + $pdf->AddPage(); + } + $fltY0 = $pdf->GetY(); + + $pdf->SetFillColor(247, 249, 252); + $pdf->SetDrawColor(200, 210, 228); + $pdf->Rect($fltLeft, $fltY0, $fltContentW, $fltCardH, 'FD'); + $pdf->SetFillColor(47, 95, 208); + $pdf->Rect($fltLeft, $fltY0, 3.2, $fltCardH, 'F'); + + $fltBadgeX = $fltLeft + 7; + $fltBadgeY = $fltY0 + ($fltCardH - $fltBadge) / 2; + $pdf->SetFillColor(47, 95, 208); + $pdf->Rect($fltBadgeX, $fltBadgeY, $fltBadge, $fltBadge, 'F'); + $pdf->SetTextColor(255, 255, 255); + $pdf->SetFont('Arial', 'B', 12); + $pdf->SetXY($fltBadgeX, $fltBadgeY + 2.8); + $pdf->Cell($fltBadge, 6, fxBibDistPrintPdfText($strNum), 0, 0, 'C'); + + $pdf->SetTextColor(25, 40, 70); + $pdf->SetFont('Arial', 'B', 11); + $pdf->SetXY($fltInnerLeft, $fltY0 + $fltPad); + $pdf->MultiCell($fltInnerW, 5.5, fxBibDistPrintPdfText($strTitle), 0, 'L'); + + if ($bodyNode) { + $pdf->SetTextColor(55, 65, 80); + $pdf->SetX($fltInnerLeft); + $pdf->SetLeftMargin($fltInnerLeft); + fxBibDistPrintWriteInline($pdf, $bodyNode, 4.8, 10); + $pdf->SetLeftMargin($fltLeft); + $pdf->Ln(2); + } + + $pdf->SetY($fltY0 + $fltCardH + 3.5); + $pdf->SetTextColor(0, 0, 0); + $pdf->SetX($fltLeft); + continue; + } + + // Autres blocs (ul/ol/img…) : rendu classique. + if ($strTag === 'p') { + // p après les cartes (rare) : texte simple. + $pdf->SetFont('Arial', '', 10); + $pdf->MultiCell($fltContentW, 5, fxBibDistPrintPdfText(fxBibDistPrintDomPlainText($el)), 0, 'L'); + $pdf->Ln(2); + $i++; + continue; + } + + fxBibDistPrintWriteHtml($pdf, $dom->saveHTML($el)); + $i++; + } +} + /** * MSIN-4471 — Rendu HTML simplifié (doc_html) vers FPDF. * Tags supportés : h2–h4, p, ul/ol/li, strong/b, em/i, br, img, blockquote, article/div/span. @@ -9715,7 +9981,9 @@ function fxBibDistPrintWriteHtml($pdf, $strHtml) { } } -/** MSIN-4471 — Dessine le QR résultats événement (kc.php?t=eve) centré. */ +/** + * MSIN-4471 — Dessine le QR résultats événement (kc.php?t=eve) centré. + */ function fxBibDistPrintDrawEventQr($pdf, $int_eve_id) { $int_eve_id = (int)$int_eve_id; if ($int_eve_id <= 0 || !function_exists('fxKcEveResultatsQrPngBinary')) { @@ -9763,18 +10031,25 @@ function fxBibOutputDistPrintIntroPages($pdf, $int_eve_id, $strLangue = 'fr') { $pdf->blnIntroMode = true; $pdf->eprTitle = ''; + $pdf->documentSubtitle = ''; $strDefaultDocTitle = fxBibTexte('bib_v4_dist_print_title', 0); + // Marges flyer : place pour bandeau + logo pied de page. + $pdf->SetMargins(12, 40, 12); + $pdf->SetAutoPageBreak(true, 18); + foreach (fxBibDistPrintIntroPageClefs() as $strClef) { $row = function_exists('fxDocGetPageRow') ? fxDocGetPageRow($strClef, 0) : array(); $strTitre = trim((string)($row['doc_titre'] ?? '')); + $strSous = trim((string)($row['doc_sous_titre'] ?? '')); $strHtml = trim((string)($row['doc_html'] ?? '')); if ($strTitre === '' && $strHtml === '') { continue; } $pdf->documentTitle = ($strTitre !== '') ? $strTitre : $strClef; + $pdf->documentSubtitle = $strSous; $pdf->AddPage(); - fxBibDistPrintWriteHtml($pdf, $strHtml); + fxBibDistPrintWriteIntroFlyer($pdf, $strHtml); if ($strClef === 'bib_dist_print_qr') { fxBibDistPrintDrawEventQr($pdf, $int_eve_id); } @@ -9782,7 +10057,10 @@ function fxBibOutputDistPrintIntroPages($pdf, $int_eve_id, $strLangue = 'fr') { $pdf->blnIntroMode = false; $pdf->documentTitle = $strDefaultDocTitle; + $pdf->documentSubtitle = ''; $pdf->eprTitle = ''; + $pdf->SetMargins(10, 12, 10); + $pdf->SetAutoPageBreak(true, 16); } /** MSIN-4433 — Génère et envoie le PDF (format lettre). */ @@ -9827,8 +10105,9 @@ function fxBibOutputDistPrintPdf($int_eve_id, $strLangue = 'fr', $strPrintedBy = public $pageText = 'Page %s / %s'; public $eventTitle = ''; public $documentTitle = ''; + public $documentSubtitle = ''; public $eprTitle = ''; - /** MSIN-4471 — Pages intro : pas de sous-titre épreuve. */ + /** MSIN-4471 — Pages intro : bandeau flyer, pas de sous-titre épreuve. */ public $blnIntroMode = false; /** MSIN-4471 — Accès public aux marges (protégées dans FPDF 1.82). */ @@ -9846,12 +10125,44 @@ function fxBibOutputDistPrintPdf($int_eve_id, $strLangue = 'fr', $strPrintedBy = } function Header() { + if ($this->blnIntroMode) { + $fltW = $this->GetPageWidth(); + // Bandeau flyer (bleu MS1 + filet or). + $this->SetFillColor(47, 95, 208); + $this->Rect(0, 0, $fltW, 32, 'F'); + $this->SetFillColor(255, 193, 7); + $this->Rect(0, 32, $fltW, 2, 'F'); + + $fltTextX = 12; + $strLogo = function_exists('fxBibDistPrintMs1LogoPath') ? fxBibDistPrintMs1LogoPath() : ''; + if ($strLogo !== '') { + $fltLogoW = 34; + $this->Image($strLogo, 10, 9, $fltLogoW); + $fltTextX = 10 + $fltLogoW + 5; + } + + $this->SetTextColor(255, 255, 255); + $this->SetXY($fltTextX, 6); + $this->SetFont('Arial', 'B', 14); + $this->Cell($fltW - $fltTextX - 10, 7, fxBibDistPrintPdfText($this->eventTitle), 0, 2, 'L'); + $this->SetFont('Arial', 'B', 12); + $this->Cell($fltW - $fltTextX - 10, 6, fxBibDistPrintPdfText($this->documentTitle), 0, 2, 'L'); + if (trim((string)$this->documentSubtitle) !== '') { + $this->SetFont('Arial', 'I', 9); + $this->SetTextColor(220, 230, 255); + $this->Cell($fltW - $fltTextX - 10, 5, fxBibDistPrintPdfText($this->documentSubtitle), 0, 2, 'L'); + } + $this->SetTextColor(0, 0, 0); + $this->SetY(38); + return; + } + $this->SetFont('Arial', 'B', 14); $this->Cell(0, 8, fxBibDistPrintPdfText($this->eventTitle), 0, 1, 'C'); - $this->SetFont('Arial', $this->blnIntroMode ? 'B' : '', $this->blnIntroMode ? 11 : 10); + $this->SetFont('Arial', '', 10); $this->Cell(0, 6, fxBibDistPrintPdfText($this->documentTitle), 0, 1, 'C'); $this->Ln(2); - if (!$this->blnIntroMode && $this->eprTitle !== '') { + if ($this->eprTitle !== '') { $this->SetFont('Arial', 'B', 11); $this->Cell(0, 7, fxBibDistPrintPdfText($this->eprTitle), 0, 1, 'L'); $this->Ln(1); @@ -9859,12 +10170,32 @@ function fxBibOutputDistPrintPdf($int_eve_id, $strLangue = 'fr', $strPrintedBy = } function Footer() { + $strLogo = function_exists('fxBibDistPrintMs1LogoPath') ? fxBibDistPrintMs1LogoPath() : ''; + $fltLogoW = 20; + $fltLogoReserve = ($strLogo !== '') ? ($fltLogoW + 6) : 0; + $fltPageW = $this->GetPageWidth(); + + if ($strLogo !== '') { + // Petit logo bas droite (toutes les pages). + $this->Image( + $strLogo, + $fltPageW - $this->rMargin - $fltLogoW, + $this->GetPageHeight() - 11, + $fltLogoW + ); + } + $this->SetY(-12); $this->SetFont('Arial', 'I', 7); - $this->Cell(0, 4, fxBibDistPrintPdfText($this->footerText), 0, 0, 'L'); - $this->SetX($this->lMargin); + $this->SetTextColor(90, 90, 90); + $fltTextW = $fltPageW - $this->lMargin - $this->rMargin - $fltLogoReserve - 22; + if ($fltTextW < 40) { + $fltTextW = 40; + } + $this->Cell($fltTextW, 4, fxBibDistPrintPdfText($this->footerText), 0, 0, 'L'); $strPage = sprintf($this->pageText, $this->PageNo(), '{nb}'); - $this->Cell(0, 4, fxBibDistPrintPdfText($strPage), 0, 0, 'R'); + $this->Cell(22, 4, fxBibDistPrintPdfText($strPage), 0, 0, 'R'); + $this->SetTextColor(0, 0, 0); } } } @@ -9875,15 +10206,16 @@ function fxBibOutputDistPrintPdf($int_eve_id, $strLangue = 'fr', $strPrintedBy = $pdf->eventTitle = fxBibDistPrintTruncate($strEventTitle, 80); $pdf->documentTitle = fxBibTexte('bib_v4_dist_print_title', 0); $pdf->SetMargins(10, 12, 10); - $pdf->SetAutoPageBreak(true, 14); + $pdf->SetAutoPageBreak(true, 16); $pdf->AliasNbPages(); // MSIN-4471 — Pages d'explication (doc) avant les tableaux d'épreuves. fxBibOutputDistPrintIntroPages($pdf, $int_eve_id, $strLangue); - // MSIN-4471 — Feuille bénévole : case remise + dossard gros + nom/prénom + ~20–25 lignes/page. + // MSIN-4471 — Feuille bénévole : recherche par nom, puis dossard à remettre. + // Ordre : ☐ | Nom, Prénom | Dossard | Sexe | questions $fltPageW = $pdf->GetPageWidth() - 20; - $fltColCheck = 9; + $fltColCheck = 10; $fltColBib = 28; $fltColSexe = 12; $fltFixedW = $fltColCheck + $fltColBib + $fltColSexe; @@ -9947,9 +10279,10 @@ function fxBibOutputDistPrintPdf($int_eve_id, $strLangue = 'fr', $strPrintedBy = $fnDrawTableHeader = function () use ($pdf, $fltColCheck, $fltColBib, $fltNameW, $fltColSexe, $fltQEach, $tabQueCols, $intNbQ, $fltHeadH) { $pdf->SetFont('Arial', 'B', 10); $pdf->SetFillColor(220, 220, 220); + // Case remise | Nom (recherche) | Dossard | Sexe | … $pdf->Cell($fltColCheck, $fltHeadH, fxBibDistPrintPdfText(fxBibTexte('bib_v4_dist_print_col_check', 0)), 1, 0, 'C', true); - $pdf->Cell($fltColBib, $fltHeadH, fxBibDistPrintPdfText(fxBibTexte('bib_v4_dist_print_col_bib', 0)), 1, 0, 'C', true); $pdf->Cell($fltNameW, $fltHeadH, fxBibDistPrintPdfText(fxBibTexte('bib_v4_dist_print_col_nom_prenom', 0)), 1, 0, 'L', true); + $pdf->Cell($fltColBib, $fltHeadH, fxBibDistPrintPdfText(fxBibTexte('bib_v4_dist_print_col_bib', 0)), 1, 0, 'C', true); $pdf->Cell($fltColSexe, $fltHeadH, fxBibDistPrintPdfText(fxBibTexte('bib_v4_dist_print_col_sexe', 0)), 1, $intNbQ === 0 ? 1 : 0, 'C', true); foreach ($tabQueCols as $i => $col) { $blnLast = ($i === $intNbQ - 1); @@ -9987,14 +10320,14 @@ function fxBibOutputDistPrintPdf($int_eve_id, $strLangue = 'fr', $strPrintedBy = $pdf->SetFont('Arial', '', 11); $pdf->Cell($fltColCheck, $fltRowH, '', 1, 0, 'C', $blnFill); - // Dossard — signal principal. + // Nom, Prénom — colonne de recherche principale. + $pdf->SetFont('Arial', 'B', 11); + $pdf->Cell($fltNameW, $fltRowH, fxBibDistPrintPdfText(fxBibDistPrintTruncate($strNomPrenom, 42)), 1, 0, 'L', $blnFill); + + // Dossard — numéro à remettre. $pdf->SetFont('Arial', 'B', 14); $pdf->Cell($fltColBib, $fltRowH, fxBibDistPrintPdfText((string)(int)($row['no_bib'] ?? 0)), 1, 0, 'C', $blnFill); - // Nom, Prénom. - $pdf->SetFont('Arial', '', 11); - $pdf->Cell($fltNameW, $fltRowH, fxBibDistPrintPdfText(fxBibDistPrintTruncate($strNomPrenom, 42)), 1, 0, 'L', $blnFill); - $pdf->SetFont('Arial', '', 11); $pdf->Cell($fltColSexe, $fltRowH, fxBibDistPrintPdfText(fxBibDistPrintSexeLabel($row['par_sexe'] ?? '', $strLangue)), 1, $intNbQ === 0 ? 1 : 0, 'C', $blnFill); diff --git a/sql/MSIN-4471-bib-dist-print-layout.sql b/sql/MSIN-4471-bib-dist-print-layout.sql index 4191867..2c27232 100644 --- a/sql/MSIN-4471-bib-dist-print-layout.sql +++ b/sql/MSIN-4471-bib-dist-print-layout.sql @@ -1,31 +1,32 @@ -- MSIN-4471 — Libellés feuille bénévole PDF distribution (nom+prénom, case remise) -- Prérequis : clés bib_v4_dist_print_* (MSIN-4433) --- Idempotent : INSERT si absent ; UPDATE libellé max questions si présent +-- Idempotent : INSERT si absent ; UPDATE si déjà présent avec texte vide SET NAMES utf8mb4; -INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_section, info_tri, info_actif, info_type, info_css, info_js, info_creation) -SELECT 'bib_v4_dist_print_col_nom_prenom', 'fr', 'Nom, Prénom', '', 'compte.php', '', 0, 1, '', '', '', NOW() +INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation) +SELECT 'bib_v4_dist_print_col_nom_prenom', 'fr', 'Nom, Prénom', '', 'compte.php', 'MSIN-4471', 0, 1, '', '', '', NOW() FROM DUAL WHERE NOT EXISTS ( SELECT 1 FROM info WHERE info_clef = 'bib_v4_dist_print_col_nom_prenom' AND info_langue = 'fr' AND info_prg = 'compte.php' ); -INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_section, info_tri, info_actif, info_type, info_css, info_js, info_creation) -SELECT 'bib_v4_dist_print_col_nom_prenom', 'en', 'Last name, First name', '', 'compte.php', '', 0, 1, '', '', '', NOW() +INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation) +SELECT 'bib_v4_dist_print_col_nom_prenom', 'en', 'Last name, First name', '', 'compte.php', 'MSIN-4471', 0, 1, '', '', '', NOW() FROM DUAL WHERE NOT EXISTS ( SELECT 1 FROM info WHERE info_clef = 'bib_v4_dist_print_col_nom_prenom' AND info_langue = 'en' AND info_prg = 'compte.php' ); -INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_section, info_tri, info_actif, info_type, info_css, info_js, info_creation) -SELECT 'bib_v4_dist_print_col_check', 'fr', '', '', 'compte.php', '', 0, 1, '', '', '', NOW() +INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation) +SELECT 'bib_v4_dist_print_col_check', 'fr', 'OK', '', 'compte.php', 'MSIN-4471', 0, 1, '', '', '', NOW() FROM DUAL WHERE NOT EXISTS ( SELECT 1 FROM info WHERE info_clef = 'bib_v4_dist_print_col_check' AND info_langue = 'fr' AND info_prg = 'compte.php' ); -INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_section, info_tri, info_actif, info_type, info_css, info_js, info_creation) -SELECT 'bib_v4_dist_print_col_check', 'en', '', '', 'compte.php', '', 0, 1, '', '', '', NOW() +INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation) +SELECT 'bib_v4_dist_print_col_check', 'en', 'OK', '', 'compte.php', 'MSIN-4471', 0, 1, '', '', '', NOW() FROM DUAL WHERE NOT EXISTS ( SELECT 1 FROM info WHERE info_clef = 'bib_v4_dist_print_col_check' AND info_langue = 'en' AND info_prg = 'compte.php' ); --- Le message max questions utilise %d via PHP (fxBibDistPrintMaxQuestions = 3) — pas de texte figé ici. +UPDATE info SET info_texte = 'OK', info_description = 'MSIN-4471' +WHERE info_clef = 'bib_v4_dist_print_col_check' AND info_prg = 'compte.php' AND (info_texte = '' OR info_texte IS NULL);