diff --git a/exportKaffeeliste.php b/exportKaffeeliste.php
index 51c1db7..330d589 100644
--- a/exportKaffeeliste.php
+++ b/exportKaffeeliste.php
@@ -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
noch
+// "padding" auf 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 $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 = '
-
-
- | ' . $titel . ' |
- 1 Strich = ' . $markPrice . '€. Bitte bezahlen bei 10 € zahlen. ' . date("d.m.Y H:s") . ' |
-
-
- | Name |
- Guthaben |
- Striche |
- ';
-
- $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 .= '';
- $html .= "| {$name} | ";
- if ($balanceCents < -1000) {
- $html .= '' . $balance . ' € | ';
- } elseif ($balanceCents > 500) {
- $html .= '' . $balance . ' € | ';
- } else {
- $html .= "{$balance} € | ";
- }
- $html .= " | ";
- $html .= " ";
- }
-
- if ($showEmptyRows) {
- for ($i = $rowCount; $i < $targetRows; $i++) {
- $html .= '| | | | ';
- }
- }
-
- if ($seitenHinweis !== null) {
- $html .= '| | | ' . $seitenHinweis . ' | ';
- }
- $html .= ' ';
-
- 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 /| komplett, Cell() setzt die uebergebene Hoehe dagegen exakt
+ * um).
+ *
+ * @param list $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');
| |