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