$rows */ protected function invoiceTable(string &$content, float $x, float $y, array $rows): float { $width = 275.0; $rowHeight = 22.0; $valueX = $x + 160; $content .= $this->line($x, $y, $x + $width, $y, 0.5); foreach (array_values(array_filter($rows)) as [$label, $value, $bold]) { $y -= $rowHeight; $content .= $this->text($x, $y + 7, $label, 9, $bold ? 'F2' : 'F1'); $content .= $this->text($valueX, $y + 7, $value, 9, $bold ? 'F2' : 'F1'); $content .= $this->line($x, $y, $x + $width, $y, 0.5); } return $y; } protected function formatEuro(float $amount): string { return number_format($amount, 2, ',', '.').' €'; } protected function buildPdf(string $content): string { $objects = [ "1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n", "2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n", "3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 ".self::PAGE_WIDTH.' '.self::PAGE_HEIGHT."] /Resources << /Font << /F1 4 0 R /F2 5 0 R >> >> /Contents 6 0 R >>\nendobj\n", "4 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>\nendobj\n", "5 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding >>\nendobj\n", "6 0 obj\n<< /Length ".strlen($content)." >>\nstream\n{$content}endstream\nendobj\n", ]; $pdf = "%PDF-1.4\n"; $offsets = [0]; foreach ($objects as $object) { $offsets[] = strlen($pdf); $pdf .= $object; } $xrefOffset = strlen($pdf); $pdf .= "xref\n0 ".(count($objects) + 1)."\n"; $pdf .= "0000000000 65535 f \n"; foreach (array_slice($offsets, 1) as $offset) { $pdf .= sprintf("%010d 00000 n \n", $offset); } $pdf .= "trailer\n<< /Size ".(count($objects) + 1)." /Root 1 0 R >>\n"; $pdf .= "startxref\n{$xrefOffset}\n%%EOF\n"; return $pdf; } protected function escapePdfText(string $text): string { $encoded = iconv('UTF-8', 'Windows-1252//TRANSLIT//IGNORE', $text); return str_replace(['\\', '(', ')'], ['\\\\', '\(', '\)'], $encoded ?: Str::ascii($text)); } protected function text(float $x, float $y, string $text, int $size = 9, string $font = 'F1', string $align = 'left'): string { if ($align === 'center') { $x = (self::PAGE_WIDTH - $this->textWidth($text, $size)) / 2; } return sprintf( "BT /%s %d Tf %.2F %.2F Td (%s) Tj ET\n", $font, $size, $x, $y, $this->escapePdfText($text), ); } protected function line(float $x1, float $y1, float $x2, float $y2, float $width = 0.5): string { return sprintf("%.2F w %.2F %.2F m %.2F %.2F l S\n", $width, $x1, $y1, $x2, $y2); } protected function wrappedText( string &$content, float $x, float $y, string $text, int $size, float $width, float $lineHeight = 12, string $font = 'F1', ): float { foreach ($this->wrap($text, $width, $size) as $line) { if ($line !== '') { $content .= $this->text($x, $y, $line, $size, $font); } $y -= $lineHeight; } return $y; } /** * @return list */ protected function wrap(string $text, float $width, int $size): array { $lines = []; $maxCharacters = max(8, (int) floor($width / ($size * 0.48))); foreach ($this->splitLines($text) as $paragraph) { if ($paragraph === '') { $lines[] = ''; continue; } $line = ''; foreach (preg_split('/\s+/', $paragraph) ?: [] as $word) { $candidate = trim($line.' '.$word); if ($line !== '' && Str::length($candidate) > $maxCharacters) { $lines[] = $line; $line = $word; continue; } $line = $candidate; } if ($line !== '') { $lines[] = $line; } } return $lines; } /** * @return list */ protected function splitLines(string $text): array { return explode("\n", str_replace(["\r\n", "\r"], "\n", $text)); } protected function textWidth(string $text, int $size): float { return Str::length($text) * $size * 0.48; } }