@php $certificatesPerPage = $cols * $rows; $certificateType = data_get($certificateSetting, 'certificate_type', 'single'); $isDoubleSided = ($certificateType === 'double'); // Multi-page support $allPages = data_get($certificateSetting, 'pages'); if (!$allPages) { $additionalPages = $additionalPages ?? []; if (!empty($additionalPages) && is_array($additionalPages)) { $allPages = array_merge([$certificateSetting], $additionalPages); } else { $allPages = [$certificateSetting]; } } @endphp
@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) Certificate Background @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
QR
@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 QR Code
@if($isDoubleSided) {{-- Sertifikat Belakang langsung di bawah sertifikat depan --}} @php // Reuse same variables for back certificate $backWidthCm = $widthCm; $backHeightCm = $heightCm; $backOffsetTopCm = $offsetTopCm; $backOffsetLeftCm = $offsetLeftCm; $backBgFilename = data_get($certificateSetting, 'card.background_back'); if ($backBgFilename) { $backBgPath = public_path('assets/images/certificate/' . $backBgFilename); $backBgBase64 = image_to_base64_data_uri($backBgPath); if (!$backBgBase64) { $backBgBase64 = $bgBase64; } } else { $backBgBase64 = $bgBase64; } $backScaleX = $scaleX; $backScaleY = $scaleY; @endphp
@if($backBgBase64) Certificate Background @endif
{{-- Back Title --}} @if(data_get($certificateSetting, 'back_title.visible', true)) @php $bTitleTop = (int) round((float) data_get($certificateSetting, 'back_title.top', 50) * $backScaleY); $bTitleLeft = (int) round((float) data_get($certificateSetting, 'back_title.left', 50) * $backScaleX); $bTitleWidth = (int) round((float) data_get($certificateSetting, 'back_title.width', 400) * $backScaleX); @endphp
{{ data_get($certificateSetting, 'back_title.text', $activity->name ?? 'Sertifikat Peserta') }}
@endif {{-- Back Subtitle --}} @if(data_get($certificateSetting, 'back_subtitle.visible', true)) @php $bSubTop = (int) round((float) data_get($certificateSetting, 'back_subtitle.top', 100) * $backScaleY); $bSubLeft = (int) round((float) data_get($certificateSetting, 'back_subtitle.left', 50) * $backScaleX); $bSubWidth = (int) round((float) data_get($certificateSetting, 'back_subtitle.width', 400) * $backScaleX); @endphp
{{ data_get($certificateSetting, 'back_subtitle.text', 'Informasi Tambahan') }}
@endif {{-- Back Content --}} @if(data_get($certificateSetting, 'back_content.visible', true)) @php $bContentTop = (int) round((float) data_get($certificateSetting, 'back_content.top', 150) * $backScaleY); $bContentLeft = (int) round((float) data_get($certificateSetting, 'back_content.left', 50) * $backScaleX); $bContentWidth = (int) round((float) data_get($certificateSetting, 'back_content.width', 400) * $backScaleX); @endphp
{{ data_get($certificateSetting, 'back_content.text', 'Sertifikat ini diterbitkan sebagai bukti keikutsertaan dalam kegiatan.') }}
@endif {{-- Back Cert ID --}} @if(data_get($certificateSetting, 'back_certid.visible', true)) @php $bCertIdTop = (int) round((float) data_get($certificateSetting, 'back_certid.top', 250) * $backScaleY); $bCertIdLeft = (int) round((float) data_get($certificateSetting, 'back_certid.left', 50) * $backScaleX); $bCertIdWidth = (int) round((float) data_get($certificateSetting, 'back_certid.width', 400) * $backScaleX); @endphp
{{ $peserta->certificate_id ?? '-' }}
@endif
@endif @endforeach @endforeach @if(count($row) < $cols) @for($i = 0; $i < $cols - count($row); $i++)
@endfor @endif @endforeach @endforeach