Files
ms1inscription-v5/php/inc_fx_dossard_print.php

378 lines
10 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 où vivent n° + prénom.
* QR ancré bas-droite (hors zone).
*
* Pour recalibrer le fond comic : ajuster seulement zone_*_pct.
*
* @return array<string,mixed>
*/
function fxDossardPrintLayout()
{
$arrSize = fxDossardPrintPageSizeMm();
$fltW = $arrSize[0]; // 215.9
$fltH = $arrSize[1]; // 139.7
// --- Zone imprimable (fractions 01 de la page) ---
// Gauche / droite / haut / bas du blanc central sous le titre.
$fltZoneLeftPct = 0.22;
$fltZoneRightPct = 0.76;
$fltZoneTopPct = 0.30;
$fltZoneBottomPct = 0.78;
$fltZoneX = $fltW * $fltZoneLeftPct;
$fltZoneY = $fltH * $fltZoneTopPct;
$fltZoneW = $fltW * ($fltZoneRightPct - $fltZoneLeftPct);
$fltZoneH = $fltH * ($fltZoneBottomPct - $fltZoneTopPct);
// Prénom collé sous le n° (règle fixe, pas un Y magique)
$fltNameGapMm = 2.0;
$fltNameLineH = 7.0;
$intNameFont = 18;
$fltQr = 28.0;
return array(
'page_w' => $fltW,
'page_h' => $fltH,
'zone_x' => $fltZoneX,
'zone_y' => $fltZoneY,
'zone_w' => $fltZoneW,
'zone_h' => $fltZoneH,
'name_gap_mm' => $fltNameGapMm,
'name_line_h' => $fltNameLineH,
'name_font' => $intNameFont,
'bib_font_max' => 180,
'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
* @return int
*/
function fxDossardPrintFitBibFont($pdf, $strBib, $fltMaxW, $fltMaxH, $intFontMax)
{
$strPdf = fxDossardPrintPdfText($strBib);
// Hauteur glyphe ≈ 0.30 mm par point (Helvetica Bold majuscules / chiffres)
$intCapByH = ($fltMaxH > 0) ? (int)floor($fltMaxH / 0.30) : 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, aligné à gauche, collé sous le n°.
$strName = trim(function_exists('fxUnescape') ? fxUnescape($arrRow['par_prenom'] ?? '') : (string)($arrRow['par_prenom'] ?? ''));
$blnHasName = ($strName !== '');
$strQrFile = fxDossardPrintBuildQrTempFile($intEveId);
$strTplType = fxDossardPrintImageType($strTemplate);
// Page = feuille coupée 8.5" × 5.5".
// FPDF (_getpagesize) range si w>h ; 'L' remet w=8.5 h=5.5.
$pdf = new FPDF('L', 'mm', array($arrLayout['page_w'], $arrLayout['page_h']));
$pdf->SetAutoPageBreak(false);
$pdf->SetMargins(0, 0, 0);
$pdf->AddPage();
// Fond pleine page
$pdf->Image(
$strTemplate,
0,
0,
$arrLayout['page_w'],
$arrLayout['page_h'],
$strTplType
);
// Hauteur dispo pour le n° = zone (prénom + gap) si prénom présent
$fltReserveName = $blnHasName
? ($arrLayout['name_line_h'] + $arrLayout['name_gap_mm'])
: 0;
$fltBibAreaH = max(20, $arrLayout['zone_h'] - $fltReserveName);
$pdf->SetTextColor(0, 0, 0);
$intBibFont = fxDossardPrintFitBibFont(
$pdf,
$strBib,
$arrLayout['zone_w'],
$fltBibAreaH,
$arrLayout['bib_font_max']
);
$pdf->SetFont('Helvetica', 'B', $intBibFont);
// Cell hauteur = zone n° : le glyphe remplit verticalement autant que possible
$pdf->SetXY($arrLayout['zone_x'], $arrLayout['zone_y']);
$pdf->Cell(
$arrLayout['zone_w'],
$fltBibAreaH,
fxDossardPrintPdfText($strBib),
0,
0,
'C'
);
// Prénom : 2 mm sous le bas de la cellule du n°, gauche de la zone
if ($blnHasName) {
$fltNameY = $arrLayout['zone_y'] + $fltBibAreaH + $arrLayout['name_gap_mm'];
$pdf->SetFont('Helvetica', 'B', intval($arrLayout['name_font']));
$pdf->SetXY($arrLayout['zone_x'], $fltNameY);
$pdf->Cell(
$arrLayout['zone_w'],
$arrLayout['name_line_h'],
fxDossardPrintPdfText($strName),
0,
0,
'L'
);
}
// QR dans la case du template
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;
}