@php $certificatesPerPage = $cols * $rows; $certificateType = data_get($certificateSetting, 'certificate_type', 'single'); $isDoubleSided = ($certificateType === 'double'); @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)
@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; $bgFilename = data_get($certificateSetting, 'card.background'); if (!$bgFilename) { $bgFilename = \Illuminate\Support\Facades\DB::table('certificate_backgrounds') ->where('activity_id', $activity->id) ->orderBy('id', 'desc') ->value('filename'); } // Convert background image to base64 for reliable printing if (!function_exists('image_to_base64_data_uri')) { function image_to_base64_data_uri($path) { if (!file_exists($path) || !is_readable($path)) { return null; } $type = mime_content_type($path); if ($type === false) { $type = 'image/' . pathinfo($path, PATHINFO_EXTENSION); } $data = file_get_contents($path); return 'data:' . $type . ';base64,' . base64_encode($data); } } $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); // Default positions and styles $titleStyle = data_get($certificateSetting, 'title', []); $photoStyle = data_get($certificateSetting, 'photo', []); $qrStyle = data_get($certificateSetting, 'qr', []); // Hitung skala terhadap ukuran dasar yang disimpan $pxW = $widthCm * 37.8; $pxH = $heightCm * 37.8; $baseW = (float) (data_get($certificateSetting, 'card.base_width_px') ?? data_get($certificateSetting, 'card.width_px') ?? 0); $baseH = (float) (data_get($certificateSetting, 'card.base_height_px') ?? data_get($certificateSetting, 'card.height_px') ?? 0); $scaleX = ($baseW > 0) ? ($pxW / $baseW) : 1.0; $scaleY = ($baseH > 0) ? ($pxH / $baseH) : 1.0; // Posisi dan ukuran semua objek di database sudah dalam pixel untuk ukuran sertifikat yang tersimpan // Ukuran sertifikat dari database: $widthCm x $heightCm // Di preview, posisi disimpan relatif terhadap card-content yang ukurannya = (widthCm * 37.8) x (heightCm * 37.8) px // Di print, kita gunakan ukuran card dalam cm yang akan di-convert ke px oleh browser // Jadi posisi dan ukuran QR code digunakan langsung tanpa skala (sama seperti di certificates_preview_pdf.blade.php) // Karena ukuran card di print menggunakan cm, browser akan otomatis convert ke px dengan ratio 1cm = 37.8px // Calculate photo size and aspect ratio (sama dengan PDF preview) $photoSizeSetting = data_get($photoStyle, 'size', 90); $photoShape = data_get($photoStyle, 'shape', 'square'); $photoFilename = optional($profileParticipant)->foto; $photoPath = $photoFilename ? public_path('assets/images/profilefoto/' . $photoFilename) : public_path('assets/images/profilefoto/default-profile.png'); $photoBase64 = image_to_base64_data_uri($photoPath); $imgSize = (file_exists($photoPath) && is_readable($photoPath)) ? @getimagesize($photoPath) : null; $imgAspectRatio = ($imgSize && isset($imgSize[0]) && isset($imgSize[1]) && $imgSize[0] > 0 && $imgSize[1] > 0) ? ($imgSize[0] / $imgSize[1]) : 1.22; @endphp
@if($bgBase64) Certificate Background @endif
@if(data_get($titleStyle, 'visible', true)) @php $titleTop = (int) round((float) data_get($titleStyle, 'top', 20) * $scaleY); $titleLeft = (int) round((float) data_get($titleStyle, 'marginLeft', data_get($titleStyle, 'left', 0)) * $scaleX); @endphp
{{ str_replace(["\r\n","\n"], ' ', ($activity->name ?? 'Sertifikat PESERTA')) }}
@endif @if(data_get($certificateSetting, 'name.visible', true)) @php $nameFont = data_get($certificateSetting, 'name.font'); if (empty($nameFont) || $nameFont === 'undefined') { $nameFont = 'DejaVu Sans'; } $nameAlignRaw = strtolower((string) data_get($certificateSetting, 'name.align', 'center')); $nameAlignCss = in_array($nameAlignRaw, ['kiri','left']) ? 'left' : (in_array($nameAlignRaw, ['kanan','right']) ? 'right' : 'center'); $nameTop = (int) round((float) data_get($certificateSetting, 'name.top', 190) * $scaleY); $nameLeft = (int) round((float) data_get($certificateSetting, 'name.left', 30) * $scaleX); $nameWidth = (int) round((float) data_get($certificateSetting, 'name.width', 180) * $scaleX); @endphp
{{ $userParticipant->name ?? '-' }}
@endif @if(data_get($certificateSetting, 'email.visible', false)) @php $emailTop = (int) round((float) data_get($certificateSetting, 'email.top', 220) * $scaleY); $emailLeft = (int) round((float) data_get($certificateSetting, 'email.left', 30) * $scaleX); $emailWidth = (int) round((float) data_get($certificateSetting, 'email.width', 180) * $scaleX); @endphp
{{ $userParticipant->email ?? '-' }}
@endif @if(data_get($certificateSetting, 'no_hp.visible', false))
{{ $profileParticipant->no_hp ?? '-' }}
@endif @if(data_get($certificateSetting, 'jenis_kelamin.visible', false)) @php $genderTop = (int) round((float) data_get($certificateSetting, 'jenis_kelamin.top', 260) * $scaleY); $genderLeft = (int) round((float) data_get($certificateSetting, 'jenis_kelamin.left', 30) * $scaleX); $genderWidth = (int) round((float) data_get($certificateSetting, 'jenis_kelamin.width', 180) * $scaleX); @endphp
{{ $profileParticipant->jenis_kelamin ?? '-' }}
@endif @if(data_get($certificateSetting, 'pekerjaan.visible', false)) @php $jobTop = (int) round((float) data_get($certificateSetting, 'pekerjaan.top', 280) * $scaleY); $jobLeft = (int) round((float) data_get($certificateSetting, 'pekerjaan.left', 30) * $scaleX); $jobWidth = (int) round((float) data_get($certificateSetting, 'pekerjaan.width', 180) * $scaleX); @endphp
{{ $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); // DEBUG: Output nilai setting QR code $debugQr = [ 'top' => $qrTop, 'left' => $qrLeft, 'size_input' => $qrSizeInput, 'size_actual' => $qrSizeActual, 'size_used' => $qrSize, 'width_cm' => $widthCm, 'height_cm' => $heightCm, 'qrStyle_raw' => $qrStyle, 'certificateSetting_qr' => data_get($certificateSetting, 'qr', []), ]; @endphp
@php $qrDataVal = route('activity.verify-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 @if(count($row) < $cols) @for($i = 0; $i < $cols - count($row); $i++)
@endfor @endif
@endforeach @endforeach