[BUGFIX] Regression when scaling/cropping in GIFBUILDER
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Classes / Imaging / GifBuilder.php
1 <?php
2 namespace TYPO3\CMS\Frontend\Imaging;
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
17 use TYPO3\CMS\Core\Utility\GeneralUtility;
18
19 /**
20 * GIFBUILDER
21 *
22 * Generating gif/png-files from TypoScript
23 * Used by the menu-objects and imgResource in TypoScript.
24 *
25 * This class allows for advanced rendering of images with various layers of images, text and graphical primitives.
26 * The concept is known from TypoScript as "GIFBUILDER" where you can define a "numerical array" (TypoScript term as well) of "GIFBUILDER OBJECTS" (like "TEXT", "IMAGE", etc.) and they will be rendered onto an image one by one.
27 * The name "GIFBUILDER" comes from the time where GIF was the only file format supported. PNG is just as well to create today (configured with TYPO3_CONF_VARS[GFX])
28 * Not all instances of this class is truely building gif/png files by layers; You may also see the class instantiated for the purpose of using the scaling functions in the parent class.
29 *
30 * Here is an example of how to use this class (from tslib_content.php, function getImgResource):
31 *
32 * $gifCreator = GeneralUtility::makeInstance(\TYPO3\CMS\Frontend\Imaging\GifBuilder::class);
33 * $gifCreator->init();
34 * $theImage='';
35 * if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib']) {
36 * $gifCreator->start($fileArray, $this->data);
37 * $theImage = $gifCreator->gifBuild();
38 * }
39 * return $gifCreator->getImageDimensions($theImage);
40 *
41 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
42 */
43 class GifBuilder extends \TYPO3\CMS\Core\Imaging\GraphicalFunctions {
44
45 /**
46 * the main image
47 *
48 * @var string
49 */
50 public $im = '';
51
52 /**
53 * the image-width
54 *
55 * @var int
56 */
57 public $w = 0;
58
59 /**
60 * the image-height
61 *
62 * @var int
63 */
64 public $h = 0;
65
66 /**
67 * map-data
68 *
69 * @var string
70 */
71 public $map;
72
73 /**
74 * @var array
75 */
76 public $workArea;
77
78 /**
79 * This holds the operational setup for gifbuilder. Basically this is a TypoScript array with properties.
80 *
81 * @var array
82 */
83 public $setup = array();
84
85 /**
86 * Contains all text strings used on this image
87 *
88 * @var array
89 */
90 public $combinedTextStrings = array();
91
92 /**
93 * Contains all filenames (basename without extension) used on this image
94 *
95 * @var array
96 */
97 public $combinedFileNames = array();
98
99 /**
100 * This is the array from which data->field: [key] is fetched. So this is the current record!
101 *
102 * @var array
103 */
104 public $data = array();
105
106 /**
107 * @var array
108 */
109 public $objBB = array();
110
111 /**
112 * @var string
113 */
114 public $myClassName = 'gifbuilder';
115
116 /**
117 * @var array
118 */
119 public $charRangeMap = array();
120
121 /**
122 * @var int[]
123 */
124 public $XY = array();
125
126 /**
127 * Initialization of the GIFBUILDER objects, in particular TEXT and IMAGE. This includes finding the bounding box, setting dimensions and offset values before the actual rendering is started.
128 * Modifies the ->setup, ->objBB internal arrays
129 * Should be called after the ->init() function which initializes the parent class functions/variables in general.
130 * The class \TYPO3\CMS\Frontend\ContentObject\Menu\GraphicalMenuContentObject also uses gifbuilder and here there is an interesting use since the function findLargestDims() from that class calls the init() and start() functions to find the total dimensions before starting the rendering of the images.
131 *
132 * @param array $conf TypoScript properties for the GIFBUILDER session. Stored internally in the variable ->setup
133 * @param array $data The current data record from \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer. Stored internally in the variable ->data
134 * @return void
135 * @see \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::getImgResource(), \TYPO3\CMS\Frontend\ContentObject\Menu\GraphicalMenuContentObject::makeGifs(), \TYPO3\CMS\Frontend\ContentObject\Menu\GraphicalMenuContentObject::findLargestDims()
136 */
137 public function start($conf, $data) {
138 if (is_array($conf)) {
139 $this->setup = $conf;
140 $this->data = $data;
141 $this->cObj = GeneralUtility::makeInstance(\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::class);
142 $this->cObj->start($this->data);
143 // Hook preprocess gifbuilder conf
144 // Added by Julle for 3.8.0
145 //
146 // Let's you pre-process the gifbuilder configuration. for
147 // example you can split a string up into lines and render each
148 // line as TEXT obj, see extension julle_gifbconf
149 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_gifbuilder.php']['gifbuilder-ConfPreProcess'])) {
150 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_gifbuilder.php']['gifbuilder-ConfPreProcess'] as $_funcRef) {
151 $_params = $this->setup;
152 $this->setup = GeneralUtility::callUserFunction($_funcRef, $_params, $this);
153 }
154 }
155 // Initializing global Char Range Map
156 $this->charRangeMap = array();
157 if (is_array($GLOBALS['TSFE']->tmpl->setup['_GIFBUILDER.']['charRangeMap.'])) {
158 foreach ($GLOBALS['TSFE']->tmpl->setup['_GIFBUILDER.']['charRangeMap.'] as $cRMcfgkey => $cRMcfg) {
159 if (is_array($cRMcfg)) {
160 // Initializing:
161 $cRMkey = $GLOBALS['TSFE']->tmpl->setup['_GIFBUILDER.']['charRangeMap.'][substr($cRMcfgkey, 0, -1)];
162 $this->charRangeMap[$cRMkey] = array();
163 $this->charRangeMap[$cRMkey]['charMapConfig'] = $cRMcfg['charMapConfig.'];
164 $this->charRangeMap[$cRMkey]['cfgKey'] = substr($cRMcfgkey, 0, -1);
165 $this->charRangeMap[$cRMkey]['multiplicator'] = (double) $cRMcfg['fontSizeMultiplicator'];
166 $this->charRangeMap[$cRMkey]['pixelSpace'] = (int)$cRMcfg['pixelSpaceFontSizeRef'];
167 }
168 }
169 }
170 // Getting sorted list of TypoScript keys from setup.
171 $sKeyArray = \TYPO3\CMS\Core\TypoScript\TemplateService::sortedKeyList($this->setup);
172 // Setting the background color, passing it through stdWrap
173 if ($conf['backColor.'] || $conf['backColor']) {
174 $this->setup['backColor'] = isset($this->setup['backColor.']) ? trim($this->cObj->stdWrap($this->setup['backColor'], $this->setup['backColor.'])) : $this->setup['backColor'];
175 }
176 if (!$this->setup['backColor']) {
177 $this->setup['backColor'] = 'white';
178 }
179 if ($conf['transparentColor.'] || $conf['transparentColor']) {
180 $this->setup['transparentColor_array'] = isset($this->setup['transparentColor.']) ? explode('|', trim($this->cObj->stdWrap($this->setup['transparentColor'], $this->setup['transparentColor.']))) : explode('|', trim($this->setup['transparentColor']));
181 }
182 if (isset($this->setup['transparentBackground.'])) {
183 $this->setup['transparentBackground'] = $this->cObj->stdWrap($this->setup['transparentBackground'], $this->setup['transparentBackground.']);
184 }
185 if (isset($this->setup['reduceColors.'])) {
186 $this->setup['reduceColors'] = $this->cObj->stdWrap($this->setup['reduceColors'], $this->setup['reduceColors.']);
187 }
188 // Set default dimensions
189 if (isset($this->setup['XY.'])) {
190 $this->setup['XY'] = $this->cObj->stdWrap($this->setup['XY'], $this->setup['XY.']);
191 }
192 if (!$this->setup['XY']) {
193 $this->setup['XY'] = '120,50';
194 }
195 // Checking TEXT and IMAGE objects for files. If any errors the objects are cleared.
196 // The Bounding Box for the objects is stored in an array
197 foreach ($sKeyArray as $theKey) {
198 $theValue = $this->setup[$theKey];
199 if ((int)$theKey && ($conf = $this->setup[$theKey . '.'])) {
200 // Swipes through TEXT and IMAGE-objects
201 switch ($theValue) {
202 case 'TEXT':
203 if ($this->setup[$theKey . '.'] = $this->checkTextObj($conf)) {
204 // Adjust font width if max size is set:
205 $maxWidth = isset($this->setup[$theKey . '.']['maxWidth.']) ? $this->cObj->stdWrap($this->setup[$theKey . '.']['maxWidth'], $this->setup[$theKey . '.']['maxWidth.']) : $this->setup[$theKey . '.']['maxWidth'];
206 if ($maxWidth) {
207 $this->setup[$theKey . '.']['fontSize'] = $this->fontResize($this->setup[$theKey . '.']);
208 }
209 // Calculate bounding box:
210 $txtInfo = $this->calcBBox($this->setup[$theKey . '.']);
211 $this->setup[$theKey . '.']['BBOX'] = $txtInfo;
212 $this->objBB[$theKey] = $txtInfo;
213 $this->setup[$theKey . '.']['imgMap'] = 0;
214 }
215 break;
216 case 'IMAGE':
217 $fileInfo = $this->getResource($conf['file'], $conf['file.']);
218 if ($fileInfo) {
219 $this->combinedFileNames[] = preg_replace('/\\.[[:alnum:]]+$/', '', basename($fileInfo[3]));
220 if ($fileInfo['processedFile'] instanceof \TYPO3\CMS\Core\Resource\ProcessedFile) {
221 // Use processed file, if a FAL file has been processed by GIFBUILDER (e.g. scaled/cropped)
222 $this->setup[$theKey . '.']['file'] = $fileInfo['processedFile']->getForLocalProcessing(FALSE);
223 } elseif (!isset($fileInfo['origFile']) && $fileInfo['originalFile'] instanceof \TYPO3\CMS\Core\Resource\File) {
224 // Use FAL file with getForLocalProcessing to circumvent problems with umlauts, if it is a FAL file (origFile not set)
225 $this->setup[$theKey . '.']['file'] = $fileInfo['originalFile']->getForLocalProcessing(FALSE);
226 } else {
227 // Use normal path from fileInfo if it is a non-FAL file (even non-FAL files have originalFile set, but only non-FAL files have origFile set)
228 $this->setup[$theKey . '.']['file'] = $fileInfo[3];
229 }
230 $this->setup[$theKey . '.']['BBOX'] = $fileInfo;
231 $this->objBB[$theKey] = $fileInfo;
232 if ($conf['mask']) {
233 $maskInfo = $this->getResource($conf['mask'], $conf['mask.']);
234 if ($maskInfo) {
235 // the same selection criteria as regarding fileInfo above apply here
236 if ($maskInfo['processedFile'] instanceof \TYPO3\CMS\Core\Resource\ProcessedFile) {
237 $this->setup[$theKey . '.']['mask'] = $maskInfo['processedFile']->getForLocalProcessing(FALSE);
238 } elseif (!isset($maskInfo['origFile']) && $maskInfo['originalFile'] instanceof \TYPO3\CMS\Core\Resource\File) {
239 $this->setup[$theKey . '.']['mask'] = $maskInfo['originalFile']->getForLocalProcessing(FALSE);
240 } else {
241 $this->setup[$theKey . '.']['mask'] = $maskInfo[3];
242 }
243 } else {
244 $this->setup[$theKey . '.']['mask'] = '';
245 }
246 }
247 } else {
248 unset($this->setup[$theKey . '.']);
249 }
250 break;
251 }
252 // Checks if disabled is set... (this is also done in menu.php / imgmenu!!)
253 if ($conf['if.']) {
254 $cObj = GeneralUtility::makeInstance(\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::class);
255 $cObj->start($this->data);
256 if (!$cObj->checkIf($conf['if.'])) {
257 unset($this->setup[$theKey]);
258 unset($this->setup[$theKey . '.']);
259 unset($this->objBB[$theKey]);
260 }
261 }
262 }
263 }
264 // Calculate offsets on elements
265 $this->setup['XY'] = $this->calcOffset($this->setup['XY']);
266 if (isset($this->setup['offset.'])) {
267 $this->setup['offset'] = $this->cObj->stdWrap($this->setup['offset'], $this->setup['offset.']);
268 }
269 $this->setup['offset'] = $this->calcOffset($this->setup['offset']);
270 if (isset($this->setup['workArea.'])) {
271 $this->setup['workArea'] = $this->cObj->stdWrap($this->setup['workArea'], $this->setup['workArea.']);
272 }
273 $this->setup['workArea'] = $this->calcOffset($this->setup['workArea']);
274 foreach ($sKeyArray as $theKey) {
275 $theValue = $this->setup[$theKey];
276 if ((int)$theKey && ($conf = $this->setup[$theKey . '.'])) {
277 switch ($theValue) {
278 case 'TEXT':
279
280 case 'IMAGE':
281 if (isset($this->setup[$theKey . '.']['offset.'])) {
282 $this->setup[$theKey . '.']['offset'] = $this->cObj->stdWrap($this->setup[$theKey . '.']['offset'], $this->setup[$theKey . '.']['offset.']);
283 }
284 if ($this->setup[$theKey . '.']['offset']) {
285 $this->setup[$theKey . '.']['offset'] = $this->calcOffset($this->setup[$theKey . '.']['offset']);
286 }
287 break;
288 case 'BOX':
289
290 case 'ELLIPSE':
291 if (isset($this->setup[$theKey . '.']['dimensions.'])) {
292 $this->setup[$theKey . '.']['dimensions'] = $this->cObj->stdWrap($this->setup[$theKey . '.']['dimensions'], $this->setup[$theKey . '.']['dimensions.']);
293 }
294 if ($this->setup[$theKey . '.']['dimensions']) {
295 $this->setup[$theKey . '.']['dimensions'] = $this->calcOffset($this->setup[$theKey . '.']['dimensions']);
296 }
297 break;
298 case 'WORKAREA':
299 if (isset($this->setup[$theKey . '.']['set.'])) {
300 $this->setup[$theKey . '.']['set'] = $this->cObj->stdWrap($this->setup[$theKey . '.']['set'], $this->setup[$theKey . '.']['set.']);
301 }
302 if ($this->setup[$theKey . '.']['set']) {
303 $this->setup[$theKey . '.']['set'] = $this->calcOffset($this->setup[$theKey . '.']['set']);
304 }
305 break;
306 case 'CROP':
307 if (isset($this->setup[$theKey . '.']['crop.'])) {
308 $this->setup[$theKey . '.']['crop'] = $this->cObj->stdWrap($this->setup[$theKey . '.']['crop'], $this->setup[$theKey . '.']['crop.']);
309 }
310 if ($this->setup[$theKey . '.']['crop']) {
311 $this->setup[$theKey . '.']['crop'] = $this->calcOffset($this->setup[$theKey . '.']['crop']);
312 }
313 break;
314 case 'SCALE':
315 if (isset($this->setup[$theKey . '.']['width.'])) {
316 $this->setup[$theKey . '.']['width'] = $this->cObj->stdWrap($this->setup[$theKey . '.']['width'], $this->setup[$theKey . '.']['width.']);
317 }
318 if ($this->setup[$theKey . '.']['width']) {
319 $this->setup[$theKey . '.']['width'] = $this->calcOffset($this->setup[$theKey . '.']['width']);
320 }
321 if (isset($this->setup[$theKey . '.']['height.'])) {
322 $this->setup[$theKey . '.']['height'] = $this->cObj->stdWrap($this->setup[$theKey . '.']['height'], $this->setup[$theKey . '.']['height.']);
323 }
324 if ($this->setup[$theKey . '.']['height']) {
325 $this->setup[$theKey . '.']['height'] = $this->calcOffset($this->setup[$theKey . '.']['height']);
326 }
327 break;
328 }
329 }
330 }
331 // Get trivial data
332 $XY = GeneralUtility::intExplode(',', $this->setup['XY']);
333 $maxWidth = isset($this->setup['maxWidth.']) ? (int)$this->cObj->stdWrap($this->setup['maxWidth'], $this->setup['maxWidth.']) : (int)$this->setup['maxWidth'];
334 $maxHeight = isset($this->setup['maxHeight.']) ? (int)$this->cObj->stdWrap($this->setup['maxHeight'], $this->setup['maxHeight.']) : (int)$this->setup['maxHeight'];
335 $XY[0] = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($XY[0], 1, $maxWidth ?: 2000);
336 $XY[1] = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($XY[1], 1, $maxHeight ?: 2000);
337 $this->XY = $XY;
338 $this->w = $XY[0];
339 $this->h = $XY[1];
340 $this->OFFSET = GeneralUtility::intExplode(',', $this->setup['offset']);
341 // this sets the workArea
342 $this->setWorkArea($this->setup['workArea']);
343 // this sets the default to the current;
344 $this->defaultWorkArea = $this->workArea;
345 }
346 }
347
348 /**
349 * Initiates the image file generation if ->setup is TRUE and if the file did not exist already.
350 * Gets filename from fileName() and if file exists in typo3temp/ dir it will - of course - not be rendered again.
351 * Otherwise rendering means calling ->make(), then ->output(), then ->destroy()
352 *
353 * @return string The filename for the created GIF/PNG file. The filename will be prefixed "GB_
354 * @see make(), fileName()
355 */
356 public function gifBuild() {
357 if ($this->setup) {
358 // Relative to PATH_site
359 $gifFileName = $this->fileName('GB/');
360 // File exists
361 if (!file_exists($gifFileName)) {
362 // Create temporary directory if not done:
363 $this->createTempSubDir('GB/');
364 // Create file:
365 $this->make();
366 $this->output($gifFileName);
367 $this->destroy();
368 }
369 return $gifFileName;
370 }
371 }
372
373 /**
374 * The actual rendering of the image file.
375 * Basically sets the dimensions, the background color, the traverses the array of GIFBUILDER objects and finally setting the transparent color if defined.
376 * Creates a GDlib resource in $this->im and works on that
377 * Called by gifBuild()
378 *
379 * @return void
380 * @access private
381 * @see gifBuild()
382 */
383 public function make() {
384 // Get trivial data
385 $XY = $this->XY;
386 // Reset internal properties
387 $this->saveAlphaLayer = FALSE;
388 // Gif-start
389 $this->im = imagecreatetruecolor($XY[0], $XY[1]);
390 $this->w = $XY[0];
391 $this->h = $XY[1];
392 // Transparent layer as background if set and requirements are met
393 if (!empty($this->setup['backColor']) && $this->setup['backColor'] === 'transparent' && $this->png_truecolor && !$this->setup['reduceColors'] && (empty($this->setup['format']) || $this->setup['format'] === 'png')) {
394 // Set transparency properties
395 imagesavealpha($this->im, TRUE);
396 // Fill with a transparent background
397 $transparentColor = imagecolorallocatealpha($this->im, 0, 0, 0, 127);
398 imagefill($this->im, 0, 0, $transparentColor);
399 // Set internal properties to keep the transparency over the rendering process
400 $this->saveAlphaLayer = TRUE;
401 // Force PNG in case no format is set
402 $this->setup['format'] = 'png';
403 } else {
404 // Fill the background with the given color
405 $BGcols = $this->convertColor($this->setup['backColor']);
406 $Bcolor = ImageColorAllocate($this->im, $BGcols[0], $BGcols[1], $BGcols[2]);
407 ImageFilledRectangle($this->im, 0, 0, $XY[0], $XY[1], $Bcolor);
408 }
409 // Traverse the GIFBUILDER objects an render each one:
410 if (is_array($this->setup)) {
411 $sKeyArray = \TYPO3\CMS\Core\TypoScript\TemplateService::sortedKeyList($this->setup);
412 foreach ($sKeyArray as $theKey) {
413 $theValue = $this->setup[$theKey];
414 if ((int)$theKey && ($conf = $this->setup[$theKey . '.'])) {
415 // apply stdWrap to all properties, except for TEXT objects
416 // all properties of the TEXT sub-object have already been stdWrap-ped
417 // before in ->checkTextObj()
418 if ($theValue !== 'TEXT') {
419 $isStdWrapped = array();
420 foreach ($conf as $key => $value) {
421 $parameter = rtrim($key, '.');
422 if (!$isStdWrapped[$parameter] && isset($conf[$parameter . '.'])) {
423 $conf[$parameter] = $this->cObj->stdWrap($conf[$parameter], $conf[$parameter . '.']);
424 $isStdWrapped[$parameter] = 1;
425 }
426 }
427 }
428
429 switch ($theValue) {
430 case 'IMAGE':
431 if ($conf['mask']) {
432 $this->maskImageOntoImage($this->im, $conf, $this->workArea);
433 } else {
434 $this->copyImageOntoImage($this->im, $conf, $this->workArea);
435 }
436 break;
437 case 'TEXT':
438 if (!$conf['hide']) {
439 if (is_array($conf['shadow.'])) {
440 $isStdWrapped = array();
441 foreach ($conf['shadow.'] as $key => $value) {
442 $parameter = rtrim($key, '.');
443 if (!$isStdWrapped[$parameter] && isset($conf[$parameter . '.'])) {
444 $conf['shadow.'][$parameter] = $this->cObj->stdWrap($conf[$parameter], $conf[$parameter . '.']);
445 $isStdWrapped[$parameter] = 1;
446 }
447 }
448 $this->makeShadow($this->im, $conf['shadow.'], $this->workArea, $conf);
449 }
450 if (is_array($conf['emboss.'])) {
451 $isStdWrapped = array();
452 foreach ($conf['emboss.'] as $key => $value) {
453 $parameter = rtrim($key, '.');
454 if (!$isStdWrapped[$parameter] && isset($conf[$parameter . '.'])) {
455 $conf['emboss.'][$parameter] = $this->cObj->stdWrap($conf[$parameter], $conf[$parameter . '.']);
456 $isStdWrapped[$parameter] = 1;
457 }
458 }
459 $this->makeEmboss($this->im, $conf['emboss.'], $this->workArea, $conf);
460 }
461 if (is_array($conf['outline.'])) {
462 $isStdWrapped = array();
463 foreach ($conf['outline.'] as $key => $value) {
464 $parameter = rtrim($key, '.');
465 if (!$isStdWrapped[$parameter] && isset($conf[$parameter . '.'])) {
466 $conf['outline.'][$parameter] = $this->cObj->stdWrap($conf[$parameter], $conf[$parameter . '.']);
467 $isStdWrapped[$parameter] = 1;
468 }
469 }
470 $this->makeOutline($this->im, $conf['outline.'], $this->workArea, $conf);
471 }
472 $conf['imgMap'] = 1;
473 $this->makeText($this->im, $conf, $this->workArea);
474 }
475 break;
476 case 'OUTLINE':
477 if ($this->setup[$conf['textObjNum']] == 'TEXT' && ($txtConf = $this->checkTextObj($this->setup[$conf['textObjNum'] . '.']))) {
478 $this->makeOutline($this->im, $conf, $this->workArea, $txtConf);
479 }
480 break;
481 case 'EMBOSS':
482 if ($this->setup[$conf['textObjNum']] == 'TEXT' && ($txtConf = $this->checkTextObj($this->setup[$conf['textObjNum'] . '.']))) {
483 $this->makeEmboss($this->im, $conf, $this->workArea, $txtConf);
484 }
485 break;
486 case 'SHADOW':
487 if ($this->setup[$conf['textObjNum']] == 'TEXT' && ($txtConf = $this->checkTextObj($this->setup[$conf['textObjNum'] . '.']))) {
488 $this->makeShadow($this->im, $conf, $this->workArea, $txtConf);
489 }
490 break;
491 case 'BOX':
492 $this->makeBox($this->im, $conf, $this->workArea);
493 break;
494 case 'EFFECT':
495 $this->makeEffect($this->im, $conf);
496 break;
497 case 'ADJUST':
498 $this->adjust($this->im, $conf);
499 break;
500 case 'CROP':
501 $this->crop($this->im, $conf);
502 break;
503 case 'SCALE':
504 $this->scale($this->im, $conf);
505 break;
506 case 'WORKAREA':
507 if ($conf['set']) {
508 // this sets the workArea
509 $this->setWorkArea($conf['set']);
510 }
511 if (isset($conf['clear'])) {
512 // This sets the current to the default;
513 $this->workArea = $this->defaultWorkArea;
514 }
515 break;
516 case 'ELLIPSE':
517 $this->makeEllipse($this->im, $conf, $this->workArea);
518 break;
519 }
520 }
521 }
522 }
523 // Preserve alpha transparency
524 if (!$this->saveAlphaLayer) {
525 if ($this->setup['transparentBackground']) {
526 // Auto transparent background is set
527 $Bcolor = ImageColorClosest($this->im, $BGcols[0], $BGcols[1], $BGcols[2]);
528 imagecolortransparent($this->im, $Bcolor);
529 } elseif (is_array($this->setup['transparentColor_array'])) {
530 // Multiple transparent colors are set. This is done via the trick that all transparent colors get
531 // converted to one color and then this one gets set as transparent as png/gif can just have one
532 // transparent color.
533 $Tcolor = $this->unifyColors($this->im, $this->setup['transparentColor_array'], (int)$this->setup['transparentColor.']['closest']);
534 if ($Tcolor >= 0) {
535 imagecolortransparent($this->im, $Tcolor);
536 }
537 }
538 }
539 }
540
541 /*********************************************
542 *
543 * Various helper functions
544 *
545 ********************************************/
546 /**
547 * Initializing/Cleaning of TypoScript properties for TEXT GIFBUILDER objects
548 *
549 * 'cleans' TEXT-object; Checks fontfile and other vital setup
550 * Finds the title if its a 'variable' (instantiates a cObj and loads it with the ->data record)
551 * Performs caseshift if any.
552 *
553 * @param array $conf GIFBUILDER object TypoScript properties
554 * @return array Modified $conf array IF the "text" property is not blank
555 * @access private
556 */
557 public function checkTextObj($conf) {
558 $cObj = GeneralUtility::makeInstance(\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::class);
559 $cObj->start($this->data);
560 $isStdWrapped = array();
561 foreach ($conf as $key => $value) {
562 $parameter = rtrim($key, '.');
563 if (!$isStdWrapped[$parameter] && isset($conf[$parameter . '.'])) {
564 $conf[$parameter] = $cObj->stdWrap($conf[$parameter], $conf[$parameter . '.']);
565 $isStdWrapped[$parameter] = 1;
566 }
567 }
568 $conf['fontFile'] = $this->checkFile($conf['fontFile']);
569 if (!$conf['fontFile']) {
570 $conf['fontFile'] = 'typo3/sysext/core/Resources/Private/Font/nimbus.ttf';
571 }
572 if (!$conf['iterations']) {
573 $conf['iterations'] = 1;
574 }
575 if (!$conf['fontSize']) {
576 $conf['fontSize'] = 12;
577 }
578 // If any kind of spacing applys, we cannot use angles!!
579 if ($conf['spacing'] || $conf['wordSpacing']) {
580 $conf['angle'] = 0;
581 }
582 if (!isset($conf['antiAlias'])) {
583 $conf['antiAlias'] = 1;
584 }
585 $conf['fontColor'] = trim($conf['fontColor']);
586 // Strip HTML
587 if (!$conf['doNotStripHTML']) {
588 $conf['text'] = strip_tags($conf['text']);
589 }
590 $this->combinedTextStrings[] = strip_tags($conf['text']);
591 // Max length = 100 if automatic line braks are not defined:
592 if (!isset($conf['breakWidth']) || !$conf['breakWidth']) {
593 $tlen = (int)$conf['textMaxLength'] ?: 100;
594 if ($this->nativeCharset) {
595 $conf['text'] = $this->csConvObj->substr($this->nativeCharset, $conf['text'], 0, $tlen);
596 } else {
597 $conf['text'] = substr($conf['text'], 0, $tlen);
598 }
599 }
600 if ((string)$conf['text'] != '') {
601 // Char range map thingie:
602 $fontBaseName = basename($conf['fontFile']);
603 if (is_array($this->charRangeMap[$fontBaseName])) {
604 // Initialize splitRendering array:
605 if (!is_array($conf['splitRendering.'])) {
606 $conf['splitRendering.'] = array();
607 }
608 $cfgK = $this->charRangeMap[$fontBaseName]['cfgKey'];
609 // Do not impose settings if a splitRendering object already exists:
610 if (!isset($conf['splitRendering.'][$cfgK])) {
611 // Set configuration:
612 $conf['splitRendering.'][$cfgK] = 'charRange';
613 $conf['splitRendering.'][$cfgK . '.'] = $this->charRangeMap[$fontBaseName]['charMapConfig'];
614 // Multiplicator of fontsize:
615 if ($this->charRangeMap[$fontBaseName]['multiplicator']) {
616 $conf['splitRendering.'][$cfgK . '.']['fontSize'] = round($conf['fontSize'] * $this->charRangeMap[$fontBaseName]['multiplicator']);
617 }
618 // Multiplicator of pixelSpace:
619 if ($this->charRangeMap[$fontBaseName]['pixelSpace']) {
620 $travKeys = array('xSpaceBefore', 'xSpaceAfter', 'ySpaceBefore', 'ySpaceAfter');
621 foreach ($travKeys as $pxKey) {
622 if (isset($conf['splitRendering.'][$cfgK . '.'][$pxKey])) {
623 $conf['splitRendering.'][$cfgK . '.'][$pxKey] = round($conf['splitRendering.'][($cfgK . '.')][$pxKey] * ($conf['fontSize'] / $this->charRangeMap[$fontBaseName]['pixelSpace']));
624 }
625 }
626 }
627 }
628 }
629 if (is_array($conf['splitRendering.'])) {
630 foreach ($conf['splitRendering.'] as $key => $value) {
631 if (is_array($conf['splitRendering.'][$key])) {
632 if (isset($conf['splitRendering.'][$key]['fontFile'])) {
633 $conf['splitRendering.'][$key]['fontFile'] = $this->checkFile($conf['splitRendering.'][$key]['fontFile']);
634 }
635 }
636 }
637 }
638 return $conf;
639 }
640 }
641
642 /**
643 * Calculation of offset using "splitCalc" and insertion of dimensions from other GIFBUILDER objects.
644 *
645 * Example:
646 * Input: 2+2, 2*3, 123, [10.w]
647 * Output: 4,6,123,45 (provided that the width of object in position 10 was 45 pixels wide)
648 *
649 * @param string $string The string to resolve/calculate the result of. The string is divided by a comma first and each resulting part is calculated into an integer.
650 * @return string The resolved string with each part (separated by comma) returned separated by comma
651 * @access private
652 */
653 public function calcOffset($string) {
654 $value = array();
655 $numbers = GeneralUtility::trimExplode(',', $this->calculateFunctions($string));
656 foreach ($numbers as $key => $val) {
657 if ((string)$val == (string)(int)$val) {
658 $value[$key] = (int)$val;
659 } else {
660 $value[$key] = $this->calculateValue($val);
661 }
662 }
663 $string = implode(',', $value);
664 return $string;
665 }
666
667 /**
668 * Returns an "imgResource" creating an instance of the ContentObjectRenderer class and calling ContentObjectRenderer::getImgResource
669 *
670 * @param string $file Filename value OR the string "GIFBUILDER", see documentation in TSref for the "datatype" called "imgResource
671 * @param array $fileArray TypoScript properties passed to the function. Either GIFBUILDER properties or imgResource properties, depending on the value of $file (whether that is "GIFBUILDER" or a file reference)
672 * @return array|NULL Returns an array with file information from ContentObjectRenderer::getImgResource()
673 * @access private
674 * @see \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::getImgResource()
675 */
676 public function getResource($file, $fileArray) {
677 if (!GeneralUtility::inList($this->imageFileExt, $fileArray['ext'])) {
678 $fileArray['ext'] = $this->gifExtension;
679 }
680 /** @var \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $cObj */
681 $cObj = GeneralUtility::makeInstance(\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::class);
682 $cObj->start($this->data);
683 return $cObj->getImgResource($file, $fileArray);
684 }
685
686 /**
687 * Returns the reference to a "resource" in TypoScript.
688 *
689 * @param string $file The resource value.
690 * @return string Returns the relative filepath
691 * @access private
692 * @see \TYPO3\CMS\Core\TypoScript\TemplateService::getFileName()
693 */
694 public function checkFile($file) {
695 return $GLOBALS['TSFE']->tmpl->getFileName($file);
696 }
697
698 /**
699 * Calculates the GIFBUILDER output filename/path based on a serialized, hashed value of this->setup
700 * and prefixes the original filename
701 * also, the filename gets an additional prefix (max 100 characters),
702 * something like "GB_MD5HASH_myfilename_is_very_long_and_such.jpg"
703 *
704 * @param string $pre Filename prefix, eg. "GB_
705 * @return string The relative filepath (relative to PATH_site)
706 * @access private
707 */
708 public function fileName($pre) {
709 /** @var $basicFileFunctions \TYPO3\CMS\Core\Utility\File\BasicFileUtility */
710 $basicFileFunctions = GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Utility\\File\\BasicFileUtility');
711 $filePrefix = implode('_', array_merge($this->combinedTextStrings, $this->combinedFileNames));
712 $filePrefix = $basicFileFunctions->cleanFileName($filePrefix);
713
714 return $this->tempPath . $pre . $filePrefix . '_' . GeneralUtility::shortMD5(serialize($this->setup)) . '.' . $this->extension();
715 }
716
717 /**
718 * Returns the file extension used in the filename
719 *
720 * @return string Extension; "jpg" or "gif"/"png
721 * @access private
722 */
723 public function extension() {
724 switch (strtolower($this->setup['format'])) {
725 case 'jpg':
726
727 case 'jpeg':
728 return 'jpg';
729 break;
730 case 'png':
731 return 'png';
732 break;
733 case 'gif':
734 return 'gif';
735 break;
736 default:
737 return $this->gifExtension;
738 }
739 }
740
741 /**
742 * Calculates the value concerning the dimensions of objects.
743 *
744 * @param string $string The string to be calculated (e.g. "[20.h]+13")
745 * @return int The calculated value (e.g. "23")
746 * @see calcOffset()
747 */
748 protected function calculateValue($string) {
749 $calculatedValue = 0;
750 $parts = GeneralUtility::splitCalc($string, '+-*/%');
751 foreach ($parts as $part) {
752 $theVal = $part[1];
753 $sign = $part[0];
754 if (((string)(int)$theVal) == ((string)$theVal)) {
755 $theVal = (int)$theVal;
756 } elseif ('[' . substr($theVal, 1, -1) . ']' == $theVal) {
757 $objParts = explode('.', substr($theVal, 1, -1));
758 $theVal = 0;
759 if (isset($this->objBB[$objParts[0]])) {
760 if ($objParts[1] == 'w') {
761 $theVal = $this->objBB[$objParts[0]][0];
762 } elseif ($objParts[1] == 'h') {
763 $theVal = $this->objBB[$objParts[0]][1];
764 } elseif ($objParts[1] == 'lineHeight') {
765 $theVal = $this->objBB[$objParts[0]][2]['lineHeight'];
766 }
767 $theVal = (int)$theVal;
768 }
769 } elseif (floatval($theVal)) {
770 $theVal = floatval($theVal);
771 } else {
772 $theVal = 0;
773 }
774 if ($sign == '-') {
775 $calculatedValue -= $theVal;
776 } elseif ($sign == '+') {
777 $calculatedValue += $theVal;
778 } elseif ($sign == '/' && $theVal) {
779 $calculatedValue = $calculatedValue / $theVal;
780 } elseif ($sign == '*') {
781 $calculatedValue = $calculatedValue * $theVal;
782 } elseif ($sign == '%' && $theVal) {
783 $calculatedValue %= $theVal;
784 }
785 }
786 return round($calculatedValue);
787 }
788
789 /**
790 * Calculates special functions:
791 * + max([10.h], [20.h]) -> gets the maximum of the given values
792 *
793 * @param string $string The raw string with functions to be calculated
794 * @return string The calculated values
795 */
796 protected function calculateFunctions($string) {
797 if (preg_match_all('#max\\(([^)]+)\\)#', $string, $matches)) {
798 foreach ($matches[1] as $index => $maxExpression) {
799 $string = str_replace($matches[0][$index], $this->calculateMaximum($maxExpression), $string);
800 }
801 }
802 return $string;
803 }
804
805 /**
806 * Calculates the maximum of a set of values defined like "[10.h],[20.h],1000"
807 *
808 * @param string $string The string to be used to calculate the maximum (e.g. "[10.h],[20.h],1000")
809 * @return int The maxium value of the given comma separated and calculated values
810 */
811 protected function calculateMaximum($string) {
812 $parts = GeneralUtility::trimExplode(',', $this->calcOffset($string), TRUE);
813 $maximum = count($parts) ? max($parts) : 0;
814 return $maximum;
815 }
816
817 }