diff --git a/backend/includes/WhatsApp.php b/backend/includes/WhatsApp.php index ccb8719..8ede06e 100644 --- a/backend/includes/WhatsApp.php +++ b/backend/includes/WhatsApp.php @@ -99,13 +99,54 @@ class WhatsAppClient { return false; } + /** + * Ensure the TTF fonts exist in the backend/fonts directory. + * If they don't, try to download them. + */ + private static function ensureFontsExist() { + $fontDir = __DIR__ . '/../fonts'; + if (!is_dir($fontDir)) { + @mkdir($fontDir, 0755, true); + } + + $fonts = [ + 'Roboto-Bold.ttf' => 'https://github.com/google/fonts/raw/main/apache/roboto/static/Roboto-Bold.ttf', + 'CourierPrime-Bold.ttf' => 'https://github.com/google/fonts/raw/main/ofl/courierprime/CourierPrime-Bold.ttf' + ]; + + foreach ($fonts as $filename => $url) { + $path = $fontDir . '/' . $filename; + if (!file_exists($path) || @filesize($path) < 1000) { + // Try file_get_contents first + $content = @file_get_contents($url); + if ($content !== false && strlen($content) > 1000) { + @file_put_contents($path, $content); + } elseif (function_exists('curl_init')) { + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_TIMEOUT, 15); + $content = curl_exec($ch); + curl_close($ch); + if ($content && strlen($content) > 1000) { + @file_put_contents($path, $content); + } + } + } + } + } + /** * Generate dynamic base64-encoded image containing OTP code using GD library. * - * @param string $otp 4-digit OTP code + * @param string $otp 3-digit OTP code * @return string Base64 encoded PNG image */ public static function generateOtpImageBase64($otp) { + // Ensure fonts exist (downloads them if missing) + self::ensureFontsExist(); + // Create a 300x100 image $im = imagecreatetruecolor(300, 100); if (!$im) { @@ -116,41 +157,75 @@ class WhatsAppClient { $bgColor = imagecolorallocate($im, 240, 244, 248); // Soft grey-blue $textColor = imagecolorallocate($im, 33, 37, 41); // Dark charcoal $noiseColor = imagecolorallocate($im, 200, 210, 220); // Light noise + $otpFg = imagecolorallocate($im, 13, 110, 253); // Royal blue for OTP // Fill background imagefill($im, 0, 0, $bgColor); - // --- 1. Draw Big OTP Text by Scaling --- - // Create a small image for the OTP - $otpWidth = 45; // 3 chars * 15px width roughly - $otpHeight = 20; - $otpIm = imagecreatetruecolor($otpWidth, $otpHeight); - $otpBg = imagecolorallocate($otpIm, 240, 244, 248); - $otpFg = imagecolorallocate($otpIm, 13, 110, 253); - imagefill($otpIm, 0, 0, $otpBg); - - $chars = str_split($otp); - $x = 2; - foreach ($chars as $char) { - $y = random_int(0, 5); // Slight vertical jitter - imagestring($otpIm, 5, $x, $y, $char, $otpFg); - $x += 14; // Font 5 width is approx 9px, leaving some space + // --- 1. Check if TTF is available and fonts are loaded --- + $useTtf = false; + $availableFonts = []; + if (function_exists('imagettftext')) { + $fontDir = __DIR__ . '/../fonts'; + if (file_exists($fontDir . '/Roboto-Bold.ttf') && @filesize($fontDir . '/Roboto-Bold.ttf') > 1000) { + $availableFonts[] = $fontDir . '/Roboto-Bold.ttf'; + } + if (file_exists($fontDir . '/CourierPrime-Bold.ttf') && @filesize($fontDir . '/CourierPrime-Bold.ttf') > 1000) { + $availableFonts[] = $fontDir . '/CourierPrime-Bold.ttf'; + } + if (!empty($availableFonts)) { + $useTtf = true; + } } - // Scale it up by 3x onto the main image - $scale = 3; - $dstWidth = $otpWidth * $scale; - $dstHeight = $otpHeight * $scale; - - // Place it randomly in the bottom right-ish area - $dstX = random_int(80, 150); - $dstY = random_int(30, 40); - - imagecopyresampled($im, $otpIm, $dstX, $dstY, 0, 0, $dstWidth, $dstHeight, $otpWidth, $otpHeight); - imagedestroy($otpIm); + $chars = str_split($otp); + + if ($useTtf) { + // Render smooth OTP using TTF fonts + // Center the text block roughly. A 3-digit OTP with size 30px needs ~120px width. + $startX = random_int(85, 115); + $yBase = 65; // Baseline of TTF text + + foreach ($chars as $char) { + // Alternating / random font selection per digit + $fontFile = $availableFonts[array_rand($availableFonts)]; + $angle = random_int(-15, 15); // Dynamic tilts to thwart OCR + $size = random_int(28, 34); // Dynamic size + $yOffset = random_int(-6, 6); // Vertical jitter + + // Draw character + imagettftext($im, $size, $angle, $startX, $yBase + $yOffset, $otpFg, $fontFile, $char); + $startX += 42; // Spacing to next char (larger spacing to handle angle/size variations) + } + } else { + // Fallback: draw scaled bitmap fonts if TTF not supported + $otpWidth = 45; // 3 chars * 15px width roughly + $otpHeight = 20; + $otpIm = imagecreatetruecolor($otpWidth, $otpHeight); + $otpBg = imagecolorallocate($otpIm, 240, 244, 248); + $otpFgTemp = imagecolorallocate($otpIm, 13, 110, 253); + imagefill($otpIm, 0, 0, $otpBg); + + $x = 2; + foreach ($chars as $char) { + $y = random_int(0, 5); // Slight vertical jitter + imagestring($otpIm, 5, $x, $y, $char, $otpFgTemp); + $x += 14; + } + + // Scale it up by 3x onto the main image + $scale = 3; + $dstWidth = $otpWidth * $scale; + $dstHeight = $otpHeight * $scale; + + $dstX = random_int(80, 140); + $dstY = random_int(30, 40); + + imagecopyresampled($im, $otpIm, $dstX, $dstY, 0, 0, $dstWidth, $dstHeight, $otpWidth, $otpHeight); + imagedestroy($otpIm); + } // --- 2. Add Background Noise (Lines & Dots) --- - // Drawing noise *after* the OTP helps to obstruct it slightly from OCR for ($i = 0; $i < 6; $i++) { imageline($im, random_int(0, 300), random_int(0, 100), random_int(0, 300), random_int(0, 100), $noiseColor); } @@ -188,3 +263,4 @@ class WhatsAppClient { return base64_encode($imageData); } } +