1 <?php 
declare(strict_types
=1); 
   4  * This file is part of the Rapsys PackBundle package. 
   6  * (c) Raphaël Gertz <symfony@rapsys.eu> 
   8  * For the full copyright and license information, please view the LICENSE 
   9  * file that was distributed with this source code. 
  12 namespace Rapsys\PackBundle\Util
; 
  14 use Symfony\Component\Filesystem\Exception\IOExceptionInterface
; 
  15 use Symfony\Component\Filesystem\Filesystem
; 
  16 use Symfony\Component\Routing\Generator\UrlGeneratorInterface
; 
  17 use Symfony\Component\Routing\RouterInterface
; 
  20  * Helps manage facebook images 
  52          * The RouterInterface instance 
  54         protected RouterInterface 
$router; 
  64          * Creates a new facebook util 
  66          * @param RouterInterface $router The RouterInterface instance 
  67          * @param string $cache The cache directory 
  68          * @param string $path The public path 
  69          * @param string $prefix The prefix 
  71         function __construct(RouterInterface 
$router, string $cache = '../var/cache', string $path = './bundles/rapsyspack', string $prefix = 'facebook', string $source = 'png/facebook.png', array $fonts = [ 'default' => 'ttf/default.ttf' ]) { 
  73                 $this->cache 
= $cache.'/'.$prefix; 
  76                 $this->fonts 
= $fonts; 
  79                 $this->path 
= $path.'/'.$prefix; 
  82                 $this->router 
= $router; 
  85                 $this->prefix 
= $prefix; 
  88                 $this->source 
= $source; 
  92          * Return the facebook image 
  94          * Generate simple image in jpeg format or load it from cache 
  96          * @param string $pathInfo The request path info 
  97          * @param array $texts The image texts 
  98          * @param int $updated The updated timestamp 
  99          * @param ?string $source The image source 
 100          * @param int $width The width 
 101          * @param int $height The height 
 102          * @return array The image array 
 104         public function getImage(string $pathInfo, array $texts, int $updated, ?string $source = null, int $width = 1200, int $height = 630): array { 
 106                 $path = $this->path
.$pathInfo.'.jpeg'; 
 108                 //Without existing path 
 109                 if (!is_dir($dir = dirname($path))) { 
 110                         //Create filesystem object 
 111                         $filesystem = new Filesystem(); 
 115                                 //XXX: set as 0775, symfony umask (0022) will reduce rights (0755) 
 116                                 $filesystem->mkdir($dir, 0775); 
 117                         } catch (IOExceptionInterface 
$e) { 
 119                                 throw new \
Exception(sprintf('Output path "%s" do not exists and unable to create it', $dir), 0, $e); 
 124                 if (is_file($path) && ($mtime = stat($path)['mtime']) && $mtime >= $updated) { 
 125                         #XXX: we used to drop texts with $data['canonical'] === true !!! 
 129                                 'og:image' => $this->router
->generate('rapsys_pack_facebook', ['mtime' => $mtime, 'path' => $pathInfo], UrlGeneratorInterface
::ABSOLUTE_URL
), 
 130                                 'og:image:alt' => str_replace("\n", ' ', implode(' - ', array_keys($texts))), 
 131                                 'og:image:height' => $height, 
 132                                 'og:image:width' => $width 
 137                 $cache = $this->cache
.$pathInfo.'.png'; 
 140                 if (!is_dir($dir = dirname($cache))) { 
 141                         //Create filesystem object 
 142                         $filesystem = new Filesystem(); 
 146                                 //XXX: set as 0775, symfony umask (0022) will reduce rights (0755) 
 147                                 $filesystem->mkdir($dir, 0775); 
 148                         } catch (IOExceptionInterface 
$e) { 
 150                                 throw new \
Exception(sprintf('Output directory "%s" do not exists and unable to create it', $dir), 0, $e); 
 155                 if ($source === null) { 
 157                         $source = realpath($this->source
); 
 160                 //Create image object 
 161                 $image = new \
Imagick(); 
 163                 //Without cache image 
 164                 if (!is_file($cache) || stat($cache)['mtime'] < stat($source)['mtime']) { 
 165                         //Check target directory 
 166                         if (!is_dir($dir = dirname($cache))) { 
 167                                 //Create filesystem object 
 168                                 $filesystem = new Filesystem(); 
 172                                         //XXX: set as 0775, symfony umask (0022) will reduce rights (0755) 
 173                                         $filesystem->mkdir($dir, 0775); 
 174                                 } catch (IOExceptionInterface 
$e) { 
 176                                         throw new \
Exception(sprintf('Output directory "%s" do not exists and unable to create it', $dir), 0, $e); 
 181                         $image->readImage($source); 
 183                         //Crop using aspect ratio 
 184                         //XXX: for better result upload image directly in aspect ratio :) 
 185                         $image->cropThumbnailImage($width, $height); 
 187                         //Strip image exif data and properties 
 188                         $image->stripImage(); 
 191                         if (!$image->writeImage($cache)) { 
 193                                 throw new \
Exception(sprintf('Unable to write image "%s"', $cache)); 
 198                         $image->readImage($cache); 
 202                 $draw = new \
ImagickDraw(); 
 204                 //Set stroke antialias 
 205                 $draw->setStrokeAntialias(true); 
 208                 $draw->setTextAntialias(true); 
 212                         'left' => \Imagick
::ALIGN_LEFT
, 
 213                         'center' => \Imagick
::ALIGN_CENTER
, 
 214                         'right' => \Imagick
::ALIGN_RIGHT
 
 218                 $defaultFont = 'dejavusans'; 
 221                 $defaultAlign = 'center'; 
 227                 $defaultStroke = '#00c3f9'; 
 233                 $defaultFill = 'white'; 
 239                 $count = count($texts); 
 241                 //Draw each text stroke 
 242                 foreach($texts as $text => $data) { 
 244                         $draw->setFont($this->fonts
[$data['font']??$defaultFont]); 
 247                         $draw->setFontSize($data['size']??$defaultSize); 
 250                         $draw->setStrokeWidth($data['width']??$defaultWidth); 
 253                         $draw->setTextAlignment($align = ($aligns[$data['align']??$defaultAlign])); 
 256                         $metrics = $image->queryFontMetrics($draw, $text); 
 259                         if (empty($data['y'])) { 
 260                                 //Position verticaly each text evenly 
 261                                 $texts[$text]['y'] = $data['y'] = (($height + 
100) / (count($texts) + 
1) * $i) - 50; 
 265                         if (empty($data['x'])) { 
 266                                 if ($align == \Imagick
::ALIGN_CENTER
) { 
 267                                         $texts[$text]['x'] = $data['x'] = $width/2; 
 268                                 } elseif ($align == \Imagick
::ALIGN_LEFT
) { 
 269                                         $texts[$text]['x'] = $data['x'] = 50; 
 270                                 } elseif ($align == \Imagick
::ALIGN_RIGHT
) { 
 271                                         $texts[$text]['x'] = $data['x'] = $width - 50; 
 276                         //XXX: add ascender part then center it back by half of textHeight 
 277                         //TODO: maybe add a boundingbox ??? 
 278                         $texts[$text]['y'] = $data['y'] +
= $metrics['ascender'] - $metrics['textHeight']/2; 
 281                         $draw->setStrokeColor(new \
ImagickPixel($data['stroke']??$defaultStroke)); 
 284                         $draw->setFillColor(new \
ImagickPixel($data['stroke']??$defaultStroke)); 
 287                         $draw->annotation($data['x'], $data['y'], $text); 
 293                 //Create stroke object 
 294                 $stroke = new \
Imagick(); 
 297                 $stroke->newImage($width, $height, new \
ImagickPixel('transparent')); 
 300                 $stroke->drawImage($draw); 
 303                 //XXX: blur the stroke canvas only 
 304                 $stroke->blurImage(5,3); 
 307                 //XXX: see https://www.php.net/manual/en/image.evaluateimage.php 
 308                 $stroke->evaluateImage(\Imagick
::EVALUATE_DIVIDE
, 1.5, \Imagick
::CHANNEL_ALPHA
); 
 311                 $image->compositeImage($stroke, \Imagick
::COMPOSITE_OVER
, 0, 0); 
 323                 $draw->setTextAntialias(true); 
 326                 foreach($texts as $text => $data) { 
 328                         $draw->setFont($this->fonts
[$data['font']??$defaultFont]); 
 331                         $draw->setFontSize($data['size']??$defaultSize); 
 334                         $draw->setTextAlignment($aligns[$data['align']??$defaultAlign]); 
 337                         $draw->setFillColor(new \
ImagickPixel($data['fill']??$defaultFill)); 
 340                         $draw->annotation($data['x'], $data['y'], $text); 
 342                         //With canonical text 
 343                         if (!empty($data['canonical'])) { 
 344                                 //Prevent canonical to finish in alt 
 345                                 unset($texts[$text]); 
 350                 $image->drawImage($draw); 
 352                 //Strip image exif data and properties 
 353                 $image->stripImage(); 
 356                 $image->setImageFormat('jpeg'); 
 358                 //Set progressive jpeg 
 359                 $image->setInterlaceScheme(\Imagick
::INTERLACE_PLANE
); 
 362                 if (!$image->writeImage($path)) { 
 364                         throw new \
Exception(sprintf('Unable to write image "%s"', $path)); 
 369                         'og:image' => $this->router
->generate('rapsys_pack_facebook', ['mtime' => stat($path)['mtime'], 'path' => $pathInfo], UrlGeneratorInterface
::ABSOLUTE_URL
), 
 370                         'og:image:alt' => str_replace("\n", ' ', implode(' - ', array_keys($texts))), 
 371                         'og:image:height' => $height, 
 372                         'og:image:width' => $width