@foreach($participants->chunk($certificatesPerPage) as $pageIndex => $page)
@if($pageIndex > 0)
@endif
@foreach($page->chunk($cols) as $rowIndex => $row)
@php
$paperUpper = strtoupper($paper ?? 'A4');
$orientationVal = $orientation ?? 'portrait';
$paperDimsRow = [21.0, 29.7];
switch ($paperUpper) {
case 'A3': $paperDimsRow = [29.7, 42.0]; break;
case 'A4': $paperDimsRow = [21.0, 29.7]; break;
case 'A5': $paperDimsRow = [14.8, 21.0]; break;
case 'LETTER': $paperDimsRow = [21.59, 27.94]; break;
case 'LEGAL': $paperDimsRow = [21.59, 35.56]; break;
case 'F4': $paperDimsRow = [21.5, 33.0]; break;
}
if ($orientationVal === 'landscape') { $paperDimsRow = [$paperDimsRow[1], $paperDimsRow[0]]; }
$marginCssRow = data_get($printSettings ?? [], 'margin', '0cm');
$marginValRow = 0.0;
if (is_string($marginCssRow)) {
if (str_contains($marginCssRow, 'mm')) {
$marginValRow = (float) str_replace(['mm',' '],'', $marginCssRow) / 10.0;
} else {
$marginValRow = (float) str_replace(['cm',' '],'', $marginCssRow);
}
}
$availableHeightRowCm = max(0.0, $paperDimsRow[1] - 2*$marginValRow);
@endphp
@foreach($row as $peserta)
@foreach($allPages as $pageData)
@php
$certificateSetting = $pageData;
@endphp
@php
$userParticipant = optional($peserta)->user;
if (!$userParticipant) {
continue;
}
$profileParticipant = optional($userParticipant->profile);
$provinceParticipant = optional($profileParticipant->province)->name ?? ($profileParticipant->other_province ?? null);
$regencyParticipant = optional($profileParticipant->regency)->name ?? ($profileParticipant->other_regency ?? null);
$districtParticipant = optional($profileParticipant->district)->name ?? ($profileParticipant->other_district ?? null);
// Apply certificate settings
$widthCm = data_get($certificateSetting, 'card.width_cm', 8.6);
$heightCm = data_get($certificateSetting, 'card.height_cm', 15);
$scaleToPaper = (bool) data_get($printSettings ?? [], 'scale_to_paper', false);
$paperName = $paper ?? 'A4';
$paperUpper = strtoupper($paperName);
$paperDims = [21.0, 29.7]; // default A4 cm
switch ($paperUpper) {
case 'A3': $paperDims = [29.7, 42.0]; break;
case 'A4': $paperDims = [21.0, 29.7]; break;
case 'A5': $paperDims = [14.8, 21.0]; break;
case 'LETTER': $paperDims = [21.59, 27.94]; break;
case 'LEGAL': $paperDims = [21.59, 35.56]; break;
case 'F4': $paperDims = [21.5, 33.0]; break;
}
if (($orientation ?? 'portrait') === 'landscape') { $paperDims = [$paperDims[1], $paperDims[0]]; }
$marginCss = data_get($printSettings ?? [], 'margin', '0cm');
$marginVal = 0.0;
if (is_string($marginCss)) {
if (str_contains($marginCss, 'mm')) {
$marginVal = (float) str_replace(['mm',' '],'', $marginCss) / 10.0;
} else {
$marginVal = (float) str_replace(['cm',' '],'', $marginCss);
}
}
$offsetTopMm = (float) data_get($printSettings ?? [], 'offset_top_mm', 0);
$offsetLeftMm = (float) data_get($printSettings ?? [], 'offset_left_mm', 0);
$sizeAdjustMm = (float) data_get($printSettings ?? [], 'size_adjust_mm', 0);
$offsetTopCm = $offsetTopMm / 10.0;
$offsetLeftCm = $offsetLeftMm / 10.0;
$sizeAdjustCm = $sizeAdjustMm / 10.0;
// Hitung area kertas tersedia
$availableWidthCm = max(0.0, $paperDims[0] - 2*$marginVal);
$availableHeightCm = max(0.0, $paperDims[1] - 2*$marginVal);
// Untuk 1x1, isi penuh area kertas namun berikan epsilon agar tidak overflow ke halaman 2
// Banyak browser menambahkan margin minimum saat print preview meski @page margin:0
// Epsilon kecil memastikan tinggi/lebar aman satu halaman
$fitEpsilonCm = 0.1;
if ((($cols ?? 1) == 1) && (($rows ?? 1) == 1)) {
$widthCm = max(0.0, ($availableWidthCm - $fitEpsilonCm) + $sizeAdjustCm);
$heightCm = max(0.0, ($availableHeightCm - $fitEpsilonCm) + $sizeAdjustCm);
} else {
// Selain 1x1, pastikan tidak overflow
$widthCm = min(max(0.0, $widthCm + $sizeAdjustCm), $availableWidthCm - $fitEpsilonCm);
$heightCm = min(max(0.0, $heightCm + $sizeAdjustCm), $availableHeightCm - $fitEpsilonCm);
}
// Tanpa bleed (hindari offset atas/kiri agar tidak tampak margin)
$bleedMm = 0.0;
// Multi-page support
$allPages = data_get($certificateSetting, 'pages');
if (!$allPages) {
$allPages = [$certificateSetting];
}
@endphp
@foreach($allPages as $pIdx => $currentPageSetting)
@php
$bgFilename = data_get($currentPageSetting, 'page.background') ?? data_get($currentPageSetting, 'card.background');
if (!$bgFilename && $pIdx === 0) {
$bgFilename = \Illuminate\Support\Facades\DB::table('certificate_backgrounds')
->where('activity_id', $activity->id)
->orderBy('id', 'desc')
->value('filename');
}
$bgPath = null;
if ($bgFilename) {
$bgPath = public_path('assets/images/certificate/' . $bgFilename);
} else {
$defaultDir = public_path('assets/images/certificate/background/default');
$files = glob($defaultDir.'/*.{png,jpg,jpeg,gif,webp}', GLOB_BRACE);
$bgPath = ($files && count($files) > 0) ? ($defaultDir.'/'.basename($files[0])) : null;
}
$bgBase64 = image_to_base64_data_uri($bgPath);
// Re-calculate scaling for this specific page if it's a multi-page design
if (data_get($certificateSetting, 'pages')) {
$widthCm = data_get($currentPageSetting, 'page.width_cm', $widthCm);
$heightCm = data_get($currentPageSetting, 'page.height_cm', $heightCm);
$pxW = $widthCm * 37.8;
$pxH = $heightCm * 37.8;
$baseW = (float) (data_get($currentPageSetting, 'card.base_width_px') ?? data_get($currentPageSetting, 'card.width_px') ?? 0);
$baseH = (float) (data_get($currentPageSetting, 'card.base_height_px') ?? data_get($currentPageSetting, 'card.height_px') ?? 0);
$scaleX = ($baseW > 0) ? ($pxW / $baseW) : 1.0;
$scaleY = ($baseH > 0) ? ($pxH / $baseH) : 1.0;
}
@endphp
@if($pIdx > 0)
@endif
@if($bgBase64)

