b14f4c1796
New licensed lake, picnic, dining, server, burger, and food photography. Rebuilt homepage gallery, logo, colors, copy, menu, specials, and exports. Added dashboard-configurable branding in [settings.php (line 18)](/Users/tyemeclifford/Documents/GH/fatbottomgrille/views/admin/settings.php:18). Added a public attribution page and full [image credits](/Users/tyemeclifford/Documents/GH/fatbottomgrille/public/assets/images/CREDITS.md). Migrated existing SQLite menu data and admin email to admin@lakesideorders.test. Verified 51 PHP files, JSON/JavaScript, storefront pages, database migration, admin settings, and menu PDF. All passed.
111 lines
4.2 KiB
PHP
111 lines
4.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace FatBottom\Services;
|
|
|
|
final class MenuPdfExporter
|
|
{
|
|
public function output(string $businessName, array $categories): never
|
|
{
|
|
$pages = [];
|
|
$lines = [];
|
|
foreach ($categories as $category) {
|
|
$lines[] = ['type' => 'category', 'text' => strtoupper((string) $category['name'])];
|
|
foreach ($category['items'] as $item) {
|
|
$lines[] = [
|
|
'type' => 'item',
|
|
'text' => sprintf('%s $%.2f', $item['name'], $item['price_cents'] / 100),
|
|
];
|
|
$lines[] = ['type' => 'description', 'text' => (string) $item['description']];
|
|
}
|
|
}
|
|
|
|
foreach (array_chunk($lines, 24) as $chunk) {
|
|
$pages[] = $this->pageStream($businessName, $chunk);
|
|
}
|
|
if ($pages === []) {
|
|
$pages[] = $this->pageStream($businessName, []);
|
|
}
|
|
|
|
$pdf = $this->buildPdf($pages);
|
|
header('Content-Type: application/pdf');
|
|
header('Content-Disposition: attachment; filename="lakeside-orders-menu.pdf"');
|
|
header('Content-Length: ' . strlen($pdf));
|
|
echo $pdf;
|
|
exit;
|
|
}
|
|
|
|
private function pageStream(string $businessName, array $lines): string
|
|
{
|
|
$escape = static function (string $value): string {
|
|
$ascii = function_exists('iconv')
|
|
? (iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $value) ?: $value)
|
|
: preg_replace('/[^\x20-\x7E]/', '', $value);
|
|
|
|
return str_replace(
|
|
['\\', '(', ')', "\r", "\n"],
|
|
['\\\\', '\\(', '\\)', ' ', ' '],
|
|
(string) $ascii
|
|
);
|
|
};
|
|
|
|
$stream = "0.035 0.071 0.102 rg\n0 0 612 792 re f\n";
|
|
$stream .= "0.78 0.58 0.20 rg\nBT /F1 25 Tf 46 738 Td (" . $escape($businessName) . ") Tj ET\n";
|
|
$stream .= "0.91 0.92 0.90 rg\nBT /F1 10 Tf 46 718 Td (LAKESIDE PICKUP | ONLINE MENU) Tj ET\n";
|
|
$stream .= "0.78 0.58 0.20 RG\n46 704 m 566 704 l S\n";
|
|
$y = 678;
|
|
foreach ($lines as $line) {
|
|
$text = $escape(substr((string) $line['text'], 0, 92));
|
|
if ($line['type'] === 'category') {
|
|
$stream .= "0.78 0.58 0.20 rg\nBT /F1 15 Tf 46 {$y} Td ({$text}) Tj ET\n";
|
|
$y -= 24;
|
|
} elseif ($line['type'] === 'item') {
|
|
$stream .= "0.96 0.95 0.89 rg\nBT /F1 12 Tf 54 {$y} Td ({$text}) Tj ET\n";
|
|
$y -= 17;
|
|
} else {
|
|
$stream .= "0.68 0.72 0.72 rg\nBT /F1 8 Tf 54 {$y} Td ({$text}) Tj ET\n";
|
|
$y -= 23;
|
|
}
|
|
}
|
|
$stream .= "0.55 0.60 0.62 rg\nBT /F1 8 Tf 46 35 Td (Prices and availability are subject to change.) Tj ET\n";
|
|
return $stream;
|
|
}
|
|
|
|
private function buildPdf(array $streams): string
|
|
{
|
|
$objects = [];
|
|
$objects[1] = '<< /Type /Catalog /Pages 2 0 R >>';
|
|
$pageIds = [];
|
|
$nextId = 4;
|
|
foreach ($streams as $index => $stream) {
|
|
$pageId = $nextId++;
|
|
$contentId = $nextId++;
|
|
$pageIds[] = $pageId . ' 0 R';
|
|
$objects[$pageId] = sprintf(
|
|
'<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Resources << /Font << /F1 3 0 R >> >> /Contents %d 0 R >>',
|
|
$contentId
|
|
);
|
|
$objects[$contentId] = "<< /Length " . strlen($stream) . " >>\nstream\n{$stream}endstream";
|
|
}
|
|
$objects[2] = '<< /Type /Pages /Kids [' . implode(' ', $pageIds) . '] /Count ' . count($pageIds) . ' >>';
|
|
$objects[3] = '<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>';
|
|
ksort($objects);
|
|
|
|
$pdf = "%PDF-1.4\n";
|
|
$offsets = [0];
|
|
foreach ($objects as $id => $object) {
|
|
$offsets[$id] = strlen($pdf);
|
|
$pdf .= "{$id} 0 obj\n{$object}\nendobj\n";
|
|
}
|
|
$xref = strlen($pdf);
|
|
$count = max(array_keys($objects)) + 1;
|
|
$pdf .= "xref\n0 {$count}\n0000000000 65535 f \n";
|
|
for ($id = 1; $id < $count; $id++) {
|
|
$pdf .= sprintf("%010d 00000 n \n", $offsets[$id] ?? 0);
|
|
}
|
|
$pdf .= "trailer\n<< /Size {$count} /Root 1 0 R >>\nstartxref\n{$xref}\n%%EOF";
|
|
return $pdf;
|
|
}
|
|
}
|