Files
ms1inscription-v5/php/inc_fx_dossard_print.php

379 lines
11 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* MSIN-4485 — Impression dossard PDF (1 participant).
*
* Format FINAL : demi-feuille Lettre coupée sur le 11"
* → papier 8.5" large × 5.5" haut (paysage)
* → PDF page = exactement cette taille
* → fond pleine page + n° / nom / QR alignés
*
* Impression : papier personnalisé 8.5 × 5.5, échelle 100 %, sans « ajuster à la page ».
*/
define('MSIN_DOSSARD_PRINT_PERM', 'inscriptions_gestion.dossard_print');
/**
* Taille page = feuille coupée (pouces → mm).
* Lettre 8.5×11 coupée en 2 sur le 11" → 8.5 × 5.5.
*
* @return array{0:float,1:float} [largeur, hauteur]
*/
function fxDossardPrintPageSizeMm()
{
return array(8.5 * 25.4, 5.5 * 25.4);
}
/** Dossier des fonds JPG/PNG (même ratio 8.5:5.5 recommandé). */
function fxDossardPrintTemplatesDir()
{
return dirname(__DIR__) . '/assets/dossard_templates';
}
/**
* Fond : eve_{id}.jpg|png sinon default.jpg|png.
*
* @return string|null
*/
function fxDossardPrintResolveTemplatePath($intEveId)
{
$strDir = fxDossardPrintTemplatesDir();
$intEveId = intval($intEveId);
$arrCandidates = array();
if ($intEveId > 0) {
$arrCandidates[] = $strDir . '/eve_' . $intEveId . '.jpg';
$arrCandidates[] = $strDir . '/eve_' . $intEveId . '.jpeg';
$arrCandidates[] = $strDir . '/eve_' . $intEveId . '.png';
}
$arrCandidates[] = $strDir . '/default.jpg';
$arrCandidates[] = $strDir . '/default.jpeg';
$arrCandidates[] = $strDir . '/default.png';
foreach ($arrCandidates as $strPath) {
if (is_file($strPath)) {
return $strPath;
}
}
return null;
}
/**
* Type FPDF d'après le fichier réel.
*
* @return string JPG|PNG|GIF|''
*/
function fxDossardPrintImageType($strPath)
{
$arrInfo = @getimagesize($strPath);
if (!is_array($arrInfo) || empty($arrInfo[2])) {
return '';
}
if ((int)$arrInfo[2] === IMAGETYPE_JPEG) {
return 'JPG';
}
if ((int)$arrInfo[2] === IMAGETYPE_PNG) {
return 'PNG';
}
if ((int)$arrInfo[2] === IMAGETYPE_GIF) {
return 'GIF';
}
return '';
}
/** Texte FPDF (core fonts = Windows-1252). */
function fxDossardPrintPdfText($str)
{
$str = (string)$str;
if (function_exists('iconv')) {
$strConverted = @iconv('UTF-8', 'Windows-1252//TRANSLIT', $str);
if ($strConverted !== false) {
return $strConverted;
}
}
if (function_exists('mb_convert_encoding')) {
return (string)mb_convert_encoding($str, 'Windows-1252', 'UTF-8');
}
return $str;
}
/** Droit impression (hors kit total). */
function fxDossardPrintCan($intComId, $intEveId)
{
if (!function_exists('fxEveAccesHasPermission')) {
require_once __DIR__ . '/inc_fx_eve_acces.php';
}
return fxEveAccesHasPermission(intval($intComId), intval($intEveId), MSIN_DOSSARD_PRINT_PERM);
}
/**
* @return array|null
*/
function fxDossardPrintLoadParticipant($intParId, $intEveId)
{
global $objDatabase;
$intParId = intval($intParId);
$intEveId = intval($intEveId);
if ($intParId <= 0 || $intEveId <= 0) {
return null;
}
$sql = 'SELECT p.par_id, p.par_prenom, p.par_nom, p.no_bib, p.eve_id, p.epr_id, p.pec_id,
e.pec_id_original, e.pec_actif, e.is_cancelled
FROM resultats_participants p
INNER JOIN resultats_epreuves_commandees e
ON e.pec_id_original = p.pec_id AND e.epr_id = p.epr_id
WHERE p.par_id = ' . $intParId . '
AND p.eve_id = ' . $intEveId . '
LIMIT 1';
$row = $objDatabase->fxGetRow($sql);
return ($row === null || $row === false) ? null : $row;
}
function fxDossardPrintQrUrl($intEveId)
{
if (!function_exists('fxKcEveResultatsQrUrl')) {
require_once __DIR__ . '/inc_fx_eve_extra.php';
}
return fxKcEveResultatsQrUrl(intval($intEveId));
}
/**
* @return string|false chemin PNG temp
*/
function fxDossardPrintBuildQrTempFile($intEveId)
{
require_once __DIR__ . '/../libs/phpqrcode-master/qrlib.php';
$strUrl = fxDossardPrintQrUrl($intEveId);
if ($strUrl === '') {
return false;
}
$strTmp = tempnam(sys_get_temp_dir(), 'ms1dossardqr_');
if ($strTmp === false) {
return false;
}
$strPng = $strTmp . '.png';
@unlink($strTmp);
QRcode::png($strUrl, $strPng, QR_ECLEVEL_M, 6, 1);
if (!is_file($strPng)) {
return false;
}
return $strPng;
}
/**
* Layout page 8.5" × 5.5" — zone imprimable en % de page.
*
* Zone = rectangle central (n° + prénom).
* QR ancré bas-droite (hors zone).
* Recalibrer le fond : seulement zone_*_pct.
*
* @return array<string,mixed>
*/
function fxDossardPrintLayout()
{
$arrSize = fxDossardPrintPageSizeMm();
$fltW = $arrSize[0];
$fltH = $arrSize[1];
// Zone blanche centrale sous le titre (fractions 01)
$fltZoneLeftPct = 0.20;
$fltZoneRightPct = 0.78;
$fltZoneTopPct = 0.28;
$fltZoneBottomPct = 0.82;
$fltZoneX = $fltW * $fltZoneLeftPct;
$fltZoneY = $fltH * $fltZoneTopPct;
$fltZoneW = $fltW * ($fltZoneRightPct - $fltZoneLeftPct);
$fltZoneH = $fltH * ($fltZoneBottomPct - $fltZoneTopPct);
$fltQr = 28.0;
return array(
'page_w' => $fltW,
'page_h' => $fltH,
'zone_x' => $fltZoneX,
'zone_y' => $fltZoneY,
'zone_w' => $fltZoneW,
'zone_h' => $fltZoneH,
// mm de marge sous le bas réel du glyphe du n°
'name_gap_mm' => 1.5,
'name_line_h' => 6.5,
'name_font' => 18,
// Facteur hauteur glyphe Helvetica Bold (mm par point de police)
'glyph_mm_per_pt' => 0.30,
'bib_font_max' => 200,
'qr_size' => $fltQr,
'qr_x' => $fltW - $fltQr - 11.5,
'qr_y' => $fltH - $fltQr - 14.5,
);
}
/**
* Plus grande police Helvetica Bold qui tient en largeur ET en hauteur (mm).
*
* @param FPDF $pdf
* @param string $strBib
* @param float $fltMaxW
* @param float $fltMaxH
* @param int $intFontMax
* @param float $fltMmPerPt
* @return int
*/
function fxDossardPrintFitBibFont($pdf, $strBib, $fltMaxW, $fltMaxH, $intFontMax, $fltMmPerPt = 0.30)
{
$strPdf = fxDossardPrintPdfText($strBib);
$fltMmPerPt = ($fltMmPerPt > 0) ? $fltMmPerPt : 0.30;
$intCapByH = ($fltMaxH > 0) ? (int)floor($fltMaxH / $fltMmPerPt) : intval($intFontMax);
$intHi = max(24, min(intval($intFontMax), $intCapByH));
$intLo = 24;
$intBest = $intLo;
while ($intLo <= $intHi) {
$intMid = (int)floor(($intLo + $intHi) / 2);
$pdf->SetFont('Helvetica', 'B', $intMid);
$fltW = $pdf->GetStringWidth($strPdf);
if ($fltW <= $fltMaxW) {
$intBest = $intMid;
$intLo = $intMid + 1;
} else {
$intHi = $intMid - 1;
}
}
return $intBest;
}
/**
* Génère et envoie le PDF (exit).
*
* @param array $arrRow
*/
function fxDossardPrintOutputPdf($arrRow)
{
$intEveId = intval($arrRow['eve_id'] ?? 0);
$strTemplate = fxDossardPrintResolveTemplatePath($intEveId);
if ($strTemplate === null) {
http_response_code(500);
header('Content-Type: text/plain; charset=utf-8');
echo 'Template dossard introuvable (assets/dossard_templates/default.jpg).';
exit;
}
$strFpdf = dirname(__DIR__) . '/fpdf182/fpdf.php';
if (!is_file($strFpdf)) {
$strDocRoot = $_SERVER['DOCUMENT_ROOT'] ?? '';
$strFpdf = rtrim((string)$strDocRoot, '/\\') . '/fpdf182/fpdf.php';
}
if (!is_file($strFpdf)) {
http_response_code(500);
header('Content-Type: text/plain; charset=utf-8');
echo 'FPDF introuvable (fpdf182/fpdf.php).';
exit;
}
require_once $strFpdf;
$arrLayout = fxDossardPrintLayout();
$strBib = trim((string)($arrRow['no_bib'] ?? ''));
if ($strBib === '' || $strBib === '0') {
$strBib = '-';
}
// MSIN-4485 — prénom seul, collé sous le n°, aligné sur le bord gauche du chiffre.
$strName = trim(function_exists('fxUnescape') ? fxUnescape($arrRow['par_prenom'] ?? '') : (string)($arrRow['par_prenom'] ?? ''));
$blnHasName = ($strName !== '');
$strQrFile = fxDossardPrintBuildQrTempFile($intEveId);
$strTplType = fxDossardPrintImageType($strTemplate);
$pdf = new FPDF('L', 'mm', array($arrLayout['page_w'], $arrLayout['page_h']));
$pdf->SetAutoPageBreak(false);
$pdf->SetMargins(0, 0, 0);
$pdf->AddPage();
$pdf->Image(
$strTemplate,
0,
0,
$arrLayout['page_w'],
$arrLayout['page_h'],
$strTplType
);
$fltMmPerPt = $arrLayout['glyph_mm_per_pt'];
$fltReserveName = $blnHasName
? ($arrLayout['name_line_h'] + $arrLayout['name_gap_mm'])
: 0;
// Hauteur max du n° = zone prénom (le n° remplit le reste)
$fltBibMaxH = max(20, $arrLayout['zone_h'] - $fltReserveName);
$pdf->SetTextColor(0, 0, 0);
$intBibFont = fxDossardPrintFitBibFont(
$pdf,
$strBib,
$arrLayout['zone_w'],
$fltBibMaxH,
$arrLayout['bib_font_max'],
$fltMmPerPt
);
$pdf->SetFont('Helvetica', 'B', $intBibFont);
$strBibPdf = fxDossardPrintPdfText($strBib);
$fltBibTextW = $pdf->GetStringWidth($strBibPdf);
// Hauteur réelle du glyphe (pas une Cell géante vide en dessous)
$fltGlyphH = $intBibFont * $fltMmPerPt;
// Bloc n°+prénom centré verticalement dans la zone
$fltBlockH = $fltGlyphH + ($blnHasName ? ($arrLayout['name_gap_mm'] + $arrLayout['name_line_h']) : 0);
$fltBlockY = $arrLayout['zone_y'] + max(0, ($arrLayout['zone_h'] - $fltBlockH) / 2);
// N° centré dans la zone ; X gauche du premier chiffre = ancrage du prénom
$fltBibX = $arrLayout['zone_x'] + ($arrLayout['zone_w'] - $fltBibTextW) / 2;
$fltBibY = $fltBlockY;
// Cell hauteur = glyphe seulement → pas d'espace mort sous le chiffre
$pdf->SetXY($fltBibX, $fltBibY);
$pdf->Cell($fltBibTextW, $fltGlyphH, $strBibPdf, 0, 0, 'L');
if ($blnHasName) {
$fltNameY = $fltBibY + $fltGlyphH + $arrLayout['name_gap_mm'];
$pdf->SetFont('Helvetica', 'B', intval($arrLayout['name_font']));
$pdf->SetXY($fltBibX, $fltNameY);
$pdf->Cell(
$arrLayout['zone_w'],
$arrLayout['name_line_h'],
fxDossardPrintPdfText($strName),
0,
0,
'L'
);
}
if ($strQrFile !== false) {
$pdf->Image(
$strQrFile,
$arrLayout['qr_x'],
$arrLayout['qr_y'],
$arrLayout['qr_size'],
$arrLayout['qr_size'],
'PNG'
);
@unlink($strQrFile);
}
$strSafeBib = preg_replace('/[^a-zA-Z0-9_-]+/', '-', $strBib);
$strFilename = 'dossard-' . $strSafeBib . '-par' . intval($arrRow['par_id']) . '.pdf';
$pdf->Output('I', $strFilename);
exit;
}