From 74b5856d275d20a52ba055ab126e28cb50074ee0 Mon Sep 17 00:00:00 2001 From: Ivan Moroz Date: Mon, 1 Jun 2026 21:21:32 +0300 Subject: [PATCH] Phase 9 review fixes: source guards, shared quality trait, dompdf Blade corrections Co-Authored-By: Claude Sonnet 4.6 --- .../Image/Concerns/RendersSingleImagePdf.php | 42 ++++++++++++++----- .../Image/Concerns/ResolvesImageQuality.php | 19 +++++++++ .../Drivers/Image/JpgToPngDriver.php | 13 ++++-- .../Drivers/Image/JpgToWebpDriver.php | 27 ++++++------ .../Drivers/Image/PngToJpgDriver.php | 27 ++++++------ .../Drivers/Image/PngToWebpDriver.php | 27 ++++++------ resources/views/pdf/single-image.blade.php | 41 +++++++----------- .../Conversion/Drivers/JpgToPdfDriverTest.php | 1 - .../Conversion/Drivers/JpgToPngDriverTest.php | 1 - .../Drivers/JpgToWebpDriverTest.php | 1 - .../Conversion/Drivers/PngToJpgDriverTest.php | 1 - .../Conversion/Drivers/PngToPdfDriverTest.php | 1 - .../Drivers/PngToWebpDriverTest.php | 1 - tests/Support/ConversionContextFactory.php | 1 - 14 files changed, 115 insertions(+), 88 deletions(-) create mode 100644 app/Conversion/Drivers/Image/Concerns/ResolvesImageQuality.php diff --git a/app/Conversion/Drivers/Image/Concerns/RendersSingleImagePdf.php b/app/Conversion/Drivers/Image/Concerns/RendersSingleImagePdf.php index c21db3d..31f3251 100644 --- a/app/Conversion/Drivers/Image/Concerns/RendersSingleImagePdf.php +++ b/app/Conversion/Drivers/Image/Concerns/RendersSingleImagePdf.php @@ -8,15 +8,26 @@ use App\Support\Conversions\DTO\ConversionResult; use Barryvdh\DomPDF\Facade\Pdf; use Illuminate\Support\Facades\Storage; +use Intervention\Image\Drivers\Gd\Driver; +use Intervention\Image\ImageManager; +use RuntimeException; trait RendersSingleImagePdf { private function renderPdf(ConversionContext $context, string $imageMimeType): ConversionResult { - $sourceContent = Storage::disk('local')->get($context->sourceFile->stored_path); + $storedPath = $context->sourceFile->stored_path; + + if (! Storage::disk('local')->exists($storedPath)) { + throw new RuntimeException( + "RendersSingleImagePdf: source file not found at [{$storedPath}]." + ); + } + + $sourceContent = Storage::disk('local')->get($storedPath); $imageDataUri = "data:{$imageMimeType};base64,".base64_encode($sourceContent); - $pageSize = $context->options['page_size'] ?? 'a4'; + $pageSize = $this->resolvePageSize($context->options['page_size'] ?? 'a4'); $orientation = $this->resolveOrientation($context->options['orientation'] ?? 'auto', $sourceContent); $margin = $this->resolveMargin($context->options['margin'] ?? 'small'); $fitMode = $context->options['fit_mode'] ?? 'contain'; @@ -37,22 +48,33 @@ private function renderPdf(ConversionContext $context, string $imageMimeType): C ); } + private function resolvePageSize(string $pageSize): string + { + if ($pageSize === 'auto' || $pageSize === '') { + return 'a4'; + } + + return $pageSize; + } + private function resolveOrientation(string $orientation, string $imageContent): string { if ($orientation !== 'auto') { return $orientation; } - $image = imagecreatefromstring($imageContent); - if ($image === false) { - return 'portrait'; - } + $manager = new ImageManager(new Driver); - $width = imagesx($image); - $height = imagesy($image); - imagedestroy($image); + try { + $image = $manager->decodeBinary($imageContent); + } catch (\Throwable $e) { + throw new RuntimeException( + "RendersSingleImagePdf: could not decode image binary to determine orientation. {$e->getMessage()}", + previous: $e, + ); + } - return $width > $height ? 'landscape' : 'portrait'; + return $image->width() > $image->height() ? 'landscape' : 'portrait'; } private function resolveMargin(string $margin): string diff --git a/app/Conversion/Drivers/Image/Concerns/ResolvesImageQuality.php b/app/Conversion/Drivers/Image/Concerns/ResolvesImageQuality.php new file mode 100644 index 0000000..d23cf44 --- /dev/null +++ b/app/Conversion/Drivers/Image/Concerns/ResolvesImageQuality.php @@ -0,0 +1,19 @@ + 60, + 'medium' => 75, + 'high' => 90, + 'max' => 100, + default => 85, + }; + } +} diff --git a/app/Conversion/Drivers/Image/JpgToPngDriver.php b/app/Conversion/Drivers/Image/JpgToPngDriver.php index 4885fb5..64b6049 100644 --- a/app/Conversion/Drivers/Image/JpgToPngDriver.php +++ b/app/Conversion/Drivers/Image/JpgToPngDriver.php @@ -10,6 +10,7 @@ use Illuminate\Support\Facades\Storage; use Intervention\Image\Drivers\Gd\Driver; use Intervention\Image\ImageManager; +use RuntimeException; final class JpgToPngDriver implements ConverterDriver { @@ -20,10 +21,16 @@ public function key(): string public function convert(ConversionContext $context): ConversionResult { - $manager = new ImageManager(new Driver); + $storedPath = $context->sourceFile->stored_path; + + if (! Storage::disk('local')->exists($storedPath)) { + throw new RuntimeException( + "JpgToPngDriver: source file not found at [{$storedPath}]." + ); + } - $sourcePath = Storage::disk('local')->path($context->sourceFile->stored_path); - $image = $manager->decodePath($sourcePath); + $manager = new ImageManager(new Driver); + $image = $manager->decodePath(Storage::disk('local')->path($storedPath)); $encoded = $image->encodeUsingFileExtension('png'); diff --git a/app/Conversion/Drivers/Image/JpgToWebpDriver.php b/app/Conversion/Drivers/Image/JpgToWebpDriver.php index 455bba0..3064ba9 100644 --- a/app/Conversion/Drivers/Image/JpgToWebpDriver.php +++ b/app/Conversion/Drivers/Image/JpgToWebpDriver.php @@ -4,15 +4,19 @@ namespace App\Conversion\Drivers\Image; +use App\Conversion\Drivers\Image\Concerns\ResolvesImageQuality; use App\Support\Conversions\Contracts\ConverterDriver; use App\Support\Conversions\DTO\ConversionContext; use App\Support\Conversions\DTO\ConversionResult; use Illuminate\Support\Facades\Storage; use Intervention\Image\Drivers\Gd\Driver; use Intervention\Image\ImageManager; +use RuntimeException; final class JpgToWebpDriver implements ConverterDriver { + use ResolvesImageQuality; + public function key(): string { return 'jpg_to_webp'; @@ -20,10 +24,16 @@ public function key(): string public function convert(ConversionContext $context): ConversionResult { - $manager = new ImageManager(new Driver); + $storedPath = $context->sourceFile->stored_path; - $sourcePath = Storage::disk('local')->path($context->sourceFile->stored_path); - $image = $manager->decodePath($sourcePath); + if (! Storage::disk('local')->exists($storedPath)) { + throw new RuntimeException( + "JpgToWebpDriver: source file not found at [{$storedPath}]." + ); + } + + $manager = new ImageManager(new Driver); + $image = $manager->decodePath(Storage::disk('local')->path($storedPath)); $quality = $this->resolveQuality($context->options['quality'] ?? 'high'); @@ -40,15 +50,4 @@ public function convert(ConversionContext $context): ConversionResult sizeBytes: Storage::disk('local')->size($outputPath), ); } - - private function resolveQuality(mixed $quality): int - { - return match ($quality) { - 'low' => 60, - 'medium' => 75, - 'high' => 90, - 'max' => 100, - default => 85, - }; - } } diff --git a/app/Conversion/Drivers/Image/PngToJpgDriver.php b/app/Conversion/Drivers/Image/PngToJpgDriver.php index 3a50876..0995c48 100644 --- a/app/Conversion/Drivers/Image/PngToJpgDriver.php +++ b/app/Conversion/Drivers/Image/PngToJpgDriver.php @@ -4,15 +4,19 @@ namespace App\Conversion\Drivers\Image; +use App\Conversion\Drivers\Image\Concerns\ResolvesImageQuality; use App\Support\Conversions\Contracts\ConverterDriver; use App\Support\Conversions\DTO\ConversionContext; use App\Support\Conversions\DTO\ConversionResult; use Illuminate\Support\Facades\Storage; use Intervention\Image\Drivers\Gd\Driver; use Intervention\Image\ImageManager; +use RuntimeException; final class PngToJpgDriver implements ConverterDriver { + use ResolvesImageQuality; + public function key(): string { return 'png_to_jpg'; @@ -20,10 +24,16 @@ public function key(): string public function convert(ConversionContext $context): ConversionResult { - $manager = new ImageManager(new Driver); + $storedPath = $context->sourceFile->stored_path; - $sourcePath = Storage::disk('local')->path($context->sourceFile->stored_path); - $image = $manager->decodePath($sourcePath); + if (! Storage::disk('local')->exists($storedPath)) { + throw new RuntimeException( + "PngToJpgDriver: source file not found at [{$storedPath}]." + ); + } + + $manager = new ImageManager(new Driver); + $image = $manager->decodePath(Storage::disk('local')->path($storedPath)); $background = $context->options['background'] ?? '#ffffff'; $quality = $this->resolveQuality($context->options['quality'] ?? 'high'); @@ -43,15 +53,4 @@ public function convert(ConversionContext $context): ConversionResult sizeBytes: Storage::disk('local')->size($outputPath), ); } - - private function resolveQuality(mixed $quality): int - { - return match ($quality) { - 'low' => 60, - 'medium' => 75, - 'high' => 90, - 'max' => 100, - default => 85, - }; - } } diff --git a/app/Conversion/Drivers/Image/PngToWebpDriver.php b/app/Conversion/Drivers/Image/PngToWebpDriver.php index a5efb8e..348258d 100644 --- a/app/Conversion/Drivers/Image/PngToWebpDriver.php +++ b/app/Conversion/Drivers/Image/PngToWebpDriver.php @@ -4,15 +4,19 @@ namespace App\Conversion\Drivers\Image; +use App\Conversion\Drivers\Image\Concerns\ResolvesImageQuality; use App\Support\Conversions\Contracts\ConverterDriver; use App\Support\Conversions\DTO\ConversionContext; use App\Support\Conversions\DTO\ConversionResult; use Illuminate\Support\Facades\Storage; use Intervention\Image\Drivers\Gd\Driver; use Intervention\Image\ImageManager; +use RuntimeException; final class PngToWebpDriver implements ConverterDriver { + use ResolvesImageQuality; + public function key(): string { return 'png_to_webp'; @@ -20,10 +24,16 @@ public function key(): string public function convert(ConversionContext $context): ConversionResult { - $manager = new ImageManager(new Driver); + $storedPath = $context->sourceFile->stored_path; - $sourcePath = Storage::disk('local')->path($context->sourceFile->stored_path); - $image = $manager->decodePath($sourcePath); + if (! Storage::disk('local')->exists($storedPath)) { + throw new RuntimeException( + "PngToWebpDriver: source file not found at [{$storedPath}]." + ); + } + + $manager = new ImageManager(new Driver); + $image = $manager->decodePath(Storage::disk('local')->path($storedPath)); $quality = $this->resolveQuality($context->options['quality'] ?? 'high'); @@ -40,15 +50,4 @@ public function convert(ConversionContext $context): ConversionResult sizeBytes: Storage::disk('local')->size($outputPath), ); } - - private function resolveQuality(mixed $quality): int - { - return match ($quality) { - 'low' => 60, - 'medium' => 75, - 'high' => 90, - 'max' => 100, - default => 85, - }; - } } diff --git a/resources/views/pdf/single-image.blade.php b/resources/views/pdf/single-image.blade.php index f92872e..83ef84d 100644 --- a/resources/views/pdf/single-image.blade.php +++ b/resources/views/pdf/single-image.blade.php @@ -3,35 +3,24 @@ -
- -
+@if ($fitMode === 'contain') +
+@else + +@endif diff --git a/tests/Feature/Conversion/Drivers/JpgToPdfDriverTest.php b/tests/Feature/Conversion/Drivers/JpgToPdfDriverTest.php index db25ab8..e537729 100644 --- a/tests/Feature/Conversion/Drivers/JpgToPdfDriverTest.php +++ b/tests/Feature/Conversion/Drivers/JpgToPdfDriverTest.php @@ -13,7 +13,6 @@ $sourcePath = ImageFixture::jpg('source.jpg', width: 600, height: 400); $context = ConversionContextFactory::forSourcePath( sourcePath: $sourcePath, - outputExtension: 'pdf', options: [ 'page_size' => 'a4', 'orientation' => 'auto', diff --git a/tests/Feature/Conversion/Drivers/JpgToPngDriverTest.php b/tests/Feature/Conversion/Drivers/JpgToPngDriverTest.php index da468bc..5bfc6ec 100644 --- a/tests/Feature/Conversion/Drivers/JpgToPngDriverTest.php +++ b/tests/Feature/Conversion/Drivers/JpgToPngDriverTest.php @@ -13,7 +13,6 @@ $sourcePath = ImageFixture::jpg('source.jpg', width: 600, height: 400); $context = ConversionContextFactory::forSourcePath( sourcePath: $sourcePath, - outputExtension: 'png', options: [ 'resize' => 'original', 'remove_metadata' => true, diff --git a/tests/Feature/Conversion/Drivers/JpgToWebpDriverTest.php b/tests/Feature/Conversion/Drivers/JpgToWebpDriverTest.php index 256f36c..5e0cd70 100644 --- a/tests/Feature/Conversion/Drivers/JpgToWebpDriverTest.php +++ b/tests/Feature/Conversion/Drivers/JpgToWebpDriverTest.php @@ -13,7 +13,6 @@ $sourcePath = ImageFixture::jpg('source.jpg', width: 600, height: 400); $context = ConversionContextFactory::forSourcePath( sourcePath: $sourcePath, - outputExtension: 'webp', options: [ 'quality' => 'high', 'resize' => 'original', diff --git a/tests/Feature/Conversion/Drivers/PngToJpgDriverTest.php b/tests/Feature/Conversion/Drivers/PngToJpgDriverTest.php index a12b7cb..6a4ba8c 100644 --- a/tests/Feature/Conversion/Drivers/PngToJpgDriverTest.php +++ b/tests/Feature/Conversion/Drivers/PngToJpgDriverTest.php @@ -13,7 +13,6 @@ $sourcePath = ImageFixture::png('source.png', width: 600, height: 400); $context = ConversionContextFactory::forSourcePath( sourcePath: $sourcePath, - outputExtension: 'jpg', options: [ 'quality' => 'high', 'background' => '#ffffff', diff --git a/tests/Feature/Conversion/Drivers/PngToPdfDriverTest.php b/tests/Feature/Conversion/Drivers/PngToPdfDriverTest.php index 3a70d20..99890b5 100644 --- a/tests/Feature/Conversion/Drivers/PngToPdfDriverTest.php +++ b/tests/Feature/Conversion/Drivers/PngToPdfDriverTest.php @@ -13,7 +13,6 @@ $sourcePath = ImageFixture::png('source.png', width: 600, height: 400); $context = ConversionContextFactory::forSourcePath( sourcePath: $sourcePath, - outputExtension: 'pdf', options: [ 'page_size' => 'a4', 'orientation' => 'auto', diff --git a/tests/Feature/Conversion/Drivers/PngToWebpDriverTest.php b/tests/Feature/Conversion/Drivers/PngToWebpDriverTest.php index 9425e7f..5615f34 100644 --- a/tests/Feature/Conversion/Drivers/PngToWebpDriverTest.php +++ b/tests/Feature/Conversion/Drivers/PngToWebpDriverTest.php @@ -13,7 +13,6 @@ $sourcePath = ImageFixture::png('source.png', width: 600, height: 400); $context = ConversionContextFactory::forSourcePath( sourcePath: $sourcePath, - outputExtension: 'webp', options: [ 'quality' => 'high', 'resize' => 'original', diff --git a/tests/Support/ConversionContextFactory.php b/tests/Support/ConversionContextFactory.php index e0ee947..d8e228e 100644 --- a/tests/Support/ConversionContextFactory.php +++ b/tests/Support/ConversionContextFactory.php @@ -16,7 +16,6 @@ final class ConversionContextFactory */ public static function forSourcePath( string $sourcePath, - string $outputExtension, array $options = [], ): ConversionContext { $sourceFile = new FileRecord;