@foreach($participants->chunk($certificatesPerPage) as $pageIndex => $page)
@if($pageIndex > 0)
@foreach($row as $peserta)
@php
$profileParticipant = optional(optional($peserta)->user)->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 & Fit to Paper Logic (same as print_certificates_html)
if ($isDesignFormat) {
$origWidthCm = (float) data_get($certificateSetting, 'page.width_cm', 29.7);
$origHeightCm = (float) data_get($certificateSetting, 'page.height_cm', 21);
} else {
$origWidthCm = (float) data_get($certificateSetting, 'card.width_cm', 8.6);
$origHeightCm = (float) data_get($certificateSetting, 'card.height_cm', 15);
}
$widthCm = $origWidthCm;
$heightCm = $origHeightCm;
$paper = data_get($printSettings ?? [], 'paper', 'A4');
$orientation = data_get($printSettings ?? [], 'orientation', 'landscape');
$paperUpper = strtoupper($paper);
$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;
case 'IDCARD': $paperDims = [5.398, 8.56]; break;
}
if ($orientation === '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);
}
}
$sizeAdjustMm = (float) data_get($printSettings ?? [], 'size_adjust_mm', 0);
$offsetTopMm = (float) data_get($printSettings ?? [], 'offset_top_mm', 0);
$offsetLeftMm = (float) data_get($printSettings ?? [], 'offset_left_mm', 0);
$sizeAdjustCm = $sizeAdjustMm / 10.0;
$offsetTopCm = $offsetTopMm / 10.0;
$offsetLeftCm = $offsetLeftMm / 10.0;
$availableWidthCm = max(0.0, $paperDims[0] - 2*$marginVal);
$availableHeightCm = max(0.0, $paperDims[1] - 2*$marginVal);
$fitEpsilonCm = 0.1;
if ((($cols ?? 1) == 1) && (($rows ?? 1) == 1)) {
$availW = max(0.0, ($availableWidthCm - $fitEpsilonCm) + $sizeAdjustCm);
$availH = max(0.0, ($availableHeightCm - $fitEpsilonCm) + $sizeAdjustCm);
// Use Stretch to Fit (match print_certificates_html.blade.php)
// This ensures that if the design was done on a stretched canvas, the preview matches it.
$widthCm = $availW;
$heightCm = $availH;
} else {
$widthCm = min(max(0.0, $widthCm + $sizeAdjustCm), $availableWidthCm - $fitEpsilonCm);
$heightCm = min(max(0.0, $heightCm + $sizeAdjustCm), $availableHeightCm - $fitEpsilonCm);
}
// MATCH PRINTING LOGIC: Scale to fit paper, then scale elements
if ($isDesignFormat) {
$savedBaseW = (float) ($origWidthCm * 37.795);
$savedBaseH = (float) ($origHeightCm * 37.795);
} else {
$savedBaseW = (float) (data_get($certificateSetting, 'card.base_width_px') ?? data_get($certificateSetting, 'card.width_px') ?? 0);
$savedBaseH = (float) (data_get($certificateSetting, 'card.base_height_px') ?? data_get($certificateSetting, 'card.height_px') ?? 0);
}
$pxW = $widthCm * 37.8;
$pxH = $heightCm * 37.8;
$baseW = ($savedBaseW > 0) ? $savedBaseW : $pxW;
$baseH = ($savedBaseH > 0) ? $savedBaseH : $pxH;
$scaleX = ($savedBaseW > 0) ? ($pxW / $savedBaseW) : 1.0;
$scaleY = ($savedBaseH > 0) ? ($pxH / $savedBaseH) : 1.0;
$fontScale = 1.0; // Fonts are not scaled in print_certificates_html.blade.php
// 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($allPages as $pIdx => $currentPageSetting)
@php
// If it's a multi-page structure, we need to recalculate some things for each page
if (data_get($certificateSetting, 'pages')) {
$widthCm = (float) data_get($currentPageSetting, 'page.width_cm', $origWidthCm);
$heightCm = (float) data_get($currentPageSetting, 'page.height_cm', $origHeightCm);
// Re-calculate scaling for this specific page
$pxW = $widthCm * 37.8;
$pxH = $heightCm * 37.8;
$baseW = (float) ($widthCm * 37.795);
$baseH = (float) ($heightCm * 37.795);
$scaleX = ($baseW > 0) ? ($pxW / $baseW) : 1.0;
$scaleY = ($baseH > 0) ? ($pxH / $baseH) : 1.0;
}
$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');
}
$bgPathCandidate = null;
if ($bgFilename) {
$bgPathCandidate = public_path('assets/images/certificate/' . ltrim($bgFilename, '/'));
if (!file_exists($bgPathCandidate) && (str_starts_with($bgFilename, 'certificate-backgrounds/') || str_starts_with($bgFilename, 'storage/'))) {
$bgPathCandidate = \Illuminate\Support\Facades\Storage::disk('public')->path(ltrim($bgFilename, 'storage/'));
}
}
$bgFileExists = $bgPathCandidate && file_exists($bgPathCandidate);
$bgPath = $bgFileExists ? $bgPathCandidate : null;
if (!$bgPath) {
$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);
$titleStyle = data_get($currentPageSetting, 'title', []);
$photoStyle = data_get($currentPageSetting, 'photo', []);
$qrStyle = data_get($currentPageSetting, 'qr', []);
$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);
$qrSize = max(0, (int) round($qrSizeInput * $scaleX));
@endphp
@if($pIdx > 0)
@endif
@if($bgBase64)

