PDF Erstellung anpassung
This commit is contained in:
+151
-95
@@ -38,99 +38,80 @@ $footerText = trim((string)($settings['pdf_footer_text'] ?? ''));
|
||||
// Ab dieser Mitgliederzahl wird auf zwei Seiten (Vorder-/Rueckseite) statt
|
||||
// einer einzelnen Seite gedruckt.
|
||||
define('EXPORT_MULTI_PAGE_THRESHOLD', 50);
|
||||
// Platzbudget in TCPDF-HTML-"px" fuer die Datenzeilen einer Seite, kalibriert
|
||||
// an der bisherigen Ausgabe (63 Zeilen a 16px auf einer vollen A4-Seite). Ist
|
||||
// eine Fusszeile konfiguriert, wird ihr reservierter Platz abgezogen, damit
|
||||
// die letzte Tabellenzeile sie nicht ueberlappt.
|
||||
define('EXPORT_FOOTER_RESERVE_PX', 40);
|
||||
define('EXPORT_PAGE_ROW_BUDGET_PX', (63 * 16) - ($footerText !== '' ? EXPORT_FOOTER_RESERVE_PX : 0));
|
||||
// Technischer Mindestwert, nur als letzte Bremse gegen 0/negative Hoehen bei
|
||||
// absurd hohen Teilnehmerzahlen - kein gestalterisches Minimum.
|
||||
define('EXPORT_HARD_MIN_ROW_HEIGHT_PX', 6);
|
||||
// TCPDF's writeHTML() ignoriert CSS-Zeilenhoehen/-innenabstaende auf
|
||||
// Tabellenzeilen vollstaendig (getestet: weder "height" auf <tr> noch
|
||||
// "padding" auf <td> haben irgendeinen Einfluss auf die gerenderte Zeilen-
|
||||
// hoehe). Deshalb wird die Tabelle unten mit TCPDFs nativer Cell()-Methode
|
||||
// gezeichnet, die den Hoehen-Parameter zuverlaessig exakt umsetzt.
|
||||
define('EXPORT_PAGE_WIDTH_MM', 200.0); // A4 (210mm) minus 5mm Rand links/rechts
|
||||
define('EXPORT_COL_NAME_MM', 40.0);
|
||||
define('EXPORT_COL_BALANCE_MM', 20.0);
|
||||
define('EXPORT_COL_MARKS_MM', EXPORT_PAGE_WIDTH_MM - EXPORT_COL_NAME_MM - EXPORT_COL_BALANCE_MM);
|
||||
define('EXPORT_TITLE_ROW_MM', 8.0);
|
||||
define('EXPORT_HEADER_ROW_MM', 6.0);
|
||||
define('EXPORT_HINT_ROW_MM', 6.0);
|
||||
// 1px (CSS/96dpi) = 25.4/96 mm - fuer die Umrechnung der Admin-Einstellung
|
||||
// (weiterhin in "px" gepflegt, siehe mandant-einstellungen.php) in mm.
|
||||
define('EXPORT_PX_TO_MM', 25.4 / 96);
|
||||
define('EXPORT_HARD_MIN_ROW_HEIGHT_MM', 2.0);
|
||||
// TCPDFs Cell() verbraucht pro gezeichneter Zeile durchgaengig etwas mehr
|
||||
// vertikalen Platz als die angeforderte Hoehe (mutmasslich die
|
||||
// Rahmenlinienbreite), gemessen ueber 61 Testzeilen a 4.2333mm: ca. 0,157mm
|
||||
// Overhead je Zeile. Ohne Beruecksichtigung summiert sich das ueber viele
|
||||
// Zeilen zu mehreren mm und kann einen ungewollten Seitenumbruch ausloesen.
|
||||
define('EXPORT_ROW_OVERHEAD_MM', 0.2);
|
||||
|
||||
$allActive = ledger_fetch_participant_summaries($pdo, $tenantId, ['active_only' => true]);
|
||||
$memberCount = count($allActive);
|
||||
$isMultiPage = $memberCount >= EXPORT_MULTI_PAGE_THRESHOLD;
|
||||
|
||||
/**
|
||||
* Ermittelt Zeilenanzahl und -hoehe fuer eine Seite: Ausgangspunkt ist die
|
||||
* vom Admin gewuenschte Zeilenhoehe. Sind Fuellzeilen erwuenscht, wird die
|
||||
* Seite bis zu dieser Hoehe komplett aufgefuellt (weniger Mitglieder ->
|
||||
* Verfuegbare Hoehe (mm) fuer die Datenzeilen einer A4-Seite: Gesamthoehe
|
||||
* minus oberem/unterem Rand, Titelzeile, Spaltenkopf und optional
|
||||
* reservierter Fusszeile bzw. Vorder-/Rueckseiten-Hinweiszeile.
|
||||
*/
|
||||
function export_page_row_budget_mm(bool $hasFooter, bool $hasSeitenHinweis): float
|
||||
{
|
||||
// Kleiner zusaetzlicher Sicherheitspuffer obendrauf (der zeilenweise
|
||||
// Cell()-Overhead wird bereits separat in export_compute_layout()
|
||||
// eingerechnet).
|
||||
$budget = 297.0 - 5.0 - 5.0 - EXPORT_TITLE_ROW_MM - EXPORT_HEADER_ROW_MM - 2.0;
|
||||
if ($hasFooter) {
|
||||
$budget -= 12.0;
|
||||
}
|
||||
if ($hasSeitenHinweis) {
|
||||
$budget -= EXPORT_HINT_ROW_MM;
|
||||
}
|
||||
|
||||
return max(EXPORT_HARD_MIN_ROW_HEIGHT_MM, $budget);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ermittelt Zeilenanzahl und -hoehe (mm) fuer eine Seite: Ausgangspunkt ist
|
||||
* die vom Admin gewuenschte Zeilenhoehe. Sind Fuellzeilen erwuenscht, wird
|
||||
* die Seite bis zu dieser Hoehe komplett aufgefuellt (weniger Mitglieder ->
|
||||
* groessere, statt immer gleich viele Zeilen). Passt die Mitgliederzahl bei
|
||||
* der gewuenschten Hoehe nicht auf eine Seite, wird bei jeder Generierung
|
||||
* automatisch so weit verkleinert, dass garantiert alle Teilnehmer passen.
|
||||
*
|
||||
* @return array{0: int, 1: float} [targetRows, rowHeightPx]
|
||||
* @return array{0: int, 1: float} [targetRows, rowHeightMm]
|
||||
*/
|
||||
function export_compute_layout(int $memberCount, bool $showEmptyRows, int $adminRowHeightPx): array
|
||||
function export_compute_layout(int $memberCount, bool $showEmptyRows, int $adminRowHeightPx, float $pageRowBudgetMm): array
|
||||
{
|
||||
$adminRowHeightPx = max(1, $adminRowHeightPx);
|
||||
$capacityAtAdminHeight = (int)floor(EXPORT_PAGE_ROW_BUDGET_PX / $adminRowHeightPx);
|
||||
$adminRowHeightMm = max(0.1, $adminRowHeightPx * EXPORT_PX_TO_MM);
|
||||
// Jede Zeile verbraucht real "Hoehe + Overhead" (siehe EXPORT_ROW_OVERHEAD_MM),
|
||||
// das muss bei der Kapazitaets- und Hoehenberechnung mit einfliessen.
|
||||
$capacityAtAdminHeight = (int)floor($pageRowBudgetMm / ($adminRowHeightMm + EXPORT_ROW_OVERHEAD_MM));
|
||||
|
||||
$targetRows = $showEmptyRows ? max($memberCount, $capacityAtAdminHeight) : $memberCount;
|
||||
$targetRows = max(1, $targetRows);
|
||||
|
||||
$rowHeight = min((float)$adminRowHeightPx, EXPORT_PAGE_ROW_BUDGET_PX / $targetRows);
|
||||
$rowHeight = max(EXPORT_HARD_MIN_ROW_HEIGHT_PX, $rowHeight);
|
||||
$rowHeight = min($adminRowHeightMm, ($pageRowBudgetMm / $targetRows) - EXPORT_ROW_OVERHEAD_MM);
|
||||
$rowHeight = max(EXPORT_HARD_MIN_ROW_HEIGHT_MM, $rowHeight);
|
||||
|
||||
return [$targetRows, $rowHeight];
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendert eine Teilnehmerseite als HTML-Tabelle fuer TCPDF.
|
||||
*
|
||||
* @param list<array> $teilnehmer
|
||||
*/
|
||||
function export_render_page_html(array $teilnehmer, string $titel, string $hinweisText, string $markPrice, bool $showEmptyRows, int $adminRowHeightPx, ?string $seitenHinweis): string
|
||||
{
|
||||
[$targetRows, $rowHeight] = export_compute_layout(count($teilnehmer), $showEmptyRows, $adminRowHeightPx);
|
||||
|
||||
$html = '
|
||||
<table border="1" style="white-space:nowrap;">
|
||||
<tr style="height: 25px;' . $hinweisText . '" valign="bottom">
|
||||
<th style="width:30%;font-size: 13px;" colspan="2"><b>' . $titel . '</b></th>
|
||||
<th style="width:70%;" valign="bottom">1 Strich = ' . $markPrice . '€. Bitte bezahlen bei 10 € zahlen. ' . date("d.m.Y H:s") . '</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="width:20%"><b>Name</b></td>
|
||||
<td style="width:10%"><b>Guthaben</b></td>
|
||||
<td><b>Striche</b></td>
|
||||
</tr>';
|
||||
|
||||
$rowCount = 0;
|
||||
foreach ($teilnehmer as $person) {
|
||||
$rowCount++;
|
||||
$name = saas_html($person['display_name']);
|
||||
$balanceCents = (int)$person['balance_cents'];
|
||||
$balance = number_format($balanceCents / 100, 2, ',', '.');
|
||||
|
||||
$html .= '<tr style="height: ' . $rowHeight . 'px;">';
|
||||
$html .= "<td>{$name}</td>";
|
||||
if ($balanceCents < -1000) {
|
||||
$html .= '<td style="background-color: rgb(204, 0, 0); color: rgb(255, 255, 255);">' . $balance . ' €</td>';
|
||||
} elseif ($balanceCents > 500) {
|
||||
$html .= '<td style="background-color: rgb(0, 102, 0); color: rgb(255, 255, 255);">' . $balance . ' €</td>';
|
||||
} else {
|
||||
$html .= "<td>{$balance} €</td>";
|
||||
}
|
||||
$html .= "<td></td>";
|
||||
$html .= "</tr>";
|
||||
}
|
||||
|
||||
if ($showEmptyRows) {
|
||||
for ($i = $rowCount; $i < $targetRows; $i++) {
|
||||
$html .= '<tr style="height: ' . $rowHeight . 'px;"><td> </td><td> </td><td> </td></tr>';
|
||||
}
|
||||
}
|
||||
|
||||
if ($seitenHinweis !== null) {
|
||||
$html .= '<tr><td> </td><td> </td><td style="background-color: rgb(255, 255, 0);text-align: right;"><b>' . $seitenHinweis . '</b></td></tr>';
|
||||
}
|
||||
$html .= '</table>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
// TCPDF-Bibliothek einbinden
|
||||
require_once(__DIR__ . '/TCPDF/tcpdf.php');
|
||||
|
||||
@@ -146,15 +127,25 @@ class MyCustomPDFWithWatermark extends TCPDF {
|
||||
$bMargin = $this->getBreakMargin();
|
||||
$auto_page_break = $this->AutoPageBreak;
|
||||
$this->SetAutoPageBreak(false, 0);
|
||||
$x = $this->x;
|
||||
$y = $this->y;
|
||||
|
||||
$this->SetAlpha(0.35);
|
||||
$this->SetFont('helvetica', '', 14);
|
||||
$this->SetTextColor(0, 0, 0);
|
||||
foreach ([40, 140, 240] as $y) {
|
||||
$this->MultiCell(90, 0, $this->watermarkText, 0, 'L', false, 1, 110, $y);
|
||||
foreach ([40, 140, 240] as $wy) {
|
||||
$this->MultiCell(90, 0, $this->watermarkText, 0, 'L', false, 1, 110, $wy);
|
||||
}
|
||||
$this->SetAlpha(1);
|
||||
$this->SetFont('helvetica', '', 9.5);
|
||||
$this->SetTextColor(0, 0, 0);
|
||||
|
||||
// MultiCell(..., $ln=1, ...) verschiebt den Seiten-Cursor als
|
||||
// Nebeneffekt - ohne dieses Zuruecksetzen wuerde der eigentliche
|
||||
// Tabelleninhalt an der Position des letzten Wasserzeichen-Absatzes
|
||||
// statt oben auf der Seite beginnen.
|
||||
$this->x = $x;
|
||||
$this->y = $y;
|
||||
|
||||
$this->SetAutoPageBreak($auto_page_break, $bMargin);
|
||||
$this->setPageMark();
|
||||
@@ -173,6 +164,88 @@ class MyCustomPDFWithWatermark extends TCPDF {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeichnet eine Teilnehmerseite mit TCPDFs nativer Cell()-Methode (siehe
|
||||
* Kommentar oben zu EXPORT_PAGE_WIDTH_MM: writeHTML() ignoriert Zeilenhoehen
|
||||
* auf <tr>/<td> komplett, Cell() setzt die uebergebene Hoehe dagegen exakt
|
||||
* um).
|
||||
*
|
||||
* @param list<array> $teilnehmer
|
||||
* @param array{0:int,1:int,2:int} $titelFarbe Hintergrundfarbe der Titelzeile
|
||||
* @param array{0:int,1:int,2:int} $titelTextFarbe Textfarbe der Titelzeile
|
||||
*/
|
||||
function export_render_page(
|
||||
TCPDF $pdf,
|
||||
array $teilnehmer,
|
||||
string $titel,
|
||||
array $titelFarbe,
|
||||
array $titelTextFarbe,
|
||||
string $markPrice,
|
||||
bool $showEmptyRows,
|
||||
int $adminRowHeightPx,
|
||||
?string $seitenHinweis
|
||||
): void {
|
||||
$rowBudgetMm = export_page_row_budget_mm(trim($pdf->footerText) !== '', $seitenHinweis !== null);
|
||||
[$targetRows, $rowHeight] = export_compute_layout(count($teilnehmer), $showEmptyRows, $adminRowHeightPx, $rowBudgetMm);
|
||||
|
||||
// Titelzeile: Name/Guthaben-Breite + Strichebreite, Hintergrundfarbe je nach Seite.
|
||||
$pdf->SetFont('helvetica', 'B', 13);
|
||||
$pdf->SetFillColor($titelFarbe[0], $titelFarbe[1], $titelFarbe[2]);
|
||||
$pdf->SetTextColor($titelTextFarbe[0], $titelTextFarbe[1], $titelTextFarbe[2]);
|
||||
$pdf->Cell(EXPORT_COL_NAME_MM + EXPORT_COL_BALANCE_MM, EXPORT_TITLE_ROW_MM, $titel, 1, 0, 'L', true);
|
||||
$pdf->SetFont('helvetica', '', 9);
|
||||
$hinweisText = '1 Strich = ' . $markPrice . '€. Bitte bezahlen bei 10 € zahlen. ' . date('d.m.Y H:i');
|
||||
$pdf->Cell(EXPORT_COL_MARKS_MM, EXPORT_TITLE_ROW_MM, $hinweisText, 1, 1, 'L', true);
|
||||
$pdf->SetTextColor(0, 0, 0);
|
||||
|
||||
// Spaltenkopf.
|
||||
$pdf->SetFont('helvetica', 'B', 9.5);
|
||||
$pdf->Cell(EXPORT_COL_NAME_MM, EXPORT_HEADER_ROW_MM, 'Name', 1, 0, 'L');
|
||||
$pdf->Cell(EXPORT_COL_BALANCE_MM, EXPORT_HEADER_ROW_MM, 'Guthaben', 1, 0, 'L');
|
||||
$pdf->Cell(EXPORT_COL_MARKS_MM, EXPORT_HEADER_ROW_MM, 'Striche', 1, 1, 'L');
|
||||
|
||||
// Datenzeilen.
|
||||
$pdf->SetFont('helvetica', '', 9.5);
|
||||
$rowCount = 0;
|
||||
foreach ($teilnehmer as $person) {
|
||||
$rowCount++;
|
||||
$name = (string)$person['display_name'];
|
||||
$balanceCents = (int)$person['balance_cents'];
|
||||
$balance = number_format($balanceCents / 100, 2, ',', '.') . ' €';
|
||||
|
||||
$pdf->Cell(EXPORT_COL_NAME_MM, $rowHeight, $name, 1, 0, 'L');
|
||||
if ($balanceCents < -1000) {
|
||||
$pdf->SetFillColor(204, 0, 0);
|
||||
$pdf->SetTextColor(255, 255, 255);
|
||||
$pdf->Cell(EXPORT_COL_BALANCE_MM, $rowHeight, $balance, 1, 0, 'L', true);
|
||||
$pdf->SetTextColor(0, 0, 0);
|
||||
} elseif ($balanceCents > 500) {
|
||||
$pdf->SetFillColor(0, 102, 0);
|
||||
$pdf->SetTextColor(255, 255, 255);
|
||||
$pdf->Cell(EXPORT_COL_BALANCE_MM, $rowHeight, $balance, 1, 0, 'L', true);
|
||||
$pdf->SetTextColor(0, 0, 0);
|
||||
} else {
|
||||
$pdf->Cell(EXPORT_COL_BALANCE_MM, $rowHeight, $balance, 1, 0, 'L');
|
||||
}
|
||||
$pdf->Cell(EXPORT_COL_MARKS_MM, $rowHeight, '', 1, 1, 'L');
|
||||
}
|
||||
|
||||
if ($showEmptyRows) {
|
||||
for ($i = $rowCount; $i < $targetRows; $i++) {
|
||||
$pdf->Cell(EXPORT_COL_NAME_MM, $rowHeight, '', 1, 0, 'L');
|
||||
$pdf->Cell(EXPORT_COL_BALANCE_MM, $rowHeight, '', 1, 0, 'L');
|
||||
$pdf->Cell(EXPORT_COL_MARKS_MM, $rowHeight, '', 1, 1, 'L');
|
||||
}
|
||||
}
|
||||
|
||||
if ($seitenHinweis !== null) {
|
||||
$pdf->Cell(EXPORT_COL_NAME_MM + EXPORT_COL_BALANCE_MM, EXPORT_HINT_ROW_MM, '', 1, 0, 'L');
|
||||
$pdf->SetFont('helvetica', 'B', 9.5);
|
||||
$pdf->SetFillColor(255, 255, 0);
|
||||
$pdf->Cell(EXPORT_COL_MARKS_MM, EXPORT_HINT_ROW_MM, $seitenHinweis, 1, 1, 'R', true);
|
||||
}
|
||||
}
|
||||
|
||||
$pdf = new MyCustomPDFWithWatermark(PDF_PAGE_ORIENTATION, 'mm', 'A4', true, 'UTF-8', false);
|
||||
$pdf->watermarkText = $watermarkText;
|
||||
$pdf->footerText = $footerText;
|
||||
@@ -184,18 +257,7 @@ $pdf->SetFont('helvetica', '', 9.5);
|
||||
|
||||
if (!$isMultiPage) {
|
||||
$pdf->AddPage();
|
||||
$pdf->writeHTML(
|
||||
export_render_page_html(
|
||||
$allActive,
|
||||
'Kaffeeliste',
|
||||
'background-color: rgb(0, 94, 63);color: rgb(255, 255, 255);',
|
||||
$markPrice,
|
||||
$showEmptyRows,
|
||||
$adminRowHeightPx,
|
||||
null
|
||||
),
|
||||
true, false, true, false, ''
|
||||
);
|
||||
export_render_page($pdf, $allActive, 'Kaffeeliste', [0, 94, 63], [255, 255, 255], $markPrice, $showEmptyRows, $adminRowHeightPx, null);
|
||||
} else {
|
||||
if ($splitMode === 'alphabetical') {
|
||||
$mid = (int)ceil($memberCount / 2);
|
||||
@@ -211,16 +273,10 @@ if (!$isMultiPage) {
|
||||
}
|
||||
|
||||
$pdf->AddPage();
|
||||
$pdf->writeHTML(
|
||||
export_render_page_html($front, $frontTitel, 'background-color: rgb(0, 94, 63);color: rgb(255, 255, 255);', $markPrice, $showEmptyRows, $adminRowHeightPx, 'Rückseite beachten!'),
|
||||
true, false, true, false, ''
|
||||
);
|
||||
export_render_page($pdf, $front, $frontTitel, [0, 94, 63], [255, 255, 255], $markPrice, $showEmptyRows, $adminRowHeightPx, 'Rückseite beachten!');
|
||||
|
||||
$pdf->AddPage();
|
||||
$pdf->writeHTML(
|
||||
export_render_page_html($back, $backTitel, 'background-color: rgb(91, 209, 215);color: rgb(0, 0, 0);', $markPrice, $showEmptyRows, $adminRowHeightPx, 'Vorderseite beachten!'),
|
||||
true, false, true, false, ''
|
||||
);
|
||||
export_render_page($pdf, $back, $backTitel, [91, 209, 215], [0, 0, 0], $markPrice, $showEmptyRows, $adminRowHeightPx, 'Vorderseite beachten!');
|
||||
}
|
||||
|
||||
$pdf->Output('Kaffeestrichliste.pdf', 'D');
|
||||
|
||||
Reference in New Issue
Block a user