From 8177bbbbfd80f51efbe8d9230ad00adce53abbc9 Mon Sep 17 00:00:00 2001 From: Patrick Organ Date: Sat, 25 May 2024 04:25:12 -0400 Subject: [PATCH 01/38] add DTOs and Enums --- src/DTOs/PdfPage.php | 36 ++++++++++++++++++++++++++++++++++++ src/Enums/OutputFormat.php | 11 +++++++++++ 2 files changed, 47 insertions(+) create mode 100644 src/DTOs/PdfPage.php create mode 100644 src/Enums/OutputFormat.php diff --git a/src/DTOs/PdfPage.php b/src/DTOs/PdfPage.php new file mode 100644 index 0000000..b0ff7bf --- /dev/null +++ b/src/DTOs/PdfPage.php @@ -0,0 +1,36 @@ +number = $pageNumber; + $this->format = $format; + $this->prefix = $prefix; + $this->path = $path; + } + + public static function make(int $pageNumber, OutputFormat $format, string $prefix, string $path): self + { + return new self($pageNumber, $format, $prefix, $path); + } + + public function getFilename(): string + { + $info = pathinfo($this->path); + + return $info['dirname'].DIRECTORY_SEPARATOR.$this->prefix.$info['filename'].'.'.$this->format->value; + } +} diff --git a/src/Enums/OutputFormat.php b/src/Enums/OutputFormat.php new file mode 100644 index 0000000..316bcb9 --- /dev/null +++ b/src/Enums/OutputFormat.php @@ -0,0 +1,11 @@ + Date: Sat, 25 May 2024 04:25:28 -0400 Subject: [PATCH 02/38] drop support for php < 8.2 --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 9bc04df..7759f44 100644 --- a/composer.json +++ b/composer.json @@ -19,11 +19,11 @@ } ], "require": { - "php" : "^7.2|^8.0", + "php" : "^8.2", "ext-imagick" : "*" }, "require-dev": { - "pestphp/pest": "^1.21" + "pestphp/pest": "^1.23" }, "autoload": { "psr-4": { From 2cf70b625e8f80341eb112d7bb6a96898e88993d Mon Sep 17 00:00:00 2001 From: Patrick Organ Date: Sat, 25 May 2024 04:25:41 -0400 Subject: [PATCH 03/38] add static helpers to exceptions --- src/Exceptions/PageDoesNotExist.php | 4 ++++ src/Exceptions/PdfDoesNotExist.php | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/Exceptions/PageDoesNotExist.php b/src/Exceptions/PageDoesNotExist.php index d34afed..4626817 100644 --- a/src/Exceptions/PageDoesNotExist.php +++ b/src/Exceptions/PageDoesNotExist.php @@ -6,4 +6,8 @@ class PageDoesNotExist extends Exception { + static function forPage(int $pageNumber): self + { + return new static("Page {$pageNumber} does not exist."); + } } diff --git a/src/Exceptions/PdfDoesNotExist.php b/src/Exceptions/PdfDoesNotExist.php index 1b3afc2..1d42563 100644 --- a/src/Exceptions/PdfDoesNotExist.php +++ b/src/Exceptions/PdfDoesNotExist.php @@ -6,4 +6,8 @@ class PdfDoesNotExist extends Exception { + public static function forFile(string $pdfFile): self + { + return new static("File '{$pdfFile}' does not exist."); + } } From fca8b3cc02f3537affdc9c1a06ad92bdf8701a9c Mon Sep 17 00:00:00 2001 From: Patrick Organ Date: Sat, 25 May 2024 04:25:46 -0400 Subject: [PATCH 04/38] wip --- src/Pdf.php | 146 +++++++++++++++++++++++++++++----------------- tests/PdfTest.php | 20 +++---- 2 files changed, 102 insertions(+), 64 deletions(-) diff --git a/src/Pdf.php b/src/Pdf.php index 2ac020e..449757e 100644 --- a/src/Pdf.php +++ b/src/Pdf.php @@ -3,6 +3,8 @@ namespace Spatie\PdfToImage; use Imagick; +use Spatie\PdfToImage\DTOs\PdfPage; +use Spatie\PdfToImage\Enums\OutputFormat; use Spatie\PdfToImage\Exceptions\InvalidFormat; use Spatie\PdfToImage\Exceptions\PageDoesNotExist; use Spatie\PdfToImage\Exceptions\PdfDoesNotExist; @@ -11,16 +13,14 @@ class Pdf { protected $pdfFile; - protected $resolution = 144; + protected int $resolution = 144; - protected $outputFormat = 'jpg'; + protected OutputFormat $outputFormat = OutputFormat::Jpg; - protected $page = 1; + protected array $pages = [1]; public $imagick; - protected $validOutputFormats = ['jpg', 'jpeg', 'png', 'webp']; - protected $layerMethod = Imagick::LAYERMETHOD_FLATTEN; protected $colorspace; @@ -34,7 +34,7 @@ class Pdf public function __construct(string $pdfFile) { if (! file_exists($pdfFile)) { - throw new PdfDoesNotExist("File `{$pdfFile}` does not exist"); + throw PdfDoesNotExist::forFile($pdfFile); } $this->pdfFile = $pdfFile; @@ -44,25 +44,21 @@ public function __construct(string $pdfFile) $this->imagick->readImage($this->pdfFile); } - public function setResolution(int $resolution) + public function resolution(int $dpiResolution) { - $this->resolution = $resolution; + $this->resolution = $dpiResolution; return $this; } - public function setOutputFormat(string $outputFormat) + public function format(OutputFormat $outputFormat) { - if (! $this->isValidOutputFormat($outputFormat)) { - throw new InvalidFormat("Format {$outputFormat} is not supported"); - } - $this->outputFormat = $outputFormat; return $this; } - public function getOutputFormat(): string + public function getFormat(): OutputFormat { return $this->outputFormat; } @@ -81,30 +77,47 @@ public function getOutputFormat(): string * @see https://secure.php.net/manual/en/imagick.constants.php * @see Pdf::getImageData() */ - public function setLayerMethod(?int $layerMethod) + public function mergeLayerMethod(?int $method) { - $this->layerMethod = $layerMethod; + $this->layerMethod = $method; return $this; } - public function isValidOutputFormat(string $outputFormat): bool + /** + * Expects a string or OutputFormat enum. If a string, expects the file extension of the format, + * without a leading period. + * @param string|null|OutputFormat $outputFormat + * @return bool + */ + public function isValidOutputFormat(null|string|OutputFormat $outputFormat): bool { - return in_array($outputFormat, $this->validOutputFormats); + if ($outputFormat === null) { + return false; + } + + if ($outputFormat instanceof OutputFormat) { + return true; + } + + return OutputFormat::tryFrom(strtolower($outputFormat)) !== null; } - public function setPage(int $page) + public function selectPage(int $page) { - if ($page > $this->getNumberOfPages() || $page < 1) { - throw new PageDoesNotExist("Page {$page} does not exist"); - } + return $this->selectPages($page); + } - $this->page = $page; + public function selectPages(int ...$pages) + { + $this->validatePageNumbers(...$pages); + + $this->pages = $pages; return $this; } - public function getNumberOfPages(): int + public function pageCount(): int { if ($this->numberOfPages === null) { $this->numberOfPages = $this->imagick->getNumberImages(); @@ -113,37 +126,55 @@ public function getNumberOfPages(): int return $this->numberOfPages; } - public function saveImage(string $pathToImage): bool + /** + * Saves the PDF as an image. Expects a path to save the image to, which should be + * a directory if multiple pages have been selected (otherwise the image will be overwritten). + * Returns either a string with a single filename that was written, or an array of paths to the saved images. + * @param string $pathToImage + * @param string $prefix + * @return array|string + */ + public function saveImage(string $pathToImage, string $prefix = ''): array|string { + $pages = [PdfPage::make($this->pages[0], $this->outputFormat, $prefix, $pathToImage)]; + if (is_dir($pathToImage)) { - $pathToImage = rtrim($pathToImage, '\/').DIRECTORY_SEPARATOR.$this->page.'.'.$this->outputFormat; + $pages = array_map(fn($page) => + PdfPage::make($page, $this->outputFormat, $prefix, rtrim($pathToImage, '\/').DIRECTORY_SEPARATOR.$page.'.'.$this->outputFormat->value), $this->pages); } - $imageData = $this->getImageData($pathToImage); + $result = []; + + foreach($pages as $page) { + $path = $page->getFilename(); + $imageData = $this->getImageData($path, $page->number); + + if (file_put_contents($path, $imageData) !== false) { + $result[] = $path; + } + } + + if (count($result) === 1) { + return $result[0]; + } - return file_put_contents($pathToImage, $imageData) !== false; + return $result; } - public function saveAllPagesAsImages(string $directory, string $prefix = ''): array + public function saveAllPagesAsImages(string $directory, string $prefix = ''): bool { - $numberOfPages = $this->getNumberOfPages(); + $numberOfPages = $this->pageCount(); if ($numberOfPages === 0) { - return []; + return false; } - return array_map(function ($pageNumber) use ($directory, $prefix) { - $this->setPage($pageNumber); + $this->selectPages(...range(1, $numberOfPages)); - $destination = "{$directory}/{$prefix}{$pageNumber}.{$this->outputFormat}"; - - $this->saveImage($destination); - - return $destination; - }, range(1, $numberOfPages)); + return $this->saveImage($directory, $prefix); } - public function getImageData(string $pathToImage): Imagick + public function getImageData(string $pathToImage, int $pageNumber): Imagick { /* * Reinitialize imagick because the target resolution must be set @@ -162,10 +193,10 @@ public function getImageData(string $pathToImage): Imagick } if (filter_var($this->pdfFile, FILTER_VALIDATE_URL)) { - return $this->getRemoteImageData($pathToImage); + return $this->getRemoteImageData($pathToImage, $pageNumber); } - $this->imagick->readImage(sprintf('%s[%s]', $this->pdfFile, $this->page - 1)); + $this->imagick->readImage(sprintf('%s[%s]', $this->pdfFile, $pageNumber - 1)); if (is_int($this->layerMethod)) { $this->imagick = $this->imagick->mergeImageLayers($this->layerMethod); @@ -175,19 +206,19 @@ public function getImageData(string $pathToImage): Imagick $this->imagick->thumbnailImage($this->thumbnailWidth, 0); } - $this->imagick->setFormat($this->determineOutputFormat($pathToImage)); + $this->imagick->setFormat($this->determineOutputFormat($pathToImage)->value); return $this->imagick; } - public function setColorspace(int $colorspace) + public function colorspace(int $colorspace) { $this->colorspace = $colorspace; return $this; } - public function setCompressionQuality(int $compressionQuality) + public function quality(int $compressionQuality) { $this->compressionQuality = $compressionQuality; @@ -201,35 +232,42 @@ public function width(int $thumbnailWidth) return $this; } - protected function getRemoteImageData(string $pathToImage): Imagick + protected function getRemoteImageData(string $pathToImage, int $pageNumber): Imagick { $this->imagick->readImage($this->pdfFile); - $this->imagick->setIteratorIndex($this->page - 1); + $this->imagick->setIteratorIndex($pageNumber - 1); if (is_int($this->layerMethod)) { $this->imagick = $this->imagick->mergeImageLayers($this->layerMethod); } - $this->imagick->setFormat($this->determineOutputFormat($pathToImage)); + $this->imagick->setFormat($this->determineOutputFormat($pathToImage)->value); return $this->imagick; } - protected function determineOutputFormat(string $pathToImage): string + protected function determineOutputFormat(string $pathToImage): OutputFormat { - $outputFormat = pathinfo($pathToImage, PATHINFO_EXTENSION); + $outputFormat = OutputFormat::tryFrom(pathinfo($pathToImage, PATHINFO_EXTENSION)); - if ($this->outputFormat != '') { + if (!empty($this->outputFormat)) { $outputFormat = $this->outputFormat; } - $outputFormat = strtolower($outputFormat); - if (! $this->isValidOutputFormat($outputFormat)) { - $outputFormat = 'jpg'; + $outputFormat = OutputFormat::Jpg; } return $outputFormat; } + + protected function validatePageNumbers(int ...$pageNumbers) + { + foreach($pageNumbers as $page) { + if ($page > $this->pageCount() || $page < 1) { + throw PageDoesNotExist::forPage($page); + } + } + } } diff --git a/tests/PdfTest.php b/tests/PdfTest.php index f987608..4ff6113 100644 --- a/tests/PdfTest.php +++ b/tests/PdfTest.php @@ -17,11 +17,11 @@ })->throws(PdfDoesNotExist::class); it('will throw an exception when trying to convert an invalid file type', function () { - (new Pdf($this->testFile))->setOutputFormat('bla'); + (new Pdf($this->testFile))->format('bla'); })->throws(InvalidFormat::class); it('will throw an exception when passed an invalid page number', function ($invalidPage) { - (new Pdf($this->testFile))->setPage(100); + (new Pdf($this->testFile))->selectPage(100); }) ->throws(PageDoesNotExist::class) ->with([5, 0, -1]); @@ -29,12 +29,12 @@ it('will correctly return the number of pages in pdf file', function () { $pdf = new Pdf($this->multipageTestFile); - expect($pdf->getNumberOfPages())->toEqual(3); + expect($pdf->pageCount())->toEqual(3); }); it('will accept a custom specified resolution', function () { $image = (new Pdf($this->testFile)) - ->setResolution(150) + ->resolution(150) ->getImageData('test.jpg') ->getImageResolution(); @@ -44,7 +44,7 @@ it('will convert a specified page', function () { $imagick = (new Pdf($this->multipageTestFile)) - ->setPage(2) + ->selectPage(2) ->getImageData('page-2.jpg'); expect($imagick)->toBeInstanceOf(Imagick::class); @@ -52,7 +52,7 @@ it('will accept a specified file type and convert to it', function () { $imagick = (new Pdf($this->testFile)) - ->setOutputFormat('png') + ->format('png') ->getImageData('test.png'); expect($imagick->getFormat())->toEqual('png'); @@ -61,7 +61,7 @@ it('will save an image in webp format', function () { $image = (new Pdf($this->testFile)) - ->setOutputFormat('webp') + ->format('webp') ->getImageData('test.webp'); expect($image->getFormat())->toEqual('webp'); @@ -70,8 +70,8 @@ it('can accept a layer', function () { $image = (new Pdf($this->testFile)) - ->setLayerMethod(Imagick::LAYERMETHOD_FLATTEN) - ->setResolution(72) + ->mergeLayerMethod(Imagick::LAYERMETHOD_FLATTEN) + ->resolution(72) ->getImageData('test.jpg') ->getImageResolution(); @@ -81,7 +81,7 @@ it('will set compression quality', function () { $imagick = (new Pdf($this->testFile)) - ->setCompressionQuality(99) + ->quality(99) ->getImageData('test.jpg'); expect($imagick->getCompressionQuality())->toEqual(99); From 96a728bfcf2268b8cc8ee1b6c110931229cde389 Mon Sep 17 00:00:00 2001 From: Patrick Organ Date: Tue, 28 May 2024 00:01:22 -0400 Subject: [PATCH 05/38] throw exception on invalid quality value --- src/Exceptions/InvalidQuality.php | 11 +++++++++++ src/Pdf.php | 7 ++++++- 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 src/Exceptions/InvalidQuality.php diff --git a/src/Exceptions/InvalidQuality.php b/src/Exceptions/InvalidQuality.php new file mode 100644 index 0000000..34cb072 --- /dev/null +++ b/src/Exceptions/InvalidQuality.php @@ -0,0 +1,11 @@ + 100) { + throw InvalidQuality::for($compressionQuality); + } + $this->compressionQuality = $compressionQuality; return $this; } - public function width(int $thumbnailWidth) + public function thumbnailWidth(int $thumbnailWidth) { $this->thumbnailWidth = $thumbnailWidth; From 77a6fc26c5f820348a4ecfdf248e1960df8c0d8f Mon Sep 17 00:00:00 2001 From: Patrick Organ Date: Tue, 28 May 2024 00:01:35 -0400 Subject: [PATCH 06/38] update and add unit tests --- tests/PdfTest.php | 76 ++++++++++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/tests/PdfTest.php b/tests/PdfTest.php index 4ff6113..75f5e7d 100644 --- a/tests/PdfTest.php +++ b/tests/PdfTest.php @@ -1,6 +1,5 @@ remoteFileUrl = 'https://tcd.blackboard.com/webapps/dur-browserCheck-BBLEARN/samples/sample.pdf'; }); - it('will throw an exception when try to convert a non existing file', function () { - new Pdf('pdfdoesnotexists.pdf'); + new Pdf('pdf-does-not-exist.pdf'); })->throws(PdfDoesNotExist::class); -it('will throw an exception when trying to convert an invalid file type', function () { - (new Pdf($this->testFile))->format('bla'); -})->throws(InvalidFormat::class); - it('will throw an exception when passed an invalid page number', function ($invalidPage) { - (new Pdf($this->testFile))->selectPage(100); + (new Pdf($this->testFile))->selectPage($invalidPage); }) ->throws(PageDoesNotExist::class) -->with([5, 0, -1]); +->with([100, 0, -1]); it('will correctly return the number of pages in pdf file', function () { $pdf = new Pdf($this->multipageTestFile); @@ -32,65 +26,79 @@ expect($pdf->pageCount())->toEqual(3); }); -it('will accept a custom specified resolution', function () { +it('will accept a custom specified resolution', function ($resolution) { $image = (new Pdf($this->testFile)) - ->resolution(150) - ->getImageData('test.jpg') + ->resolution($resolution) + ->getImageData('test.jpg', 1) ->getImageResolution(); - expect($image['x'])->toEqual(150); - expect($image['y'])->toEqual(150); -}); + expect($image['x'])->toEqual($resolution); + expect($image['y'])->toEqual($resolution); +}) +->with([127, 150, 166]); it('will convert a specified page', function () { $imagick = (new Pdf($this->multipageTestFile)) ->selectPage(2) - ->getImageData('page-2.jpg'); + ->getImageData('page-2.jpg', 2); expect($imagick)->toBeInstanceOf(Imagick::class); }); -it('will accept a specified file type and convert to it', function () { - $imagick = (new Pdf($this->testFile)) - ->format('png') - ->getImageData('test.png'); +it('will select multiple pages', function () { + $pdf = (new Pdf($this->multipageTestFile)) + ->selectPages(1, 3); - expect($imagick->getFormat())->toEqual('png'); - expect($imagick->getFormat())->not->toEqual('jpg'); -}); + $imagick1 = $pdf + ->getImageData('page-1.jpg', 1); -it('will save an image in webp format', function () { - $image = (new Pdf($this->testFile)) - ->format('webp') - ->getImageData('test.webp'); + $imagick2 = $pdf + ->getImageData('page-3.jpg', 3); - expect($image->getFormat())->toEqual('webp'); - expect($image)->not->toEqual('jpg'); + expect($imagick1)->toBeInstanceOf(Imagick::class); + expect($imagick2)->toBeInstanceOf(Imagick::class); }); +it('will accept an output format and convert to it', function ($format) { + $fmt = \Spatie\PdfToImage\Enums\OutputFormat::from($format); + + $imagick = (new Pdf($this->testFile)) + ->format($fmt) + ->getImageData('test.' . $fmt->value, 1); + + expect($imagick->getFormat())->toEqual($fmt->value); +}) +->with(['jpg', 'png', 'webp']); + it('can accept a layer', function () { $image = (new Pdf($this->testFile)) ->mergeLayerMethod(Imagick::LAYERMETHOD_FLATTEN) ->resolution(72) - ->getImageData('test.jpg') + ->getImageData('test.jpg', 1) ->getImageResolution(); expect($image['x'])->toEqual(72); expect($image['y'])->toEqual(72); }); -it('will set compression quality', function () { +it('will throw an exception when passed an invalid quality value', function ($quality) { + (new Pdf($this->testFile))->quality($quality); +}) +->throws(\Spatie\PdfToImage\Exceptions\InvalidQuality::class) +->with([-1, 0, 101]); + +it('will set output quality', function () { $imagick = (new Pdf($this->testFile)) ->quality(99) - ->getImageData('test.jpg'); + ->getImageData('test.jpg', 1); expect($imagick->getCompressionQuality())->toEqual(99); }); it('will create a thumbnail at specified width', function () { $imagick = (new Pdf($this->multipageTestFile)) - ->width(400) - ->getImageData('test.jpg') + ->thumbnailWidth(400) + ->getImageData('test.jpg', 1) ->getImageGeometry(); expect($imagick['width'])->toBe(400); From b4c8f04fa6809b0600b1e9a917e30a2bfa9c8605 Mon Sep 17 00:00:00 2001 From: Patrick Organ Date: Tue, 28 May 2024 00:31:13 -0400 Subject: [PATCH 07/38] wip --- src/Exceptions/PageDoesNotExist.php | 2 +- src/Exceptions/PdfDoesNotExist.php | 2 +- src/Pdf.php | 25 ++---------- tests/PdfTest.php | 62 ++++++++++++++++++++++++++++- tests/output/.gitignore | 2 + 5 files changed, 68 insertions(+), 25 deletions(-) create mode 100644 tests/output/.gitignore diff --git a/src/Exceptions/PageDoesNotExist.php b/src/Exceptions/PageDoesNotExist.php index 4626817..b2f4dfc 100644 --- a/src/Exceptions/PageDoesNotExist.php +++ b/src/Exceptions/PageDoesNotExist.php @@ -6,7 +6,7 @@ class PageDoesNotExist extends Exception { - static function forPage(int $pageNumber): self + static function for(int $pageNumber): self { return new static("Page {$pageNumber} does not exist."); } diff --git a/src/Exceptions/PdfDoesNotExist.php b/src/Exceptions/PdfDoesNotExist.php index 1d42563..dde4fbc 100644 --- a/src/Exceptions/PdfDoesNotExist.php +++ b/src/Exceptions/PdfDoesNotExist.php @@ -6,7 +6,7 @@ class PdfDoesNotExist extends Exception { - public static function forFile(string $pdfFile): self + public static function for(string $pdfFile): self { return new static("File '{$pdfFile}' does not exist."); } diff --git a/src/Pdf.php b/src/Pdf.php index de55944..36d8297 100644 --- a/src/Pdf.php +++ b/src/Pdf.php @@ -35,7 +35,7 @@ class Pdf public function __construct(string $pdfFile) { if (! file_exists($pdfFile)) { - throw PdfDoesNotExist::forFile($pdfFile); + throw PdfDoesNotExist::for($pdfFile); } $this->pdfFile = $pdfFile; @@ -162,7 +162,7 @@ public function saveImage(string $pathToImage, string $prefix = ''): array|strin return $result; } - public function saveAllPagesAsImages(string $directory, string $prefix = ''): bool + public function saveAllPagesAsImages(string $directory, string $prefix = ''): array|string { $numberOfPages = $this->pageCount(); @@ -193,10 +193,6 @@ public function getImageData(string $pathToImage, int $pageNumber): Imagick $this->imagick->setCompressionQuality($this->compressionQuality); } - if (filter_var($this->pdfFile, FILTER_VALIDATE_URL)) { - return $this->getRemoteImageData($pathToImage, $pageNumber); - } - $this->imagick->readImage(sprintf('%s[%s]', $this->pdfFile, $pageNumber - 1)); if (is_int($this->layerMethod)) { @@ -237,21 +233,6 @@ public function thumbnailWidth(int $thumbnailWidth) return $this; } - protected function getRemoteImageData(string $pathToImage, int $pageNumber): Imagick - { - $this->imagick->readImage($this->pdfFile); - - $this->imagick->setIteratorIndex($pageNumber - 1); - - if (is_int($this->layerMethod)) { - $this->imagick = $this->imagick->mergeImageLayers($this->layerMethod); - } - - $this->imagick->setFormat($this->determineOutputFormat($pathToImage)->value); - - return $this->imagick; - } - protected function determineOutputFormat(string $pathToImage): OutputFormat { $outputFormat = OutputFormat::tryFrom(pathinfo($pathToImage, PATHINFO_EXTENSION)); @@ -271,7 +252,7 @@ protected function validatePageNumbers(int ...$pageNumbers) { foreach($pageNumbers as $page) { if ($page > $this->pageCount() || $page < 1) { - throw PageDoesNotExist::forPage($page); + throw PageDoesNotExist::for($page); } } } diff --git a/tests/PdfTest.php b/tests/PdfTest.php index 75f5e7d..c0b02d3 100644 --- a/tests/PdfTest.php +++ b/tests/PdfTest.php @@ -4,10 +4,23 @@ use Spatie\PdfToImage\Exceptions\PdfDoesNotExist; use Spatie\PdfToImage\Pdf; +function unlinkAllOutputImages(string $path) { + $files = glob($path . '/*'); + + foreach ($files as $file) { + if (is_file($file) && pathinfo($file, PATHINFO_EXTENSION) !== 'gitignore') { + unlink($file); + } + } +} + beforeEach(function () { $this->testFile = __DIR__.'/files/test.pdf'; $this->multipageTestFile = __DIR__.'/files/multipage-test.pdf'; - $this->remoteFileUrl = 'https://tcd.blackboard.com/webapps/dur-browserCheck-BBLEARN/samples/sample.pdf'; +}); + +afterEach(function () { + unlinkAllOutputImages(__DIR__.'/output'); }); it('will throw an exception when try to convert a non existing file', function () { @@ -103,3 +116,50 @@ expect($imagick['width'])->toBe(400); }); + +it('checks if the provided output format string is a supported format', function ($formats, $expected) { + $pdf = (new Pdf($this->testFile)); + + foreach($formats as $format) { + expect($pdf->isValidOutputFormat($format))->toBe($expected); + } +}) +->with([ + 'supported formats' => [['jpg', 'png', 'webp'], true], + 'unsupported formats' => [['bmp', 'gif', ''], false], +]); + +it('saves a page to an image', function() { + $targetFilename = __DIR__.'/output/test.jpg'; + + (new Pdf($this->testFile)) + ->selectPage(1) + ->saveImage($targetFilename); + + expect(file_exists($targetFilename))->toBeTrue(); +}); + +it('saves only selected pages to images', function() { + $targetPath = __DIR__.'/output'; + + (new Pdf($this->multipageTestFile)) + ->selectPages(1, 3) + ->format(\Spatie\PdfToImage\Enums\OutputFormat::Png) + ->saveImage($targetPath); + + foreach ([1, 3] as $pageNumber) { + expect(file_exists($targetPath . '/' . $pageNumber . '.png'))->toBeTrue(); + } +}); + +it('saves all pages to images', function() { + $targetPath = __DIR__.'/output'; + + (new Pdf($this->multipageTestFile)) + ->format(\Spatie\PdfToImage\Enums\OutputFormat::Jpg) + ->saveAllPagesAsImages($targetPath); + + foreach (range(1, 3) as $pageNumber) { + expect(file_exists($targetPath . '/' . $pageNumber . '.jpg'))->toBeTrue(); + } +}); diff --git a/tests/output/.gitignore b/tests/output/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/tests/output/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore From f4c5ac49b722edb66e587a082e3159ff626c7cab Mon Sep 17 00:00:00 2001 From: Patrick Organ Date: Tue, 28 May 2024 00:31:47 -0400 Subject: [PATCH 08/38] remove unneeded exception --- src/Exceptions/InvalidFormat.php | 9 --------- src/Pdf.php | 1 - 2 files changed, 10 deletions(-) delete mode 100644 src/Exceptions/InvalidFormat.php diff --git a/src/Exceptions/InvalidFormat.php b/src/Exceptions/InvalidFormat.php deleted file mode 100644 index be68c71..0000000 --- a/src/Exceptions/InvalidFormat.php +++ /dev/null @@ -1,9 +0,0 @@ - Date: Tue, 28 May 2024 00:46:28 -0400 Subject: [PATCH 09/38] add LayerMethod enum and support --- src/Enums/LayerMethod.php | 32 +++++++++++++++++++++++++++ src/Exceptions/InvalidLayerMethod.php | 4 ++++ src/Pdf.php | 29 +++++++++++++++--------- tests/PdfTest.php | 12 +++++++--- 4 files changed, 63 insertions(+), 14 deletions(-) create mode 100644 src/Enums/LayerMethod.php diff --git a/src/Enums/LayerMethod.php b/src/Enums/LayerMethod.php new file mode 100644 index 0000000..922f8a9 --- /dev/null +++ b/src/Enums/LayerMethod.php @@ -0,0 +1,32 @@ +layerMethod = $method; return $this; @@ -194,8 +201,8 @@ public function getImageData(string $pathToImage, int $pageNumber): Imagick $this->imagick->readImage(sprintf('%s[%s]', $this->pdfFile, $pageNumber - 1)); - if (is_int($this->layerMethod)) { - $this->imagick = $this->imagick->mergeImageLayers($this->layerMethod); + if ($this->layerMethod !== LayerMethod::None) { + $this->imagick = $this->imagick->mergeImageLayers($this->layerMethod->value); } if ($this->thumbnailWidth !== null) { diff --git a/tests/PdfTest.php b/tests/PdfTest.php index c0b02d3..b0bc18d 100644 --- a/tests/PdfTest.php +++ b/tests/PdfTest.php @@ -1,5 +1,6 @@ throws(PdfDoesNotExist::class); @@ -33,7 +34,7 @@ function unlinkAllOutputImages(string $path) { ->throws(PageDoesNotExist::class) ->with([100, 0, -1]); -it('will correctly return the number of pages in pdf file', function () { +it('will correctly return the number of pages in a pdf file', function () { $pdf = new Pdf($this->multipageTestFile); expect($pdf->pageCount())->toEqual(3); @@ -85,7 +86,7 @@ function unlinkAllOutputImages(string $path) { it('can accept a layer', function () { $image = (new Pdf($this->testFile)) - ->mergeLayerMethod(Imagick::LAYERMETHOD_FLATTEN) + ->layerMethod(\Spatie\PdfToImage\Enums\LayerMethod::Flatten) ->resolution(72) ->getImageData('test.jpg', 1) ->getImageResolution(); @@ -94,6 +95,11 @@ function unlinkAllOutputImages(string $path) { expect($image['y'])->toEqual(72); }); +it('throws an error when passed an invalid layer method', function () { + (new Pdf($this->testFile))->layerMethod(-100); +}) +->throws(InvalidLayerMethod::class); + it('will throw an exception when passed an invalid quality value', function ($quality) { (new Pdf($this->testFile))->quality($quality); }) From 10f758b4b27af3e7c406d3f569a5ed55439a497a Mon Sep 17 00:00:00 2001 From: Patrick Organ Date: Tue, 28 May 2024 00:57:12 -0400 Subject: [PATCH 10/38] add thumbnailSize() method, exception, and tests --- src/Exceptions/InvalidThumbnailSize.php | 16 +++++++++++++ src/Pdf.php | 29 ++++++++++++++++++++--- tests/PdfTest.php | 31 +++++++++++++++++++++---- 3 files changed, 68 insertions(+), 8 deletions(-) create mode 100644 src/Exceptions/InvalidThumbnailSize.php diff --git a/src/Exceptions/InvalidThumbnailSize.php b/src/Exceptions/InvalidThumbnailSize.php new file mode 100644 index 0000000..9c828cb --- /dev/null +++ b/src/Exceptions/InvalidThumbnailSize.php @@ -0,0 +1,16 @@ +thumbnailWidth !== null) { - $this->imagick->thumbnailImage($this->thumbnailWidth, 0); + $this->imagick->thumbnailImage($this->thumbnailWidth, $this->thumbnailHeight ?? 0); } $this->imagick->setFormat($this->determineOutputFormat($pathToImage)->value); @@ -232,9 +235,29 @@ public function quality(int $compressionQuality) return $this; } - public function thumbnailWidth(int $thumbnailWidth) + /** + * Set the thumbnail size for the image. If no height is provided, the thumbnail height will + * be scaled according to the width. + * + * @param int $width + * @param int $height + * + * @throws \Spatie\PdfToImage\Exceptions\InvalidThumbnailSize + * + * @return $this + */ + public function thumbnailSize(int $width, int|null $height = null) { - $this->thumbnailWidth = $thumbnailWidth; + if ($width < 0) { + throw InvalidThumbnailSize::forWidth($width); + } + + if ($height !== null && $height < 0) { + throw InvalidThumbnailSize::forHeight($height); + } + + $this->thumbnailWidth = $width; + $this->thumbnailHeight = $height ?? 0; return $this; } diff --git a/tests/PdfTest.php b/tests/PdfTest.php index b0bc18d..1d76697 100644 --- a/tests/PdfTest.php +++ b/tests/PdfTest.php @@ -114,15 +114,36 @@ function unlinkAllOutputImages(string $path) { expect($imagick->getCompressionQuality())->toEqual(99); }); -it('will create a thumbnail at specified width', function () { - $imagick = (new Pdf($this->multipageTestFile)) - ->thumbnailWidth(400) - ->getImageData('test.jpg', 1) - ->getImageGeometry(); +it('will create a thumbnail at specified sizes', function () { + $pdf = (new Pdf($this->multipageTestFile)); + + $imagick = $pdf + ->thumbnailSize(400) + ->getImageData('test.jpg', 1) + ->getImageGeometry(); expect($imagick['width'])->toBe(400); + expect($imagick['height'])->toBeGreaterThan(50); + + $imagick = $pdf + ->thumbnailSize(200, 300) + ->getImageData('test.jpg', 1) + ->getImageGeometry(); + + expect($imagick['width'])->toBe(200); + expect($imagick['height'])->toBe(300); }); +it('will throw an exception when passed an invalid thumbnail size', function ($width, $height) { + (new Pdf($this->testFile))->thumbnailSize($width, $height); +}) +->throws(\Spatie\PdfToImage\Exceptions\InvalidThumbnailSize::class) +->with([ + 'invalid width' => [-1, 100], + 'invalid height' => [100, -1], + 'invalid size' => [-1, -1], +]); + it('checks if the provided output format string is a supported format', function ($formats, $expected) { $pdf = (new Pdf($this->testFile)); From a5901bcbb061cc18f634d013f74d42109460e8ff Mon Sep 17 00:00:00 2001 From: Patrick Organ Date: Tue, 28 May 2024 01:15:42 -0400 Subject: [PATCH 11/38] add unit tests --- tests/PdfTest.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/PdfTest.php b/tests/PdfTest.php index 1d76697..08ca1ad 100644 --- a/tests/PdfTest.php +++ b/tests/PdfTest.php @@ -84,6 +84,14 @@ function unlinkAllOutputImages(string $path) { }) ->with(['jpg', 'png', 'webp']); +it('gets the output format', function () { + $pdf = new Pdf($this->testFile); + expect($pdf->getFormat()->value)->toEqual('jpg'); + + $pdf->format(\Spatie\PdfToImage\Enums\OutputFormat::Png); + expect($pdf->getFormat()->value)->toEqual('png'); +}); + it('can accept a layer', function () { $image = (new Pdf($this->testFile)) ->layerMethod(\Spatie\PdfToImage\Enums\LayerMethod::Flatten) From 1681c2adb30ccf01c5527e785ff5b8dfd41b7e89 Mon Sep 17 00:00:00 2001 From: Patrick Organ Date: Tue, 28 May 2024 01:24:03 -0400 Subject: [PATCH 12/38] update readme --- README.md | 120 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 86 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 214eb3d..13acbf3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Convert a pdf to an image +# Convert a PDF to an image [![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/pdf-to-image.svg?style=flat-square)](https://packagist.org/packages/spatie/pdf-to-image) [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](.github/LICENSE.md) @@ -6,7 +6,7 @@ [![StyleCI](https://styleci.io/repos/38419604/shield?branch=master)](https://styleci.io/repos/38419604) [![Total Downloads](https://img.shields.io/packagist/dt/spatie/pdf-to-image.svg?style=flat-square)](https://packagist.org/packages/spatie/pdf-to-image) -This package provides an easy to work with class to convert PDF's to images. +This package provides an easy-to-work-with class to convert a PDF to one or more image. Spatie is a webdesign agency in Antwerp, Belgium. You'll find an overview of all our open source projects [on our website](https://spatie.be/opensource). @@ -20,58 +20,107 @@ We highly appreciate you sending us a postcard from your hometown, mentioning wh ## Requirements -You should have [Imagick](http://php.net/manual/en/imagick.setresolution.php) and [Ghostscript](http://www.ghostscript.com/) installed. See [issues regarding Ghostscript](#issues-regarding-ghostscript). +You should have [Imagick](http://php.net/manual/en/imagick.setresolution.php) and [Ghostscript](http://www.ghostscript.com/) installed. +See [issues regarding Ghostscript](#issues-regarding-ghostscript) and [Imagick Issues](#imagick-issues) for more information. ## Installation -The package can be installed via composer: -``` bash +The package can be installed via composer and requires PHP 8.2+.: + +```bash composer require spatie/pdf-to-image ``` ## Usage -Converting a pdf to an image is easy. +Converting a PDF to an image is easy. ```php -$pdf = new Spatie\PdfToImage\Pdf($pathToPdf); +$pdf = new \Spatie\PdfToImage\Pdf($pathToPdf); $pdf->saveImage($pathToWhereImageShouldBeStored); ``` -If the path you pass to `saveImage` has the extensions `jpg`, `jpeg`, or `png` the image will be saved in that format. -Otherwise the output will be a jpg. +If the filename you pass to `saveImage` has the extensions `jpg`, `jpeg`, `png`, or `webp` the image will be saved in that format; otherwise the output format will be `jpg`. ## Other methods -You can get the total number of pages in the pdf: +Get the total number of pages in the pdf: + +```php +/** @var int $numberOfPages */ +$numberOfPages = $pdf->pageCount(); +``` + +Check if a file type is a supported output format: + ```php -$pdf->getNumberOfPages(); //returns an int +/** @var bool $isSupported */ +$isSupported = $pdf->isValidOutputFormat('jpg'); ``` -By default the first page of the pdf will be rendered. If you want to render another page you can do so: +By default, only the first page of the PDF will be rendered. To render another page, call the `selectPage()` method: + ```php -$pdf->setPage(2) +$pdf->selectPage(2) ->saveImage($pathToWhereImageShouldBeStored); //saves the second page ``` -You can override the output format: +Or, select multiple pages with the `selectPages()` method: + +```php +$pdf->selectPages(2, 4, 5) + ->saveImage($directoryToWhereImageShouldBeStored); //saves the 2nd, 4th and 5th pages +``` + +Change the output format: + +```php +$pdf->format(\Spatie\PdfToImage\Enums\OutputFormat::Webp) + ->saveImage($pathToWhereImageShouldBeStored); //the saved image will be in webp format +``` + +Set the output quality _(the compression quality)_ from 0 to 100: + ```php -$pdf->setOutputFormat('png') - ->saveImage($pathToWhereImageShouldBeStored); //the output wil be a png, no matter what +$pdf->quality(90) // set an output quality of 90% + ->saveImage($pathToWhereImageShouldBeStored); ``` -You can set the quality of compression from 0 to 100: +Set the output resolution DPI: + ```php -$pdf->setCompressionQuality(100); // sets the compression quality to maximum +$pdf->resolution(300) // resolution of 300 dpi + ->saveImage($pathToWhereImageShouldBeStored); ``` -You can specify the width of the resulting image: +Specify the thumbnail size of the output image: + ```php $pdf - ->width(400) + ->thumbnailSize(400) // set thumbnail width to 400px; height is calculated automatically + ->saveImage($pathToWhereImageShouldBeStored); + +// or: +$pdf + ->thumbnailSize(400, 300) // set thumbnail width to 400px and the height to 300px ->saveImage($pathToWhereImageShouldBeStored); ``` +Save all pages to images: + +```php +$pdf->saveAllPagesAsImages($directoryToWhereImagesShouldBeStored); +``` + +Set the Merge Layer Method for Imagick: + +```php +$pdf->layerMethod(\Spatie\PdfToImage\Enums\LayerMethod::Merge); + +// or disable layer merging: +$pdf->layerMethod(\Spatie\PdfToImage\Enums\LayerMethod::None); +``` + ## Issues regarding Ghostscript This package uses Ghostscript through Imagick. For this to work Ghostscripts `gs` command should be accessible from the PHP process. For the PHP CLI process (e.g. Laravel's asynchronous jobs, commands, etc...) this is usually already the case. @@ -90,37 +139,40 @@ env[PATH] = /usr/local/bin:/usr/bin:/bin This will instruct PHP FPM to look for the `gs` binary in the right places. +## Imagick Issues + +If you receive an error with the message `attempt to perform an operation not allowed by the security policy 'PDF'`, you may need to add the following line to your `policy.xml` file. This file is usually located in `/etc/ImageMagick-[VERSION]/policy.xml`, such as `/etc/ImageMagick-7/policy.xml`. + +```xml + +``` + ## Testing +`spatie/pdf-to-image` uses the PEST framework for unit tests. They can be run with the following command: + ``` bash -composer test +./vendor/bin/pest ``` ## Changelog -Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. +Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. ## Contributing Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details. -## Security - -If you've found a bug regarding security please mail [security@spatie.be](mailto:security@spatie.be) instead of using the issue tracker. - -## Postcardware - -You're free to use this package, but if it makes it to your production environment we highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. - -Our address is: Spatie, Kruikstraat 22, 2018 Antwerp, Belgium. +## Security Vulnerabilities -We publish all received postcards [on our company website](https://spatie.be/en/opensource/postcards). +Please review [our security policy](../../security/policy) on how to report security vulnerabilities. ## Credits -- [Freek Van der Herten](https://github.com/spatie) +- [Freek Van der Herten](https://github.com/freekmurze) +- [Patrick Organ](https://github.com/patinthehat) - [All Contributors](../../contributors) ## License -The MIT License (MIT). Please see [License File](.github/LICENSE.md) for more information. +The MIT License (MIT). Please see [License File](LICENSE.md) for more information. From 586e4cc081a1597953c3ceee9fc1ac6a33240ea1 Mon Sep 17 00:00:00 2001 From: Patrick Organ Date: Tue, 28 May 2024 01:30:38 -0400 Subject: [PATCH 13/38] wip --- src/Pdf.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Pdf.php b/src/Pdf.php index 9759bf5..66dba96 100644 --- a/src/Pdf.php +++ b/src/Pdf.php @@ -49,14 +49,14 @@ public function __construct(string $pdfFile) $this->imagick->readImage($this->pdfFile); } - public function resolution(int $dpiResolution) + public function resolution(int $dpiResolution): static { $this->resolution = $dpiResolution; return $this; } - public function format(OutputFormat $outputFormat) + public function format(OutputFormat $outputFormat): static { $this->outputFormat = $outputFormat; @@ -83,7 +83,7 @@ public function getFormat(): OutputFormat * @see https://secure.php.net/manual/en/imagick.constants.php * @see Pdf::getImageData() */ - public function layerMethod(LayerMethod|int $method) + public function layerMethod(LayerMethod|int $method): static { if (is_int($method) && ! LayerMethod::isValid($method)) { throw InvalidLayerMethod::for($method); @@ -113,12 +113,12 @@ public function isValidOutputFormat(null|string|OutputFormat $outputFormat): boo return OutputFormat::tryFrom(strtolower($outputFormat)) !== null; } - public function selectPage(int $page) + public function selectPage(int $page): static { return $this->selectPages($page); } - public function selectPages(int ...$pages) + public function selectPages(int ...$pages): static { $this->validatePageNumbers(...$pages); From 986a75ca4ec85620aa2a92627d81b46858295857 Mon Sep 17 00:00:00 2001 From: Patrick Organ Date: Tue, 28 May 2024 01:30:44 -0400 Subject: [PATCH 14/38] update readme --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 13acbf3..ec2f743 100644 --- a/README.md +++ b/README.md @@ -25,12 +25,14 @@ See [issues regarding Ghostscript](#issues-regarding-ghostscript) and [Imagick I ## Installation -The package can be installed via composer and requires PHP 8.2+.: +The package can be installed via composer and requires PHP 8.2+: ```bash composer require spatie/pdf-to-image ``` +> If you are using PHP < 8.2, use version 2.0 of this package. + ## Usage Converting a PDF to an image is easy. @@ -42,6 +44,8 @@ $pdf->saveImage($pathToWhereImageShouldBeStored); If the filename you pass to `saveImage` has the extensions `jpg`, `jpeg`, `png`, or `webp` the image will be saved in that format; otherwise the output format will be `jpg`. +The `saveImage()` method returns an array with the filenames of the saved images if multiple images are saved, otherwise returns a string with the path to the saved image. + ## Other methods Get the total number of pages in the pdf: From e1e286dd8ac82633cffd0dddf9cddc72f6c5220e Mon Sep 17 00:00:00 2001 From: Patrick Organ Date: Tue, 28 May 2024 01:30:50 -0400 Subject: [PATCH 15/38] wip --- composer.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/composer.json b/composer.json index 7759f44..66feb1f 100644 --- a/composer.json +++ b/composer.json @@ -16,6 +16,12 @@ "email": "freek@spatie.be", "homepage": "https://spatie.be", "role": "Developer" + }, + { + "name": "Patrick Organ", + "email": "patrick@permafrost.dev", + "homepage": "https://permafrost.dev", + "role": "Developer" } ], "require": { From 8d4eb6223e226df106cc68c1b53f7fc990804fa4 Mon Sep 17 00:00:00 2001 From: Patrick Organ Date: Tue, 28 May 2024 01:34:37 -0400 Subject: [PATCH 16/38] update workflows; replace php-cs-fixer with pint --- .github/FUNDING.yml | 1 + .github/workflows/dependabot-auto-merge.yml | 2 +- .../workflows/{php-cs-fixer.yml => pint.yml} | 8 ++-- .github/workflows/run-tests.yml | 2 +- .php-cs-fixer.dist.php | 43 ------------------- 5 files changed, 6 insertions(+), 50 deletions(-) rename .github/workflows/{php-cs-fixer.yml => pint.yml} (58%) delete mode 100644 .php-cs-fixer.dist.php diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 5ccc87c..fe5143b 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1,2 @@ github: spatie +custom: https://spatie.be/open-source/support-us diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index 12c07fd..7befd49 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -13,7 +13,7 @@ jobs: - name: Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@v2.1.0 + uses: dependabot/fetch-metadata@v2 with: github-token: "${{ secrets.GITHUB_TOKEN }}" compat-lookup: true diff --git a/.github/workflows/php-cs-fixer.yml b/.github/workflows/pint.yml similarity index 58% rename from .github/workflows/php-cs-fixer.yml rename to .github/workflows/pint.yml index 2ec0448..2c9f7ca 100644 --- a/.github/workflows/php-cs-fixer.yml +++ b/.github/workflows/pint.yml @@ -12,12 +12,10 @@ jobs: with: ref: ${{ github.head_ref }} - - name: Run PHP CS Fixer - uses: docker://oskarstark/php-cs-fixer-ga - with: - args: --config=.php-cs-fixer.dist.php --allow-risky=yes + - name: Fix styling issues + uses: aglipanci/laravel-pint-action@v2 - name: Commit changes - uses: stefanzweifel/git-auto-commit-action@v4 + uses: stefanzweifel/git-auto-commit-action@v5 with: commit_message: Fix styling diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 9e94820..23baf13 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -8,7 +8,7 @@ jobs: strategy: fail-fast: true matrix: - php: [8.3, 8.2, 8.1, 8.0, 7.4] + php: [8.3, 8.2] dependency-version: [prefer-lowest, prefer-stable] os: [ubuntu-latest] diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php deleted file mode 100644 index ca68de3..0000000 --- a/.php-cs-fixer.dist.php +++ /dev/null @@ -1,43 +0,0 @@ -notPath('bootstrap/*') - ->notPath('storage/*') - ->notPath('resources/view/mail/*') - ->in([ - __DIR__ . '/src', - __DIR__ . '/tests', - ]) - ->name('*.php') - ->notName('*.blade.php') - ->ignoreDotFiles(true) - ->ignoreVCS(true); - -return (new PhpCsFixer\Config) - ->setRules([ - '@PSR12' => true, - 'array_syntax' => ['syntax' => 'short'], - 'ordered_imports' => ['sort_algorithm' => 'alpha'], - 'no_unused_imports' => true, - 'not_operator_with_successor_space' => true, - 'trailing_comma_in_multiline' => true, - 'phpdoc_scalar' => true, - 'unary_operator_spaces' => true, - 'binary_operator_spaces' => true, - 'blank_line_before_statement' => [ - 'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'], - ], - 'phpdoc_single_line_var_spacing' => true, - 'phpdoc_var_without_name' => true, - 'class_attributes_separation' => [ - 'elements' => [ - 'method' => 'one' - ], - ], - 'method_argument_space' => [ - 'on_multiline' => 'ensure_fully_multiline', - 'keep_multiple_spaces_after_comma' => true, - ], - 'single_trait_insert_per_statement' => true, - ]) - ->setFinder($finder); From c1dc10ffb188881d43626e057636ed3f46afbb0e Mon Sep 17 00:00:00 2001 From: patinthehat Date: Tue, 28 May 2024 05:36:09 +0000 Subject: [PATCH 17/38] Fix styling --- src/Exceptions/PageDoesNotExist.php | 2 +- src/Pdf.php | 23 ++++--------- tests/PdfTest.php | 53 +++++++++++++++-------------- 3 files changed, 35 insertions(+), 43 deletions(-) diff --git a/src/Exceptions/PageDoesNotExist.php b/src/Exceptions/PageDoesNotExist.php index b2f4dfc..da41621 100644 --- a/src/Exceptions/PageDoesNotExist.php +++ b/src/Exceptions/PageDoesNotExist.php @@ -6,7 +6,7 @@ class PageDoesNotExist extends Exception { - static function for(int $pageNumber): self + public static function for(int $pageNumber): self { return new static("Page {$pageNumber} does not exist."); } diff --git a/src/Pdf.php b/src/Pdf.php index 66dba96..1e3396c 100644 --- a/src/Pdf.php +++ b/src/Pdf.php @@ -75,7 +75,6 @@ public function getFormat(): OutputFormat * To disable merging image layers, set to LayerMethod::None. * * @param \Spatie\PdfToImage\Enums\LayerMethod|int - * * @return $this * * @throws \Spatie\PdfToImage\Exceptions\InvalidLayerMethod @@ -97,8 +96,6 @@ public function layerMethod(LayerMethod|int $method): static /** * Expects a string or OutputFormat enum. If a string, expects the file extension of the format, * without a leading period. - * @param string|null|OutputFormat $outputFormat - * @return bool */ public function isValidOutputFormat(null|string|OutputFormat $outputFormat): bool { @@ -140,22 +137,18 @@ public function pageCount(): int * Saves the PDF as an image. Expects a path to save the image to, which should be * a directory if multiple pages have been selected (otherwise the image will be overwritten). * Returns either a string with a single filename that was written, or an array of paths to the saved images. - * @param string $pathToImage - * @param string $prefix - * @return array|string */ public function saveImage(string $pathToImage, string $prefix = ''): array|string { $pages = [PdfPage::make($this->pages[0], $this->outputFormat, $prefix, $pathToImage)]; if (is_dir($pathToImage)) { - $pages = array_map(fn($page) => - PdfPage::make($page, $this->outputFormat, $prefix, rtrim($pathToImage, '\/').DIRECTORY_SEPARATOR.$page.'.'.$this->outputFormat->value), $this->pages); + $pages = array_map(fn ($page) => PdfPage::make($page, $this->outputFormat, $prefix, rtrim($pathToImage, '\/').DIRECTORY_SEPARATOR.$page.'.'.$this->outputFormat->value), $this->pages); } $result = []; - foreach($pages as $page) { + foreach ($pages as $page) { $path = $page->getFilename(); $imageData = $this->getImageData($path, $page->number); @@ -239,14 +232,12 @@ public function quality(int $compressionQuality) * Set the thumbnail size for the image. If no height is provided, the thumbnail height will * be scaled according to the width. * - * @param int $width - * @param int $height + * @param int $height + * @return $this * * @throws \Spatie\PdfToImage\Exceptions\InvalidThumbnailSize - * - * @return $this */ - public function thumbnailSize(int $width, int|null $height = null) + public function thumbnailSize(int $width, ?int $height = null) { if ($width < 0) { throw InvalidThumbnailSize::forWidth($width); @@ -266,7 +257,7 @@ protected function determineOutputFormat(string $pathToImage): OutputFormat { $outputFormat = OutputFormat::tryFrom(pathinfo($pathToImage, PATHINFO_EXTENSION)); - if (!empty($this->outputFormat)) { + if (! empty($this->outputFormat)) { $outputFormat = $this->outputFormat; } @@ -279,7 +270,7 @@ protected function determineOutputFormat(string $pathToImage): OutputFormat protected function validatePageNumbers(int ...$pageNumbers) { - foreach($pageNumbers as $page) { + foreach ($pageNumbers as $page) { if ($page > $this->pageCount() || $page < 1) { throw PageDoesNotExist::for($page); } diff --git a/tests/PdfTest.php b/tests/PdfTest.php index 08ca1ad..92cac7b 100644 --- a/tests/PdfTest.php +++ b/tests/PdfTest.php @@ -5,8 +5,9 @@ use Spatie\PdfToImage\Exceptions\PdfDoesNotExist; use Spatie\PdfToImage\Pdf; -function unlinkAllOutputImages(string $path) { - $files = glob($path . '/*'); +function unlinkAllOutputImages(string $path) +{ + $files = glob($path.'/*'); foreach ($files as $file) { if (is_file($file) && pathinfo($file, PATHINFO_EXTENSION) !== 'gitignore') { @@ -31,8 +32,8 @@ function unlinkAllOutputImages(string $path) { it('will throw an exception when passed an invalid page number', function ($invalidPage) { (new Pdf($this->testFile))->selectPage($invalidPage); }) -->throws(PageDoesNotExist::class) -->with([100, 0, -1]); + ->throws(PageDoesNotExist::class) + ->with([100, 0, -1]); it('will correctly return the number of pages in a pdf file', function () { $pdf = new Pdf($this->multipageTestFile); @@ -49,7 +50,7 @@ function unlinkAllOutputImages(string $path) { expect($image['x'])->toEqual($resolution); expect($image['y'])->toEqual($resolution); }) -->with([127, 150, 166]); + ->with([127, 150, 166]); it('will convert a specified page', function () { $imagick = (new Pdf($this->multipageTestFile)) @@ -78,11 +79,11 @@ function unlinkAllOutputImages(string $path) { $imagick = (new Pdf($this->testFile)) ->format($fmt) - ->getImageData('test.' . $fmt->value, 1); + ->getImageData('test.'.$fmt->value, 1); expect($imagick->getFormat())->toEqual($fmt->value); }) -->with(['jpg', 'png', 'webp']); + ->with(['jpg', 'png', 'webp']); it('gets the output format', function () { $pdf = new Pdf($this->testFile); @@ -106,13 +107,13 @@ function unlinkAllOutputImages(string $path) { it('throws an error when passed an invalid layer method', function () { (new Pdf($this->testFile))->layerMethod(-100); }) -->throws(InvalidLayerMethod::class); + ->throws(InvalidLayerMethod::class); it('will throw an exception when passed an invalid quality value', function ($quality) { (new Pdf($this->testFile))->quality($quality); }) -->throws(\Spatie\PdfToImage\Exceptions\InvalidQuality::class) -->with([-1, 0, 101]); + ->throws(\Spatie\PdfToImage\Exceptions\InvalidQuality::class) + ->with([-1, 0, 101]); it('will set output quality', function () { $imagick = (new Pdf($this->testFile)) @@ -145,26 +146,26 @@ function unlinkAllOutputImages(string $path) { it('will throw an exception when passed an invalid thumbnail size', function ($width, $height) { (new Pdf($this->testFile))->thumbnailSize($width, $height); }) -->throws(\Spatie\PdfToImage\Exceptions\InvalidThumbnailSize::class) -->with([ - 'invalid width' => [-1, 100], - 'invalid height' => [100, -1], - 'invalid size' => [-1, -1], -]); + ->throws(\Spatie\PdfToImage\Exceptions\InvalidThumbnailSize::class) + ->with([ + 'invalid width' => [-1, 100], + 'invalid height' => [100, -1], + 'invalid size' => [-1, -1], + ]); it('checks if the provided output format string is a supported format', function ($formats, $expected) { $pdf = (new Pdf($this->testFile)); - foreach($formats as $format) { + foreach ($formats as $format) { expect($pdf->isValidOutputFormat($format))->toBe($expected); } }) -->with([ - 'supported formats' => [['jpg', 'png', 'webp'], true], - 'unsupported formats' => [['bmp', 'gif', ''], false], -]); + ->with([ + 'supported formats' => [['jpg', 'png', 'webp'], true], + 'unsupported formats' => [['bmp', 'gif', ''], false], + ]); -it('saves a page to an image', function() { +it('saves a page to an image', function () { $targetFilename = __DIR__.'/output/test.jpg'; (new Pdf($this->testFile)) @@ -174,7 +175,7 @@ function unlinkAllOutputImages(string $path) { expect(file_exists($targetFilename))->toBeTrue(); }); -it('saves only selected pages to images', function() { +it('saves only selected pages to images', function () { $targetPath = __DIR__.'/output'; (new Pdf($this->multipageTestFile)) @@ -183,11 +184,11 @@ function unlinkAllOutputImages(string $path) { ->saveImage($targetPath); foreach ([1, 3] as $pageNumber) { - expect(file_exists($targetPath . '/' . $pageNumber . '.png'))->toBeTrue(); + expect(file_exists($targetPath.'/'.$pageNumber.'.png'))->toBeTrue(); } }); -it('saves all pages to images', function() { +it('saves all pages to images', function () { $targetPath = __DIR__.'/output'; (new Pdf($this->multipageTestFile)) @@ -195,6 +196,6 @@ function unlinkAllOutputImages(string $path) { ->saveAllPagesAsImages($targetPath); foreach (range(1, 3) as $pageNumber) { - expect(file_exists($targetPath . '/' . $pageNumber . '.jpg'))->toBeTrue(); + expect(file_exists($targetPath.'/'.$pageNumber.'.jpg'))->toBeTrue(); } }); From 4e0c7bbef4cb6afc085a1b63f1e03f12d2f83cb9 Mon Sep 17 00:00:00 2001 From: Patrick Organ Date: Tue, 28 May 2024 01:46:55 -0400 Subject: [PATCH 18/38] move license to root --- .github/LICENSE.md => LICENSE.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/LICENSE.md => LICENSE.md (100%) diff --git a/.github/LICENSE.md b/LICENSE.md similarity index 100% rename from .github/LICENSE.md rename to LICENSE.md From f14a90659885e3df35b2cdf02347a591fd0741a2 Mon Sep 17 00:00:00 2001 From: patinthehat Date: Tue, 28 May 2024 05:48:06 +0000 Subject: [PATCH 19/38] Fix styling --- src/Pdf.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Pdf.php b/src/Pdf.php index 1e3396c..ca372c5 100644 --- a/src/Pdf.php +++ b/src/Pdf.php @@ -232,7 +232,6 @@ public function quality(int $compressionQuality) * Set the thumbnail size for the image. If no height is provided, the thumbnail height will * be scaled according to the width. * - * @param int $height * @return $this * * @throws \Spatie\PdfToImage\Exceptions\InvalidThumbnailSize From 9f4114f8fdd0001dcca1cc7e73885f0ea8eafef7 Mon Sep 17 00:00:00 2001 From: Patrick Organ Date: Tue, 28 May 2024 02:18:36 -0400 Subject: [PATCH 20/38] upgrade pest dep to v2 --- composer.json | 2 +- phpunit.xml.dist | 48 +++++++++++++++++++++--------------------------- 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/composer.json b/composer.json index 66feb1f..5505ede 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,7 @@ "ext-imagick" : "*" }, "require-dev": { - "pestphp/pest": "^1.23" + "pestphp/pest": "^2.34" }, "autoload": { "psr-4": { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 1f0d1c0..478fc00 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,29 +1,23 @@ - - - - tests - - - - - src/ - - - - - - - - - + + + + + + + + + + + tests + + + + + + + + src/ + + From 23915e7380da05ae96d1d9737ec85c4fc09f6288 Mon Sep 17 00:00:00 2001 From: Patrick Organ Date: Tue, 28 May 2024 02:49:11 -0400 Subject: [PATCH 21/38] remove initialization of imagick instance from constructor as it is unneeded --- src/Pdf.php | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/Pdf.php b/src/Pdf.php index ca372c5..f96142b 100644 --- a/src/Pdf.php +++ b/src/Pdf.php @@ -14,7 +14,7 @@ class Pdf { - protected string $pdfFile; + protected string $filename; protected int $resolution = 144; @@ -36,17 +36,13 @@ class Pdf private ?int $numberOfPages = null; - public function __construct(string $pdfFile) + public function __construct(string $filename) { - if (! file_exists($pdfFile)) { - throw PdfDoesNotExist::for($pdfFile); + if (! file_exists($filename)) { + throw PdfDoesNotExist::for($filename); } - $this->pdfFile = $pdfFile; - - $this->imagick = new Imagick(); - - $this->imagick->readImage($this->pdfFile); + $this->filename = $filename; } public function resolution(int $dpiResolution): static @@ -126,6 +122,11 @@ public function selectPages(int ...$pages): static public function pageCount(): int { + if ($this->imagick === null) { + $this->imagick = new Imagick(); + $this->imagick->readImage($this->filename); + } + if ($this->numberOfPages === null) { $this->numberOfPages = $this->imagick->getNumberImages(); } @@ -195,7 +196,7 @@ public function getImageData(string $pathToImage, int $pageNumber): Imagick $this->imagick->setCompressionQuality($this->compressionQuality); } - $this->imagick->readImage(sprintf('%s[%s]', $this->pdfFile, $pageNumber - 1)); + $this->imagick->readImage(sprintf('%s[%s]', $this->filename, $pageNumber - 1)); if ($this->layerMethod !== LayerMethod::None) { $this->imagick = $this->imagick->mergeImageLayers($this->layerMethod->value); From ac7743ad250693b77371a447d5da634b09df607e Mon Sep 17 00:00:00 2001 From: Patrick Organ Date: Tue, 28 May 2024 06:16:56 -0400 Subject: [PATCH 22/38] implement suggestions round 1 --- src/DTOs/PdfPage.php | 23 ++++++++--------------- src/Pdf.php | 2 +- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/DTOs/PdfPage.php b/src/DTOs/PdfPage.php index b0ff7bf..ed5124f 100644 --- a/src/DTOs/PdfPage.php +++ b/src/DTOs/PdfPage.php @@ -6,20 +6,13 @@ class PdfPage { - public readonly int $number; - - public readonly OutputFormat $format; - - public readonly string $prefix; - - public readonly string $path; - - public function __construct(int $pageNumber, OutputFormat $format, string $prefix, string $path) - { - $this->number = $pageNumber; - $this->format = $format; - $this->prefix = $prefix; - $this->path = $path; + public function __construct( + public int $number, + public OutputFormat $format, + public string $prefix, + public string $path + ) { + // } public static function make(int $pageNumber, OutputFormat $format, string $prefix, string $path): self @@ -27,7 +20,7 @@ public static function make(int $pageNumber, OutputFormat $format, string $prefi return new self($pageNumber, $format, $prefix, $path); } - public function getFilename(): string + public function filename(): string { $info = pathinfo($this->path); diff --git a/src/Pdf.php b/src/Pdf.php index f96142b..2701610 100644 --- a/src/Pdf.php +++ b/src/Pdf.php @@ -150,7 +150,7 @@ public function saveImage(string $pathToImage, string $prefix = ''): array|strin $result = []; foreach ($pages as $page) { - $path = $page->getFilename(); + $path = $page->filename(); $imageData = $this->getImageData($path, $page->number); if (file_put_contents($path, $imageData) !== false) { From 48bcfe3f336dbca48f52ebf0c46fe7d86ddb8446 Mon Sep 17 00:00:00 2001 From: Patrick Organ Date: Tue, 28 May 2024 13:32:32 -0400 Subject: [PATCH 23/38] add size() method, refactor helper method on exception --- src/Exceptions/InvalidSize.php | 21 +++ src/Exceptions/InvalidThumbnailSize.php | 16 -- src/Pdf.php | 32 +++- tests/PdfTest.php | 201 ------------------------ tests/Unit/PdfTest.php | 54 +++++++ 5 files changed, 103 insertions(+), 221 deletions(-) create mode 100644 src/Exceptions/InvalidSize.php delete mode 100644 src/Exceptions/InvalidThumbnailSize.php delete mode 100644 tests/PdfTest.php create mode 100644 tests/Unit/PdfTest.php diff --git a/src/Exceptions/InvalidSize.php b/src/Exceptions/InvalidSize.php new file mode 100644 index 0000000..f548443 --- /dev/null +++ b/src/Exceptions/InvalidSize.php @@ -0,0 +1,21 @@ +imagick->readImage(sprintf('%s[%s]', $this->filename, $pageNumber - 1)); + if ($this->resizeWidth !== null) { + $this->imagick->resizeImage($this->resizeWidth, $this->resizeHeight ?? 0, Imagick::FILTER_POINT, 0); + } + if ($this->layerMethod !== LayerMethod::None) { $this->imagick = $this->imagick->mergeImageLayers($this->layerMethod->value); } @@ -235,16 +243,16 @@ public function quality(int $compressionQuality) * * @return $this * - * @throws \Spatie\PdfToImage\Exceptions\InvalidThumbnailSize + * @throws \Spatie\PdfToImage\Exceptions\InvalidSize */ public function thumbnailSize(int $width, ?int $height = null) { if ($width < 0) { - throw InvalidThumbnailSize::forWidth($width); + throw InvalidSize::forThumbnail($width, 'width'); } if ($height !== null && $height < 0) { - throw InvalidThumbnailSize::forHeight($height); + throw InvalidSize::forThumbnail($height, 'height'); } $this->thumbnailWidth = $width; @@ -253,6 +261,22 @@ public function thumbnailSize(int $width, ?int $height = null) return $this; } + public function size(int $width, ?int $height = null) + { + if ($width < 0) { + throw InvalidSize::forImage($width, 'width'); + } + + if ($height !== null && $height < 0) { + throw InvalidSize::forImage($height, 'height'); + } + + $this->resizeWidth = $width; + $this->resizeHeight = $height ?? 0; + + return $this; + } + protected function determineOutputFormat(string $pathToImage): OutputFormat { $outputFormat = OutputFormat::tryFrom(pathinfo($pathToImage, PATHINFO_EXTENSION)); diff --git a/tests/PdfTest.php b/tests/PdfTest.php deleted file mode 100644 index 92cac7b..0000000 --- a/tests/PdfTest.php +++ /dev/null @@ -1,201 +0,0 @@ -testFile = __DIR__.'/files/test.pdf'; - $this->multipageTestFile = __DIR__.'/files/multipage-test.pdf'; -}); - -afterEach(function () { - unlinkAllOutputImages(__DIR__.'/output'); -}); - -it('will throw an exception when trying to convert a non existing file', function () { - new Pdf('pdf-does-not-exist.pdf'); -})->throws(PdfDoesNotExist::class); - -it('will throw an exception when passed an invalid page number', function ($invalidPage) { - (new Pdf($this->testFile))->selectPage($invalidPage); -}) - ->throws(PageDoesNotExist::class) - ->with([100, 0, -1]); - -it('will correctly return the number of pages in a pdf file', function () { - $pdf = new Pdf($this->multipageTestFile); - - expect($pdf->pageCount())->toEqual(3); -}); - -it('will accept a custom specified resolution', function ($resolution) { - $image = (new Pdf($this->testFile)) - ->resolution($resolution) - ->getImageData('test.jpg', 1) - ->getImageResolution(); - - expect($image['x'])->toEqual($resolution); - expect($image['y'])->toEqual($resolution); -}) - ->with([127, 150, 166]); - -it('will convert a specified page', function () { - $imagick = (new Pdf($this->multipageTestFile)) - ->selectPage(2) - ->getImageData('page-2.jpg', 2); - - expect($imagick)->toBeInstanceOf(Imagick::class); -}); - -it('will select multiple pages', function () { - $pdf = (new Pdf($this->multipageTestFile)) - ->selectPages(1, 3); - - $imagick1 = $pdf - ->getImageData('page-1.jpg', 1); - - $imagick2 = $pdf - ->getImageData('page-3.jpg', 3); - - expect($imagick1)->toBeInstanceOf(Imagick::class); - expect($imagick2)->toBeInstanceOf(Imagick::class); -}); - -it('will accept an output format and convert to it', function ($format) { - $fmt = \Spatie\PdfToImage\Enums\OutputFormat::from($format); - - $imagick = (new Pdf($this->testFile)) - ->format($fmt) - ->getImageData('test.'.$fmt->value, 1); - - expect($imagick->getFormat())->toEqual($fmt->value); -}) - ->with(['jpg', 'png', 'webp']); - -it('gets the output format', function () { - $pdf = new Pdf($this->testFile); - expect($pdf->getFormat()->value)->toEqual('jpg'); - - $pdf->format(\Spatie\PdfToImage\Enums\OutputFormat::Png); - expect($pdf->getFormat()->value)->toEqual('png'); -}); - -it('can accept a layer', function () { - $image = (new Pdf($this->testFile)) - ->layerMethod(\Spatie\PdfToImage\Enums\LayerMethod::Flatten) - ->resolution(72) - ->getImageData('test.jpg', 1) - ->getImageResolution(); - - expect($image['x'])->toEqual(72); - expect($image['y'])->toEqual(72); -}); - -it('throws an error when passed an invalid layer method', function () { - (new Pdf($this->testFile))->layerMethod(-100); -}) - ->throws(InvalidLayerMethod::class); - -it('will throw an exception when passed an invalid quality value', function ($quality) { - (new Pdf($this->testFile))->quality($quality); -}) - ->throws(\Spatie\PdfToImage\Exceptions\InvalidQuality::class) - ->with([-1, 0, 101]); - -it('will set output quality', function () { - $imagick = (new Pdf($this->testFile)) - ->quality(99) - ->getImageData('test.jpg', 1); - - expect($imagick->getCompressionQuality())->toEqual(99); -}); - -it('will create a thumbnail at specified sizes', function () { - $pdf = (new Pdf($this->multipageTestFile)); - - $imagick = $pdf - ->thumbnailSize(400) - ->getImageData('test.jpg', 1) - ->getImageGeometry(); - - expect($imagick['width'])->toBe(400); - expect($imagick['height'])->toBeGreaterThan(50); - - $imagick = $pdf - ->thumbnailSize(200, 300) - ->getImageData('test.jpg', 1) - ->getImageGeometry(); - - expect($imagick['width'])->toBe(200); - expect($imagick['height'])->toBe(300); -}); - -it('will throw an exception when passed an invalid thumbnail size', function ($width, $height) { - (new Pdf($this->testFile))->thumbnailSize($width, $height); -}) - ->throws(\Spatie\PdfToImage\Exceptions\InvalidThumbnailSize::class) - ->with([ - 'invalid width' => [-1, 100], - 'invalid height' => [100, -1], - 'invalid size' => [-1, -1], - ]); - -it('checks if the provided output format string is a supported format', function ($formats, $expected) { - $pdf = (new Pdf($this->testFile)); - - foreach ($formats as $format) { - expect($pdf->isValidOutputFormat($format))->toBe($expected); - } -}) - ->with([ - 'supported formats' => [['jpg', 'png', 'webp'], true], - 'unsupported formats' => [['bmp', 'gif', ''], false], - ]); - -it('saves a page to an image', function () { - $targetFilename = __DIR__.'/output/test.jpg'; - - (new Pdf($this->testFile)) - ->selectPage(1) - ->saveImage($targetFilename); - - expect(file_exists($targetFilename))->toBeTrue(); -}); - -it('saves only selected pages to images', function () { - $targetPath = __DIR__.'/output'; - - (new Pdf($this->multipageTestFile)) - ->selectPages(1, 3) - ->format(\Spatie\PdfToImage\Enums\OutputFormat::Png) - ->saveImage($targetPath); - - foreach ([1, 3] as $pageNumber) { - expect(file_exists($targetPath.'/'.$pageNumber.'.png'))->toBeTrue(); - } -}); - -it('saves all pages to images', function () { - $targetPath = __DIR__.'/output'; - - (new Pdf($this->multipageTestFile)) - ->format(\Spatie\PdfToImage\Enums\OutputFormat::Jpg) - ->saveAllPagesAsImages($targetPath); - - foreach (range(1, 3) as $pageNumber) { - expect(file_exists($targetPath.'/'.$pageNumber.'.jpg'))->toBeTrue(); - } -}); diff --git a/tests/Unit/PdfTest.php b/tests/Unit/PdfTest.php new file mode 100644 index 0000000..d9a4dd6 --- /dev/null +++ b/tests/Unit/PdfTest.php @@ -0,0 +1,54 @@ +throws(PdfDoesNotExist::class); + +it('will throw an exception when passed an invalid page number', function ($invalidPage) { + (new Pdf($this->testFile))->selectPage($invalidPage); +}) + ->throws(PageDoesNotExist::class) + ->with([100, 0, -1]); + +it('will correctly return the number of pages in a pdf file', function () { + $pdf = new Pdf($this->multipageTestFile); + + expect($pdf->pageCount())->toEqual(3); +}); + +it('will accept a custom specified resolution', function ($resolution) { + $image = (new Pdf($this->testFile)) + ->resolution($resolution) + ->getImageData('test.jpg', 1) + ->getImageResolution(); + + expect($image['x'])->toEqual($resolution); + expect($image['y'])->toEqual($resolution); +}) + ->with([127, 150, 16]); + +it('will convert a specified page', function () { + $imagick = (new Pdf($this->multipageTestFile)) + ->selectPage(2) + ->getImageData('page-2.jpg', 2); + + expect($imagick)->toBeInstanceOf(Imagick::class); +}); + +it('will select multiple pages', function () { + $pdf = (new Pdf($this->multipageTestFile)) + ->selectPages(1, 3); + + $imagick1 = $pdf + ->getImageData('page-1.jpg', 1); + + $imagick2 = $pdf + ->getImageData('page-3.jpg', 3); + + expect($imagick1)->toBeInstanceOf(Imagick::class); + expect($imagick2)->toBeInstanceOf(Imagick::class); +}); From c942bb3ced6b840916dbfc1fcbafddb4104de5da Mon Sep 17 00:00:00 2001 From: Patrick Organ Date: Tue, 28 May 2024 13:33:07 -0400 Subject: [PATCH 24/38] reorganize and split up unit tests to be more managable; add pest configuration and TestCase class --- tests/Pest.php | 6 +++ tests/TestCase.php | 27 ++++++++++++++ tests/Unit/LayersTest.php | 20 ++++++++++ tests/Unit/OutputFormatsTest.php | 34 +++++++++++++++++ tests/Unit/PdfTest.php | 4 +- tests/Unit/QualityTest.php | 17 +++++++++ tests/Unit/SavesImagesTest.php | 38 +++++++++++++++++++ tests/Unit/SizesTest.php | 63 ++++++++++++++++++++++++++++++++ 8 files changed, 207 insertions(+), 2 deletions(-) create mode 100644 tests/Pest.php create mode 100644 tests/TestCase.php create mode 100644 tests/Unit/LayersTest.php create mode 100644 tests/Unit/OutputFormatsTest.php create mode 100644 tests/Unit/QualityTest.php create mode 100644 tests/Unit/SavesImagesTest.php create mode 100644 tests/Unit/SizesTest.php diff --git a/tests/Pest.php b/tests/Pest.php new file mode 100644 index 0000000..001fc61 --- /dev/null +++ b/tests/Pest.php @@ -0,0 +1,6 @@ +in(__DIR__); + diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..d262753 --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,27 @@ +unlinkAllOutputImages($this->outputDirectory); + } +} diff --git a/tests/Unit/LayersTest.php b/tests/Unit/LayersTest.php new file mode 100644 index 0000000..0d494ce --- /dev/null +++ b/tests/Unit/LayersTest.php @@ -0,0 +1,20 @@ +testFile)) + ->layerMethod(\Spatie\PdfToImage\Enums\LayerMethod::Flatten) + ->resolution(72) + ->getImageData('test.jpg', 1) + ->getImageResolution(); + + expect($image['x'])->toEqual(72); + expect($image['y'])->toEqual(72); +}); + +it('throws an error when passed an invalid layer method', function () { + (new Pdf($this->testFile))->layerMethod(-100); +}) + ->throws(InvalidLayerMethod::class); diff --git a/tests/Unit/OutputFormatsTest.php b/tests/Unit/OutputFormatsTest.php new file mode 100644 index 0000000..33a4278 --- /dev/null +++ b/tests/Unit/OutputFormatsTest.php @@ -0,0 +1,34 @@ +testFile)) + ->format($fmt) + ->getImageData('test.'.$fmt->value, 1); + + expect($imagick->getFormat())->toEqual($fmt->value); +}) + ->with(['jpg', 'png', 'webp']); + +it('gets the output format', function () { + $pdf = new Pdf($this->testFile); + expect($pdf->getFormat()->value)->toEqual('jpg'); + + $pdf->format(\Spatie\PdfToImage\Enums\OutputFormat::Png); + expect($pdf->getFormat()->value)->toEqual('png'); +}); + +it('checks if the provided output format string is a supported format', function ($formats, $expected) { + $pdf = (new Pdf($this->testFile)); + + foreach ($formats as $format) { + expect($pdf->isValidOutputFormat($format))->toBe($expected); + } +}) + ->with([ + 'supported formats' => [['jpg', 'png', 'webp'], true], + 'unsupported formats' => [['bmp', 'gif', ''], false], + ]); diff --git a/tests/Unit/PdfTest.php b/tests/Unit/PdfTest.php index d9a4dd6..743a23a 100644 --- a/tests/Unit/PdfTest.php +++ b/tests/Unit/PdfTest.php @@ -31,7 +31,7 @@ }) ->with([127, 150, 16]); -it('will convert a specified page', function () { +it('can select a single page', function () { $imagick = (new Pdf($this->multipageTestFile)) ->selectPage(2) ->getImageData('page-2.jpg', 2); @@ -39,7 +39,7 @@ expect($imagick)->toBeInstanceOf(Imagick::class); }); -it('will select multiple pages', function () { +it('can select multiple pages', function () { $pdf = (new Pdf($this->multipageTestFile)) ->selectPages(1, 3); diff --git a/tests/Unit/QualityTest.php b/tests/Unit/QualityTest.php new file mode 100644 index 0000000..1161916 --- /dev/null +++ b/tests/Unit/QualityTest.php @@ -0,0 +1,17 @@ +testFile))->quality($quality); +}) + ->throws(\Spatie\PdfToImage\Exceptions\InvalidQuality::class) + ->with([-1, 0, 101]); + +it('will set output quality', function () { + $imagick = (new Pdf($this->testFile)) + ->quality(99) + ->getImageData('test.jpg', 1); + + expect($imagick->getCompressionQuality())->toEqual(99); +}); diff --git a/tests/Unit/SavesImagesTest.php b/tests/Unit/SavesImagesTest.php new file mode 100644 index 0000000..d4fc387 --- /dev/null +++ b/tests/Unit/SavesImagesTest.php @@ -0,0 +1,38 @@ +unlinkAllOutputImages($this->outputDirectory); +}); + +it('saves a page to an image', function () { + $targetFilename = $this->outputDirectory.'/test.jpg'; + + (new Pdf($this->testFile)) + ->selectPage(1) + ->saveImage($targetFilename); + + expect(file_exists($targetFilename))->toBeTrue(); +}); + +it('saves only selected pages to images', function () { + (new Pdf($this->multipageTestFile)) + ->selectPages(1, 3) + ->format(\Spatie\PdfToImage\Enums\OutputFormat::Png) + ->saveImage($this->outputDirectory); + + foreach ([1, 3] as $pageNumber) { + expect(file_exists($this->outputDirectory.'/'.$pageNumber.'.png'))->toBeTrue(); + } +}); + +it('saves all pages to images', function () { + (new Pdf($this->multipageTestFile)) + ->format(\Spatie\PdfToImage\Enums\OutputFormat::Jpg) + ->saveAllPagesAsImages($this->outputDirectory); + + foreach (range(1, 3) as $pageNumber) { + expect(file_exists($this->outputDirectory.'/'.$pageNumber.'.jpg'))->toBeTrue(); + } +}); diff --git a/tests/Unit/SizesTest.php b/tests/Unit/SizesTest.php new file mode 100644 index 0000000..fe448dc --- /dev/null +++ b/tests/Unit/SizesTest.php @@ -0,0 +1,63 @@ +multipageTestFile)); + + $imagick = $pdf + ->thumbnailSize(400) + ->getImageData('test.jpg', 1) + ->getImageGeometry(); + + expect($imagick['width'])->toBe(400); + expect($imagick['height'])->toBeGreaterThan(50); + + $imagick = $pdf + ->thumbnailSize(200, 300) + ->getImageData('test.jpg', 1) + ->getImageGeometry(); + + expect($imagick['width'])->toBe(200); + expect($imagick['height'])->toBe(300); +}); + +it('will throw an exception when passed an invalid thumbnail size', function ($width, $height) { + (new Pdf($this->testFile))->thumbnailSize($width, $height); +}) + ->throws(\Spatie\PdfToImage\Exceptions\InvalidSize::class) + ->with([ + 'invalid width' => [-1, 100], + 'invalid height' => [100, -1], + 'invalid size' => [-1, -1], + ]); + +it('will create an image at specified sizes', function () { + $pdf = (new Pdf($this->multipageTestFile)); + + $size = $pdf + ->size(400) + ->getImageData('test.jpg', 1) + ->getImageGeometry(); + + expect($size['width'])->toBe(400); + expect($size['height'])->toBeGreaterThan(20); + + $size = $pdf + ->size(200, 300) + ->getImageData('test.jpg', 1) + ->getImageGeometry(); + + expect($size['width'])->toBe(200); + expect($size['height'])->toBe(300); +}); + +it('will throw an exception when passed an invalid image size', function ($width, $height) { + (new Pdf($this->testFile))->size($width, $height); +}) + ->throws(\Spatie\PdfToImage\Exceptions\InvalidSize::class) + ->with([ + 'invalid width' => [-1, 100], + 'invalid height' => [100, -1], + 'invalid size' => [-1, -1], + ]); From d47f79978f0e94a570ba186f5391662f1916daea Mon Sep 17 00:00:00 2001 From: Patrick Organ Date: Tue, 28 May 2024 13:38:58 -0400 Subject: [PATCH 25/38] update readme --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index ec2f743..3ad94c9 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,20 @@ $pdf ->saveImage($pathToWhereImageShouldBeStored); ``` +Set the output image width: + +```php +$pdf->size(400) // set the width to 400px; height is calculated automatically + ->saveImage($pathToWhereImageShouldBeStored); +``` + +Set the output image width and height: + +```php +$pdf->size(400, 300) // set the width to 400px and the height to 300px + ->saveImage($pathToWhereImageShouldBeStored); +``` + Save all pages to images: ```php From 3288cf5c5e309745fbf411ce7a1b88ea8754acc5 Mon Sep 17 00:00:00 2001 From: patinthehat Date: Tue, 28 May 2024 17:42:54 +0000 Subject: [PATCH 26/38] Fix styling --- src/Exceptions/InvalidSize.php | 2 +- tests/Pest.php | 1 - tests/TestCase.php | 5 ++++- tests/Unit/LayersTest.php | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Exceptions/InvalidSize.php b/src/Exceptions/InvalidSize.php index f548443..f97564e 100644 --- a/src/Exceptions/InvalidSize.php +++ b/src/Exceptions/InvalidSize.php @@ -6,7 +6,7 @@ class InvalidSize extends \Exception { public static function for(int $value, string $type, string $property): self { - return new static(ucfirst($type) . " {$property} must be greater than or equal to 0, {$value} given."); + return new static(ucfirst($type)." {$property} must be greater than or equal to 0, {$value} given."); } public static function forThumbnail(int $value, string $property): self diff --git a/tests/Pest.php b/tests/Pest.php index 001fc61..ecee802 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -3,4 +3,3 @@ use Spatie\PdfToImage\Test\TestCase; uses(TestCase::class)->in(__DIR__); - diff --git a/tests/TestCase.php b/tests/TestCase.php index d262753..5275037 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -7,7 +7,9 @@ class TestCase extends BaseTestCase { public $testFile = __DIR__.'/files/test.pdf'; + public $multipageTestFile = __DIR__.'/files/multipage-test.pdf'; + public $outputDirectory = __DIR__.'/output'; public function unlinkAllOutputImages(string $path): void @@ -21,7 +23,8 @@ public function unlinkAllOutputImages(string $path): void } } - public function afterEach() { + public function afterEach() + { $this->unlinkAllOutputImages($this->outputDirectory); } } diff --git a/tests/Unit/LayersTest.php b/tests/Unit/LayersTest.php index 0d494ce..f93c2eb 100644 --- a/tests/Unit/LayersTest.php +++ b/tests/Unit/LayersTest.php @@ -1,7 +1,7 @@ testFile)) From 1cce4cb0b0305be7699be1ea8730e2a943549754 Mon Sep 17 00:00:00 2001 From: Patrick Organ Date: Tue, 28 May 2024 13:44:05 -0400 Subject: [PATCH 27/38] update readme --- README.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/README.md b/README.md index 3ad94c9..0f2d4d2 100644 --- a/README.md +++ b/README.md @@ -8,16 +8,6 @@ This package provides an easy-to-work-with class to convert a PDF to one or more image. -Spatie is a webdesign agency in Antwerp, Belgium. You'll find an overview of all our open source projects [on our website](https://spatie.be/opensource). - -## Support us - -[](https://spatie.be/github-ad-click/pdf-to-image) - -We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us). - -We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards). - ## Requirements You should have [Imagick](http://php.net/manual/en/imagick.setresolution.php) and [Ghostscript](http://www.ghostscript.com/) installed. From 1c84a549170c24fada55386d68a7ab723f784e78 Mon Sep 17 00:00:00 2001 From: Patrick Organ Date: Wed, 29 May 2024 00:59:44 -0400 Subject: [PATCH 28/38] wip --- src/DTOs/PageSize.php | 18 ++++++++++++++++++ src/Pdf.php | 33 +++++++++++++++++++++++---------- tests/Unit/LayersTest.php | 2 +- tests/Unit/PdfTest.php | 12 ++++-------- tests/Unit/SavesImagesTest.php | 8 ++++---- tests/Unit/SizesTest.php | 15 +++++++++++---- 6 files changed, 61 insertions(+), 27 deletions(-) create mode 100644 src/DTOs/PageSize.php diff --git a/src/DTOs/PageSize.php b/src/DTOs/PageSize.php new file mode 100644 index 0000000..474183b --- /dev/null +++ b/src/DTOs/PageSize.php @@ -0,0 +1,18 @@ +imagick === null) { $this->imagick = new Imagick(); - $this->imagick->readImage($this->filename); + $this->imagick->pingImage($this->filename); } if ($this->numberOfPages === null) { @@ -138,12 +139,26 @@ public function pageCount(): int return $this->numberOfPages; } + public function getSize(): PageSize + { + if ($this->imagick === null) { + $this->imagick = new Imagick(); + $this->imagick->pingImage($this->filename); + } + + $geometry = $this->imagick->getImageGeometry(); + + return PageSize::make($geometry['width'], $geometry['height']); + } + /** * Saves the PDF as an image. Expects a path to save the image to, which should be * a directory if multiple pages have been selected (otherwise the image will be overwritten). - * Returns either a string with a single filename that was written, or an array of paths to the saved images. + * Returns an array of paths to the saved images. + * + * @return string[] */ - public function saveImage(string $pathToImage, string $prefix = ''): array|string + public function save(string $pathToImage, string $prefix = ''): array { $pages = [PdfPage::make($this->pages[0], $this->outputFormat, $prefix, $pathToImage)]; @@ -162,14 +177,10 @@ public function saveImage(string $pathToImage, string $prefix = ''): array|strin } } - if (count($result) === 1) { - return $result[0]; - } - return $result; } - public function saveAllPagesAsImages(string $directory, string $prefix = ''): array|string + public function saveAllPages(string $directory, string $prefix = ''): array { $numberOfPages = $this->pageCount(); @@ -179,7 +190,7 @@ public function saveAllPagesAsImages(string $directory, string $prefix = ''): ar $this->selectPages(...range(1, $numberOfPages)); - return $this->saveImage($directory, $prefix); + return $this->save($directory, $prefix); } public function getImageData(string $pathToImage, int $pageNumber): Imagick @@ -294,8 +305,10 @@ protected function determineOutputFormat(string $pathToImage): OutputFormat protected function validatePageNumbers(int ...$pageNumbers) { + $count = $this->pageCount(); + foreach ($pageNumbers as $page) { - if ($page > $this->pageCount() || $page < 1) { + if ($page > $count || $page < 1) { throw PageDoesNotExist::for($page); } } diff --git a/tests/Unit/LayersTest.php b/tests/Unit/LayersTest.php index f93c2eb..a5a9762 100644 --- a/tests/Unit/LayersTest.php +++ b/tests/Unit/LayersTest.php @@ -5,7 +5,7 @@ it('can accept a layer', function () { $image = (new Pdf($this->testFile)) - ->layerMethod(\Spatie\PdfToImage\Enums\LayerMethod::Flatten) + ->layerMethod(\Spatie\PdfToImage\Enums\LayerMethod::None) ->resolution(72) ->getImageData('test.jpg', 1) ->getImageResolution(); diff --git a/tests/Unit/PdfTest.php b/tests/Unit/PdfTest.php index 743a23a..2459be2 100644 --- a/tests/Unit/PdfTest.php +++ b/tests/Unit/PdfTest.php @@ -29,12 +29,12 @@ expect($image['x'])->toEqual($resolution); expect($image['y'])->toEqual($resolution); }) - ->with([127, 150, 16]); + ->with([127, 16]); it('can select a single page', function () { - $imagick = (new Pdf($this->multipageTestFile)) - ->selectPage(2) - ->getImageData('page-2.jpg', 2); + $imagick = (new Pdf($this->testFile)) + ->selectPage(1) + ->getImageData('page-1.jpg', 1); expect($imagick)->toBeInstanceOf(Imagick::class); }); @@ -46,9 +46,5 @@ $imagick1 = $pdf ->getImageData('page-1.jpg', 1); - $imagick2 = $pdf - ->getImageData('page-3.jpg', 3); - expect($imagick1)->toBeInstanceOf(Imagick::class); - expect($imagick2)->toBeInstanceOf(Imagick::class); }); diff --git a/tests/Unit/SavesImagesTest.php b/tests/Unit/SavesImagesTest.php index d4fc387..d2dc00d 100644 --- a/tests/Unit/SavesImagesTest.php +++ b/tests/Unit/SavesImagesTest.php @@ -11,7 +11,7 @@ (new Pdf($this->testFile)) ->selectPage(1) - ->saveImage($targetFilename); + ->save($targetFilename); expect(file_exists($targetFilename))->toBeTrue(); }); @@ -20,17 +20,17 @@ (new Pdf($this->multipageTestFile)) ->selectPages(1, 3) ->format(\Spatie\PdfToImage\Enums\OutputFormat::Png) - ->saveImage($this->outputDirectory); + ->save($this->outputDirectory); foreach ([1, 3] as $pageNumber) { expect(file_exists($this->outputDirectory.'/'.$pageNumber.'.png'))->toBeTrue(); } }); -it('saves all pages to images', function () { +it('saves all pages as images', function () { (new Pdf($this->multipageTestFile)) ->format(\Spatie\PdfToImage\Enums\OutputFormat::Jpg) - ->saveAllPagesAsImages($this->outputDirectory); + ->saveAllPages($this->outputDirectory); foreach (range(1, 3) as $pageNumber) { expect(file_exists($this->outputDirectory.'/'.$pageNumber.'.jpg'))->toBeTrue(); diff --git a/tests/Unit/SizesTest.php b/tests/Unit/SizesTest.php index fe448dc..525211a 100644 --- a/tests/Unit/SizesTest.php +++ b/tests/Unit/SizesTest.php @@ -3,7 +3,7 @@ use Spatie\PdfToImage\Pdf; it('will create a thumbnail at specified sizes', function () { - $pdf = (new Pdf($this->multipageTestFile)); + $pdf = (new Pdf($this->testFile)); $imagick = $pdf ->thumbnailSize(400) @@ -29,11 +29,10 @@ ->with([ 'invalid width' => [-1, 100], 'invalid height' => [100, -1], - 'invalid size' => [-1, -1], ]); it('will create an image at specified sizes', function () { - $pdf = (new Pdf($this->multipageTestFile)); + $pdf = (new Pdf($this->testFile)); $size = $pdf ->size(400) @@ -59,5 +58,13 @@ ->with([ 'invalid width' => [-1, 100], 'invalid height' => [100, -1], - 'invalid size' => [-1, -1], ]); + +it("can get a PDF page's size", function () { + $pdf = new Pdf($this->testFile); + $size = $pdf->getSize(); + + expect($size->width)->toBeGreaterThanOrEqual(100); + expect($size->height)->toBeGreaterThanOrEqual(100); +}); + From 3664c56dbd988059774bec252d17e3e78520487a Mon Sep 17 00:00:00 2001 From: Patrick Organ Date: Wed, 29 May 2024 00:59:51 -0400 Subject: [PATCH 29/38] update readme --- README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 0f2d4d2..cb17f38 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Convert a PDF to an image [![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/pdf-to-image.svg?style=flat-square)](https://packagist.org/packages/spatie/pdf-to-image) -[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](.github/LICENSE.md) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) [![Quality Score](https://img.shields.io/scrutinizer/g/spatie/pdf-to-image.svg?style=flat-square)](https://scrutinizer-ci.com/g/spatie/pdf-to-image) [![StyleCI](https://styleci.io/repos/38419604/shield?branch=master)](https://styleci.io/repos/38419604) [![Total Downloads](https://img.shields.io/packagist/dt/spatie/pdf-to-image.svg?style=flat-square)](https://packagist.org/packages/spatie/pdf-to-image) @@ -29,12 +29,12 @@ Converting a PDF to an image is easy. ```php $pdf = new \Spatie\PdfToImage\Pdf($pathToPdf); -$pdf->saveImage($pathToWhereImageShouldBeStored); +$pdf->save($pathToWhereImageShouldBeStored); ``` If the filename you pass to `saveImage` has the extensions `jpg`, `jpeg`, `png`, or `webp` the image will be saved in that format; otherwise the output format will be `jpg`. -The `saveImage()` method returns an array with the filenames of the saved images if multiple images are saved, otherwise returns a string with the path to the saved image. +The `save()` method returns an array with the filenames of the saved images if multiple images are saved, otherwise returns a string with the path to the saved image. ## Other methods @@ -56,35 +56,35 @@ By default, only the first page of the PDF will be rendered. To render another p ```php $pdf->selectPage(2) - ->saveImage($pathToWhereImageShouldBeStored); //saves the second page + ->save($pathToWhereImageShouldBeStored); //saves the second page ``` Or, select multiple pages with the `selectPages()` method: ```php $pdf->selectPages(2, 4, 5) - ->saveImage($directoryToWhereImageShouldBeStored); //saves the 2nd, 4th and 5th pages + ->save($directoryToWhereImageShouldBeStored); //saves the 2nd, 4th and 5th pages ``` Change the output format: ```php $pdf->format(\Spatie\PdfToImage\Enums\OutputFormat::Webp) - ->saveImage($pathToWhereImageShouldBeStored); //the saved image will be in webp format + ->save($pathToWhereImageShouldBeStored); //the saved image will be in webp format ``` Set the output quality _(the compression quality)_ from 0 to 100: ```php $pdf->quality(90) // set an output quality of 90% - ->saveImage($pathToWhereImageShouldBeStored); + ->save($pathToWhereImageShouldBeStored); ``` Set the output resolution DPI: ```php $pdf->resolution(300) // resolution of 300 dpi - ->saveImage($pathToWhereImageShouldBeStored); + ->save($pathToWhereImageShouldBeStored); ``` Specify the thumbnail size of the output image: @@ -92,32 +92,32 @@ Specify the thumbnail size of the output image: ```php $pdf ->thumbnailSize(400) // set thumbnail width to 400px; height is calculated automatically - ->saveImage($pathToWhereImageShouldBeStored); + ->save($pathToWhereImageShouldBeStored); // or: $pdf ->thumbnailSize(400, 300) // set thumbnail width to 400px and the height to 300px - ->saveImage($pathToWhereImageShouldBeStored); + ->save($pathToWhereImageShouldBeStored); ``` Set the output image width: ```php $pdf->size(400) // set the width to 400px; height is calculated automatically - ->saveImage($pathToWhereImageShouldBeStored); + ->save($pathToWhereImageShouldBeStored); ``` Set the output image width and height: ```php $pdf->size(400, 300) // set the width to 400px and the height to 300px - ->saveImage($pathToWhereImageShouldBeStored); + ->save($pathToWhereImageShouldBeStored); ``` Save all pages to images: ```php -$pdf->saveAllPagesAsImages($directoryToWhereImagesShouldBeStored); +$pdf->saveAllPages($directoryToWhereImagesShouldBeStored); ``` Set the Merge Layer Method for Imagick: From e641b3b4ae0962d5c32c6d9d135e762a67b270a1 Mon Sep 17 00:00:00 2001 From: Patrick Organ Date: Wed, 29 May 2024 01:03:36 -0400 Subject: [PATCH 30/38] update readme --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index cb17f38..06b94cc 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,16 @@ $pdf->size(400, 300) // set the width to 400px and the height to 300px ->save($pathToWhereImageShouldBeStored); ``` +Get the dimensions of the PDF. This can be used to determine if the PDF is extremely high-resolution. + +```php +/** @var \Spatie\PdfToImage\DTOs\PageSize $size */ +$size = $pdf->getSize(); + +$width = $size->width; +$height = $size->height; +``` + Save all pages to images: ```php From 15dc729554036d3f73ee9584675f131fa8acf19e Mon Sep 17 00:00:00 2001 From: patinthehat Date: Wed, 29 May 2024 05:03:57 +0000 Subject: [PATCH 31/38] Fix styling --- src/Pdf.php | 2 +- tests/Unit/SizesTest.php | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Pdf.php b/src/Pdf.php index 451e482..2154b48 100644 --- a/src/Pdf.php +++ b/src/Pdf.php @@ -3,8 +3,8 @@ namespace Spatie\PdfToImage; use Imagick; -use Spatie\PdfToImage\DTOs\PdfPage; use Spatie\PdfToImage\DTOs\PageSize; +use Spatie\PdfToImage\DTOs\PdfPage; use Spatie\PdfToImage\Enums\LayerMethod; use Spatie\PdfToImage\Enums\OutputFormat; use Spatie\PdfToImage\Exceptions\InvalidLayerMethod; diff --git a/tests/Unit/SizesTest.php b/tests/Unit/SizesTest.php index 525211a..646e818 100644 --- a/tests/Unit/SizesTest.php +++ b/tests/Unit/SizesTest.php @@ -67,4 +67,3 @@ expect($size->width)->toBeGreaterThanOrEqual(100); expect($size->height)->toBeGreaterThanOrEqual(100); }); - From 99f18ad315d8d978b532fbfcb583531f6e805ef0 Mon Sep 17 00:00:00 2001 From: Patrick Organ Date: Wed, 29 May 2024 01:21:51 -0400 Subject: [PATCH 32/38] update readme --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 06b94cc..6e80138 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ [![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/pdf-to-image.svg?style=flat-square)](https://packagist.org/packages/spatie/pdf-to-image) [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) [![Quality Score](https://img.shields.io/scrutinizer/g/spatie/pdf-to-image.svg?style=flat-square)](https://scrutinizer-ci.com/g/spatie/pdf-to-image) -[![StyleCI](https://styleci.io/repos/38419604/shield?branch=master)](https://styleci.io/repos/38419604) [![Total Downloads](https://img.shields.io/packagist/dt/spatie/pdf-to-image.svg?style=flat-square)](https://packagist.org/packages/spatie/pdf-to-image) This package provides an easy-to-work-with class to convert a PDF to one or more image. From ddb0443b03e94fe9b948c637e677575579471570 Mon Sep 17 00:00:00 2001 From: Patrick Organ Date: Mon, 10 Jun 2024 09:17:56 -0400 Subject: [PATCH 33/38] requested minor adjustments from round 2 reviews --- src/Exceptions/InvalidLayerMethod.php | 2 +- src/Exceptions/InvalidQuality.php | 6 ++++-- src/Exceptions/InvalidSize.php | 10 ++++++---- src/Exceptions/PageDoesNotExist.php | 2 +- src/Exceptions/PdfDoesNotExist.php | 2 +- src/Pdf.php | 2 +- 6 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/Exceptions/InvalidLayerMethod.php b/src/Exceptions/InvalidLayerMethod.php index 8ffd409..401337d 100644 --- a/src/Exceptions/InvalidLayerMethod.php +++ b/src/Exceptions/InvalidLayerMethod.php @@ -6,7 +6,7 @@ class InvalidLayerMethod extends Exception { - public static function for(int $value) + public static function for(int $value): static { return new static("Invalid layer method value ({$value})."); } diff --git a/src/Exceptions/InvalidQuality.php b/src/Exceptions/InvalidQuality.php index 34cb072..1fb8c61 100644 --- a/src/Exceptions/InvalidQuality.php +++ b/src/Exceptions/InvalidQuality.php @@ -2,9 +2,11 @@ namespace Spatie\PdfToImage\Exceptions; -class InvalidQuality extends \Exception +use Exception; + +class InvalidQuality extends Exception { - public static function for(int $value): self + public static function for(int $value): static { return new static("Quality must be between 1 and 100, {$value} given."); } diff --git a/src/Exceptions/InvalidSize.php b/src/Exceptions/InvalidSize.php index f97564e..8561791 100644 --- a/src/Exceptions/InvalidSize.php +++ b/src/Exceptions/InvalidSize.php @@ -2,19 +2,21 @@ namespace Spatie\PdfToImage\Exceptions; -class InvalidSize extends \Exception +use Exception; + +class InvalidSize extends Exception { - public static function for(int $value, string $type, string $property): self + public static function for(int $value, string $type, string $property): static { return new static(ucfirst($type)." {$property} must be greater than or equal to 0, {$value} given."); } - public static function forThumbnail(int $value, string $property): self + public static function forThumbnail(int $value, string $property): static { return self::for($value, 'thumbnail', $property); } - public static function forImage(int $value, string $property): self + public static function forImage(int $value, string $property): static { return self::for($value, 'image', $property); } diff --git a/src/Exceptions/PageDoesNotExist.php b/src/Exceptions/PageDoesNotExist.php index da41621..00c83e5 100644 --- a/src/Exceptions/PageDoesNotExist.php +++ b/src/Exceptions/PageDoesNotExist.php @@ -6,7 +6,7 @@ class PageDoesNotExist extends Exception { - public static function for(int $pageNumber): self + public static function for(int $pageNumber): static { return new static("Page {$pageNumber} does not exist."); } diff --git a/src/Exceptions/PdfDoesNotExist.php b/src/Exceptions/PdfDoesNotExist.php index dde4fbc..d908a2e 100644 --- a/src/Exceptions/PdfDoesNotExist.php +++ b/src/Exceptions/PdfDoesNotExist.php @@ -6,7 +6,7 @@ class PdfDoesNotExist extends Exception { - public static function for(string $pdfFile): self + public static function for(string $pdfFile): static { return new static("File '{$pdfFile}' does not exist."); } diff --git a/src/Pdf.php b/src/Pdf.php index 2154b48..714f08f 100644 --- a/src/Pdf.php +++ b/src/Pdf.php @@ -303,7 +303,7 @@ protected function determineOutputFormat(string $pathToImage): OutputFormat return $outputFormat; } - protected function validatePageNumbers(int ...$pageNumbers) + protected function validatePageNumbers(int ...$pageNumbers): void { $count = $this->pageCount(); From c9dffbc714d376d7e2cbca570bc05c10f4973c6c Mon Sep 17 00:00:00 2001 From: Patrick Organ Date: Mon, 10 Jun 2024 09:19:15 -0400 Subject: [PATCH 34/38] add missing return type --- src/Enums/LayerMethod.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Enums/LayerMethod.php b/src/Enums/LayerMethod.php index 922f8a9..ec9ef55 100644 --- a/src/Enums/LayerMethod.php +++ b/src/Enums/LayerMethod.php @@ -25,7 +25,7 @@ enum LayerMethod: int case Mosaic = Imagick::LAYERMETHOD_MOSAIC; case TrimBounds = Imagick::LAYERMETHOD_TRIMBOUNDS; - public static function isValid(int $value) + public static function isValid(int $value): bool { return self::tryFrom($value) instanceof self; } From c4a2a3f437b5ed86152757e19a0cf533637e8eaf Mon Sep 17 00:00:00 2001 From: Patrick Organ Date: Mon, 10 Jun 2024 09:23:09 -0400 Subject: [PATCH 35/38] nitpicks --- src/Exceptions/InvalidSize.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Exceptions/InvalidSize.php b/src/Exceptions/InvalidSize.php index 8561791..7067552 100644 --- a/src/Exceptions/InvalidSize.php +++ b/src/Exceptions/InvalidSize.php @@ -13,11 +13,11 @@ public static function for(int $value, string $type, string $property): static public static function forThumbnail(int $value, string $property): static { - return self::for($value, 'thumbnail', $property); + return static::for($value, 'thumbnail', $property); } public static function forImage(int $value, string $property): static { - return self::for($value, 'image', $property); + return static::for($value, 'image', $property); } } From ec42f282f8ba8cadb70968697b05f2d09573c63d Mon Sep 17 00:00:00 2001 From: Patrick Organ Date: Mon, 10 Jun 2024 09:33:28 -0400 Subject: [PATCH 36/38] nitpicks --- src/Pdf.php | 73 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 65 insertions(+), 8 deletions(-) diff --git a/src/Pdf.php b/src/Pdf.php index 714f08f..b09ef64 100644 --- a/src/Pdf.php +++ b/src/Pdf.php @@ -39,7 +39,7 @@ class Pdf protected ?int $resizeHeight = null; - private ?int $numberOfPages = null; + protected ?int $numberOfPages = null; public function __construct(string $filename) { @@ -50,6 +50,13 @@ public function __construct(string $filename) $this->filename = $filename; } + /** + * Sets the resolution of the generated image in DPI. + * Default is 144. + * + * @param int $dpiResolution + * @return static + */ public function resolution(int $dpiResolution): static { $this->resolution = $dpiResolution; @@ -57,6 +64,13 @@ public function resolution(int $dpiResolution): static return $this; } + /** + * Sets the output format of the generated image. + * Default is OutputFormat::Jpg. + * + * @param \Spatie\PdfToImage\Enums\OutputFormat $outputFormat + * @return static + */ public function format(OutputFormat $outputFormat): static { $this->outputFormat = $outputFormat; @@ -125,6 +139,11 @@ public function selectPages(int ...$pages): static return $this; } + /** + * Returns the number of pages in the PDF. + * + * @return int + */ public function pageCount(): int { if ($this->imagick === null) { @@ -139,6 +158,12 @@ public function pageCount(): int return $this->numberOfPages; } + /** + * Returns a DTO representing the size of the PDF, which + * contains the width and height in pixels. + * + * @return \Spatie\PdfToImage\DTOs\PageSize + */ public function getSize(): PageSize { if ($this->imagick === null) { @@ -156,7 +181,7 @@ public function getSize(): PageSize * a directory if multiple pages have been selected (otherwise the image will be overwritten). * Returns an array of paths to the saved images. * - * @return string[] + * @return array */ public function save(string $pathToImage, string $prefix = ''): array { @@ -180,12 +205,20 @@ public function save(string $pathToImage, string $prefix = ''): array return $result; } + /** + * Saves all pages of the PDF as images. Expects a directory to save the images to, + * and an optional prefix for the image filenames. Returns an array of paths to the saved images. + * + * @param string $directory + * @param string $prefix + * @return array + */ public function saveAllPages(string $directory, string $prefix = ''): array { $numberOfPages = $this->pageCount(); if ($numberOfPages === 0) { - return false; + return []; } $this->selectPages(...range(1, $numberOfPages)); @@ -230,14 +263,22 @@ public function getImageData(string $pathToImage, int $pageNumber): Imagick return $this->imagick; } - public function colorspace(int $colorspace) + public function colorspace(int $colorspace): static { $this->colorspace = $colorspace; return $this; } - public function quality(int $compressionQuality) + /** + * Set the compression quality for the image. The value should be between 1 and 100, where + * 1 is the lowest quality and 100 is the highest. + * + * @param int $compressionQuality + * @return static + * @throws \Spatie\PdfToImage\Exceptions\InvalidQuality + */ + public function quality(int $compressionQuality): static { if ($compressionQuality < 1 || $compressionQuality > 100) { throw InvalidQuality::for($compressionQuality); @@ -252,11 +293,11 @@ public function quality(int $compressionQuality) * Set the thumbnail size for the image. If no height is provided, the thumbnail height will * be scaled according to the width. * - * @return $this + * @return static * * @throws \Spatie\PdfToImage\Exceptions\InvalidSize */ - public function thumbnailSize(int $width, ?int $height = null) + public function thumbnailSize(int $width, ?int $height = null): static { if ($width < 0) { throw InvalidSize::forThumbnail($width, 'width'); @@ -272,7 +313,15 @@ public function thumbnailSize(int $width, ?int $height = null) return $this; } - public function size(int $width, ?int $height = null) + /** + * Set the size of the image. If no height is provided, the height will be scaled according to the width. + * + * @param int $width + * @param int|null $height + * @return static + * @throws \Spatie\PdfToImage\Exceptions\InvalidSize + */ + public function size(int $width, ?int $height = null): static { if ($width < 0) { throw InvalidSize::forImage($width, 'width'); @@ -303,6 +352,14 @@ protected function determineOutputFormat(string $pathToImage): OutputFormat return $outputFormat; } + /** + * Validate that the page numbers are within the range of the PDF, which is 1 to the number of pages. + * Throws a PageDoesNotExist exception if a page number is out of range. + * + * @param int ...$pageNumbers + * @return void + * @throws \Spatie\PdfToImage\Exceptions\PageDoesNotExist + */ protected function validatePageNumbers(int ...$pageNumbers): void { $count = $this->pageCount(); From 8af515fdc89079ae056e77e5edbb4beb06988a89 Mon Sep 17 00:00:00 2001 From: patinthehat Date: Mon, 10 Jun 2024 13:34:19 +0000 Subject: [PATCH 37/38] Fix styling --- src/Pdf.php | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/Pdf.php b/src/Pdf.php index b09ef64..513df10 100644 --- a/src/Pdf.php +++ b/src/Pdf.php @@ -53,9 +53,6 @@ public function __construct(string $filename) /** * Sets the resolution of the generated image in DPI. * Default is 144. - * - * @param int $dpiResolution - * @return static */ public function resolution(int $dpiResolution): static { @@ -67,9 +64,6 @@ public function resolution(int $dpiResolution): static /** * Sets the output format of the generated image. * Default is OutputFormat::Jpg. - * - * @param \Spatie\PdfToImage\Enums\OutputFormat $outputFormat - * @return static */ public function format(OutputFormat $outputFormat): static { @@ -141,8 +135,6 @@ public function selectPages(int ...$pages): static /** * Returns the number of pages in the PDF. - * - * @return int */ public function pageCount(): int { @@ -161,8 +153,6 @@ public function pageCount(): int /** * Returns a DTO representing the size of the PDF, which * contains the width and height in pixels. - * - * @return \Spatie\PdfToImage\DTOs\PageSize */ public function getSize(): PageSize { @@ -209,8 +199,6 @@ public function save(string $pathToImage, string $prefix = ''): array * Saves all pages of the PDF as images. Expects a directory to save the images to, * and an optional prefix for the image filenames. Returns an array of paths to the saved images. * - * @param string $directory - * @param string $prefix * @return array */ public function saveAllPages(string $directory, string $prefix = ''): array @@ -274,8 +262,6 @@ public function colorspace(int $colorspace): static * Set the compression quality for the image. The value should be between 1 and 100, where * 1 is the lowest quality and 100 is the highest. * - * @param int $compressionQuality - * @return static * @throws \Spatie\PdfToImage\Exceptions\InvalidQuality */ public function quality(int $compressionQuality): static @@ -293,7 +279,6 @@ public function quality(int $compressionQuality): static * Set the thumbnail size for the image. If no height is provided, the thumbnail height will * be scaled according to the width. * - * @return static * * @throws \Spatie\PdfToImage\Exceptions\InvalidSize */ @@ -316,9 +301,6 @@ public function thumbnailSize(int $width, ?int $height = null): static /** * Set the size of the image. If no height is provided, the height will be scaled according to the width. * - * @param int $width - * @param int|null $height - * @return static * @throws \Spatie\PdfToImage\Exceptions\InvalidSize */ public function size(int $width, ?int $height = null): static @@ -356,8 +338,6 @@ protected function determineOutputFormat(string $pathToImage): OutputFormat * Validate that the page numbers are within the range of the PDF, which is 1 to the number of pages. * Throws a PageDoesNotExist exception if a page number is out of range. * - * @param int ...$pageNumbers - * @return void * @throws \Spatie\PdfToImage\Exceptions\PageDoesNotExist */ protected function validatePageNumbers(int ...$pageNumbers): void From 7a68ada53eabd7e707f8bd88b522e9cbbaa13674 Mon Sep 17 00:00:00 2001 From: Patrick Organ Date: Mon, 10 Jun 2024 14:38:37 -0400 Subject: [PATCH 38/38] wip --- src/Pdf.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Pdf.php b/src/Pdf.php index 513df10..a3ba2f2 100644 --- a/src/Pdf.php +++ b/src/Pdf.php @@ -52,7 +52,7 @@ public function __construct(string $filename) /** * Sets the resolution of the generated image in DPI. - * Default is 144. + * Default is 144 DPI. */ public function resolution(int $dpiResolution): static {