Files
ladill-give/app/Services/Qr/QrPdfExporter.php
T
isaaccladandCursor 0860b08af7 Initial Ladill Give extraction — online giving pages at give.ladill.com.
Donation checkout via Ladill Pay (9% fee), church/giving QR pages, SSO, and platform scan forwarding.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-09 07:25:49 +00:00

94 lines
3.4 KiB
PHP

<?php
namespace App\Services\Qr;
class QrPdfExporter
{
public function fromPng(string $pngBinary, string $title): string
{
if (! extension_loaded('gd')) {
throw new \RuntimeException('PDF export requires the GD extension.');
}
$image = imagecreatefromstring($pngBinary);
if ($image === false) {
throw new \RuntimeException('Could not read QR image for PDF export.');
}
$width = imagesx($image);
$height = imagesy($image);
$canvas = imagecreatetruecolor($width, $height);
$white = imagecolorallocate($canvas, 255, 255, 255);
imagefilledrectangle($canvas, 0, 0, $width, $height, $white);
imagecopy($canvas, $image, 0, 0, 0, 0, $width, $height);
imagedestroy($image);
ob_start();
imagejpeg($canvas, null, 95);
$jpeg = (string) ob_get_clean();
imagedestroy($canvas);
return $this->buildPdf($jpeg, $title, $width, $height);
}
private function buildPdf(string $jpeg, string $title, int $imgW, int $imgH): string
{
$pageW = 595.28;
$pageH = 841.89;
$margin = 48;
$maxQr = min($pageW - ($margin * 2), 320);
$scale = min($maxQr / max(1, $imgW), $maxQr / max(1, $imgH));
$drawW = $imgW * $scale;
$drawH = $imgH * $scale;
$x = ($pageW - $drawW) / 2;
$y = 120;
$safeTitle = $this->pdfEscape(substr($title, 0, 80));
$objects = [];
$objects[] = "1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj\n";
$objects[] = "2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj\n";
$objects[] = sprintf(
"3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 %.2F %.2F] /Resources << /Font << /F1 4 0 R >> /XObject << /Im1 5 0 R >> >> /Contents 6 0 R >> endobj\n",
$pageW,
$pageH,
);
$objects[] = "4 0 obj << /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold >> endobj\n";
$objects[] = sprintf(
"5 0 obj << /Type /XObject /Subtype /Image /Width %d /Height %d /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode /Length %d >> stream\n%s\nendstream\nendobj\n",
$imgW,
$imgH,
strlen($jpeg),
$jpeg,
);
$content = "BT /F1 16 Tf 48 " . ($pageH - 72) . " Td ({$safeTitle}) Tj ET\n";
$content .= sprintf("q %.4F 0 0 %.4F %.2F %.2F cm /Im1 Do Q\n", $drawW, $drawH, $x, $pageH - $y - $drawH);
$content .= "BT /F1 10 Tf 48 48 Td (Generated by Ladill QR Codes) Tj ET\n";
$objects[] = sprintf("6 0 obj << /Length %d >> stream\n%s\nendstream\nendobj\n", strlen($content), $content);
$pdf = "%PDF-1.4\n";
$offsets = [0];
foreach ($objects as $object) {
$offsets[] = strlen($pdf);
$pdf .= $object;
}
$xrefPos = strlen($pdf);
$pdf .= "xref\n0 " . count($offsets) . "\n";
$pdf .= "0000000000 65535 f \n";
for ($i = 1; $i < count($offsets); $i++) {
$pdf .= sprintf("%010d 00000 n \n", $offsets[$i]);
}
$pdf .= "trailer << /Size " . count($offsets) . " /Root 1 0 R >>\n";
$pdf .= "startxref\n{$xrefPos}\n%%EOF";
return $pdf;
}
private function pdfEscape(string $text): string
{
return str_replace(['\\', '(', ')'], ['\\\\', '\\(', '\\)'], $text);
}
}