*/ private array $issuer = []; private string $logoFile = ''; private string $pageLabel = 'Seite'; /** * What to call the issuer's own VAT number in the footer. * * It used to be printed bare, between the register number and the court, and * a bare `ATU12345678` is only recognisable to somebody who already knows * what they are looking at. An auditor scanning a document for "UID" found * the RECIPIENT's, which is labelled, and not ours — so the one number an * Austrian invoice is legally worthless without read as a reference of some * kind. The document was valid; it was unreadable. */ private string $vatLabel = 'UID'; /** @param array $issuer */ public function setIssuer(array $issuer, string $logoFile = '', string $pageLabel = 'Seite', string $vatLabel = 'UID'): void { $this->issuer = $issuer; $this->logoFile = $logoFile; $this->pageLabel = $pageLabel; $this->vatLabel = $vatLabel; } public function Header(): void // phpcs:ignore { if ($this->logoFile === '' || ! is_file($this->logoFile)) { return; } // Top right, height-constrained. Width 0 lets TCPDF keep the aspect // ratio — a logo squeezed to a fixed box is worse than none. $this->Image($this->logoFile, 140, 12, 0, 18, '', '', 'T', false, 300, 'R'); } public function Footer(): void // phpcs:ignore { $this->SetY(-26); $this->SetFont('dejavusans', '', 6.5); $this->SetTextColor(110, 110, 122); $columns = self::footerColumns($this->issuer, $this->vatLabel); // A hairline above it, the same one the totals block uses, so the page // reads as one document rather than two halves. $this->SetDrawColor(233, 233, 238); $this->Line(20, $this->GetY() - 3, 190, $this->GetY() - 3); $width = 42.5; foreach ($columns as $index => $lines) { $this->SetXY(20 + ($index * $width), -24); $this->MultiCell($width - 2, 3, implode("\n", $lines), 0, 'L', false, 0, '', '', true, 0, false, true, 0, 'T'); } $this->SetXY(150, -30); $this->SetFont('dejavusans', '', 6.5); $this->Cell(40, 3, $this->pageLabel.' '.$this->getAliasNumPage().'/'.$this->getAliasNbPages(), 0, 0, 'R'); } /** * What the four footer columns SAY, apart from where they are drawn. * * Separated from Footer() because the two are different kinds of decision and * only one of them can be read: what belongs on the bottom of an invoice is a * legal question with a right answer, while millimetre coordinates are a * layout. Inside a TCPDF method the answer is only observable by rendering a * PDF and un-subsetting its fonts, which is why the VAT number went unlabelled * for months without anything noticing. * * Empty entries are dropped rather than printed as blank lines: a company with * no register number should not have a gap where one would be. * * @param array $issuer * @return array> */ public static function footerColumns(array $issuer, string $vatLabel = 'UID'): array { $columns = [ [ $issuer['name'] ?? '', $issuer['address'] ?? '', trim(($issuer['postcode'] ?? '').' '.($issuer['city'] ?? '')), ], [ $issuer['phone'] ?? '', $issuer['email'] ?? '', $issuer['website'] ?? '', ], [ $issuer['register_number'] ?? '', // Labelled, unlike its neighbours, because this is the one the // reader is looking FOR — and an unlabelled ATU number between a // register number and a court reads as a reference of some kind. self::labelled($vatLabel, $issuer['vat_id'] ?? ''), $issuer['register_court'] ?? '', ], [ $issuer['bank_name'] ?? '', $issuer['iban'] ?? '', $issuer['bic'] ?? '', ], ]; return array_map( fn (array $lines) => array_values(array_filter( array_map(fn ($line) => trim((string) $line), $lines), fn (string $line) => $line !== '', )), $columns, ); } /** * "UID: ATU12345678", or nothing at all when there is no number. * * An empty string rather than a label on its own, so a company with no VAT * number recorded keeps the blank line it had before this label existed. */ private static function labelled(string $label, mixed $value): string { $value = trim((string) $value); return $value === '' ? '' : $label.': '.$value; } }