@php $certificatesPerPage = $cols * $rows; @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, selalu isi penuh area kertas agar konsisten di setiap halaman if ((($cols ?? 1) == 1) && (($rows ?? 1) == 1)) { $widthCm = $availableWidthCm + $sizeAdjustCm; $heightCm = $availableHeightCm + $sizeAdjustCm; } else { // Selain 1x1, pastikan tidak overflow $widthCm = min($widthCm + $sizeAdjustCm, $availableWidthCm); $heightCm = min($heightCm + $sizeAdjustCm, $availableHeightCm); } // Tanpa bleed (hindari offset atas/kiri agar tidak tampak margin) $bleedMm = 0.0; $bgFilename = data_get($certificateSetting, 'card.background'); // 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 = $bgFilename ? public_path('assets/images/certificate/' . $bgFilename) : public_path('assets/images/certificate/default-certificate.png'); $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', []); // 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))
{{ str_replace(["\r\n","\n"], ' ', ($activity->name ?? 'Sertifikat PESERTA')) }}
@endif @if(data_get($certificateSetting, 'name.visible', false))
{{ $userParticipant->name ?? '-' }}
@endif @if(data_get($certificateSetting, 'email.visible', false))
{{ $userParticipant->email ?? '-' }}
@endif @if(data_get($certificateSetting, 'no_hp.visible', false))
{{ $profileParticipant->no_hp ?? '-' }}
@endif @if(data_get($certificateSetting, 'jenis_kelamin.visible', false))
{{ $profileParticipant->jenis_kelamin ?? '-' }}
@endif @if(data_get($certificateSetting, 'pekerjaan.visible', false))
{{ $profileParticipant->pekerjaan ?? '-' }}
@endif @if(data_get($certificateSetting, 'jabatan.visible', false))
{{ $profileParticipant->jabatan ?? '-' }}
@endif @if(data_get($certificateSetting, 'alamat.visible', false))
{{ $profileParticipant->alamat ?? '-' }}
@endif @if(data_get($certificateSetting, 'province.visible', false))
{{ $provinceParticipant ?? '-' }}
@endif @if(data_get($certificateSetting, 'regency.visible', false))
{{ $regencyParticipant ?? '-' }}
@endif @if(data_get($certificateSetting, 'district.visible', false))
{{ $districtParticipant ?? '-' }}
@endif @if(data_get($certificateSetting, 'certificate_id.visible', false))
{{ $peserta->certificate_id ?? '-' }}
@endif @if($photoBase64 && data_get($photoStyle, 'visible', true))
@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 = data_get($qrStyle, 'top', 320); $qrLeft = data_get($qrStyle, 'left', 90); $qrSizeInput = data_get($qrStyle, 'size', 80); $qrSizeActual = data_get($qrStyle, 'actual_size'); // Terapkan pengurangan 20px dari ukuran database untuk ukuran yang ditampilkan $qrSize = max($qrSizeInput - 20, 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.download-certificate', ['id' => $activity->id]) . '?show_certificate=1'; 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
@endforeach @if(count($row) < $cols) @for($i = 0; $i < $cols - count($row); $i++)
@endfor @endif
@endforeach @endforeach