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