TYPO3 CMS  TYPO3_7-6
SpriteGenerator.php
Go to the documentation of this file.
1 <?php
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
19 
24 {
30  protected $templateSprite = '
31 .###NAMESPACE###-###SPRITENAME### {
32  background-image: url(\'###SPRITEURL###\') !important;
33  height: ###DEFAULTHEIGHT###px;
34  width: ###DEFAULTWIDTH###px;
35 }
36 ';
37 
44 .backgroundsize .###NAMESPACE###-###SPRITENAME### {
45  background-image: url(\'###SPRITEURL###\') !important;
46  background-size:###BGWIDTH### ###BGHEIGHT###;
47 }
48 ';
54  protected $templateIcon = '.###NAMESPACE###-###ICONNAME### {
55  background-position: -###LEFT###px -###TOP###px !important;
56 ###SIZE_INFO###
57 }
58 ';
59 
63  protected $enableHighDensitySprite = true;
64 
70  protected $defaultWidth = 0;
71 
77  protected $defaultHeight = 0;
78 
84  protected $spriteWidth = 0;
85 
91  protected $spriteHeight = 0;
92 
98  protected $spriteName = '';
99 
105  protected $spriteFolder = 'typo3temp/sprites/';
106 
112  protected $cssFolder = 'typo3temp/sprites/';
113 
119  protected $omitSpriteNameInIconName = false;
120 
126  protected $nameSpace = 't3-icon';
127 
134  protected $includeTimestampInCSS = true;
135 
142  protected $spriteBases = [];
143 
149  protected $iconsData = [];
150 
156  protected $iconSizes = [];
157 
163  protected $iconNamesPerSize = [];
164 
170  protected $space = 2;
171 
177  public function __construct($spriteName)
178  {
179  $this->spriteName = $spriteName;
180  }
181 
188  public function setNamespace($nameSpace)
189  {
190  $this->nameSpace = $nameSpace;
191  return $this;
192  }
193 
200  public function setSpriteName($spriteName)
201  {
202  $this->spriteName = $spriteName;
203  return $this;
204  }
205 
212  public function setSpriteFolder($folder)
213  {
214  $this->spriteFolder = $folder;
215  return $this;
216  }
217 
224  public function setCSSFolder($folder)
225  {
226  $this->cssFolder = $folder;
227  return $this;
228  }
229 
236  public function setEnableHighDensitySprite($enable = true)
237  {
238  $this->enableHighDensitySprite = $enable;
239  return $this;
240  }
241 
248  public function setOmitSpriteNameInIconName($value)
249  {
250  $this->omitSpriteNameInIconName = is_bool($value) ? $value : false;
251  return $this;
252  }
253 
260  public function setIconSpace($value)
261  {
262  $this->space = (int)$value;
263  return $this;
264  }
265 
272  public function setIncludeTimestampInCSS($value)
273  {
274  $this->includeTimestampInCSS = is_bool($value) ? $value : true;
275  return $this;
276  }
277 
286  public function generateSpriteFromFolder(array $inputFolder)
287  {
288  $iconArray = [];
289  foreach ($inputFolder as $folder) {
290  // Detect all files to be included in sprites
291  $iconArray = array_merge($iconArray, $this->getFolder($folder));
292  }
293  return $this->generateSpriteFromArray($iconArray);
294  }
295 
304  public function generateSpriteFromArray(array $files)
305  {
306  if (!$this->omitSpriteNameInIconName) {
307  $this->spriteBases[] = $this->spriteName;
308  }
309  $this->buildFileInformationCache($files);
310  // Calculate Icon Position in sprite
311  $this->calculateSpritePositions();
312  $this->generateGraphic();
313  if ($this->enableHighDensitySprite) {
315  }
316  $this->generateCSS();
317  $iconNames = array_keys($this->iconsData);
318  natsort($iconNames);
319  return [
320  'spriteImage' => PATH_site . $this->spriteFolder . $this->spriteName . '.png',
321  'cssFile' => PATH_site . $this->cssFolder . $this->spriteName . '.css',
322  'iconNames' => $iconNames
323  ];
324  }
325 
331  protected function generateCSS()
332  {
334  $templateService = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
335  $cssData = '';
336  if ($this->includeTimestampInCSS) {
337  $timestamp = '?' . time();
338  } else {
339  $timestamp = '';
340  }
341  $spritePathForCSS = $this->resolveSpritePath();
342  $markerArray = [
343  '###NAMESPACE###' => $this->nameSpace,
344  '###DEFAULTWIDTH###' => $this->defaultWidth,
345  '###DEFAULTHEIGHT###' => $this->defaultHeight,
346  '###SPRITENAME###' => '',
347  '###SPRITEURL###' => $spritePathForCSS ? $spritePathForCSS . '/' : ''
348  ];
349  $markerArray['###SPRITEURL###'] .= $this->spriteName . '.png' . $timestamp;
350  foreach ($this->spriteBases as $base) {
351  $markerArray['###SPRITENAME###'] = $base;
352  $cssData .= $templateService->substituteMarkerArray($this->templateSprite, $markerArray);
353 
354  if ($this->enableHighDensitySprite) {
355  $highDensityMarkerArray = array_merge($markerArray, [
356  '###BGWIDTH###' => $this->spriteWidth . 'px',
357  '###BGHEIGHT###' => $this->spriteHeight . 'px',
358  '###SPRITEURL###' => str_replace(
359  $this->spriteName . '.png',
360  $this->spriteName . '@x2.png',
361  $markerArray['###SPRITEURL###']
362  )
363  ]);
364  $cssData .= $templateService->substituteMarkerArray($this->templateSpriteHighDensity, $highDensityMarkerArray);
365  }
366  }
367 
368  foreach ($this->iconsData as $data) {
369  $temp = $data['iconNameParts'];
370  array_shift($temp);
371  $cssName = implode('-', $temp);
372  $markerArrayIcons = [
373  '###NAMESPACE###' => $this->nameSpace,
374  '###ICONNAME###' => $cssName,
375  '###LEFT###' => $data['left'],
376  '###TOP###' => $data['top'],
377  '###SIZE_INFO###' => ''
378  ];
379  if ($data['height'] != $this->defaultHeight) {
380  $markerArrayIcons['###SIZE_INFO###'] .= TAB . 'height: ' . $data['height'] . 'px;' . LF;
381  }
382  if ($data['width'] != $this->defaultWidth) {
383  $markerArrayIcons['###SIZE_INFO###'] .= TAB . 'width: ' . $data['width'] . 'px;' . LF;
384  }
385  $cssData .= $templateService->substituteMarkerArray($this->templateIcon, $markerArrayIcons);
386  }
387  GeneralUtility::writeFile(PATH_site . $this->cssFolder . $this->spriteName . '.css', $cssData);
388  }
389 
395  protected function resolveSpritePath()
396  {
397  // Fix window paths
398  $this->cssFolder = str_replace('\\', '/', $this->cssFolder);
399  $this->spriteFolder = str_replace('\\', '/', $this->spriteFolder);
400  $cssPathSegments = GeneralUtility::trimExplode('/', trim($this->cssFolder, '/'));
401  $graphicPathSegments = GeneralUtility::trimExplode('/', trim($this->spriteFolder, '/'));
402  $i = 0;
403  while (isset($cssPathSegments[$i]) && isset($graphicPathSegments[$i]) && $cssPathSegments[$i] == $graphicPathSegments[$i]) {
404  unset($cssPathSegments[$i]);
405  unset($graphicPathSegments[$i]);
406  ++$i;
407  }
408  foreach ($cssPathSegments as $key => $value) {
409  $cssPathSegments[$key] = '..';
410  }
411  $completePath = array_merge($cssPathSegments, $graphicPathSegments);
412  $path = implode('/', $completePath);
413  return GeneralUtility::resolveBackPath($path);
414  }
415 
421  protected function generateGraphic()
422  {
423  $tempSprite = GeneralUtility::tempnam($this->spriteName, '.png');
424  $filePath = PATH_site . $this->spriteFolder . $this->spriteName . '.png';
425 
426  // Create black true color image with given size
427  $newSprite = imagecreatetruecolor($this->spriteWidth, $this->spriteHeight);
428  imagesavealpha($newSprite, true);
429  // Make it transparent
430  imagefill($newSprite, 0, 0, imagecolorallocatealpha($newSprite, 0, 255, 255, 127));
431  foreach ($this->iconsData as $icon) {
432  $function = 'imagecreatefrom' . strtolower($icon['fileExtension']);
433  if (function_exists($function)) {
434  $currentIcon = $function($icon['fileName']);
435  imagecopy($newSprite, $currentIcon, $icon['left'], $icon['top'], 0, 0, $icon['width'], $icon['height']);
436  }
437  }
438  imagepng($newSprite, $tempSprite);
439  GeneralUtility::upload_copy_move($tempSprite, $filePath);
440  GeneralUtility::unlink_tempfile($tempSprite);
441  }
442 
448  protected function generateHighDensityGraphic()
449  {
450  $tempSprite = GeneralUtility::tempnam($this->spriteName . '@x2', '.png');
451  $filePath = PATH_site . $this->spriteFolder . $this->spriteName . '@x2.png';
452 
453  // Create black true color image with given size
454  $newSprite = imagecreatetruecolor($this->spriteWidth * 2, $this->spriteHeight * 2);
455  imagesavealpha($newSprite, true);
456  // Make it transparent
457  imagefill($newSprite, 0, 0, imagecolorallocatealpha($newSprite, 0, 255, 255, 127));
458  foreach ($this->iconsData as $icon) {
459  $function = 'imagecreatefrom' . strtolower($icon['fileExtension']);
460  if (function_exists($function)) {
461  if ($icon['fileNameHighDensity'] !== false) {
462  // copy HighDensity file
463  $currentIcon = $function($icon['fileNameHighDensity']);
464  imagecopy($newSprite, $currentIcon, $icon['left'] * 2, $icon['top'] * 2, 0, 0, $icon['width'] * 2, $icon['height'] * 2);
465  } else {
466  // scale up normal file
467  $currentIcon = $function($icon['fileName']);
468  imagecopyresized($newSprite, $currentIcon, $icon['left'] * 2, $icon['top'] * 2, 0, 0, $icon['width'] * 2, $icon['height'] * 2, $icon['width'], $icon['height']);
469  }
470  }
471  }
472  imagepng($newSprite, $tempSprite);
473  GeneralUtility::upload_copy_move($tempSprite, $filePath);
474  GeneralUtility::unlink_tempfile($tempSprite);
475  }
480  protected function calculateSpritePositions()
481  {
482  // Calculate width of every icon-size-group
483  $sizes = [];
484  foreach ($this->iconSizes as $sizeTag => $count) {
485  $size = $this->explodeSizeTag($sizeTag);
486  $rowWidth = (int)ceil(sqrt($count)) * $size['width'];
487  while (isset($sizes[$rowWidth])) {
488  $rowWidth++;
489  }
490  $sizes[$rowWidth] = $sizeTag;
491  }
492  // Reverse sorting: widest group to top
493  krsort($sizes);
494  $currentTop = 0;
495  // Integrate all icons grouped by icons size into the sprite
496  foreach ($sizes as $sizeTag) {
497  $size = $this->explodeSizeTag($sizeTag);
498  $currentLeft = 0;
499  $rowCounter = 0;
500  $rowSize = ceil(sqrt($this->iconSizes[$sizeTag]));
501  $rowWidth = $rowSize * $size['width'] + ($rowSize - 1) * $this->space;
502  $this->spriteWidth = $rowWidth > $this->spriteWidth ? $rowWidth : $this->spriteWidth;
503  $firstLine = true;
504  natsort($this->iconNamesPerSize[$sizeTag]);
505  foreach ($this->iconNamesPerSize[$sizeTag] as $iconName) {
506  if ($rowCounter == $rowSize - 1) {
507  $rowCounter = -1;
508  } elseif ($rowCounter == 0) {
509  if (!$firstLine) {
510  $currentTop += $size['height'];
511  $currentTop += $this->space;
512  }
513  $firstLine = false;
514  $currentLeft = 0;
515  }
516  $this->iconsData[$iconName]['left'] = $currentLeft;
517  $this->iconsData[$iconName]['top'] = $currentTop;
518  $currentLeft += $size['width'];
519  $currentLeft += $this->space;
520  $rowCounter++;
521  }
522  $currentTop += $size['height'];
523  $currentTop += $this->space;
524  }
525  $this->spriteHeight = $currentTop;
526  }
527 
535  protected function getFolder($directoryPath)
536  {
537  $subFolders = GeneralUtility::get_dirs(PATH_site . $directoryPath);
538  if (!$this->omitSpriteNameInIconName) {
539  $subFolders[] = '';
540  }
541  $resultArray = [];
542  foreach ($subFolders as $folder) {
543  if ($folder !== '.svn') {
544  $icons = GeneralUtility::getFilesInDir(PATH_site . $directoryPath . $folder . '/', 'gif,png,jpg');
545  if (!in_array($folder, $this->spriteBases) && !empty($icons) && $folder !== '') {
546  $this->spriteBases[] = $folder;
547  }
548  foreach ($icons as $icon) {
549  $fileInfo = pathinfo($icon);
550  $iconName = ($folder ? $folder . '-' : '') . $fileInfo['filename'];
551  if (!$this->omitSpriteNameInIconName) {
552  $iconName = $this->spriteName . '-' . $iconName;
553  }
554  $resultArray[$iconName] = $directoryPath . $folder . '/' . $icon;
555  }
556  }
557  }
558  return $resultArray;
559  }
560 
567  protected function buildFileInformationCache(array $files)
568  {
569  foreach ($files as $iconName => $iconFile) {
570  $iconNameParts = GeneralUtility::trimExplode('-', $iconName);
571  if (!in_array($iconNameParts[0], $this->spriteBases)) {
572  $this->spriteBases[] = $iconNameParts[0];
573  }
574  $fileInfo = @pathinfo((PATH_site . $iconFile));
575  $imageInfo = @getimagesize((PATH_site . $iconFile));
576  $this->iconsData[$iconName] = [
577  'iconName' => $iconName,
578  'iconNameParts' => $iconNameParts,
579  'singleName' => $fileInfo['filename'],
580  'fileExtension' => $fileInfo['extension'],
581  'fileName' => PATH_site . $iconFile,
582  'width' => $imageInfo[0],
583  'height' => $imageInfo[1],
584  'left' => 0,
585  'top' => 0,
586  'fileNameHighDensity' => false
587  ];
588  if ($this->enableHighDensitySprite) {
589  $highDensityFile = str_replace('.' . $fileInfo['extension'], '@x2.' . $fileInfo['extension'], $iconFile);
590  if (@file_exists(PATH_site . $highDensityFile)) {
591  $this->iconsData[$iconName]['fileNameHighDensity'] = $highDensityFile;
592  }
593  }
594  $sizeTag = $imageInfo[0] . 'x' . $imageInfo[1];
595  if (isset($this->iconSizes[$sizeTag])) {
596  $this->iconSizes[$sizeTag] += 1;
597  } else {
598  $this->iconSizes[$sizeTag] = 1;
599  $this->iconNamesPerSize[$sizeTag] = [];
600  }
601  $this->iconNamesPerSize[$sizeTag][] = $iconName;
602  }
603  // Find most common image size, save it as default
604  asort($this->iconSizes);
605  $defaultSize = $this->explodeSizeTag(array_pop(array_keys($this->iconSizes)));
606  $this->defaultWidth = $defaultSize['width'];
607  $this->defaultHeight = $defaultSize['height'];
608  }
609 
616  protected function explodeSizeTag($tag = '')
617  {
618  $size = GeneralUtility::trimExplode('x', $tag);
619  return [
620  'width' => $size[0],
621  'height' => $size[1]
622  ];
623  }
624 }
static unlink_tempfile($uploadedTempFileName)
static getFilesInDir($path, $extensionList='', $prependPath=false, $order='', $excludePattern='')
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
static tempnam($filePrefix, $fileSuffix='')
static writeFile($file, $content, $changePermissions=false)
static upload_copy_move($source, $destination)