Added feature #16228: [Feature] TCA tree (Thanks to Steffen Ritter) - part 1 (sprites)
[Packages/TYPO3.CMS.git] / t3lib / spritemanager / class.t3lib_spritemanager_spritegenerator.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2010 Steffen Ritter <info@steffen-ritter.net>
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 * A copy is found in the textfile GPL.txt and important notices to the license
17 * from the author is found in LICENSE.txt distributed with these scripts.
18 *
19 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27
28
29 /**
30 * sprite generator
31 *
32 * @author Steffen Ritter <info@steffen-ritter.net>
33 * @package TYPO3
34 * @subpackage t3lib
35 */
36
37 class t3lib_spritemanager_SpriteGenerator {
38 /**
39 * template creating CSS for the spritefile
40 *
41 * @var string
42 */
43 protected $templateSprite = '
44 .###NAMESPACE###-###SPRITENAME### {
45 background-image: url(\'###SPRITEURL###\') !important;
46 height: ###DEFAULTHEIGHT###px;
47 width: ###DEFAULTWIDTH###px;
48 }
49 ';
50
51 /**
52 *
53 * template creating CSS for position and size of a single icon
54 *
55 * @var string
56 */
57 protected $templateIcon = '.###NAMESPACE###-###ICONNAME### {
58 background-position: -###LEFT###px -###TOP###px !important;
59 ###SIZE_INFO###
60 }
61 ';
62
63 /**
64 * most common icon-width in the sprite
65 *
66 * @var int
67 */
68 protected $defaultWidth = 0;
69
70 /**
71 * most common icon-height in the sprite
72 *
73 * @var int
74 */
75 protected $defaultHeight = 0;
76
77 /**
78 * calculated width of the sprite
79 *
80 * @var int
81 */
82 protected $spriteWidth = 0;
83
84 /**
85 * calculated height of the sprite
86 * @var int
87 */
88 protected $spriteHeight = 0;
89
90 /**
91 * sprite name, will be the filename, too
92 *
93 * @var string
94 */
95 protected $spriteName = '';
96
97 /**
98 * the folder the sprite-images will be saved (relative to PATH_site)
99 *
100 * @var string
101 */
102 protected $spriteFolder = 'typo3temp/sprites/';
103
104 /**
105 * the folder the sprite-cs will be saved (relative to PATH_site)
106 *
107 * @var string
108 */
109 protected $cssFolder = 'typo3temp/sprites/';
110
111 /**
112 * the spriteName will not be included in icon names
113 *
114 * @var boolean
115 */
116 protected $ommitSpriteNameInIconName = FALSE;
117
118 /**
119 * @var boolean
120 * @deprecated IE6 support will be dropped within 4.6 - then gifcopies are superflous
121 */
122 protected $generateGIFCopy = TRUE;
123
124 /**
125 * namespace of css classes
126 *
127 * @var string
128 */
129 protected $nameSpace = 't3-icon';
130
131 /**
132 * setting this to true, the timestamp of the creation will be included to the background import
133 * helps to easily rebuild sprites without cache problems
134 *
135 * @var boolean
136 */
137 protected $includeTimestampInCSS = TRUE;
138
139 /**
140 * all bases/root-names included in the sprite which has to be in css
141 * as sprite to include the background-image
142 *
143 * @var array
144 */
145 protected $spriteBases = array();
146
147 /**
148 * collects data about all icons to process
149 *
150 * @var array
151 */
152 protected $iconsData = array();
153
154 /**
155 * collects all sizes of icons within this sprite and there count
156 *
157 * @var array
158 */
159 protected $iconSizes = array();
160
161 /**
162 * maps icon-sizes to iconnames
163 *
164 * @var array
165 */
166 protected $iconNamesPerSize = array();
167
168 /**
169 * space in px between to icons in the sprite (gap)
170 *
171 * @var int
172 */
173 protected $space = 2;
174
175 /**
176 * Initializes the configuration of the spritegenerator
177 *
178 * @param string $spriteName the name of the sprite to be generated
179 * @return void
180 */
181 public function __construct($spriteName) {
182 $this->spriteName = $spriteName;
183 }
184
185 /**
186 * Sets namespace of css code
187 *
188 * @param string $string
189 * @return t3lib_spritemanager_SpriteGenerator an instance of $this, to enable chaining.
190 */
191 public function setNamespace($nameSpace) {
192 $this->nameSpace = $nameSpace;
193 return $this;
194 }
195
196 /**
197 * Sets the spritename
198 *
199 * @param string $spriteName the name of the sprite to be generated
200 * @return t3lib_spritemanager_SpriteGenerator an instance of $this, to enable chaining.
201 */
202 public function setSpriteName($spriteName) {
203 $this->spriteName = $spriteName;
204 return $this;
205 }
206
207 /**
208 * Sets the sprite-graphics target-folder
209 *
210 * @param string $folder the target folder where the generated sprite is stored
211 * @return t3lib_spritemanager_SpriteGenerator an instance of $this, to enable chaining.
212 */
213 public function setSpriteFolder($folder) {
214 $this->spriteFolder = $folder;
215 return $this;
216 }
217
218 /**
219 * Sets the sprite-css target-folder
220 *
221 * @param string $folder the target folder where the generated CSS files are stored
222 * @return t3lib_spritemanager_SpriteGenerator an instance of $this, to enable chaining.
223 */
224 public function setCSSFolder($folder) {
225 $this->cssFolder = $folder;
226 return $this;
227 }
228
229 /**
230 * Setter do enable the exclusion of the sprites-name from iconnames
231 *
232 * @param boolean $value
233 * @return t3lib_spritemanager_SpriteGenerator an instance of $this, to enable chaining.
234 */
235 public function setOmmitSpriteNameInIconName($value) {
236 $this->ommitSpriteNameInIconName = is_bool($value) ? $value : FALSE;
237 return $this;
238 }
239
240 /**
241 * Setter to adjust how much space is between to icons in the sprite
242 *
243 * @param int $value
244 * @return t3lib_spritemanager_SpriteGenerator an instance of $this, to enable chaining.
245 */
246 public function setIconSpace($value) {
247 $this->space = intval($value);
248 return $this;
249 }
250
251 /**
252 * Setter to enable/disable generating a GIF-Copy of the sprite
253 *
254 * @param boolean $value
255 * @deprecated IE6 support will be dropped within 4.6 - then gifcopies are superflous
256 * @return t3lib_spritemanager_SpriteGenerator an instance of $this, to enable chaining.
257 */
258 public function setGenerateGifCopy($value) {
259 $this->generateGIFCopy = is_bool($value) ? $value : TRUE;
260 return $this;
261 }
262
263 /**
264 * Setter for timestamp inclusion: imageFiles will be included with ?timestamp
265 *
266 * @param boolean $value
267 * @return t3lib_spritemanager_SpriteGenerator an instance of $this, to enable chaining.
268 */
269 public function setIncludeTimestampInCSS($value) {
270 $this->includeTimestampInCSS = is_bool($value) ? $value : TRUE;
271 return $this;
272 }
273
274 /**
275 * Teads all png,gif,jpg files from the passed folder name (including 1 subfolder level)
276 * extracts size information and stores data in internal array,
277 * afterwards triggers sprite generation.
278 *
279 * @param array $inputFolder folder from which files are read
280 * @return array
281 */
282 public function generateSpriteFromFolder(array $inputFolder) {
283 $iconArray = array();
284 foreach ($inputFolder as $folder) {
285 // detect all files to be included in sprites
286 $iconArray = array_merge(
287 $iconArray,
288 $this->getFolder($folder)
289 );
290 }
291 return $this->generateSpriteFromArray($iconArray);
292 }
293
294 /**
295 * Method processes an array of files into an sprite,
296 * the array can be build from files within an folder or
297 * by hand (as the SpriteManager does)
298 *
299 * @param array $files array(icon name => icon file)
300 * @return array
301 */
302 public function generateSpriteFromArray(array $files) {
303 if (!$this->ommitSpriteNameInIconName) {
304 $this->spriteBases[] = $this->spriteName;
305 }
306
307 $this->buildFileInformationCache($files);
308 // calculate Icon Position in sprite
309 $this->calculateSpritePositions();
310
311 $this->generateGraphic();
312
313 $this->generateCSS();
314
315 $iconNames = array_keys($this->iconsData);
316 natsort($iconNames);
317
318 return array(
319 'spriteImage' => PATH_site . $this->spriteFolder . $this->spriteName . '.png',
320 'spriteGifImage'=> PATH_site . $this->spriteFolder . $this->spriteName . '.gif',
321 'cssFile' => PATH_site . $this->cssFolder . $this->spriteName . '.css',
322 'cssGif' => PATH_site . $this->cssFolder . $this->spriteName . '-ie6.css',
323 'iconNames' => $iconNames
324 );
325 }
326
327 /**
328 * Generates the css files
329 *
330 * @return void
331 */
332 protected function generateCSS() {
333 $cssData = '';
334 $cssIe6 = '';
335
336 if ($this->includeTimestampInCSS) {
337 $timestamp = '?' . time();
338 } else {
339 $timestamp = '';
340 }
341
342 $spritePathForCSS = $this->resolveSpritePath();
343
344 $markerArray = array(
345 '###NAMESPACE###' => $this->nameSpace,
346 '###DEFAULTWIDTH###' => $this->defaultWidth,
347 '###DEFAULTHEIGHT###' => $this->defaultHeight,
348 '###SPRITENAME###' => '',
349 '###SPRITEURL###' => ($spritePathForCSS ? $spritePathForCSS . '/' : '')
350 );
351 $markerArray['###SPRITEURL###'] .= $this->spriteName . '.png' . $timestamp;
352
353 foreach ($this->spriteBases as $base) {
354 $markerArray['###SPRITENAME###'] = $base;
355 $cssData .= t3lib_parsehtml::substituteMarkerArray($this->templateSprite, $markerArray);
356 }
357
358 if ($this->generateGIFCopy) {
359 $markerArray['###SPRITEURL###'] = str_replace('.png', '.gif', $markerArray['###SPRITEURL###']);
360 foreach ($this->spriteBases as $base) {
361 $cssIe6 .= t3lib_parsehtml::substituteMarkerArray($this->templateSprite, $markerArray);
362 }
363 }
364
365 foreach ($this->iconsData as $key => $data) {
366 $temp = $data['iconNameParts'];
367 array_shift($temp);
368 $cssName = implode('-' , $temp);
369 $markerArrayIcons = array(
370 '###NAMESPACE###' => $this->nameSpace,
371 '###ICONNAME###' => $cssName,
372 '###LEFT###' => $data['left'],
373 '###TOP###' => $data['top'],
374 '###SIZE_INFO###' => ''
375 );
376 if ($data['height'] != $this->defaultHeight) {
377 $markerArrayIcons['###SIZE_INFO###'] .= TAB . 'height: ' . $data['height'] . 'px;' . LF;
378 }
379 if ($data['width'] != $this->defaultWidth) {
380 $markerArrayIcons['###SIZE_INFO###'] .= TAB . 'width: ' . $data['width'] . 'px;' . LF;
381 }
382 $cssData .= t3lib_parsehtml::substituteMarkerArray($this->templateIcon, $markerArrayIcons);
383
384 }
385
386 t3lib_div::writeFile(PATH_site . $this->cssFolder . $this->spriteName . '.css', $cssData);
387 if ($this->generateGIFCopy) {
388 t3lib_div::writeFile(PATH_site . $this->cssFolder . $this->spriteName . '-ie6.css', $cssIe6);
389 }
390 }
391
392 /**
393 * Compares image path to CSS path and creates the relative backpath from css to the sprites
394 *
395 * @return string
396 */
397 protected function resolveSpritePath() {
398 // Fix window paths
399 $this->cssFolder = str_replace('\\', '/', $this->cssFolder);
400 $this->spriteFolder = str_replace('\\', '/', $this->spriteFolder);
401
402 $cssPathSegments = t3lib_div::trimExplode('/', trim($this->cssFolder, '/'));
403 $graphicPathSegments = t3lib_div::trimExplode('/', trim($this->spriteFolder, '/'));
404
405 $i = 0;
406 while (isset($cssPathSegments[$i]) && isset($graphicPathSegments[$i]) &&
407 $cssPathSegments[$i] == $graphicPathSegments[$i]) {
408 unset($cssPathSegments[$i]);
409 unset($graphicPathSegments[$i]);
410 ++$i;
411 }
412 foreach ($cssPathSegments AS $key => $value) {
413 $cssPathSegments[$key] = '..';
414 }
415 $completePath = array_merge($cssPathSegments, $graphicPathSegments);
416 $path = implode('/', $completePath);
417 return t3lib_div::resolveBackPath($path);
418 }
419
420 /**
421 * The actual sprite generator, renders the command for Im/GM and executes
422 *
423 * @return void
424 */
425 protected function generateGraphic() {
426 $iconParameters = array();
427 $tempSprite = t3lib_div::tempnam($this->spriteName);
428
429 $filePath = array(
430 'mainFile' => PATH_site . $this->spriteFolder . $this->spriteName . '.png',
431 'gifFile' => NULL
432 );
433 // create black true color image with given size
434 $newSprite = imagecreatetruecolor($this->spriteWidth, $this->spriteHeight);
435 imagesavealpha($newSprite, true);
436 // make it transparent
437 imagefill($newSprite, 0, 0, imagecolorallocatealpha($newSprite, 0, 255, 255, 127));
438 foreach ($this->iconsData AS $icon) {
439 $function = 'imagecreatefrom' . strtolower($icon['fileExtension']);
440 if(function_exists($function)) {
441 $currentIcon = $function($icon['fileName']);
442 imagecopy($newSprite, $currentIcon, $icon['left'], $icon['top'], 0, 0, $icon['width'], $icon['height']);
443 }
444 }
445 imagepng($newSprite, $tempSprite . '.png');
446
447 if ($this->generateGIFCopy) {
448 $filePath['gifFile'] = PATH_site . $this->spriteFolder . $this->spriteName . '.gif';
449 $gifSprite = imagecreatetruecolor($this->spriteWidth, $this->spriteHeight);
450 // make it transparent
451 imagefill($gifSprite, 0, 0, imagecolorallocate($gifSprite, 127, 127, 127));
452 foreach ($this->iconsData AS $icon) {
453 $function = 'imagecreatefrom' . strtolower($icon['fileExtension']);
454 if(function_exists($function)) {
455 $currentIcon = $function($icon['fileName']);
456 imagecopy($gifSprite, $currentIcon, $icon['left'], $icon['top'], 0, 0, $icon['width'], $icon['height']);
457 }
458 }
459 imagecolortransparent($gifSprite, imagecolorallocate($gifSprite, 127, 127, 127));
460 imagegif($gifSprite, $tempSprite . '.gif');
461 }
462
463 t3lib_div::upload_copy_move($tempSprite . '.png', $filePath['mainFile']);
464 t3lib_div::unlink_tempfile($tempSprite . '.png');
465 if ($this->generateGIFCopy) {
466 t3lib_div::upload_copy_move($tempSprite . '.gif', $filePath['gifFile']);
467 t3lib_div::unlink_tempfile($tempSprite . '.gif');
468 }
469 }
470 /**
471 * Arranges icons in sprites,
472 * afterwards all icons have information about ther position in sprite
473 */
474 protected function calculateSpritePositions() {
475 $currentLeft = 0;
476 $currentTop = 0;
477 // calculate width of every icon-size-group
478 $sizes = array();
479 foreach ($this->iconSizes as $sizeTag => $count) {
480 $size = $this->explodeSizeTag($sizeTag);
481 $rowWidth = ceil(sqrt($count)) * $size['width'];
482 while (isset($sizes[$rowWidth])) {
483 $rowWidth++;
484 }
485 $sizes[$rowWidth] = $sizeTag;
486 }
487 // reverse sorting: widest group to top
488 krsort($sizes);
489 // integerate all icons grouped by icons size into the sprite
490 foreach ($sizes as $sizeTag) {
491 $size = $this->explodeSizeTag($sizeTag);
492 $currentLeft = 0;
493 $rowCounter = 0;
494
495 $rowSize = ceil(sqrt($this->iconSizes[$sizeTag]));
496
497 $rowWidth = $rowSize * $size['width'] + ($rowSize - 1) * $this->space;
498 $this->spriteWidth = ($rowWidth > $this->spriteWidth ? $rowWidth : $this->spriteWidth);
499 $firstLine = TRUE;
500
501 natsort($this->iconNamesPerSize[$sizeTag]);
502 foreach ($this->iconNamesPerSize[$sizeTag] as $iconName) {
503 if ($rowCounter == $rowSize - 1) {
504 $rowCounter = -1;
505 } elseif ($rowCounter == 0) {
506 if (!$firstLine) {
507 $currentTop += $size['height'];
508 $currentTop += $this->space;
509 }
510 $firstLine = FALSE;
511 $currentLeft = 0;
512 }
513 $this->iconsData[$iconName]['left'] = $currentLeft;
514 $this->iconsData[$iconName]['top'] = $currentTop;
515
516 $currentLeft += $size['width'];
517 $currentLeft += $this->space;
518
519 $rowCounter++;
520 }
521 $currentTop += $size['height'];
522 $currentTop += $this->space;
523 }
524 $this->spriteHeight = $currentTop;
525 }
526
527 /**
528 * Function getFolder traverses the target directory,
529 * locates all iconFiles and collects them into an array
530 *
531 * @param string path to an folder which contains images
532 * @return array returns an array with all files key: iconname, value: fileName
533 */
534 protected function getFolder($directoryPath) {
535 $subFolders = t3lib_div::get_dirs(PATH_site . $directoryPath);
536 if (!$this->ommitSpriteNameInIconName) {
537 $subFolders[] = '';
538 }
539 $resultArray = array();
540
541 foreach ($subFolders as $folder) {
542 if ($folder !== '.svn') {
543 $icons = t3lib_div::getFilesInDir(PATH_site . $directoryPath . $folder . '/', 'gif,png,jpg');
544 if (!in_array($folder, $this->spriteBases) && count($icons) && $folder !== '') {
545 $this->spriteBases[] = $folder;
546 }
547 foreach ($icons AS $icon) {
548 $fileInfo = pathinfo($icon);
549
550 $iconName = ($folder ? $folder . '-' : '') . $fileInfo['filename'];
551 if (!$this->ommitSpriteNameInIconName) {
552 $iconName = $this->spriteName . '-' . $iconName;
553 }
554
555 $resultArray[$iconName] = $directoryPath . $folder . '/' . $icon;
556 }
557 }
558 }
559 return $resultArray;
560 }
561
562 /**
563 * Generates file information cache from file array
564 *
565 * @param array list of all files with their icon name
566 * @return void
567 */
568 protected function buildFileInformationCache(array $files) {
569 foreach ($files as $iconName => $iconFile) {
570
571 $iconNameParts = t3lib_div::trimExplode('-', $iconName);
572 if(!in_array($iconNameParts[0], $this->spriteBases)) {
573 $this->spriteBases[] = $iconNameParts[0];
574 }
575 $fileInfo = @pathinfo(PATH_site . $iconFile);
576 $imageInfo = @getimagesize(PATH_site . $iconFile);
577
578 $this->iconsData[$iconName] = array(
579 'iconName' => $iconName,
580 'iconNameParts' => $iconNameParts,
581 'singleName' => $fileInfo['filename'],
582 'fileExtension' => $fileInfo['extension'],
583 'fileName' => PATH_site . $iconFile,
584 'width' => $imageInfo[0],
585 'height' => $imageInfo[1],
586 'left' => 0,
587 'top' => 0
588 );
589
590 $sizeTag = $imageInfo[0] . 'x' . $imageInfo[1];
591 if (isset($this->iconSizes[$sizeTag])) {
592 $this->iconSizes[$sizeTag] += 1;
593 } else {
594 $this->iconSizes[$sizeTag] = 1;
595 $this->iconNamesPerSize[$sizeTag] = array();
596 }
597 $this->iconNamesPerSize[$sizeTag][] = $iconName;
598 }
599 // find most common image size, save it as default
600 asort($this->iconSizes);
601 $defaultSize = $this->explodeSizeTag(array_pop(array_keys($this->iconSizes)));
602 $this->defaultWidth = $defaultSize['width'];
603 $this->defaultHeight = $defaultSize['height'];
604 }
605
606 /**
607 * Transforms size tag into size array
608 *
609 * @param string a size tag at the cache arrays
610 * @return array
611 */
612 protected function explodeSizeTag($tag = '') {
613 $size = t3lib_div::trimExplode("x", $tag);
614 return array(
615 'width' => $size[0],
616 'height'=> $size[1]
617 );
618 }
619
620 }
621
622 if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/spritemanager/class.t3lib_spritemanager_spritegenerator.php']) {
623 include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['t3lib/spritemanager/class.t3lib_spritemanager_spritegenerator.php']);
624 }
625 ?>