diff --git a/src/packages/plg_system_mokoog/src/Helper/ImageGenerator.php b/src/packages/plg_system_mokoog/src/Helper/ImageGenerator.php new file mode 100644 index 0000000..2837cdc --- /dev/null +++ b/src/packages/plg_system_mokoog/src/Helper/ImageGenerator.php @@ -0,0 +1,160 @@ + + * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. + * @license GNU General Public License version 3 or later; see LICENSE + */ + +namespace Joomla\Plugin\System\MokoOG\Helper; + +defined('_JEXEC') or die; + +use Joomla\CMS\Filesystem\Folder; + +class ImageGenerator +{ + private const WIDTH = 1200; + private const HEIGHT = 630; + private const OUTPUT_DIR = 'images/mokoog/generated'; + + /** + * Generate an OG image with title text overlaid on a template background. + * + * @param string $title Article title to overlay + * @param string $templateImage Path to template/background image relative to JPATH_ROOT + * @param string $fontFile Absolute path to TTF font file + * @param int $fontSize Font size in points (default 42) + * @param array $fontColor RGB array [r, g, b] (default white) + * @param int $quality JPEG quality (default 90) + * + * @return string Path to generated image relative to JPATH_ROOT, or empty on failure + */ + public static function generate( + string $title, + string $templateImage, + string $fontFile = '', + int $fontSize = 42, + array $fontColor = [255, 255, 255], + int $quality = 90 + ): string { + $templateAbs = JPATH_ROOT . '/' . ltrim($templateImage, '/'); + + if (!is_file($templateAbs)) { + return ''; + } + + if (!$fontFile || !is_file($fontFile)) { + return ''; + } + + $outputDir = JPATH_ROOT . '/' . self::OUTPUT_DIR; + + if (!is_dir($outputDir)) { + Folder::create($outputDir); + } + + $hash = md5($title . $templateImage . $fontSize); + $outputName = 'overlay_' . $hash . '.jpg'; + $outputPath = $outputDir . '/' . $outputName; + $outputRel = self::OUTPUT_DIR . '/' . $outputName; + + // Skip if already generated + if (is_file($outputPath)) { + return $outputRel; + } + + // Load template image + $imageInfo = @getimagesize($templateAbs); + + if (!$imageInfo) { + return ''; + } + + $source = match ($imageInfo[2]) { + IMAGETYPE_JPEG => @imagecreatefromjpeg($templateAbs), + IMAGETYPE_PNG => @imagecreatefrompng($templateAbs), + IMAGETYPE_WEBP => @imagecreatefromwebp($templateAbs), + default => false, + }; + + if (!$source) { + return ''; + } + + // Create output canvas at target dimensions + $canvas = imagecreatetruecolor(self::WIDTH, self::HEIGHT); + + imagecopyresampled( + $canvas, + $source, + 0, 0, 0, 0, + self::WIDTH, self::HEIGHT, + $imageInfo[0], $imageInfo[1] + ); + + imagedestroy($source); + + // Semi-transparent overlay for text readability + $overlay = imagecolorallocatealpha($canvas, 0, 0, 0, 64); + imagefilledrectangle($canvas, 0, (int) (self::HEIGHT * 0.55), self::WIDTH, self::HEIGHT, $overlay); + + // Render title text with word wrapping + $textColor = imagecolorallocate($canvas, $fontColor[0], $fontColor[1], $fontColor[2]); + $wrappedTitle = self::wrapText($title, $fontFile, $fontSize, (int) (self::WIDTH * 0.85)); + $textX = (int) (self::WIDTH * 0.075); + $textY = (int) (self::HEIGHT * 0.72); + + imagettftext($canvas, $fontSize, 0, $textX, $textY, $textColor, $fontFile, $wrappedTitle); + + // Save + imagejpeg($canvas, $outputPath, $quality); + imagedestroy($canvas); + + return $outputRel; + } + + /** + * Wrap text to fit within a maximum pixel width. + * + * @param string $text Text to wrap + * @param string $fontFile Path to TTF font + * @param int $fontSize Font size in points + * @param int $maxWidth Maximum width in pixels + * + * @return string Wrapped text with newlines + */ + private static function wrapText(string $text, string $fontFile, int $fontSize, int $maxWidth): string + { + $words = explode(' ', $text); + $lines = []; + $line = ''; + + foreach ($words as $word) { + $testLine = $line ? $line . ' ' . $word : $word; + $bbox = imagettfbbox($fontSize, 0, $fontFile, $testLine); + $lineWidth = abs($bbox[4] - $bbox[0]); + + if ($lineWidth > $maxWidth && $line !== '') { + $lines[] = $line; + $line = $word; + } else { + $line = $testLine; + } + } + + if ($line !== '') { + $lines[] = $line; + } + + // Limit to 3 lines, truncate last line if needed + if (\count($lines) > 3) { + $lines = \array_slice($lines, 0, 3); + $lines[2] = mb_substr($lines[2], 0, -3) . '...'; + } + + return implode("\n", $lines); + } +}