Skip to content

Commit

Permalink
Add favicon generation functionalities (#194)
Browse files Browse the repository at this point in the history
* Add favicon generation functionalities

Introduce favicon generation capabilities using the GDImage library and workbox integration for service worker builds. This includes browser config for Windows 8+ tile architecture, supporting different image sizes and output formats. New validations in the configuration test ensure that these additions function as expected. Modifications in the service worker compiler class aim at better usage of interfaces and performance improvements.
  • Loading branch information
Spomky authored May 11, 2024
1 parent 3183d22 commit 27059f7
Show file tree
Hide file tree
Showing 22 changed files with 1,033 additions and 268 deletions.
19 changes: 13 additions & 6 deletions castor.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php

declare(strict_types=1);
use Castor\Attribute\AsOption;

use Castor\Attribute\AsTask;
use function Castor\io;
Expand Down Expand Up @@ -57,9 +58,9 @@ function test(bool $coverageHtml = false, bool $coverageText = false, null|strin

#[AsTask(description: 'Coding standards check')]
function cs(
#[\Castor\Attribute\AsOption(description: 'Fix issues if possible')]
#[AsOption(description: 'Fix issues if possible')]
bool $fix = false,
#[\Castor\Attribute\AsOption(description: 'Clear cache')]
#[AsOption(description: 'Clear cache')]
bool $clearCache = false
): void {
io()->title('Running coding standards check');
Expand All @@ -77,10 +78,16 @@ function cs(
}

#[AsTask(description: 'Running PHPStan')]
function stan(): void
function stan(
#[AsOption(description: 'Generate baseline')]
bool $baseline = false
): void
{
io()->title('Running PHPStan');
$command = ['php', 'vendor/bin/phpstan', 'analyse'];
if ($baseline) {
$command[] = '--generate-baseline';
}
$environment = [
'XDEBUG_MODE' => 'off',
];
Expand Down Expand Up @@ -119,7 +126,7 @@ function checkLicenses(
io()->error('Cannot determine licenses');
exit(1);
}
$licenses = json_decode($result->getOutput(), true);
$licenses = json_decode((string) $result->getOutput(), true);
$disallowed = array_filter(
$licenses['dependencies'],
static fn (array $info, $name) => ! in_array($name, $allowedExceptions, true)
Expand Down Expand Up @@ -161,9 +168,9 @@ function checkLicenses(

#[AsTask(description: 'Run Rector')]
function rector(
#[\Castor\Attribute\AsOption(description: 'Fix issues if possible')]
#[AsOption(description: 'Fix issues if possible')]
bool $fix = false,
#[\Castor\Attribute\AsOption(description: 'Clear cache')]
#[AsOption(description: 'Clear cache')]
bool $clearCache = false
): void {
io()->title('Running Rector');
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"symfony/asset-mapper": "^6.4|^7.0",
"symfony/config": "^6.4|^7.0",
"symfony/dependency-injection": "^6.4|^7.0",
"symfony/deprecation-contracts": "^3.5",
"symfony/http-kernel": "^6.4|^7.0",
"symfony/property-access": "^6.4|^7.0",
"symfony/property-info": "^6.4|^7.0",
Expand Down
69 changes: 12 additions & 57 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,6 @@ parameters:
count: 1
path: src/Command/CreateIconsCommand.php

-
message: "#^Parameter \\#1 \\$mimeType of method Symfony\\\\Component\\\\Mime\\\\MimeTypes\\:\\:getExtensions\\(\\) expects string, string\\|null given\\.$#"
count: 1
path: src/Command/CreateIconsCommand.php

-
message: "#^Parameter \\#1 \\$source of method SpomkyLabs\\\\PwaBundle\\\\Command\\\\CreateIconsCommand\\:\\:getSourcePath\\(\\) expects string, mixed given\\.$#"
count: 1
Expand All @@ -90,19 +85,9 @@ parameters:
count: 1
path: src/Command/CreateIconsCommand.php

-
message: "#^Parameter \\#4 \\$format of method SpomkyLabs\\\\PwaBundle\\\\ImageProcessor\\\\ImageProcessorInterface\\:\\:process\\(\\) expects string\\|null, mixed given\\.$#"
count: 1
path: src/Command/CreateIconsCommand.php

-
message: "#^Parameter \\#5 \\.\\.\\.\\$values of function sprintf expects bool\\|float\\|int\\|string\\|null, mixed given\\.$#"
count: 1
path: src/Command/CreateIconsCommand.php

-
message: "#^Cannot cast mixed to int\\.$#"
count: 6
count: 2
path: src/Command/CreateScreenshotCommand.php

-
Expand All @@ -125,21 +110,6 @@ parameters:
count: 3
path: src/Command/CreateScreenshotCommand.php

-
message: "#^Parameter \\#1 \\$image of method SpomkyLabs\\\\PwaBundle\\\\ImageProcessor\\\\ImageProcessorInterface\\:\\:getSizes\\(\\) expects string, string\\|false given\\.$#"
count: 1
path: src/Command/CreateScreenshotCommand.php

-
message: "#^Parameter \\#1 \\$image of method SpomkyLabs\\\\PwaBundle\\\\ImageProcessor\\\\ImageProcessorInterface\\:\\:process\\(\\) expects string, string\\|false given\\.$#"
count: 1
path: src/Command/CreateScreenshotCommand.php

-
message: "#^Parameter \\#1 \\$mimeType of method Symfony\\\\Component\\\\Mime\\\\MimeTypes\\:\\:getExtensions\\(\\) expects string, string\\|null given\\.$#"
count: 1
path: src/Command/CreateScreenshotCommand.php

-
message: "#^Parameter \\#2 \\$uri of method Symfony\\\\Component\\\\Panther\\\\Client\\:\\:request\\(\\) expects string, mixed given\\.$#"
count: 1
Expand All @@ -150,11 +120,6 @@ parameters:
count: 1
path: src/Command/CreateScreenshotCommand.php

-
message: "#^Parameter \\#4 \\$format of method SpomkyLabs\\\\PwaBundle\\\\ImageProcessor\\\\ImageProcessorInterface\\:\\:process\\(\\) expects string\\|null, mixed given\\.$#"
count: 1
path: src/Command/CreateScreenshotCommand.php

-
message: "#^Class SpomkyLabs\\\\PwaBundle\\\\Dto\\\\BackgroundSync has an uninitialized property \\$forceSyncFallback\\. Give it default value or assign it in the constructor\\.$#"
count: 1
Expand Down Expand Up @@ -446,34 +411,19 @@ parameters:
path: src/ImageProcessor/GDImageProcessor.php

-
message: "#^Parameter \\#1 \\$dst_image of function imagecopyresampled expects GdImage, GdImage\\|false given\\.$#"
count: 1
path: src/ImageProcessor/GDImageProcessor.php

-
message: "#^Parameter \\#1 \\$image of function imagealphablending expects GdImage, GdImage\\|false given\\.$#"
count: 1
path: src/ImageProcessor/GDImageProcessor.php

-
message: "#^Parameter \\#1 \\$image of function imagepng expects GdImage, GdImage\\|false given\\.$#"
count: 1
path: src/ImageProcessor/GDImageProcessor.php

-
message: "#^Parameter \\#1 \\$image of function imagesavealpha expects GdImage, GdImage\\|false given\\.$#"
count: 2
message: "#^Should not use node with type \"Stmt_Echo\", please change the code\\.$#"
count: 3
path: src/ImageProcessor/GDImageProcessor.php

-
message: "#^Parameter \\#1 \\$image of function imagesx expects GdImage, GdImage\\|false given\\.$#"
message: "#^Since spomky\\-labs/pwa\\-bundle 1\\.2\\.0\\: The \"format\", \"width\" and \"height\" parameters are deprecated and will be removed in 2\\.0\\.0\\. Please use \"configuration\" instead\\.\\.$#"
count: 1
path: src/ImageProcessor/GDImageProcessor.php

-
message: "#^Parameter \\#1 \\$image of function imagesy expects GdImage, GdImage\\|false given\\.$#"
message: "#^Since spomky\\-labs/pwa\\-bundle 1\\.2\\.0\\: The \"format\", \"width\" and \"height\" parameters are deprecated and will be removed in 2\\.0\\.0\\. Please use \"configuration\" instead\\.\\.$#"
count: 1
path: src/ImageProcessor/GDImageProcessor.php
path: src/ImageProcessor/ImagickImageProcessor.php

-
message: "#^PHPDoc tag @return with type array\\<string, string\\> is incompatible with native type string\\.$#"
Expand Down Expand Up @@ -630,6 +580,11 @@ parameters:
count: 1
path: src/Resources/config/definition/web_client.php

-
message: "#^Cannot call method process\\(\\) on SpomkyLabs\\\\PwaBundle\\\\ImageProcessor\\\\ImageProcessorInterface\\|null\\.$#"
count: 1
path: src/Service/FaviconsCompiler.php

-
message: "#^Parameter \\#2 \\.\\.\\.\\$values of function sprintf expects bool\\|float\\|int\\|string\\|null, mixed given\\.$#"
count: 1
Expand All @@ -643,4 +598,4 @@ parameters:
-
message: "#^Method SpomkyLabs\\\\PwaBundle\\\\SpomkyLabsPwaBundle\\:\\:loadExtension\\(\\) has parameter \\$config with no value type specified in iterable type array\\.$#"
count: 1
path: src/SpomkyLabsPwaBundle.php
path: src/SpomkyLabsPwaBundle.php
4 changes: 3 additions & 1 deletion rector.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
PHPUnitSetList::ANNOTATIONS_TO_ATTRIBUTES,
]);
$config->phpVersion(PhpVersion::PHP_82);
$config->paths([__DIR__ . '/src', __DIR__ . '/tests', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']);
$config->paths(
[__DIR__ . '/src', __DIR__ . '/tests', __DIR__ . '/castor.php', __DIR__ . '/ecs.php', __DIR__ . '/rector.php']
);
$config->skip([
RemoveEmptyClassMethodRector::class => [__DIR__ . '/tests/Controller/'],
]);
Expand Down
29 changes: 9 additions & 20 deletions src/Command/CreateIconsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace SpomkyLabs\PwaBundle\Command;

use SpomkyLabs\PwaBundle\ImageProcessor\Configuration;
use SpomkyLabs\PwaBundle\ImageProcessor\ImageProcessorInterface;
use Symfony\Component\AssetMapper\AssetMapperInterface;
use Symfony\Component\Console\Attribute\AsCommand;
Expand All @@ -17,7 +18,6 @@
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Mime\MimeTypes;
use Symfony\Component\Yaml\Yaml;
use function count;
use function is_string;

#[AsCommand(name: 'pwa:create:icons', description: 'Generate icons for your PWA')]
Expand Down Expand Up @@ -60,7 +60,7 @@ protected function configure(): void
'f',
InputOption::VALUE_OPTIONAL,
'The format of the icons',
null,
'png',
['png', 'jpg', 'webp']
);
$this->addArgument(
Expand All @@ -84,6 +84,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$dest = rtrim((string) $input->getOption('output'), '/');
$filename = $input->getOption('filename');
$format = $input->getOption('format');
if (! is_string($format)) {
$io->error('The format must be a string.');
return self::FAILURE;
}
$sizes = $input->getArgument('sizes');

$sourcePath = $this->getSourcePath($source);
Expand All @@ -97,36 +101,21 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$this->filesystem->mkdir($dest);
}

$mime = MimeTypes::getDefault();
if ($format === null) {
$mimeType = $mime->guessMimeType($sourcePath);
$extensions = $mime->getExtensions($mimeType);
if (count($extensions) === 0) {
$io->error(sprintf('Unable to guess the extension for the mime type "%s".', $mimeType));
return self::FAILURE;
}
$format = current($extensions);
}

$generatedIcons = [];
foreach ($sizes as $size) {
$size = (int) $size;
$outputSize = $size === 0 ? 'any' : sprintf('%sx%s', $size, $size);
$io->info(sprintf('Processing icon %s', $outputSize));
$tmp = $this->imageProcessor->process(
file_get_contents($sourcePath),
$size === 0 ? null : $size,
$size === 0 ? null : $size,
$format
);
$configuration = Configuration::create($size, $size, $format);
$tmp = $this->imageProcessor->process(file_get_contents($sourcePath), null, null, null, $configuration);
$filePath = sprintf('%s/%s-%s.%s', $dest, $filename, $outputSize, $format);
$this->filesystem->dumpFile($filePath, $tmp);
$asset = $this->assetMapper->getAssetFromSourcePath($filePath);
$config = [
'src' => $asset === null ? $filePath : $asset->logicalPath,
'sizes' => [$size],
];
$destMimeType = $mime->guessMimeType($filePath);
$destMimeType = MimeTypes::getDefault()->guessMimeType($filePath);
if ($destMimeType !== null) {
$config['type'] = $destMimeType;
}
Expand Down
56 changes: 28 additions & 28 deletions src/Command/CreateScreenshotCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace SpomkyLabs\PwaBundle\Command;

use Facebook\WebDriver\WebDriverDimension;
use SpomkyLabs\PwaBundle\ImageProcessor\Configuration;
use SpomkyLabs\PwaBundle\ImageProcessor\ImageProcessorInterface;
use Symfony\Component\AssetMapper\AssetMapperInterface;
use Symfony\Component\Console\Attribute\AsCommand;
Expand All @@ -21,7 +22,8 @@
use Symfony\Component\Yaml\Yaml;
use Throwable;
use function assert;
use function count;
use function is_int;
use function is_string;

#[AsCommand(
name: 'pwa:create:screenshot',
Expand Down Expand Up @@ -72,7 +74,7 @@ protected function configure(): void
'f',
InputOption::VALUE_OPTIONAL,
'The format of the screenshots',
null,
'png',
['png', 'jpg', 'webp']
);
}
Expand All @@ -91,20 +93,30 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$height = $input->getOption('height');
$width = $input->getOption('width');
$format = $input->getOption('format');
if (! is_string($format)) {
$io->error('The format must be defined.');
return self::FAILURE;
}

$client = $this->getClient();
$crawler = $client->request('GET', $url);

$tmpName = $this->filesystem
->tempnam('', 'pwa-');
if ($width !== null xor $height !== null) {
$io->error('If you define a width, you must define a height.');
return self::FAILURE;
}
if ($width !== null && $height !== null) {
if ($width < 0 || $height < 0) {
$width = (int) $width;
$height = (int) $height;
if ($width <= 0 || $height <= 0) {
$io->error('Width and height must be positive integers.');
return self::FAILURE;
}
$client->manage()
->window()
->setSize(new WebDriverDimension((int) $width, (int) $height));
->setSize(new WebDriverDimension($width, $height));
}
$client->manage()
->window()
Expand All @@ -118,38 +130,26 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$title = null;
}

if ($format !== null) {
$data = $this->imageProcessor->process(file_get_contents($tmpName), null, null, $format);
file_put_contents($tmpName, $data);
}
if ($width === null || $height === null) {
['width' => $width, 'height' => $height] = $this->imageProcessor->getSizes(file_get_contents($tmpName));
}

$mime = MimeTypes::getDefault();
$mimeType = $mime->guessMimeType($tmpName);
$extensions = $mime->getExtensions($mimeType);
if (count($extensions) === 0) {
$io->error(sprintf('Unable to guess the extension for the mime type "%s".', $mimeType));
return self::FAILURE;
}
$sizes = '';
if ($width !== null && $height !== null) {
$sizes = sprintf('-%dx%d', (int) $width, (int) $height);
}
$data = file_get_contents($tmpName);
assert(is_string($data));
['width' => $width, 'height' => $height] = $this->imageProcessor->getSizes($data);
assert(is_int($width));
assert(is_int($height));
$configuration = Configuration::create($width, $height, $format);
$data = $this->imageProcessor->process($data, null, null, null, $configuration);
file_put_contents($tmpName, $data);

$format = current($extensions);
$filename = sprintf('%s/%s%s.%s', $dest, $input->getOption('filename'), $sizes, $format);
$filename = sprintf('%s/%s-%dx%d.%s', $dest, $input->getOption('filename'), $width, $height, $format);

$this->filesystem->copy($tmpName, $filename, true);
$this->filesystem->remove($tmpName);
$asset = $this->assetMapper->getAssetFromSourcePath($filename);
$outputMimeType = $mime->guessMimeType($filename);
$outputMimeType = MimeTypes::getDefault()->guessMimeType($filename);

$config = [
'src' => $asset === null ? $filename : $asset->logicalPath,
'width' => (int) $width,
'height' => (int) $height,
'width' => $width,
'height' => $height,
'reference' => $url,
];
if ($outputMimeType !== null) {
Expand Down
Loading

0 comments on commit 27059f7

Please sign in to comment.