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