Skip to content

Commit 2b84f68

Browse files
committed
[FEATURE] generate placeholder images internal
1 parent a529e86 commit 2b84f68

File tree

5 files changed

+184
-4
lines changed

5 files changed

+184
-4
lines changed

Classes/Domain/Model/PlaceholderImage.php

+10-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44

55
use SMS\FluidComponents\Interfaces\ImageWithDimensions;
66
use SMS\FluidComponents\Interfaces\ProcessableImage;
7+
use SMS\FluidComponents\Service\PlaceholderImageService;
78
use TYPO3\CMS\Core\Imaging\ImageManipulation\Area;
9+
use TYPO3\CMS\Core\Utility\GeneralUtility;
810

911
/**
1012
* Data structure for a placeholder image to be passed to a component.
@@ -29,16 +31,16 @@ class PlaceholderImage extends Image implements ImageWithDimensions, Processable
2931
/**
3032
* Image format of the image
3133
*/
32-
protected string $format = 'gif';
34+
protected string $format = 'svg';
3335

3436
/**
3537
* Creates an image object for a placeholder image.
3638
*/
37-
public function __construct(int $width, int $height, string $format = 'gif')
39+
public function __construct(int $width, int $height, ?string $format = null)
3840
{
3941
$this->width = $width;
4042
$this->height = $height;
41-
$this->format = $format;
43+
$this->format = $format ?? $this->format;
4244
}
4345

4446
public function getWidth(): int
@@ -58,7 +60,11 @@ public function getFormat(): string
5860

5961
public function getPublicUrl(): string
6062
{
61-
return 'https://via.placeholder.com/' . $this->width . 'x' . $this->height . '.' . $this->format;
63+
return GeneralUtility::makeInstance(PlaceholderImageService::class)->generate(
64+
$this->width,
65+
$this->height,
66+
$this->format,
67+
);
6268
}
6369

6470
public function process(int $width, int $height, ?string $format, Area $cropArea): ProcessableImage
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace SMS\FluidComponents\Service;
4+
5+
use TYPO3\CMS\Core\Information\Typo3Version;
6+
use TYPO3\CMS\Core\Utility\GeneralUtility;
7+
use TYPO3\CMS\Core\View\ViewFactoryData;
8+
use TYPO3\CMS\Core\View\ViewFactoryInterface;
9+
use TYPO3\CMS\Fluid\View\StandaloneView;
10+
use TYPO3\CMS\Frontend\Imaging\GifBuilder;
11+
12+
class PlaceholderImageService
13+
{
14+
const int STROKE_WIDTH = 18;
15+
const int STROKE_HEIGHT = 3;
16+
const string BACKGROUND_COLOR = '#C0C0C0';
17+
const string COLOR = '#0B0B13';
18+
19+
public function __construct(
20+
private readonly GifBuilder $gifBuilder,
21+
private readonly ?ViewFactoryInterface $viewFactory = null,
22+
) {
23+
}
24+
25+
public function generate(int $width, int $height, string $format): string
26+
{
27+
$text = sprintf('%dx%d.%s', $width, $height, $format);
28+
if ($format !== 'svg') {
29+
return $this->generateBitmap($width, $height, $format, $text);
30+
}
31+
32+
if (GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion() < 13) {
33+
$view = new StandaloneView();
34+
$view->setTemplatePathAndFilename('EXT:fluid_components/Resources/Private/Templates/Placeholder.svg');
35+
} else {
36+
$view = $this->viewFactory->create(new ViewFactoryData(
37+
templatePathAndFilename: 'EXT:fluid_components/Resources/Private/Templates/Placeholder.svg',
38+
));
39+
}
40+
41+
$view->assignMultiple([
42+
'width' => $width,
43+
'height' => $height,
44+
'backgroundColor' => self::BACKGROUND_COLOR,
45+
'color' => self::COLOR,
46+
'text' => $text,
47+
]);
48+
return 'data:image/svg+xml;base64,' . base64_encode($view->render());
49+
}
50+
51+
private function generateBitmap(int $width, int $height, string $format, string $text): string
52+
{
53+
$configuration = [
54+
'XY' => implode(',', [$width, $height]),
55+
'backColor' => self::BACKGROUND_COLOR,
56+
'format' => $format,
57+
'10' => 'TEXT',
58+
'10.' => [
59+
'text' => $text,
60+
'fontColor' => self::COLOR,
61+
'fontSize' => round($width / 9),
62+
'align' => 'center',
63+
'offset' => implode(',', [0, $height / 1.75]),
64+
],
65+
];
66+
67+
$strokes = [
68+
[0 ,0 , self::STROKE_WIDTH, self::STROKE_HEIGHT],
69+
[0 ,0 , self::STROKE_HEIGHT, self::STROKE_WIDTH],
70+
[($width - self::STROKE_WIDTH), 0, $width , self::STROKE_HEIGHT],
71+
[($width - self::STROKE_HEIGHT), 0, $width, self::STROKE_WIDTH],
72+
[0, ($height - self::STROKE_HEIGHT), self::STROKE_WIDTH, $height],
73+
[0, ($height - self::STROKE_WIDTH), self::STROKE_HEIGHT, $height],
74+
[($width - self::STROKE_WIDTH), ($height - self::STROKE_HEIGHT), $width, $height],
75+
[($width - self::STROKE_HEIGHT), ($height - self::STROKE_WIDTH), $width, $height],
76+
];
77+
78+
foreach ($strokes as $key => $dimensions) {
79+
$configuration[10 * ($key + 2)] = 'BOX';
80+
$configuration[10 * ($key + 2) . '.'] = [
81+
'dimensions' => implode(',', $dimensions),
82+
'color' => self::COLOR,
83+
];
84+
}
85+
86+
$this->gifBuilder->start($configuration, []);
87+
88+
if (GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion() < 13 && is_string($imagePath = $this->gifBuilder->gifBuild())) {
89+
return $imagePath;
90+
}
91+
return (string) $this->gifBuilder->gifBuild()->getPublicUrl();
92+
}
93+
}

