fontPath = null; foreach ($fonts as $font) { if (file_exists($font)) { $this->fontPath = $font; break; } } $this->logoPath = __DIR__ . '/../assets/logo.png'; } public function generate($data) { $width = 900; // Increased width to accommodate 6 columns comfortably // Calculate dynamic height $baseHeight = 450; $rowHeight = 40; $numRows = count($data['allocations'] ?? []); $height = $baseHeight + ($numRows * $rowHeight) + 150; $image = imagecreatetruecolor($width, $height); // Colors $white = imagecolorallocate($image, 255, 255, 255); $black = imagecolorallocate($image, 0, 0, 0); $grey = imagecolorallocate($image, 100, 100, 100); $lightGrey = imagecolorallocate($image, 240, 240, 240); imagefilledrectangle($image, 0, 0, $width, $height, $white); // Logo if (file_exists($this->logoPath)) { $logo = @imagecreatefrompng($this->logoPath); if ($logo) { $logoW = imagesx($logo); $logoH = imagesy($logo); $targetW = 100; $targetH = ($targetW / $logoW) * $logoH; imagecopyresampled($image, $logo, 40, 30, 0, 0, (int) $targetW, (int) $targetH, (int) $logoW, (int) $logoH); } } // School Name Logic $schoolName = "ACE JUNIOR COLLEGE"; $levelName = isset($data['level_name']) ? strtoupper($data['level_name']) : ''; // Check for Senior classes (SSS 1, SSS 2, SSS 3, or Senior) if (strpos($levelName, 'SSS') !== false || strpos($levelName, 'SENIOR') !== false) { $schoolName = "ACE SENIOR COLLEGE"; } // Header $this->centerText($image, 18, 50, $schoolName, $black); $this->centerText($image, 10, 75, "...education for excellence", $grey); $this->centerText($image, 10, 100, "1, Ebute - Igbogbo Road, Ipakodo, Ikorodu, Lagos, Nigeria", $black); $this->centerText($image, 10, 120, "Phone No(s): 08027449739, 08077275777", $black); $this->centerText($image, 10, 140, "mailto:info@acecollege.com.ng https://www.acecollege.com.ng", $black); // Receipt Title $titleY = 180; $receiptNo = $data['receipt_no'] ?? 'N/A'; $this->centerText($image, 12, $titleY, "SCHOOL FEES RECEIPT - REFERENCE NO: $receiptNo", $black); // Session Info $session = $data['allocations'][0]['academic_session'] ?? '----'; $term = $data['allocations'][0]['term_of_session'] ?? '-'; $nextSession = is_numeric($session) ? ($session + 1) : ''; $levelPart = isset($data['level_name']) ? ' - ' . strtoupper($data['level_name']) : ''; $sessionStr = "$session/$nextSession ACADEMIC SESSION, TERM $term" . $levelPart; $this->centerText($image, 10, $titleY + 25, $sessionStr, $grey); // Student Box $boxY = 230; imagerectangle($image, 40, $boxY, $width - 40, $boxY + 40, $grey); // We expect student_name to be passed in $data. If not, fallback. $studentName = isset($data['student_name']) ? strtoupper($data['student_name']) : (isset($data['student_code']) ? "STUDENT CODE: " . $data['student_code'] : 'UNKNOWN STUDENT'); $this->centerText($image, 14, $boxY + 26, $studentName, $grey); // Table Header $tableY = 300; // 6 Columns: Description, Term/Session, Amount Billed, Amount Paid, Paid To Date, To Balance // Width 900. Margins 40. Active 820. // Approx X: 40, 320, 440, 560, 680, 800 (Right Boundary for last col is width-40 = 860) // Let's adjust spacing. $cols = [40, 300, 440, 550, 660, 780]; imageline($image, 40, $tableY, $width - 40, $tableY, $black); imageline($image, 40, $tableY + 30, $width - 40, $tableY + 30, $black); if ($this->fontPath) { imagettftext($image, 8, 0, $cols[0], $tableY + 20, $black, $this->fontPath, "FEE DESCRIPTION"); imagettftext($image, 8, 0, $cols[1], $tableY + 20, $black, $this->fontPath, "TERM/SESSION"); imagettftext($image, 8, 0, $cols[2], $tableY + 20, $black, $this->fontPath, "AMOUNT BILLED"); imagettftext($image, 8, 0, $cols[3], $tableY + 20, $black, $this->fontPath, "AMOUNT PAID"); imagettftext($image, 8, 0, $cols[4], $tableY + 20, $black, $this->fontPath, "PAID TO DATE"); imagettftext($image, 8, 0, $cols[5], $tableY + 20, $black, $this->fontPath, "TO BALANCE"); } else { // Fallback font imagestring($image, 3, $cols[0], $tableY + 5, "FEE DESCRIPTION", $black); imagestring($image, 3, $cols[1], $tableY + 5, "TERM/SESSION", $black); imagestring($image, 3, $cols[2], $tableY + 5, "BILLED", $black); imagestring($image, 3, $cols[3], $tableY + 5, "PAID", $black); imagestring($image, 3, $cols[4], $tableY + 5, "PAID TO DATE", $black); imagestring($image, 3, $cols[5], $tableY + 5, "TO BALANCE", $black); } // Rows $currY = $tableY + 30; $totalBilled = 0; $totalPaidToDate = 0; $totalBalance = 0; // Total Paid is passed from outside $totalAmountPaid = 0; if (isset($data['allocations']) && is_array($data['allocations'])) { foreach ($data['allocations'] as $alloc) { $desc = $alloc['description']; $session = $alloc['academic_session'] ?? ''; $term = $alloc['term_of_session'] ?? ''; // Format: Term/Session e.g. "1/2025" or "1st/2025" // Assuming simpler "1/2025" format for space $termSessionStr = "$term/$session"; $billedVal = $alloc['amount_billed'] ?? 0; $paidVal = $alloc['amount'] ?? 0; $paidToDateVal = $alloc['total_paid_to_date'] ?? 0; $balanceVal = $alloc['balance'] ?? 0; $totalBilled += $billedVal; $totalAmountPaid += $paidVal; $totalPaidToDate += $paidToDateVal; $totalBalance += $balanceVal; $billed = number_format($billedVal, 2); $paid = number_format($paidVal, 2); $paidToDate = number_format($paidToDateVal, 2); $balance = number_format($balanceVal, 2); $currY += 30; if ($this->fontPath) { imagettftext($image, 8, 0, $cols[0], $currY, $black, $this->fontPath, substr($desc, 0, 35)); imagettftext($image, 8, 0, $cols[1], $currY, $black, $this->fontPath, $termSessionStr); // Term/Session } else { imagestring($image, 3, $cols[0], $currY - 10, substr($desc, 0, 35), $black); imagestring($image, 3, $cols[1], $currY - 10, $termSessionStr, $black); } // Right Alignment using approx right boundaries // Col 2 (Billed): starts 440. Right align around 530? // Col 3 (Paid): starts 550. // Col 4 (PTD): starts 660. // Col 5 (Bal): starts 780. $rbBilled = 530; $rbPaid = 640; $rbPTD = 760; $rbBal = $width - 40; $this->rightAlignText($image, 8, $rbBilled, $currY, $billed, $black); $this->rightAlignText($image, 8, $rbPaid, $currY, $paid, $black); $this->rightAlignText($image, 8, $rbPTD, $currY, $paidToDate, $black); $this->rightAlignText($image, 8, $rbBal, $currY, $balance, $black); } } // Total Row $currY += 20; imageline($image, 40, $currY, $width - 40, $currY, $black); $currY += 26; $totalPaidStr = number_format($data['total_paid'] ?? 0, 2); $totalBilledStr = number_format($totalBilled, 2); $totalPaidToDateStr = number_format($totalPaidToDate, 2); $totalBalanceStr = number_format($totalBalance, 2); // Columns for totals $rbBilled = 530; $rbPaid = 640; $rbPTD = 760; $rbBal = $width - 40; $this->rightAlignText($image, 9, $rbBilled, $currY, $totalBilledStr, $grey); $this->rightAlignText($image, 9, $rbPaid, $currY, $totalPaidStr, $grey); $this->rightAlignText($image, 9, $rbPTD, $currY, $totalPaidToDateStr, $grey); $this->rightAlignText($image, 9, $rbBal, $currY, $totalBalanceStr, $grey); imageline($image, 40, $currY + 14, $width - 40, $currY + 14, $black); // Footer $footerY = $currY + 50; if ($this->fontPath) { imagettftext($image, 9, 0, 40, $footerY, $black, $this->fontPath, "RECEIPT DULY VERIFIED BY"); imagettftext($image, 9, 0, 40, $footerY + 20, $black, $this->fontPath, "BURSAR'S OFFICE"); } else { imagestring($image, 3, 40, $footerY - 10, "RECEIPT DULY VERIFIED BY", $black); imagestring($image, 3, 40, $footerY + 10, "BURSAR'S OFFICE", $black); } $receivedY = $footerY; $this->rightAlignText($image, 9, $width - 40, $receivedY, "RECEIVED IN", $black); $this->rightAlignText($image, 9, $width - 40, $receivedY + 15, "BANK", $black); $this->rightAlignText($image, 9, $width - 40, $receivedY + 40, "RECEIVED ON", $black); $dateStr = isset($data['payment_date']) ? strtoupper(date("F d, Y", strtotime($data['payment_date']))) : date("F d, Y"); $this->rightAlignText($image, 9, $width - 40, $receivedY + 55, $dateStr, $black); ob_start(); imagepng($image); $content = ob_get_clean(); imagedestroy($image); return $content; } public function generateBase64($data) { $content = $this->generate($data); return base64_encode($content); } private function centerText($image, $size, $y, $text, $color) { if ($this->fontPath) { try { $box = imagettfbbox($size, 0, $this->fontPath, $text); $textW = $box[2] - $box[0]; $x = (int) ((imagesx($image) - $textW) / 2); imagettftext($image, $size, 0, $x, (int) $y, $color, $this->fontPath, $text); return; } catch (Exception $e) { // Fallthrough to built-in font } } // Fallback or if fontPath is missing $gdSize = (int) min(5, max(1, floor($size / 3))); $charWidth = imagefontwidth($gdSize); $textW = strlen($text) * $charWidth; $x = (int) ((imagesx($image) - $textW) / 2); imagestring($image, $gdSize, $x, (int) $y, $text, $color); } private function rightAlignText($image, $size, $rightX, $y, $text, $color) { if ($this->fontPath) { try { $box = imagettfbbox($size, 0, $this->fontPath, $text); $textW = $box[2] - $box[0]; $x = (int) ($rightX - $textW); imagettftext($image, $size, 0, $x, (int) $y, $color, $this->fontPath, $text); return; } catch (Exception $e) { // Fallthrough } } $gdSize = (int) min(5, max(1, floor($size / 3))); $charWidth = imagefontwidth($gdSize); $x = (int) ($rightX - (strlen($text) * $charWidth)); imagestring($image, $gdSize, $x, (int) $y, $text, $color); } }