@endif
@php
$isDesignFormat = (bool) data_get($currentPageSetting, 'page');
@endphp
@if($isDesignFormat)
@php
$designElements = [];
foreach ($currentPageSetting as $k => $v) {
if ($k === 'page') { continue; }
if (is_array($v) || is_object($v)) {
$designElements[$k] = is_array($v) ? $v : (array) $v;
}
}
@endphp
@foreach($designElements as $elKey => $el)
@php $el = is_array($el) ? $el : (array) $el; $elVisible = array_key_exists('visible', $el) ? $el['visible'] : true; @endphp
@if($elVisible === false) @continue @endif
@if(data_get($el, 'data_key') === 'qr' || str_starts_with((string)$elKey, 'qr_'))
@php
$qLeft = (int) round((float) data_get($el, 'left', 0) * $scaleX);
$qTop = (int) round((float) data_get($el, 'top', 0) * $scaleY);
$qW = (int) round((float) data_get($el, 'width', 100) * $scaleX);
$qH = (int) round((float) data_get($el, 'height', 100) * $scaleY);
$qrValDesign = route('activity.download-certificate', ['id' => $activity->id]) . '?certificate_id=' . urlencode((string) ($peserta->certificate_id ?? ''));
try {
$qrBinaryDesign = \SimpleSoftwareIO\QrCode\Facades\QrCode::format('png')->size(max($qW, 40))->generate((string) $qrValDesign);
$qrSrcDesign = 'data:image/png;base64,'.base64_encode($qrBinaryDesign);
} catch (\Throwable $e) {
$qrSrcDesign = 'https://api.qrserver.com/v1/create-qr-code/?size='.max($qW,40).'x'.max($qH,40).'&data='.urlencode((string) $qrValDesign);
}
@endphp
@else
@php
$dk = data_get($el, 'data_key', '');
$textContent = data_get($el, 'text', '');
if ($dk === 'name') { $textContent = optional(optional($peserta)->user)->name ?? '-'; }
elseif ($dk === 'email') { $textContent = optional(optional($peserta)->user)->email ?? '-'; }
elseif ($dk === 'instansi') { $textContent = optional($profileParticipant)->instansi ?? '-'; }
elseif ($dk === 'no_hp') { $textContent = optional($profileParticipant)->no_hp ?? '-'; }
elseif ($dk === 'jenis_kelamin') { $textContent = optional($profileParticipant)->jenis_kelamin ?? '-'; }
elseif ($dk === 'pekerjaan') { $textContent = optional($profileParticipant)->pekerjaan ?? '-'; }
elseif ($dk === 'jabatan') { $textContent = optional($profileParticipant)->jabatan ?? '-'; }
elseif ($dk === 'alamat') { $textContent = optional($profileParticipant)->alamat ?? '-'; }
elseif ($dk === 'province' || $dk === 'provinsi') { $textContent = $provinceParticipant ?? '-'; }
elseif ($dk === 'regency' || $dk === 'kabupaten') { $textContent = $regencyParticipant ?? '-'; }
elseif ($dk === 'district' || $dk === 'kecamatan') { $textContent = $districtParticipant ?? '-'; }
elseif ($dk === 'certificate_id') { $textContent = $peserta->certificate_id ?? '-'; }
$elLeft = (int) round((float) data_get($el, 'left', 0) * $scaleX);
$elTop = (int) round((float) data_get($el, 'top', 0) * $scaleY);
$elWidth = (int) round((float) data_get($el, 'width', 400) * $scaleX);
$elSize = (int) round((float) data_get($el, 'size', 24) * min($scaleX, $scaleY));
$elAlign = data_get($el, 'align', 'left');
if (!in_array($elAlign, ['left','center','right'])) { $elAlign = 'left'; }
$elFont = data_get($el, 'font') ?: 'DejaVu Sans';
if ($elFont === 'undefined') { $elFont = 'DejaVu Sans'; }
@endphp
{{ $textContent }}
@endif
@endforeach
@else
@include('pdf.certificates.legacy_elements', ['certificateSetting' => $currentPageSetting, 'peserta' => $peserta, 'scaleX' => $scaleX, 'scaleY' => $scaleY])
@endif
@endforeach
@if($isDoubleSided && !data_get($certificateSetting, 'pages'))
left:{{ $jobLeft }}px;
width:{{ $jobWidth }}px;
font-size:{{ data_get($certificateSetting, 'pekerjaan.size', 16) }}px;
color:{{ data_get($certificateSetting, 'pekerjaan.color', '#333333') }};
font-family:'{{ data_get($certificateSetting, 'pekerjaan.font', 'DejaVu Sans') }}';
font-weight:{{ data_get($certificateSetting, 'pekerjaan.weight', 'normal') }};
font-style:{{ data_get($certificateSetting, 'pekerjaan.italic', 'normal') }};
text-align:{{ data_get($certificateSetting, 'pekerjaan.align', 'center') }};
white-space: pre-wrap;
overflow-wrap: anywhere;
word-break: break-word;
line-height: 1.2;
padding: 0 2mm;
box-sizing: border-box;
">{{ $profileParticipant->pekerjaan ?? '-' }}
@endif
@if(data_get($certificateSetting, 'instansi.visible', false))
@php
$instTop = (int) round((float) data_get($certificateSetting, 'instansi.top', 290) * $scaleY);
$instLeft = (int) round((float) data_get($certificateSetting, 'instansi.left', 30) * $scaleX);
$instWidth = (int) round((float) data_get($certificateSetting, 'instansi.width', 180) * $scaleX);
@endphp
{{ $profileParticipant->instansi ?? '-' }}
@endif
@if(data_get($certificateSetting, 'jabatan.visible', false))
@php
$roleTop = (int) round((float) data_get($certificateSetting, 'jabatan.top', 300) * $scaleY);
$roleLeft = (int) round((float) data_get($certificateSetting, 'jabatan.left', 30) * $scaleX);
$roleWidth = (int) round((float) data_get($certificateSetting, 'jabatan.width', 180) * $scaleX);
@endphp
{{ $profileParticipant->jabatan ?? '-' }}
@endif
@if(data_get($certificateSetting, 'alamat.visible', false))
@php
$addrTop = (int) round((float) data_get($certificateSetting, 'alamat.top', 320) * $scaleY);
$addrLeft = (int) round((float) data_get($certificateSetting, 'alamat.left', 30) * $scaleX);
$addrWidth = (int) round((float) data_get($certificateSetting, 'alamat.width', 180) * $scaleX);
@endphp
{{ $profileParticipant->alamat ?? '-' }}
@endif
@if(data_get($certificateSetting, 'province.visible', false))
@php
$provTop = (int) round((float) data_get($certificateSetting, 'province.top', 340) * $scaleY);
$provLeft = (int) round((float) data_get($certificateSetting, 'province.left', 30) * $scaleX);
$provWidth = (int) round((float) data_get($certificateSetting, 'province.width', 180) * $scaleX);
@endphp
{{ $provinceParticipant ?? '-' }}
@endif
@if(data_get($certificateSetting, 'regency.visible', false))
@php
$regTop = (int) round((float) data_get($certificateSetting, 'regency.top', 360) * $scaleY);
$regLeft = (int) round((float) data_get($certificateSetting, 'regency.left', 30) * $scaleX);
$regWidth = (int) round((float) data_get($certificateSetting, 'regency.width', 180) * $scaleX);
@endphp
{{ $regencyParticipant ?? '-' }}
@endif
@if(data_get($certificateSetting, 'district.visible', false))
@php
$distTop = (int) round((float) data_get($certificateSetting, 'district.top', 380) * $scaleY);
$distLeft = (int) round((float) data_get($certificateSetting, 'district.left', 30) * $scaleX);
$distWidth = (int) round((float) data_get($certificateSetting, 'district.width', 180) * $scaleX);
@endphp
{{ $districtParticipant ?? '-' }}
@endif
@if(data_get($certificateSetting, 'certificate_id.visible', false))
@php
$certTop = (int) round((int) data_get($certificateSetting, 'certificate_id.top', 360) * $scaleY);
$certLeft = (int) round((int) data_get($certificateSetting, 'certificate_id.left', 30) * $scaleX);
$certWidth = (int) round((int) data_get($certificateSetting, 'certificate_id.width', 180) * $scaleX);
@endphp
{{ $peserta->certificate_id ?? '-' }}
@endif
@if($photoBase64 && data_get($photoStyle, 'visible', true))
@php $photoScaled = (int) round((float) data_get($photoStyle, 'size', 90) * $scaleX); @endphp
@endif
@php
// Mengambil posisi dan ukuran QR code
// PENTING: Di setting page, QR di-scale oleh JavaScript berdasarkan getCardScale()
// getCardScale() menghitung: scale = currentCardSize (yang di-render) / baseCardSize (defaultValue)
// actual_size adalah ukuran QR yang sudah di-scale untuk preview di setting page
// Di print, card menggunakan ukuran PENUH dari database dalam cm
// Untuk menyamakan ukuran QR di print dengan yang terlihat di setting:
// - Gunakan actual_size jika ada (ukuran yang benar-benar terlihat di setting)
// - Tapi actual_size adalah untuk card yang di-scale di preview, sedangkan di print card adalah ukuran penuh
// - Jadi perlu hitung balik: actual_size / scale_preview = size_input, lalu size_input * scale_print = ukuran di print
// - Tapi lebih mudah: gunakan actual_size dan kalikan dengan inverse scale preview
// - Atau lebih sederhana: jika card di print adalah baseCardSize, QR = actual_size * (baseCardSize / currentCardSize_preview)
// SOLUSI SEDERHANA: Gunakan size input langsung karena di print, card adalah ukuran penuh (100%)
// actual_size adalah untuk card yang di-scale di preview, jadi tidak relevan untuk print
$qrTop = (int) round(((float) data_get($qrStyle, 'top', 320) * $scaleY));
$qrLeft = (int) round((float) data_get($qrStyle, 'left', 90) * $scaleX);
$qrSizeInput = (float) data_get($qrStyle, 'size', 80);
$qrSizeActual = (float) data_get($qrStyle, 'actual_size', 0);
$qrSize = max((int) round($qrSizeInput * $scaleX), 0);
@endphp
@php
$qrDataVal = route('activity.download-certificate', ['id' => $activity->id]) . '?certificate_id=' . urlencode((string) ($peserta->certificate_id ?? ''));
try {
$qrBinary = \SimpleSoftwareIO\QrCode\Facades\QrCode::format('png')->size(max($qrSize, 40))->generate((string) $qrDataVal);
$qrBase64 = base64_encode($qrBinary);
$qrSrc = 'data:image/png;base64,'.$qrBase64;
} catch (\Throwable $e) {
$qrSrc = 'https://api.qrserver.com/v1/create-qr-code/?size='.max($qrSize,40).'x'.max($qrSize,40).'&data='.urlencode((string) $qrDataVal);
}
@endphp