Files
eCert-MBIP/app/Services/CertificateService.php
Saufi 12aea2cbff feat: papar no. IC di bawah nama dalam sijil dan pratonton
- writeIcBelow(): auto-kira kedudukan Y (nama_y + nama_font * 1.5)
  dan saiz font (50% daripada saiz font nama), align sama dengan nama
- generate(): tulis no_kp peserta sebenar di bawah nama
- generatePreview(): tulis contoh '800808-08-8888' di bawah nama sample
- Guna font DejaVuSans.ttf (regular) untuk IC, Bold untuk nama

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 22:06:44 +08:00

143 lines
5.4 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Services;
use App\Models\Certificate;
use App\Models\Program;
use App\Models\Participant;
use App\Models\CertificateTemplate;
use Illuminate\Support\Facades\Storage;
use Intervention\Image\ImageManager;
use Intervention\Image\Drivers\Gd\Driver;
use Intervention\Image\Encoders\JpegEncoder;
use Intervention\Image\Typography\FontFactory;
class CertificateService
{
private ImageManager $manager;
public function __construct()
{
$this->manager = new ImageManager(new Driver());
}
public function generate(Certificate $certificate): void
{
$certificate->update(['status' => 'generating']);
try {
$certificate->load(['participant', 'template', 'program']);
$template = $certificate->template;
if (! $template) {
throw new \RuntimeException('Template sijil tidak dijumpai.');
}
$templatePath = Storage::disk('local')->path($template->image_path);
if (! file_exists($templatePath)) {
throw new \RuntimeException('Fail template sijil tidak dijumpai di storage.');
}
$image = $this->manager->decodePath($templatePath);
$config = $template->config_json ?? [];
$fields = $config['fields'] ?? [];
if (isset($fields['name'])) {
$this->writeText($image, $certificate->participant->name, $fields['name']);
$this->writeIcBelow($image, $certificate->participant->no_kp, $fields['name']);
}
if (isset($fields['certificate_no']) && $certificate->certificate_no) {
$this->writeText($image, $certificate->certificate_no, $fields['certificate_no']);
}
$outputDir = 'certificates/' . $certificate->program_id;
$outputFile = $outputDir . '/' . $certificate->uuid . '.jpg';
Storage::disk('local')->makeDirectory($outputDir);
$image->encode(new JpegEncoder(90))
->save(Storage::disk('local')->path($outputFile));
$certificate->update([
'file_path' => $outputFile,
'status' => 'generated',
'generated_at' => now(),
'error_message'=> null,
]);
} catch (\Throwable $e) {
$certificate->update([
'status' => 'failed',
'error_message' => $e->getMessage(),
]);
}
}
public function generatePreview(CertificateTemplate $template, string $sampleName, string $sampleNo = '', ?array $overrideFields = null, string $sampleIc = '800808-08-8888'): string
{
$templatePath = Storage::disk('local')->path($template->image_path);
$image = $this->manager->decodePath($templatePath);
$fields = $overrideFields ?? ($template->config_json['fields'] ?? []);
if (isset($fields['name'])) {
$this->writeText($image, $sampleName, $fields['name']);
$this->writeIcBelow($image, $sampleIc, $fields['name']);
}
if (isset($fields['certificate_no'])) {
$this->writeText($image, $sampleNo ?: 'ECT/2025/0001', $fields['certificate_no']);
}
return $image->encode(new JpegEncoder(85))->toString();
}
// Tulis IC di bawah nama — auto-posisi, font 50% nama
private function writeIcBelow(\Intervention\Image\Interfaces\ImageInterface $image, string $ic, array $nameCfg): void
{
$nameFontSize = (int) ($nameCfg['font_size'] ?? 48);
$icFontSize = (int) round($nameFontSize * 0.5);
// Y offset: line height nama (1.4×) + sedikit jarak
$icY = (int) ($nameCfg['y'] ?? 0) + (int) round($nameFontSize * 1.5);
$this->writeText($image, $ic, array_merge($nameCfg, [
'font_size' => $icFontSize,
'y' => $icY,
'font_file' => $nameCfg['font_file'] ?? 'DejaVuSans.ttf',
]));
}
private function writeText(\Intervention\Image\Interfaces\ImageInterface $image, string $text, array $cfg): void
{
$fontFile = $this->resolveFontPath($cfg['font_file'] ?? 'DejaVuSans-Bold.ttf');
$fontSize = (int) ($cfg['font_size'] ?? 48);
$fontColor = (string)($cfg['font_color'] ?? '#000000');
$align = (string)($cfg['align'] ?? 'center');
$valign = (string)($cfg['valign'] ?? 'top');
$x = (int) ($cfg['x'] ?? 0);
$y = (int) ($cfg['y'] ?? 0);
$image->text($text, $x, $y, function (FontFactory $font) use ($fontFile, $fontSize, $fontColor, $align, $valign) {
$font->filename($fontFile);
$font->size($fontSize);
$font->color($fontColor);
$font->align($align, $valign); // v4: satu kaedah untuk horizontal + vertical
});
}
private function resolveFontPath(string $filename): string
{
$custom = resource_path('fonts/' . $filename);
if (file_exists($custom)) {
return $custom;
}
return resource_path('fonts/DejaVuSans-Bold.ttf');
}
public function buildCertificateNo(Program $program, Participant $participant, int $sequence): string
{
$year = now()->format('Y');
$prefix = strtoupper(substr(preg_replace('/[^A-Za-z]/', '', $program->title), 0, 4));
return sprintf('%s/%s/%04d', $prefix, $year, $sequence);
}
}