Deploy Ladill QR Plus / deploy (push) Successful in 28s
Full control center for ticketed events, contributions, attendees, badges, and programmes — not a QR utility clone. Includes SSO shell, import command, and platform cutover runbook. Co-authored-by: Cursor <cursoragent@cursor.com>
94 lines
3.4 KiB
PHP
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);
|
|
}
|
|
}
|