Configuration/Services.yaml

+3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ services:
88
resource: '../Classes/*'
99
exclude: '../Classes/Domain/Model/*'
1010

11+
SMS\FluidComponents\Service\PlaceholderImageService:
12+
public: true
13+
1114
SMS\FluidComponents\Command\GenerateXsdCommand:
1215
tags:
1316
- name: 'console.command'
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace SMS\FluidComponents\Tests\Functional\Service;
4+
5+
use SMS\FluidComponents\Service\PlaceholderImageService;
6+
use TYPO3\CMS\Core\Information\Typo3Version;
7+
use TYPO3\CMS\Core\Utility\GeneralUtility;
8+
use TYPO3\CMS\Frontend\Imaging\GifBuilder;
9+
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
10+
11+
class PlaceholderImageServiceTest extends FunctionalTestCase
12+
{
13+
protected bool $initializeDatabase = false;
14+
protected array $testExtensionsToLoad = [
15+
'typo3conf/ext/fluid_components',
16+
];
17+
18+
protected function setUp(): void
19+
{
20+
parent::setUp();
21+
}
22+
23+
public function testGenerateBitmap(): void
24+
{
25+
$gifBuilder = GeneralUtility::makeInstance(GifBuilder::class);
26+
$service = new PlaceholderImageService($gifBuilder);
27+
28+
$result = $service->generate(100, 50, 'png');
29+
$this->assertStringContainsString('100x50.png_', $result);
30+
$this->assertStringEndsWith('.png', $result);
31+
}
32+
33+
public function testGenerateSvg(): void
34+
{
35+
$gifBuilder = GeneralUtility::makeInstance(GifBuilder::class);
36+
37+
if (GeneralUtility::makeInstance(Typo3Version::class)->getMajorVersion() < 13) {
38+
$viewFactory = null;
39+
} else {
40+
$viewFactory = $this->createMock(\TYPO3\CMS\Core\View\ViewFactoryInterface::class);
41+
$view = $this->createMock(\TYPO3\CMS\Core\View\ViewInterface::class);
42+
$view->method('render')->willReturn('<svg></svg>');
43+
$viewFactory->method('create')->willReturn($view);
44+
GeneralUtility::addInstance(\TYPO3\CMS\Core\View\ViewFactoryInterface::class, $viewFactory);
45+
}
46+
47+
$service = new PlaceholderImageService($gifBuilder, $viewFactory);
48+
49+
$result = $service->generate(100, 50, 'svg');
50+
$this->assertStringStartsWith('data:image/svg+xml;base64,', $result);
51+
$this->assertStringEndsNotWith('data:image/svg+xml;base64,', $result);
52+
}
53+
54+
}

0 commit comments

Comments
 (0)