dynamic_image_generator-1.0.x-dev/image_creating_engine/src/Service/InbuiltImageGenerator.php
image_creating_engine/src/Service/InbuiltImageGenerator.php
<?php
namespace Drupal\image_creating_engine\Service;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\TempStore\PrivateTempStoreFactory;
use Drupal\file\Entity\File;
use Drupal\file\FileInterface;
use Drupal\Core\File\FileUrlGeneratorInterface;
/**
* Service for generating images using wkhtmltoimage.
* This service now uses wkhtmltoimage instead of Chrome/Browsershot for better server compatibility.
*/
class InbuiltImageGenerator {
/**
* The file system service.
*
* @var \Drupal\Core\File\FileSystemInterface
*/
protected $fileSystem;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The logger service.
*
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
/**
* The config factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* The renderer service.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* The private tempstore.
*
* @var \Drupal\Core\TempStore\PrivateTempStore
*/
protected $tempStore;
/**
* The file URL generator service.
*
* @var \Drupal\Core\File\FileUrlGeneratorInterface
*/
protected $fileUrlGenerator;
/**
* Constructs an InbuiltImageGenerator object.
*/
public function __construct(
FileSystemInterface $file_system,
EntityTypeManagerInterface $entity_type_manager,
LoggerChannelFactoryInterface $logger_factory,
ConfigFactoryInterface $config_factory,
RendererInterface $renderer,
PrivateTempStoreFactory $temp_store_factory,
FileUrlGeneratorInterface $file_url_generator
) {
$this->fileSystem = $file_system;
$this->entityTypeManager = $entity_type_manager;
$this->logger = $logger_factory->get('image_creating_engine');
$this->configFactory = $config_factory;
$this->renderer = $renderer;
$this->tempStore = $temp_store_factory->get('image_creating_engine');
$this->fileUrlGenerator = $file_url_generator;
}
/**
* Generate an image from HTML and CSS using wkhtmltoimage.
*
* @param string $html
* The HTML content.
* @param string $css
* The CSS content.
* @param array $options
* An array of options for image generation.
*
* @return array|null
* An array containing the file URI and File entity, or NULL on failure.
*/
public function generateImage($html, $css, array $options = []) {
try {
// Check if wkhtmltoimage is available
if (!$this->checkDependencies()) {
return NULL;
}
// Set up options for image generation
$width = $options['width'] ?? 0;
$height = $options['height'] ?? 0;
$format = strtolower($options['format'] ?? 'png');
$quality = $options['quality'] ?? 94;
// Make sure format is valid
if (!in_array($format, ['png', 'jpg', 'jpeg'])) {
$format = 'png';
}
// Set up the output file path
$output_dir = 'public://dynamic_image_generator/generated';
$this->fileSystem->prepareDirectory($output_dir, FileSystemInterface::CREATE_DIRECTORY);
$output_file = $output_dir . '/image_' . uniqid() . '.' . $format;
$output_path = $this->fileSystem->realpath($output_file);
// Generate the image using wkhtmltoimage
$result = $this->generateImageWithWkhtml($html, $css, $output_path, $width, $height, $format, $quality);
if ($result && file_exists($output_path) && filesize($output_path) > 0) {
// Create a file entity for the generated image
$file = File::create([
'uri' => $output_file,
'uid' => \Drupal::currentUser()->id(),
'status' => FileInterface::STATUS_PERMANENT,
]);
$file->save();
// Generate the URL properly
$file_url = $this->fileUrlGenerator->generateAbsoluteString($output_file);
$this->logger->info('Successfully generated image at: @uri with dimensions @widthx@height, size: @size bytes, URL: @url', [
'@uri' => $output_file,
'@width' => $width,
'@height' => $height,
'@size' => filesize($output_path),
'@url' => $file_url,
]);
// Return both the file URI and the File entity
return [
'uri' => $output_file,
'file' => $file,
'url' => $file_url,
'width' => $width,
'height' => $height,
'format' => $format,
'generator' => 'wkhtmltoimage',
'success' => TRUE,
];
} else {
$this->logger->error('Failed to generate image with wkhtmltoimage - file not created or empty');
}
return NULL;
}
catch (\Exception $e) {
$this->logger->error('Error generating image: @error', ['@error' => $e->getMessage()]);
return NULL;
}
}
/**
* Check if wkhtmltoimage is available.
*
* @return bool
* TRUE if wkhtmltoimage is available, FALSE otherwise.
*/
protected function checkDependencies() {
$wkhtml_path = $this->findWkhtmlPath();
if (!$wkhtml_path) {
$this->logger->error('wkhtmltoimage is not installed or not in PATH. Install with: sudo apt-get install wkhtmltopdf');
return FALSE;
}
// Test if wkhtmltoimage can run
$test_command = escapeshellarg($wkhtml_path) . ' --version 2>&1';
$output = shell_exec($test_command);
if (!$output || strpos($output, 'wkhtmltoimage') === FALSE) {
$this->logger->error('wkhtmltoimage test failed. Output: @output', ['@output' => $output]);
return FALSE;
}
$this->logger->info('wkhtmltoimage available at: @path, version: @version', [
'@path' => $wkhtml_path,
'@version' => trim($output),
]);
return TRUE;
}
/**
* Find wkhtmltoimage executable path.
*
* @return string|null
* Path to wkhtmltoimage executable or NULL if not found.
*/
protected function findWkhtmlPath() {
$possible_paths = [
'/usr/bin/wkhtmltoimage',
'/usr/local/bin/wkhtmltoimage',
'/opt/bin/wkhtmltoimage',
'/bin/wkhtmltoimage',
];
// Check direct paths first
foreach ($possible_paths as $path) {
if (file_exists($path) && is_executable($path)) {
return $path;
}
}
// Try which command
$which_result = trim(shell_exec('which wkhtmltoimage 2>/dev/null') ?: '');
if ($which_result && file_exists($which_result) && is_executable($which_result)) {
return $which_result;
}
return NULL;
}
/**
* Generate image using wkhtmltoimage.
*/
protected function generateImageWithWkhtml($html, $css, $output_path, $width, $height, $format, $quality) {
try {
$wkhtml_path = $this->findWkhtmlPath();
if (!$wkhtml_path) {
throw new \Exception('wkhtmltoimage not found');
}
// Create temporary HTML file
$temp_html = tempnam(sys_get_temp_dir(), 'wkhtml_') . '.html';
$full_html = $this->buildCompleteHtml($html, $css);
file_put_contents($temp_html, $full_html);
// Build wkhtmltoimage command with only supported options
$command_parts = [
escapeshellarg($wkhtml_path),
'--width', (string)$width,
'--height', (string)$height,
'--format', ($format === 'jpg' ? 'jpg' : 'png'),
];
// Add quality for JPEG
if ($format === 'jpg' || $format === 'jpeg') {
$command_parts[] = '--quality';
$command_parts[] = (string)$quality;
}
// Add only the supported options for your version
$command_parts = array_merge($command_parts, [
'--log-level', 'error', // Reduce verbose output
escapeshellarg($temp_html),
escapeshellarg($output_path),
]);
$command = implode(' ', $command_parts) . ' 2>&1';
$this->logger->info('Executing wkhtmltoimage command: @command', ['@command' => $command]);
// Execute command
$output = shell_exec($command);
$success = file_exists($output_path) && filesize($output_path) > 0;
if ($success) {
$this->logger->info('wkhtmltoimage generated image successfully. Size: @size bytes', [
'@size' => filesize($output_path),
]);
} else {
$this->logger->error('wkhtmltoimage failed. Command: @command, Output: @output', [
'@command' => $command,
'@output' => $output
]);
}
// Clean up temporary file
if (file_exists($temp_html)) {
unlink($temp_html);
}
return $success;
} catch (\Exception $e) {
$this->logger->error('Error in wkhtmltoimage generation: @error', ['@error' => $e->getMessage()]);
// Clean up temporary file
if (isset($temp_html) && file_exists($temp_html)) {
unlink($temp_html);
}
return FALSE;
}
}
/**
* Build complete HTML document with CSS.
*/
protected function buildCompleteHtml($html, $css) {
$full_html = '<!DOCTYPE html>';
$full_html .= '<html lang="en">';
$full_html .= '<head>';
$full_html .= '<meta charset="UTF-8">';
$full_html .= '<meta name="viewport" content="width=device-width, initial-scale=1.0">';
$full_html .= '<style>';
$full_html .= 'body { margin: 0; padding: 20px; font-family: Arial, sans-serif; }';
$full_html .= $css;
$full_html .= '</style>';
$full_html .= '</head>';
$full_html .= '<body>';
$full_html .= $html;
$full_html .= '</body>';
$full_html .= '</html>';
return $full_html;
}
}
