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