mirror of
https://github.com/getformwork/formwork.git
synced 2025-01-30 03:47:43 +01:00
Handle SVG and animated images correctly
This commit is contained in:
parent
d9a0f08203
commit
efc52336e0
@ -202,7 +202,8 @@ final class App
|
||||
->parameter('associations.image/jpeg', [ImageFactory::class, 'make'])
|
||||
->parameter('associations.image/png', [ImageFactory::class, 'make'])
|
||||
->parameter('associations.image/webp', [ImageFactory::class, 'make'])
|
||||
->parameter('associations.image/gif', [ImageFactory::class, 'make']);
|
||||
->parameter('associations.image/gif', [ImageFactory::class, 'make'])
|
||||
->parameter('associations.image/svg+xml', [ImageFactory::class, 'make']);
|
||||
|
||||
$container->define(ImageFactory::class);
|
||||
|
||||
|
37
formwork/src/Images/Decoder/SvgDecoder.php
Normal file
37
formwork/src/Images/Decoder/SvgDecoder.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Formwork\Images\Decoder;
|
||||
|
||||
use DOMDocument;
|
||||
use DOMElement;
|
||||
use Generator;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class SvgDecoder implements DecoderInterface
|
||||
{
|
||||
public function decode(string &$data): Generator
|
||||
{
|
||||
$domDocument = new DOMDocument();
|
||||
|
||||
$domDocument->loadXML($data);
|
||||
|
||||
$root = $domDocument->documentElement;
|
||||
|
||||
if (!($root instanceof DOMElement && $root->nodeName === 'svg')) {
|
||||
throw new InvalidArgumentException('Invalid SVG data');
|
||||
}
|
||||
|
||||
if (!$root->hasAttribute('width')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$root->hasAttribute('height')) {
|
||||
return;
|
||||
}
|
||||
|
||||
yield [
|
||||
'width' => $root->getAttribute('width'),
|
||||
'height' => $root->getAttribute('height'),
|
||||
];
|
||||
}
|
||||
}
|
@ -47,6 +47,8 @@ abstract class AbstractHandler implements HandlerInterface
|
||||
*/
|
||||
abstract public function getInfo(): ImageInfo;
|
||||
|
||||
abstract public function supportsTransforms(): bool;
|
||||
|
||||
abstract public static function supportsColorProfile(): bool;
|
||||
|
||||
/**
|
||||
@ -136,6 +138,10 @@ abstract class AbstractHandler implements HandlerInterface
|
||||
|
||||
public function process(?TransformCollection $transformCollection = null, ?string $handler = null): AbstractHandler
|
||||
{
|
||||
if (!$this->supportsTransforms()) {
|
||||
throw new RuntimeException(sprintf('Image handler of type %s does not support transforms for the current image', static::class));
|
||||
}
|
||||
|
||||
$handler ??= static::class;
|
||||
|
||||
if (!is_subclass_of($handler, self::class)) {
|
||||
|
@ -62,6 +62,11 @@ class GifHandler extends AbstractHandler
|
||||
return new ImageInfo($info);
|
||||
}
|
||||
|
||||
public function supportsTransforms(): bool
|
||||
{
|
||||
return !$this->getInfo()->isAnimation();
|
||||
}
|
||||
|
||||
public static function supportsColorProfile(): bool
|
||||
{
|
||||
return false;
|
||||
|
@ -28,6 +28,8 @@ interface HandlerInterface
|
||||
*/
|
||||
public function getInfo(): ImageInfo;
|
||||
|
||||
public function supportsTransforms(): bool;
|
||||
|
||||
public static function supportsColorProfile(): bool;
|
||||
|
||||
/**
|
||||
|
@ -50,6 +50,11 @@ class JpegHandler extends AbstractHandler
|
||||
return new ImageInfo($info);
|
||||
}
|
||||
|
||||
public function supportsTransforms(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function supportsColorProfile(): bool
|
||||
{
|
||||
return true;
|
||||
|
@ -53,6 +53,11 @@ class PngHandler extends AbstractHandler
|
||||
return new ImageInfo($info);
|
||||
}
|
||||
|
||||
public function supportsTransforms(): bool
|
||||
{
|
||||
return !$this->getInfo()->isAnimation();
|
||||
}
|
||||
|
||||
public static function supportsColorProfile(): bool
|
||||
{
|
||||
return true;
|
||||
|
102
formwork/src/Images/Handler/SvgHandler.php
Normal file
102
formwork/src/Images/Handler/SvgHandler.php
Normal file
@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace Formwork\Images\Handler;
|
||||
|
||||
use Formwork\Images\ColorProfile\ColorProfile;
|
||||
use Formwork\Images\ColorProfile\ColorSpace;
|
||||
use Formwork\Images\Decoder\SvgDecoder;
|
||||
use Formwork\Images\Exif\ExifData;
|
||||
use Formwork\Images\Handler\Exceptions\UnsupportedFeatureException;
|
||||
use Formwork\Images\ImageInfo;
|
||||
use GdImage;
|
||||
|
||||
class SvgHandler extends AbstractHandler
|
||||
{
|
||||
public function getInfo(): ImageInfo
|
||||
{
|
||||
$info = [
|
||||
'mimeType' => 'image/svg+xml',
|
||||
'width' => 0,
|
||||
'height' => 0,
|
||||
'colorSpace' => ColorSpace::RGB,
|
||||
'colorDepth' => 8,
|
||||
'colorNumber' => null,
|
||||
'hasAlphaChannel' => true,
|
||||
'isAnimation' => false,
|
||||
'animationFrames' => null,
|
||||
'animationRepeatCount' => null,
|
||||
];
|
||||
|
||||
foreach ($this->decoder->decode($this->data) as $dimensions) {
|
||||
$info['width'] = (int) $dimensions['width'];
|
||||
$info['height'] = (int) $dimensions['height'];
|
||||
}
|
||||
|
||||
return new ImageInfo($info);
|
||||
}
|
||||
|
||||
public function supportsTransforms(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function supportsColorProfile(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function hasColorProfile(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getColorProfile(): ?ColorProfile
|
||||
{
|
||||
throw new UnsupportedFeatureException('SVG does not support color profiles');
|
||||
}
|
||||
|
||||
public function setColorProfile(ColorProfile $colorProfile): void
|
||||
{
|
||||
throw new UnsupportedFeatureException('SVG does not support color profiles');
|
||||
}
|
||||
|
||||
public function removeColorProfile(): void
|
||||
{
|
||||
throw new UnsupportedFeatureException('SVG does not support color profiles');
|
||||
}
|
||||
|
||||
public static function supportsExifData(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function hasExifData(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getExifData(): ?ExifData
|
||||
{
|
||||
throw new UnsupportedFeatureException('SVG does not support EXIF data');
|
||||
}
|
||||
|
||||
public function setExifData(ExifData $exifData): void
|
||||
{
|
||||
throw new UnsupportedFeatureException('SVG does not support EXIF data');
|
||||
}
|
||||
|
||||
public function removeExifData(): void
|
||||
{
|
||||
throw new UnsupportedFeatureException('SVG does not support EXIF data');
|
||||
}
|
||||
|
||||
protected function getDecoder(): SvgDecoder
|
||||
{
|
||||
return new SvgDecoder();
|
||||
}
|
||||
|
||||
protected function setDataFromGdImage(GdImage $gdImage): void
|
||||
{
|
||||
throw new UnsupportedFeatureException('SVG does not support GdImage data');
|
||||
}
|
||||
}
|
@ -80,6 +80,11 @@ class WebpHandler extends AbstractHandler
|
||||
return new ImageInfo($info);
|
||||
}
|
||||
|
||||
public function supportsTransforms(): bool
|
||||
{
|
||||
return !$this->getInfo()->isAnimation();
|
||||
}
|
||||
|
||||
public static function supportsColorProfile(): bool
|
||||
{
|
||||
return true;
|
||||
|
@ -9,6 +9,7 @@ use Formwork\Images\Handler\AbstractHandler;
|
||||
use Formwork\Images\Handler\GifHandler;
|
||||
use Formwork\Images\Handler\JpegHandler;
|
||||
use Formwork\Images\Handler\PngHandler;
|
||||
use Formwork\Images\Handler\SvgHandler;
|
||||
use Formwork\Images\Handler\WebpHandler;
|
||||
use Formwork\Images\Transform\Blur;
|
||||
use Formwork\Images\Transform\BlurMode;
|
||||
@ -73,11 +74,17 @@ class Image extends File
|
||||
if (!isset($this->mimeType)) {
|
||||
$info = getimagesize($this->path);
|
||||
|
||||
if ($info === false) {
|
||||
throw new RuntimeException('Failed to get image info');
|
||||
if ($info !== false) {
|
||||
return $this->mimeType = $info['mime'];
|
||||
}
|
||||
|
||||
$this->mimeType = $info['mime'];
|
||||
$mimeTypeFromFile = MimeType::fromFile($this->path);
|
||||
|
||||
if ($mimeTypeFromFile === 'image/svg+xml') {
|
||||
return $this->mimeType = $mimeTypeFromFile;
|
||||
}
|
||||
|
||||
throw new RuntimeException('Failed to get image info');
|
||||
}
|
||||
|
||||
return $this->mimeType;
|
||||
@ -288,7 +295,7 @@ class Image extends File
|
||||
{
|
||||
$mimeType ??= $this->mimeType();
|
||||
|
||||
if (!$forceCache && $mimeType === $this->mimeType() && $this->transforms->isEmpty()) {
|
||||
if (!$forceCache && $mimeType === $this->mimeType() && (!$this->handler()->supportsTransforms() || $this->transforms->isEmpty())) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -332,13 +339,22 @@ class Image extends File
|
||||
public function saveAs(string $path, ?string $mimeType = null): void
|
||||
{
|
||||
$handler = match ($mimeType ?? $this->mimeType()) {
|
||||
'image/jpeg' => JpegHandler::class,
|
||||
'image/png' => PngHandler::class,
|
||||
'image/gif' => GifHandler::class,
|
||||
'image/webp' => WebpHandler::class,
|
||||
default => throw new RuntimeException(sprintf('Unsupported image type %s', $mimeType))
|
||||
'image/jpeg' => JpegHandler::class,
|
||||
'image/png' => PngHandler::class,
|
||||
'image/gif' => GifHandler::class,
|
||||
'image/webp' => WebpHandler::class,
|
||||
'image/svg+xml' => SvgHandler::class,
|
||||
default => throw new RuntimeException(sprintf('Unsupported image type %s', $mimeType))
|
||||
};
|
||||
|
||||
if (!$this->handler()->supportsTransforms()) {
|
||||
if ($mimeType === $this->mimeType()) {
|
||||
$this->handler()->saveAs($path);
|
||||
return;
|
||||
}
|
||||
throw new RuntimeException(sprintf('Unsupported image conversion from %s to %s', $this->mimeType(), $mimeType));
|
||||
}
|
||||
|
||||
$this->handler()->process($this->transforms, $handler)->saveAs($path);
|
||||
}
|
||||
|
||||
@ -364,11 +380,12 @@ class Image extends File
|
||||
$mimeType ??= $this->mimeType();
|
||||
|
||||
$format = match ($mimeType) {
|
||||
'image/jpeg' => $mimeType . $this->options['jpegQuality'] . $this->options['jpegProgressive'] . $this->options['preserveColorProfile'] . $this->options['preserveExifData'],
|
||||
'image/png' => $mimeType . $this->options['pngCompression'] . $this->options['preserveColorProfile'] . $this->options['preserveExifData'],
|
||||
'image/webp' => $mimeType . $this->options['webpQuality'] . $this->options['preserveColorProfile'] . $this->options['preserveExifData'],
|
||||
'image/gif' => $mimeType . $this->options['gifColors'],
|
||||
default => throw new RuntimeException(sprintf('Unsupported image type %s', $mimeType))
|
||||
'image/jpeg' => $mimeType . $this->options['jpegQuality'] . $this->options['jpegProgressive'] . $this->options['preserveColorProfile'] . $this->options['preserveExifData'],
|
||||
'image/png' => $mimeType . $this->options['pngCompression'] . $this->options['preserveColorProfile'] . $this->options['preserveExifData'],
|
||||
'image/webp' => $mimeType . $this->options['webpQuality'] . $this->options['preserveColorProfile'] . $this->options['preserveExifData'],
|
||||
'image/gif' => $mimeType . $this->options['gifColors'],
|
||||
'image/svg+xml' => $mimeType,
|
||||
default => throw new RuntimeException(sprintf('Unsupported image type %s', $mimeType))
|
||||
};
|
||||
|
||||
return substr(hash('sha256', $this->path . $this->transforms->getSpecifier() . $format . FileSystem::lastModifiedTime($this->path)), 0, 32);
|
||||
@ -388,11 +405,12 @@ class Image extends File
|
||||
protected function getHandler(): AbstractHandler
|
||||
{
|
||||
return match ($this->mimeType()) {
|
||||
'image/jpeg' => JpegHandler::fromPath($this->path),
|
||||
'image/png' => PngHandler::fromPath($this->path),
|
||||
'image/gif' => GifHandler::fromPath($this->path),
|
||||
'image/webp' => WebpHandler::fromPath($this->path),
|
||||
default => throw new RuntimeException('Unsupported image type'),
|
||||
'image/jpeg' => JpegHandler::fromPath($this->path),
|
||||
'image/png' => PngHandler::fromPath($this->path),
|
||||
'image/gif' => GifHandler::fromPath($this->path),
|
||||
'image/webp' => WebpHandler::fromPath($this->path),
|
||||
'image/svg+xml' => SvgHandler::fromPath($this->path),
|
||||
default => throw new RuntimeException('Unsupported image type'),
|
||||
};
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user