| Current Path : /proc/thread-self/root/var/www/surf/TYPO3/vendor/typo3/cms-frontend/Classes/Imaging/ |
| Current File : //proc/thread-self/root/var/www/surf/TYPO3/vendor/typo3/cms-frontend/Classes/Imaging/GifBuilder.php |
<?php
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace TYPO3\CMS\Frontend\Imaging;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Core\Context\FileProcessingAspect;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Imaging\GraphicalFunctions;
use TYPO3\CMS\Core\Resource\Exception;
use TYPO3\CMS\Core\Resource\File;
use TYPO3\CMS\Core\Resource\ProcessedFile;
use TYPO3\CMS\Core\Resource\Service\ConfigurationService;
use TYPO3\CMS\Core\Utility\ArrayUtility;
use TYPO3\CMS\Core\Utility\File\BasicFileUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\MathUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\Resource\FilePathSanitizer;
/**
* GIFBUILDER
*
* Generating gif/png-files from TypoScript
* Used by the menu-objects and imgResource in TypoScript.
*
* This class allows for advanced rendering of images with various layers of images, text and graphical primitives.
* 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.
* 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])
* Not all instances of this class is truly 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.
*
* Here is an example of how to use this class (from tslib_content.php, function getImgResource):
*
* $gifCreator = GeneralUtility::makeInstance(\TYPO3\CMS\Frontend\Imaging\GifBuilder::class);
* $gifCreator->init();
* $theImage='';
* if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib']) {
* $gifCreator->start($fileArray, $this->data);
* $theImage = $gifCreator->gifBuild();
* }
* return $gifCreator->getImageDimensions($theImage);
*/
class GifBuilder extends GraphicalFunctions
{
/**
* Contains all text strings used on this image
*
* @var array
*/
public $combinedTextStrings = [];
/**
* Contains all filenames (basename without extension) used on this image
*
* @var array
*/
public $combinedFileNames = [];
/**
* This is the array from which data->field: [key] is fetched. So this is the current record!
*
* @var array
*/
public $data = [];
/**
* @var array
*/
public $objBB = [];
/**
* @var string
*/
public $myClassName = 'gifbuilder';
/**
* @var array
*/
public $charRangeMap = [];
/**
* @var int[]
*/
public $XY = [];
protected ?ContentObjectRenderer $cObj = null;
/**
* @var array
*/
public $defaultWorkArea = [];
/**
* 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.
* Modifies the ->setup, ->objBB internal arrays
* Should be called after the ->init() function which initializes the parent class functions/variables in general.
*
* @param array $conf TypoScript properties for the GIFBUILDER session. Stored internally in the variable ->setup
* @param array $data The current data record from \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer. Stored internally in the variable ->data
* @see ContentObjectRenderer::getImgResource()
*/
public function start($conf, $data)
{
if (is_array($conf)) {
$this->setup = $conf;
$this->data = $data;
$this->cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
$this->cObj->start($this->data);
// Hook preprocess gifbuilder conf
// Added by Julle for 3.8.0
//
// Lets you pre-process the gifbuilder configuration. for
// example you can split a string up into lines and render each
// line as TEXT obj, see extension julle_gifbconf
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_gifbuilder.php']['gifbuilder-ConfPreProcess'] ?? [] as $_funcRef) {
$_params = $this->setup;
$ref = $this; // introduced for phpstan to not lose type information when passing $this into callUserFunction
$this->setup = GeneralUtility::callUserFunction($_funcRef, $_params, $ref);
}
// Initializing Char Range Map
$this->charRangeMap = [];
foreach (($conf['charRangeMap.'] ?? []) as $cRMcfgkey => $cRMcfg) {
if (is_array($cRMcfg)) {
$cRMkey = $conf['charRangeMap.'][substr($cRMcfgkey, 0, -1)];
$this->charRangeMap[$cRMkey] = [];
$this->charRangeMap[$cRMkey]['charMapConfig'] = $cRMcfg['charMapConfig.'] ?? [];
$this->charRangeMap[$cRMkey]['cfgKey'] = substr($cRMcfgkey, 0, -1);
$this->charRangeMap[$cRMkey]['multiplicator'] = (float)$cRMcfg['fontSizeMultiplicator'];
$this->charRangeMap[$cRMkey]['pixelSpace'] = (int)$cRMcfg['pixelSpaceFontSizeRef'];
}
}
// Getting sorted list of TypoScript keys from setup.
$sKeyArray = ArrayUtility::filterAndSortByNumericKeys($this->setup);
// Setting the background color, passing it through stdWrap
$this->setup['backColor'] = $this->cObj->stdWrapValue('backColor', $this->setup ?? []);
if (!$this->setup['backColor']) {
$this->setup['backColor'] = 'white';
}
$this->setup['transparentColor_array'] = explode('|', trim((string)$this->cObj->stdWrapValue('transparentColor', $this->setup ?? [])));
$this->setup['transparentBackground'] = $this->cObj->stdWrapValue('transparentBackground', $this->setup ?? []);
$this->setup['reduceColors'] = $this->cObj->stdWrapValue('reduceColors', $this->setup ?? []);
// Set default dimensions
$this->setup['XY'] = $this->cObj->stdWrapValue('XY', $this->setup ?? []);
if (!$this->setup['XY']) {
$this->setup['XY'] = '120,50';
}
// Checking TEXT and IMAGE objects for files. If any errors the objects are cleared.
// The Bounding Box for the objects is stored in an array
foreach ($sKeyArray as $theKey) {
$theValue = $this->setup[$theKey];
if ((int)$theKey && ($conf = $this->setup[$theKey . '.'] ?? [])) {
// Swipes through TEXT and IMAGE-objects
switch ($theValue) {
case 'TEXT':
if ($this->setup[$theKey . '.'] = $this->checkTextObj($conf)) {
// Adjust font width if max size is set:
$maxWidth = $this->cObj->stdWrapValue('maxWidth', $this->setup[$theKey . '.'] ?? []);
if ($maxWidth) {
$this->setup[$theKey . '.']['fontSize'] = $this->fontResize($this->setup[$theKey . '.']);
}
// Calculate bounding box:
$txtInfo = $this->calcBBox($this->setup[$theKey . '.']);
$this->setup[$theKey . '.']['BBOX'] = $txtInfo;
$this->objBB[$theKey] = $txtInfo;
$this->setup[$theKey . '.']['imgMap'] = 0;
}
break;
case 'IMAGE':
$fileInfo = $this->getResource($conf['file'] ?? '', $conf['file.'] ?? []);
if ($fileInfo) {
$this->combinedFileNames[] = preg_replace('/\\.[[:alnum:]]+$/', '', PathUtility::basename($fileInfo[3]));
if (($fileInfo['processedFile'] ?? null) instanceof ProcessedFile) {
// Use processed file, if a FAL file has been processed by GIFBUILDER (e.g. scaled/cropped)
$this->setup[$theKey . '.']['file'] = $fileInfo['processedFile']->getForLocalProcessing(false);
} elseif (!isset($fileInfo['origFile']) && ($fileInfo['originalFile'] ?? null) instanceof File) {
// Use FAL file with getForLocalProcessing to circumvent problems with umlauts, if it is a FAL file (origFile not set)
$originalFile = $fileInfo['originalFile'];
$this->setup[$theKey . '.']['file'] = $originalFile->getForLocalProcessing(false);
} else {
// 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)
$this->setup[$theKey . '.']['file'] = $fileInfo[3];
}
// only pass necessary parts of fileInfo further down, to not incorporate facts as
// CropScaleMask runs in this request, that may not occur in subsequent calls and change
// the md5 of the generated file name
$essentialFileInfo = $fileInfo;
unset($essentialFileInfo['originalFile'], $essentialFileInfo['processedFile']);
$this->setup[$theKey . '.']['BBOX'] = $essentialFileInfo;
$this->objBB[$theKey] = $essentialFileInfo;
if ($conf['mask'] ?? false) {
$maskInfo = $this->getResource($conf['mask'], $conf['mask.'] ?? []);
if ($maskInfo) {
// the same selection criteria as regarding fileInfo above apply here
if (($maskInfo['processedFile'] ?? null) instanceof ProcessedFile) {
$this->setup[$theKey . '.']['mask'] = $maskInfo['processedFile']->getForLocalProcessing(false);
} elseif (!isset($maskInfo['origFile']) && $maskInfo['originalFile'] instanceof File) {
$originalFile = $maskInfo['originalFile'];
$this->setup[$theKey . '.']['mask'] = $originalFile->getForLocalProcessing(false);
} else {
$this->setup[$theKey . '.']['mask'] = $maskInfo[3];
}
} else {
$this->setup[$theKey . '.']['mask'] = '';
}
}
} else {
unset($this->setup[$theKey . '.']);
}
break;
}
// Checks if disabled is set... (this is also done in menu.php / imgmenu!!)
if ($conf['if.'] ?? false) {
$cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
$cObj->start($this->data);
if (!$cObj->checkIf($conf['if.'])) {
unset($this->setup[$theKey]);
unset($this->setup[$theKey . '.']);
unset($this->objBB[$theKey]);
}
}
}
}
// Calculate offsets on elements
$this->setup['XY'] = $this->calcOffset($this->setup['XY']);
$this->setup['offset'] = (string)$this->cObj->stdWrapValue('offset', $this->setup ?? []);
$this->setup['offset'] = $this->calcOffset($this->setup['offset']);
$this->setup['workArea'] = (string)$this->cObj->stdWrapValue('workArea', $this->setup ?? []);
$this->setup['workArea'] = $this->calcOffset($this->setup['workArea']);
foreach ($sKeyArray as $theKey) {
$theValue = $this->setup[$theKey];
if ((int)$theKey && ($this->setup[$theKey . '.'] ?? false)) {
switch ($theValue) {
case 'TEXT':
case 'IMAGE':
if (isset($this->setup[$theKey . '.']['offset.'])) {
$this->setup[$theKey . '.']['offset'] = $this->cObj->stdWrapValue('offset', $this->setup[$theKey . '.']);
unset($this->setup[$theKey . '.']['offset.']);
}
if ($this->setup[$theKey . '.']['offset'] ?? false) {
$this->setup[$theKey . '.']['offset'] = $this->calcOffset($this->setup[$theKey . '.']['offset']);
}
break;
case 'BOX':
case 'ELLIPSE':
if (isset($this->setup[$theKey . '.']['dimensions.'])) {
$this->setup[$theKey . '.']['dimensions'] = $this->cObj->stdWrapValue('dimensions', $this->setup[$theKey . '.']);
unset($this->setup[$theKey . '.']['dimensions.']);
}
if ($this->setup[$theKey . '.']['dimensions'] ?? false) {
$this->setup[$theKey . '.']['dimensions'] = $this->calcOffset($this->setup[$theKey . '.']['dimensions']);
}
break;
case 'WORKAREA':
if (isset($this->setup[$theKey . '.']['set.'])) {
$this->setup[$theKey . '.']['set'] = $this->cObj->stdWrapValue('set', $this->setup[$theKey . '.']);
unset($this->setup[$theKey . '.']['set.']);
}
if ($this->setup[$theKey . '.']['set'] ?? false) {
$this->setup[$theKey . '.']['set'] = $this->calcOffset($this->setup[$theKey . '.']['set']);
}
break;
case 'CROP':
if (isset($this->setup[$theKey . '.']['crop.'])) {
$this->setup[$theKey . '.']['crop'] = $this->cObj->stdWrapValue('crop', $this->setup[$theKey . '.']);
unset($this->setup[$theKey . '.']['crop.']);
}
if ($this->setup[$theKey . '.']['crop'] ?? false) {
$this->setup[$theKey . '.']['crop'] = $this->calcOffset($this->setup[$theKey . '.']['crop']);
}
break;
case 'SCALE':
if (isset($this->setup[$theKey . '.']['width.'])) {
$this->setup[$theKey . '.']['width'] = $this->cObj->stdWrapValue('width', $this->setup[$theKey . '.']);
unset($this->setup[$theKey . '.']['width.']);
}
if ($this->setup[$theKey . '.']['width'] ?? false) {
$this->setup[$theKey . '.']['width'] = $this->calcOffset($this->setup[$theKey . '.']['width']);
}
if (isset($this->setup[$theKey . '.']['height.'])) {
$this->setup[$theKey . '.']['height'] = $this->cObj->stdWrapValue('height', $this->setup[$theKey . '.']);
unset($this->setup[$theKey . '.']['height.']);
}
if ($this->setup[$theKey . '.']['height'] ?? false) {
$this->setup[$theKey . '.']['height'] = $this->calcOffset($this->setup[$theKey . '.']['height']);
}
break;
}
}
}
// Get trivial data
$XY = GeneralUtility::intExplode(',', $this->setup['XY']);
$maxWidth = (int)$this->cObj->stdWrapValue('maxWidth', $this->setup ?? []);
$maxHeight = (int)$this->cObj->stdWrapValue('maxHeight', $this->setup ?? []);
$XY[0] = MathUtility::forceIntegerInRange($XY[0], 1, $maxWidth ?: 2000);
$XY[1] = MathUtility::forceIntegerInRange($XY[1], 1, $maxHeight ?: 2000);
$this->XY = $XY;
$this->w = $XY[0];
$this->h = $XY[1];
$this->OFFSET = GeneralUtility::intExplode(',', $this->setup['offset']);
// this sets the workArea
$this->setWorkArea($this->setup['workArea']);
// this sets the default to the current;
$this->defaultWorkArea = $this->workArea;
}
}
/**
* Initiates the image file generation if ->setup is TRUE and if the file did not exist already.
* Gets filename from fileName() and if file exists in typo3temp/assets/images/ dir it will - of course - not be rendered again.
* Otherwise rendering means calling ->make(), then ->output(), then ->destroy()
*
* @return string The filename for the created GIF/PNG file.
* @see make()
* @see fileName()
*/
public function gifBuild()
{
if (!$this->setup) {
return '';
}
// Relative to Environment::getPublicPath()
$gifFileName = $this->fileName('assets/images/');
if (!file_exists(Environment::getPublicPath() . '/' . $gifFileName)) {
// Create temporary directory if not done
GeneralUtility::mkdir_deep(Environment::getPublicPath() . '/typo3temp/assets/images/');
// Create file
$this->make();
$this->output(Environment::getPublicPath() . '/' . $gifFileName);
$this->destroy();
}
return $gifFileName;
}
/**
* The actual rendering of the image file.
* Basically sets the dimensions, the background color, the traverses the array of GIFBUILDER objects and finally setting the transparent color if defined.
* Creates a GDlib resource in $this->im and works on that
* Called by gifBuild()
*
* @internal
* @see gifBuild()
*/
public function make()
{
// Get trivial data
$XY = $this->XY;
// Reset internal properties
$this->saveAlphaLayer = false;
// Gif-start
$im = imagecreatetruecolor($XY[0], $XY[1]);
if ($im === false) {
throw new \RuntimeException('imagecreatetruecolor returned false', 1598350445);
}
$this->im = $im;
$this->w = $XY[0];
$this->h = $XY[1];
// Transparent layer as background if set and requirements are met
if (!empty($this->setup['backColor']) && $this->setup['backColor'] === 'transparent' && !$this->setup['reduceColors'] && (empty($this->setup['format']) || $this->setup['format'] === 'png')) {
// Set transparency properties
imagesavealpha($this->im, true);
// Fill with a transparent background
$transparentColor = imagecolorallocatealpha($this->im, 0, 0, 0, 127);
imagefill($this->im, 0, 0, $transparentColor);
// Set internal properties to keep the transparency over the rendering process
$this->saveAlphaLayer = true;
// Force PNG in case no format is set
$this->setup['format'] = 'png';
$BGcols = [];
} else {
// Fill the background with the given color
$BGcols = $this->convertColor($this->setup['backColor']);
$Bcolor = imagecolorallocate($this->im, $BGcols[0], $BGcols[1], $BGcols[2]);
imagefilledrectangle($this->im, 0, 0, $XY[0], $XY[1], $Bcolor);
}
// Traverse the GIFBUILDER objects and render each one:
if (is_array($this->setup)) {
$sKeyArray = ArrayUtility::filterAndSortByNumericKeys($this->setup);
foreach ($sKeyArray as $theKey) {
$theValue = $this->setup[$theKey];
if ((int)$theKey && ($conf = $this->setup[$theKey . '.'] ?? [])) {
// apply stdWrap to all properties, except for TEXT objects
// all properties of the TEXT sub-object have already been stdWrap-ped
// before in ->checkTextObj()
if ($theValue !== 'TEXT') {
$isStdWrapped = [];
foreach ($conf as $key => $value) {
$parameter = rtrim($key, '.');
if (!($isStdWrapped[$parameter] ?? false) && isset($conf[$parameter . '.'])) {
$conf[$parameter] = $this->cObj->stdWrapValue($parameter, $conf);
$isStdWrapped[$parameter] = 1;
}
}
}
switch ($theValue) {
case 'IMAGE':
if ($conf['mask'] ?? false) {
$this->maskImageOntoImage($this->im, $conf, $this->workArea);
} else {
$this->copyImageOntoImage($this->im, $conf, $this->workArea);
}
break;
case 'TEXT':
if (!($conf['hide'] ?? false)) {
if (is_array($conf['shadow.'] ?? null)) {
$isStdWrapped = [];
foreach ($conf['shadow.'] as $key => $value) {
$parameter = rtrim($key, '.');
if (!$isStdWrapped[$parameter] && isset($conf[$parameter . '.'])) {
$conf['shadow.'][$parameter] = $this->cObj->stdWrapValue($parameter, $conf);
$isStdWrapped[$parameter] = 1;
}
}
$this->makeShadow($this->im, $conf['shadow.'], $this->workArea, $conf);
}
if (is_array($conf['emboss.'] ?? null)) {
$isStdWrapped = [];
foreach ($conf['emboss.'] as $key => $value) {
$parameter = rtrim($key, '.');
if (!$isStdWrapped[$parameter] && isset($conf[$parameter . '.'])) {
$conf['emboss.'][$parameter] = $this->cObj->stdWrapValue($parameter, $conf);
$isStdWrapped[$parameter] = 1;
}
}
$this->makeEmboss($this->im, $conf['emboss.'], $this->workArea, $conf);
}
if (is_array($conf['outline.'] ?? null)) {
$isStdWrapped = [];
foreach ($conf['outline.'] as $key => $value) {
$parameter = rtrim($key, '.');
if (!$isStdWrapped[$parameter] && isset($conf[$parameter . '.'])) {
$conf['outline.'][$parameter] = $this->cObj->stdWrapValue($parameter, $conf);
$isStdWrapped[$parameter] = 1;
}
}
$this->makeOutline($this->im, $conf['outline.'], $this->workArea, $conf);
}
$conf['imgMap'] = 1;
$this->makeText($this->im, $conf, $this->workArea);
}
break;
case 'OUTLINE':
if ($this->setup[$conf['textObjNum']] === 'TEXT' && ($txtConf = $this->checkTextObj($this->setup[$conf['textObjNum'] . '.']))) {
$this->makeOutline($this->im, $conf, $this->workArea, $txtConf);
}
break;
case 'EMBOSS':
if ($this->setup[$conf['textObjNum']] === 'TEXT' && ($txtConf = $this->checkTextObj($this->setup[$conf['textObjNum'] . '.']))) {
$this->makeEmboss($this->im, $conf, $this->workArea, $txtConf);
}
break;
case 'SHADOW':
if ($this->setup[$conf['textObjNum']] === 'TEXT' && ($txtConf = $this->checkTextObj($this->setup[$conf['textObjNum'] . '.']))) {
$this->makeShadow($this->im, $conf, $this->workArea, $txtConf);
}
break;
case 'BOX':
$this->makeBox($this->im, $conf, $this->workArea);
break;
case 'EFFECT':
$this->makeEffect($this->im, $conf);
break;
case 'ADJUST':
$this->adjust($this->im, $conf);
break;
case 'CROP':
$this->crop($this->im, $conf);
break;
case 'SCALE':
$this->scale($this->im, $conf);
break;
case 'WORKAREA':
if ($conf['set']) {
// this sets the workArea
$this->setWorkArea($conf['set']);
}
if (isset($conf['clear'])) {
// This sets the current to the default;
$this->workArea = $this->defaultWorkArea;
}
break;
case 'ELLIPSE':
$this->makeEllipse($this->im, $conf, $this->workArea);
break;
}
}
}
}
// Preserve alpha transparency
if (!$this->saveAlphaLayer) {
if ($this->setup['transparentBackground']) {
// Auto transparent background is set
$Bcolor = imagecolorclosest($this->im, $BGcols[0], $BGcols[1], $BGcols[2]);
imagecolortransparent($this->im, $Bcolor);
} elseif (is_array($this->setup['transparentColor_array'])) {
// Multiple transparent colors are set. This is done via the trick that all transparent colors get
// converted to one color and then this one gets set as transparent as png/gif can just have one
// transparent color.
$Tcolor = $this->unifyColors($this->im, $this->setup['transparentColor_array'], (bool)($this->setup['transparentColor.']['closest'] ?? false));
if ($Tcolor >= 0) {
imagecolortransparent($this->im, $Tcolor);
}
}
}
}
/*********************************************
*
* Various helper functions
*
********************************************/
/**
* Initializing/Cleaning of TypoScript properties for TEXT GIFBUILDER objects
*
* 'cleans' TEXT-object; Checks fontfile and other vital setup
* Finds the title if its a 'variable' (instantiates a cObj and loads it with the ->data record)
* Performs caseshift if any.
*
* @param array $conf GIFBUILDER object TypoScript properties
* @return array|null Modified $conf array IF the "text" property is not blank
* @internal
*/
public function checkTextObj($conf)
{
$cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
$cObj->start($this->data);
$isStdWrapped = [];
foreach ($conf as $key => $value) {
$parameter = rtrim($key, '.');
if (!($isStdWrapped[$parameter] ?? false) && isset($conf[$parameter . '.'])) {
$conf[$parameter] = $cObj->stdWrapValue($parameter, $conf);
$isStdWrapped[$parameter] = 1;
}
}
if (!is_null($conf['fontFile'] ?? null)) {
$conf['fontFile'] = $this->checkFile($conf['fontFile']);
}
if (!($conf['fontFile'] ?? false)) {
$conf['fontFile'] = $this->checkFile('EXT:core/Resources/Private/Font/nimbus.ttf');
}
if (!($conf['iterations'] ?? false)) {
$conf['iterations'] = 1;
}
if (!($conf['fontSize'] ?? false)) {
$conf['fontSize'] = 12;
}
// If any kind of spacing applies, we cannot use angles!!
if (($conf['spacing'] ?? false) || ($conf['wordSpacing'] ?? false)) {
$conf['angle'] = 0;
}
if (!isset($conf['antiAlias'])) {
$conf['antiAlias'] = 1;
}
$conf['fontColor'] = trim($conf['fontColor'] ?? '');
// Strip HTML
if (!($conf['doNotStripHTML'] ?? false)) {
$conf['text'] = strip_tags($conf['text'] ?? '');
}
$this->combinedTextStrings[] = strip_tags($conf['text'] ?? '');
// Max length = 100 if automatic line breaks are not defined:
if (!isset($conf['breakWidth']) || !$conf['breakWidth']) {
$tlen = (int)($conf['textMaxLength'] ?? 0) ?: 100;
$conf['text'] = mb_substr($conf['text'], 0, $tlen, 'utf-8');
}
if ((string)$conf['text'] != '') {
// Char range map thingie:
$fontBaseName = PathUtility::basename($conf['fontFile']);
if (is_array($this->charRangeMap[$fontBaseName] ?? null)) {
// Initialize splitRendering array:
if (!is_array($conf['splitRendering.'])) {
$conf['splitRendering.'] = [];
}
$cfgK = $this->charRangeMap[$fontBaseName]['cfgKey'];
// Do not impose settings if a splitRendering object already exists:
if (!isset($conf['splitRendering.'][$cfgK])) {
// Set configuration:
$conf['splitRendering.'][$cfgK] = 'charRange';
$conf['splitRendering.'][$cfgK . '.'] = $this->charRangeMap[$fontBaseName]['charMapConfig'];
// Multiplicator of fontsize:
if ($this->charRangeMap[$fontBaseName]['multiplicator']) {
$conf['splitRendering.'][$cfgK . '.']['fontSize'] = round($conf['fontSize'] * $this->charRangeMap[$fontBaseName]['multiplicator']);
}
// Multiplicator of pixelSpace:
if ($this->charRangeMap[$fontBaseName]['pixelSpace']) {
$travKeys = ['xSpaceBefore', 'xSpaceAfter', 'ySpaceBefore', 'ySpaceAfter'];
foreach ($travKeys as $pxKey) {
if (isset($conf['splitRendering.'][$cfgK . '.'][$pxKey])) {
$conf['splitRendering.'][$cfgK . '.'][$pxKey] = round($conf['splitRendering.'][$cfgK . '.'][$pxKey] * ($conf['fontSize'] / $this->charRangeMap[$fontBaseName]['pixelSpace']));
}
}
}
}
}
if (is_array($conf['splitRendering.'] ?? null)) {
foreach ($conf['splitRendering.'] as $key => $value) {
if (is_array($conf['splitRendering.'][$key])) {
if (isset($conf['splitRendering.'][$key]['fontFile'])) {
$conf['splitRendering.'][$key]['fontFile'] = $this->checkFile($conf['splitRendering.'][$key]['fontFile']);
}
}
}
}
return $conf;
}
return null;
}
/**
* Calculation of offset using "splitCalc" and insertion of dimensions from other GIFBUILDER objects.
*
* Example:
* Input: 2+2, 2*3, 123, [10.w]
* Output: 4,6,123,45 (provided that the width of object in position 10 was 45 pixels wide)
*
* @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.
* @return string The resolved string with each part (separated by comma) returned separated by comma
* @internal
*/
public function calcOffset($string)
{
$value = [];
$numbers = GeneralUtility::trimExplode(',', $this->calculateFunctions($string));
foreach ($numbers as $key => $val) {
if ((string)$val == (string)(int)$val) {
$value[$key] = (int)$val;
} else {
$value[$key] = $this->calculateValue($val);
}
}
$string = implode(',', $value);
return $string;
}
/**
* Returns an "imgResource" creating an instance of the ContentObjectRenderer class and calling ContentObjectRenderer::getImgResource
*
* @param string $file Filename value OR the string "GIFBUILDER", see documentation in TSref for the "datatype" called "imgResource
* @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)
* @return array|null Returns an array with file information from ContentObjectRenderer::getImgResource()
* @internal
* @see ContentObjectRenderer::getImgResource()
*/
public function getResource($file, $fileArray)
{
$context = GeneralUtility::makeInstance(Context::class);
$deferProcessing = !$context->hasAspect('fileProcessing') || $context->getPropertyFromAspect('fileProcessing', 'deferProcessing');
$context->setAspect('fileProcessing', new FileProcessingAspect(false));
try {
if (!in_array($fileArray['ext'] ?? '', $this->imageFileExt, true)) {
$fileArray['ext'] = $this->gifExtension;
}
$cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
$cObj->start($this->data);
return $cObj->getImgResource($file, $fileArray);
} finally {
$context->setAspect('fileProcessing', new FileProcessingAspect($deferProcessing));
}
}
/**
* Returns the reference to a "resource" in TypoScript.
*
* @param string $file The resource value.
* @return string|null Returns the relative filepath or null if it's invalid
* @internal
*/
public function checkFile($file)
{
try {
return GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($file, true);
} catch (Exception $e) {
return null;
}
}
/**
* Calculates the GIFBUILDER output filename/path based on a serialized, hashed value of this->setup
* and prefixes the original filename
* also, the filename gets an additional prefix (max 100 characters),
* something like "GB_MD5HASH_myfilename_is_very_long_and_such.jpg"
*
* @param string $pre Filename prefix, eg. "GB_
* @return string The filepath, relative to Environment::getPublicPath()
* @internal
*/
public function fileName($pre)
{
$basicFileFunctions = GeneralUtility::makeInstance(BasicFileUtility::class);
$filePrefix = implode('_', array_merge($this->combinedTextStrings, $this->combinedFileNames));
$filePrefix = $basicFileFunctions->cleanFileName(ltrim($filePrefix, '.'));
// shorten prefix to avoid overly long file names
$filePrefix = substr($filePrefix, 0, 100);
$configurationService = GeneralUtility::makeInstance(ConfigurationService::class);
// we use ConfigurationService::serialize here to use as much of $this->setup as possible,
// but preventing inclusion of objects that could cause problems with json_encode
$hashInputForFileName = [
$configurationService->serialize($this->setup),
$filePrefix,
$this->im,
$this->XY,
$this->w,
$this->h,
$this->map,
$this->OFFSET,
$this->workArea,
$this->combinedTextStrings,
$this->combinedFileNames,
$this->data,
];
return 'typo3temp/' . $pre . $filePrefix . '_' . md5((string)json_encode($hashInputForFileName)) . '.' . $this->extension();
}
/**
* Returns the file extension used in the filename
*
* @return string Extension; "jpg" or "gif"/"png
* @internal
*/
public function extension()
{
switch (strtolower($this->setup['format'] ?? '')) {
case 'jpg':
case 'jpeg':
return 'jpg';
case 'png':
return 'png';
case 'gif':
return 'gif';
default:
return $this->gifExtension;
}
}
/**
* Calculates the value concerning the dimensions of objects.
*
* @param string $string The string to be calculated (e.g. "[20.h]+13")
* @return int The calculated value (e.g. "23")
* @see calcOffset()
*/
protected function calculateValue($string)
{
$calculatedValue = 0;
$parts = GeneralUtility::splitCalc($string, '+-*/%');
foreach ($parts as $part) {
$theVal = $part[1];
$sign = $part[0];
if (((string)(int)$theVal) == ((string)$theVal)) {
$theVal = (int)$theVal;
} elseif ('[' . substr($theVal, 1, -1) . ']' == $theVal) {
$objParts = explode('.', substr($theVal, 1, -1));
$theVal = 0;
if (isset($this->objBB[$objParts[0]], $objParts[1])) {
if ($objParts[1] === 'w' && isset($this->objBB[$objParts[0]][0])) {
$theVal = $this->objBB[$objParts[0]][0];
} elseif ($objParts[1] === 'h' && isset($this->objBB[$objParts[0]][1])) {
$theVal = $this->objBB[$objParts[0]][1];
} elseif ($objParts[1] === 'lineHeight' && isset($this->objBB[$objParts[0]][2]['lineHeight'])) {
$theVal = $this->objBB[$objParts[0]][2]['lineHeight'];
}
$theVal = (int)$theVal;
}
} elseif ((float)$theVal) {
$theVal = (float)$theVal;
} else {
$theVal = 0;
}
if ($sign === '-') {
$calculatedValue -= $theVal;
} elseif ($sign === '+') {
$calculatedValue += $theVal;
} elseif ($sign === '/' && $theVal) {
$calculatedValue /= $theVal;
} elseif ($sign === '*') {
$calculatedValue *= $theVal;
} elseif ($sign === '%' && $theVal) {
$calculatedValue %= $theVal;
}
}
return (int)round($calculatedValue);
}
/**
* Calculates special functions:
* + max([10.h], [20.h]) -> gets the maximum of the given values
*
* @param string $string The raw string with functions to be calculated
* @return string The calculated values
*/
protected function calculateFunctions($string)
{
if (preg_match_all('#max\\(([^)]+)\\)#', $string, $matches)) {
foreach ($matches[1] as $index => $maxExpression) {
$string = str_replace($matches[0][$index], (string)$this->calculateMaximum($maxExpression), $string);
}
}
return $string;
}
/**
* Calculates the maximum of a set of values defined like "[10.h],[20.h],1000"
*
* @param string $string The string to be used to calculate the maximum (e.g. "[10.h],[20.h],1000")
* @return int The maximum value of the given comma separated and calculated values
*/
protected function calculateMaximum($string)
{
$parts = GeneralUtility::trimExplode(',', $this->calcOffset($string), true);
$maximum = !empty($parts) ? max($parts) : 0;
return $maximum;
}
}