{{-- A menu as a PDF (mPDF): the email/portal recipe-card design laid out
for A4 — a first page with the targets and the day at a glance, then one
page per meal with its dish, nutrition, core components and ingredients.
A menu built from components has no dishes: its meals list their
components on the first page and follow one another, a page per day.
mPDF only rounds the corners of block elements, can't shrink a float to
its content and ignores margins between floats, so a row of pills is
floats with set widths, each wrapped in a padded float for the
gap, and pictures come with their corners already rounded (MenuMedia). --}}
@php
$isAr = app()->getLocale() === 'ar';
$dir = $isAr ? 'rtl' : 'ltr';
$start = $isAr ? 'right' : 'left';
$end = $isAr ? 'left' : 'right';
$isRecipeMenu = ($menu->generation_input['source'] ?? null) === \App\Services\RecipeMenuService::SOURCE;
// MenuPlanPdfService passes a text's width (mm) in the PDF's own fonts.
$measure ??= fn (string $text, float $pt, bool $bold = false): float => mb_strlen($text) * $pt * ($bold ? 0.2 : 0.18);
$name = fn (array $r) => $isAr ? ($r['name_ar'] ?: $r['name_en']) : ($r['name_en'] ?: $r['name_ar']);
$otherName = fn (array $r) => $isAr ? $r['name_en'] : $r['name_ar'];
$itemName = fn (array $i) => ($isAr ? ($i['name_ar'] ?? null) : ($i['name_en'] ?? null)) ?: ($i['name'] ?? '');
$number = fn ($value) => rtrim(rtrim(number_format((float) $value, 1), '0'), '.');
$amount = fn (array $i) => $number($i['grams']) . ' ' . (($i['unit'] ?? null) === 'ml' ? __('common.ml') : __('common.grams'));
$grams = __('common.grams');
$kcal = __('menus.recipes.kcal');
$totalsOf = fn ($items) => [
'calories' => (int) round(collect($items)->sum('calories')),
'protein_g' => round(collect($items)->sum('protein_g'), 1),
'carbs_g' => round(collect($items)->sum('carbs_g'), 1),
'fat_g' => round(collect($items)->sum('fat_g'), 1),
'fiber_g' => round(collect($items)->sum('fiber_g'), 1),
];
$macrosLine = fn (array $t) => collect(['protein' => $t['protein_g'], 'carbs' => $t['carbs_g'], 'fat' => $t['fat_g'], 'fiber' => $t['fiber_g'] ?? 0])
->map(fn ($value, $key) => __('menus.recipes.' . $key) . ' ' . $number($value) . ' ' . $grams)
->implode(' · ');
$icon = fn (string $key, float $size, string $style = '') => ($src = $media->icon($key))
? ''
: '';
$thumb = fn (array $item) => ($photo = \App\Services\RecipeMenuService::pickPhoto(($componentPhotos ?? [])[$item['component_id'] ?? 0] ?? [], (float) $item['grams']))
? $media->photo($photo, 96, true)
: null;
// A rounded pill floated to the start of its row (or the end). Its width
// is the measured text, and its wrapper's padding is the gap to the next.
$pill = function (string $text, string $style, ?string $iconKey = null, float $pt = 9, bool $bold = true, bool $atEnd = false) use ($measure, $icon, $start, $end): string {
$img = $iconKey ? $icon($iconKey, 4.2) : '';
$width = round($measure($text, $pt, $bold) + 8 + ($img ? 5.5 : 0), 1);
return '
|
{{ $menu->title }}
{{ $patient?->full_name }} · {{ $patient?->file_number }}
|
{{ $menu->starts_on?->format('Y-m-d') }} |
{{-- Lines as rows: mPDF ignores block margins inside a cell
and packs its lines too tightly for Arabic beside Latin. --}}
|
{!! $icon('kcal', 6.5) !!}
{{ number_format($mealTotals['calories']) }}
{{ $kcal }} · {{ __('menus.recipes.meal_target') }} {{ number_format($meal['target_calories']) }}
|
| @endif |
{!! $icon($key, 6) !!}
{{ $value }} {{ $unit }}
{{ __('menus.recipes.' . $key) }}
|
@endforeach
|
{!! $icon($category . ($on ? '-on' : '-off'), 9) !!}
{{ __('menus.recipes.categories.' . $category) }}
|
@endforeach
|
{!! $icon($benefit['slug'] . ($on ? '-on' : '-off'), 9) !!}
{{ $benefit['name'] }}
|
@endforeach
| @endif @if ($item) @php $src = $thumb($item); @endphp |
@if ($src)
•
@endif
|
{{ $itemName($item) }} | {{ $amount($item) }} | @else@endif @endforeach |