@else
 }})
@endif
@if($isDesignFormat || data_get($certificateSetting, 'pages'))
@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
{{-- Legacy fixed elements (kept for backward compatibility) --}}
@include('pdf.certificates.legacy_elements', ['certificateSetting' => $currentPageSetting, 'peserta' => $peserta, 'scaleX' => $scaleX, 'scaleY' => $scaleY])
@endif
@endforeach
@if($isDoubleSided && !data_get($certificateSetting, 'pages'))
@php
$titleFont = data_get($titleStyle, 'font');
if (empty($titleFont) || $titleFont === 'undefined') { $titleFont = 'DejaVu Sans'; }
@endphp
@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');
// Use exact values from database - match the exact precision shown in debug
$nameTopRaw = (float) data_get($certificateSetting, 'name.top', 190);
$nameLeftRaw = (float) data_get($certificateSetting, 'name.left', 30);
$nameWidthRaw = (float) data_get($certificateSetting, 'name.width', 180);
// Apply scaling exactly like print_certificates_html.blade.php
// Calculate with full precision first, then round for display
$nameTopCalculated = $nameTopRaw * $scaleY;
$nameLeftCalculated = $nameLeftRaw * $scaleX;
$nameWidthCalculated = $nameWidthRaw * $scaleX;
// Round to match print behavior (print uses int round)
$nameTop = (int) round($nameTopCalculated);
$nameLeft = (int) round($nameLeftCalculated);
$nameWidth = (int) round($nameWidthCalculated);
@endphp
{{ optional(optional($peserta)->user)->name ?? '-' }}
@endif
@if(data_get($certificateSetting, 'email.visible', true))
@php
$emailFont = data_get($certificateSetting, 'email.font');
if (empty($emailFont) || $emailFont === 'undefined') { $emailFont = 'DejaVu Sans'; }
@endphp
@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
{{ optional(optional($peserta)->user)->email ?? '-' }}
@endif
@if(data_get($certificateSetting, 'no_hp.visible', false))
{{ optional($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
{{ optional($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
{{ optional($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
{{ optional($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
{{ optional($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
{{ optional($profileParticipant)->alamat ?? '-' }}
@endif
@if(data_get($certificateSetting, 'province.visible', false))
@php
$provFont = data_get($certificateSetting, 'province.font');
if (empty($provFont) || $provFont === 'undefined') { $provFont = 'DejaVu Sans'; }
$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
$regFont = data_get($certificateSetting, 'regency.font');
if (empty($regFont) || $regFont === 'undefined') { $regFont = 'DejaVu Sans'; }
$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
$distFont = data_get($certificateSetting, 'district.font');
if (empty($distFont) || $distFont === 'undefined') { $distFont = 'DejaVu Sans'; }
$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
$certIdFont = data_get($certificateSetting, 'certificate_id.font');
if (empty($certIdFont) || $certIdFont === 'undefined') { $certIdFont = 'DejaVu Sans'; }
$certTop = (int) round((float) data_get($certificateSetting, 'certificate_id.top', 360) * $scaleY);
$certLeft = (int) round((float) data_get($certificateSetting, 'certificate_id.left', 30) * $scaleX);
$certWidth = (int) round((float) data_get($certificateSetting, 'certificate_id.width', 180) * $scaleX);
@endphp
{{ $peserta->certificate_id ?? '-' }}
@endif
@if(data_get($photoStyle, 'visible', true))
@if($photoBase64)
@endif
@endif
@if(data_get($qrStyle, 'visible', true))
@php
$qrVal = route('activity.download-certificate', ['id' => $activity->id]) . '?certificate_id=' . urlencode((string) ($peserta->certificate_id ?? ''));
$qrSizeInt = (int) round($qrSize);
try {
$qrBinary = \SimpleSoftwareIO\QrCode\Facades\QrCode::format('png')->size(max($qrSizeInt,40))->generate((string) $qrVal);
$qrSrc = 'data:image/png;base64,'.base64_encode($qrBinary);
} catch (\Throwable $e) {
$qrSrc = 'https://api.qrserver.com/v1/create-qr-code/?size='.max($qrSizeInt,40).'x'.max($qrSizeInt,40).'&data='.urlencode((string) $qrVal);
}
@endphp
@endif
@endif