From 171def165d56ed63d151366e801178274cf4e171 Mon Sep 17 00:00:00 2001 From: "M. Vugteveen" Date: Fri, 1 Aug 2025 22:23:03 +0200 Subject: [PATCH 1/3] Update composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 16ae878..d54a928 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "minimum-stability": "dev", "require": { "php": "^8.1", - "symfony/process": "^6.4.2" + "symfony/process": "^6.4.2|^7.2" }, "require-dev": { "phpunit/phpunit": "^9.6" From 6cfad8a58b15b2be1c050db80406d28f92195894 Mon Sep 17 00:00:00 2001 From: "M. Vugteveen" Date: Thu, 25 Sep 2025 11:34:32 +0200 Subject: [PATCH 2/3] use implode, allow setting qpdf path --- src/Pdf.php | 138 +++++++++++++++++++++++----------------------------- 1 file changed, 60 insertions(+), 78 deletions(-) diff --git a/src/Pdf.php b/src/Pdf.php index c9fd2ba..fe7836e 100644 --- a/src/Pdf.php +++ b/src/Pdf.php @@ -3,104 +3,107 @@ namespace Msmahon\QpdfPhpWrapper; use Exception; +use Symfony\Component\Process\Process; use Msmahon\QpdfPhpWrapper\Enums\ExitCode; use Msmahon\QpdfPhpWrapper\Enums\Rotation; use Symfony\Component\Process\Exception\ProcessFailedException; -use Symfony\Component\Process\Process; class Pdf { + protected string $qpdfPath = 'qpdf'; + + public function setQpdfPath(string $path) + { + $this->qpdfPath = $path; + + return $this; + } + /** * Get the version of qpdf installed on the server * - * @return int * @throws Exception if version cannot be determined */ public function getQpdfVersion(): int { - $process = new Process(['qpdf', '--version']); + $process = new Process([$this->qpdfPath, '--version']); $process->run(); preg_match('/qpdf version (?\d+)\./', $process->getOutput(), $matches); if (! $this->isSuccessful($process)) { throw new ProcessFailedException($process); } - return (int)$matches['version']; + + return (int) $matches['version']; } /** * Validate a file is a pdf and is not corrupted * - * @param string $path local file path - * @return bool + * @param string $path local file path */ public function fileIsPdf(string $path): bool { - $process = new Process(['qpdf', '--check', $path]); + $process = new Process([$this->qpdfPath, '--check', $path]); $process->run(); + return $this->isSuccessful($process); } /** * Get the number of pages in a pdf * - * @param string $path - * @return int * @throws ProcessFailedException */ public function getNumberOfPages(string $path): int { - $process = new Process(['qpdf', '--show-npages', $path]); + $process = new Process([$this->qpdfPath, '--show-npages', $path]); $process->run(); - if (!$this->isSuccessful($process)) { + if (! $this->isSuccessful($process)) { throw new ProcessFailedException($process); } preg_match('/\d+/', $process->getOutput(), $pages); - return (int)$pages[0]; + + return (int) $pages[0]; } /** * Rotate a pdf * - * @param string $path - * @param Rotation $direction - * @param string $range - * @return bool * @throws ProcessFailedException */ public function rotate(string $path, Rotation $direction, string $range): bool { - $process = new Process(['qpdf', $path, "--rotate=$direction->value:$range", '--', '--replace-input']); + $process = new Process([$this->qpdfPath, $path, "--rotate=$direction->value:$range", '--', '--replace-input']); $process->run(); - if (!$this->isSuccessful($process)) { + if (! $this->isSuccessful($process)) { throw new ProcessFailedException($process); } + return true; } /** * Remove all pages but the specified range * - * @param string $path - * @param string|int $range - * @return bool * @throws ProcessFailedException */ public function trimToRange(string $path, string|int $range): bool { - $process = new Process(['qpdf', $path, '--pages', '.', $range, '--', '--replace-input']); + $process = new Process([$this->qpdfPath, $path, '--pages', '.', $range, '--', '--replace-input']); $process->run(); - if (!$this->isSuccessful($process)) { + if (! $this->isSuccessful($process)) { throw new ProcessFailedException($process); } + return true; } /** * Combine pages from multiple pdfs into one pdf * - * @param array $filePages Each file should have its own subarray - * with the document filepath to copy from and optional page range. - * Leaving the page range blank will copy the whole document. + * @param array $filePages Each file should have its own subarray + * with the document filepath to copy from and optional page range. + * Leaving the page range blank will copy the whole document. * * [ * ['/path/to/first.jpeg'], @@ -108,25 +111,21 @@ public function trimToRange(string $path, string|int $range): bool * ['/path/to/third'] * ] * - * @param string $outputPath - * @return bool * @throws ProcessFailedException */ public function combineRangesFromFiles(array $filePages, string $outputPath): bool { - $process = new Process(['qpdf', '--empty', '--pages', ...$this->flatten($filePages), '--', $outputPath]); + $process = new Process([$this->qpdfPath, '--empty', '--pages', ...$this->flatten($filePages), '--', $outputPath]); $process->run(); - if (!$this->isSuccessful($process)) { + if (! $this->isSuccessful($process)) { throw new ProcessFailedException($process); } + return true; } /** * Flatten a multi-dimensional array - * - * @param array $array - * @return array */ private function flatten(array $array): array { @@ -134,27 +133,25 @@ private function flatten(array $array): array array_walk_recursive($array, function ($a) use (&$return) { $return[] = $a; }); + return $return; } /** * Copy pages from one pdf to another * - * @param string $path - * @param string $outputPath - * @param string $range - * @return bool * @throws ProcessFailedException * @throws Exception */ public function copyPages(string $path, string $outputPath, string $range): bool { $pages = $this->parseRange($range, $path); - $process = new Process(['qpdf', '--empty', '--pages', $path, join(',', $pages), '--', $outputPath]); + $process = new Process([$this->qpdfPath, '--empty', '--pages', $path, implode(',', $pages), '--', $outputPath]); $process->run(); - if (!$this->isSuccessful($process)) { + if (! $this->isSuccessful($process)) { throw new ProcessFailedException($process); } + return true; } @@ -164,44 +161,41 @@ public function copyPages(string $path, string $outputPath, string $range): bool * This method works by overwriting the original file with the * inverse of the range you wish to remove. Opposite of trimToRange. * - * @param string $path - * @param string|int $range - * @return bool * @throws Exception */ public function removePages(string $path, string|int $range): bool { $pagesToCopy = implode(',', array_diff(range(1, $this->getNumberOfPages($path)), $this->parseRange($range, $path))); - $process = new Process(['qpdf', $path, '--pages', $path, $pagesToCopy, '--', '--replace-input']); + $process = new Process([$this->qpdfPath, $path, '--pages', $path, $pagesToCopy, '--', '--replace-input']); $process->run(); - if (!$this->isSuccessful($process)) { + if (! $this->isSuccessful($process)) { throw new ProcessFailedException($process); } + return true; } /** * Get PDF info in json format * - * @param string $path - * @return mixed * @throws ProcessFailedException */ public function jsonInfo(string $path): mixed { - $process = new Process(['qpdf', $path, '--json']); + $process = new Process([$this->qpdfPath, $path, '--json']); $process->run(); - if (!$this->isSuccessful($process)) { + if (! $this->isSuccessful($process)) { throw new ProcessFailedException($process); } + return json_decode($process->getOutput()); } /** * Get page size in inches * - * @param string $path * @return array Example: [[8.5, 11], [11, 8.5]] + * * @throws Exception */ public function pageSizes(string $path): array @@ -209,30 +203,26 @@ public function pageSizes(string $path): array $pagesData = $this->getPagesData($path); $dimensions = $this->getPagesDimensions($pagesData); $orientations = $this->getPagesOrientations($pagesData); + return array_map(function ($index, $page) use ($orientations) { if (isset($orientations[$index]) && $orientations[$index] % 180 !== 0) { - return array_reverse((array)$page); + return array_reverse((array) $page); } + return $page; }, array_keys($dimensions), array_values($dimensions)); } /** * Get page orientation in degrees - * - * @param mixed $pagesData - * @return array */ private function getPagesOrientations(mixed $pagesData): array { - return array_map(fn($object) => $object->{'/Rotate'} ?? null, $pagesData); + return array_map(fn ($object) => $object->{'/Rotate'} ?? null, $pagesData); } /** * Get page dimensions in inches - * - * @param mixed $pagesData - * @return array */ private function getPagesDimensions(mixed $pagesData): array { @@ -241,6 +231,7 @@ private function getPagesDimensions(mixed $pagesData): array if ($dimensions) { return array_map(fn ($value) => $value / 72, array_slice($dimensions, 2)); } + return null; }, $pagesData); } @@ -248,11 +239,9 @@ private function getPagesDimensions(mixed $pagesData): array /** * Get page data from qpdf json info * - * @param string $path - * @return array * @throws Exception */ - private function getPagesData(string $path) : array + private function getPagesData(string $path): array { $jsonInfo = $this->jsonInfo($path); $documentObjects = $this->getQpdfVersion() < 11 ? $jsonInfo->objects : $jsonInfo->qpdf[1]; @@ -261,8 +250,10 @@ private function getPagesData(string $path) : array if (isset($page->{'/Type'}) && $page->{'/Type'} === '/Page') { return $page; } + return null; }, get_object_vars($documentObjects)); + return array_values(array_filter($pages)); } @@ -272,12 +263,9 @@ private function getPagesData(string $path) : array * Example: '1,3,2,5-8,10-z' becomes [1,2,3,5,6,8,10,11,12] (assuming 12-page document) * You may only use 'z' with a file path * - * @param string $range - * @param string|null $path - * @return array * @throws Exception */ - public function parseRange(string $range, string $path = null): array + public function parseRange(string $range, ?string $path = null): array { $pages = explode(',', $range); $result = []; @@ -294,56 +282,50 @@ public function parseRange(string $range, string $path = null): array $result = array_merge($result, range($range[0], $range[1])); } } else { - $result[] = intval($page); + $result[] = (int) $page; } } if (isset($path)) { // ignore ranges beyond the number of pages in the document - $result = array_filter($result, fn($page) => $page <= $this->getNumberOfPages($path)); + $result = array_filter($result, fn ($page) => $page <= $this->getNumberOfPages($path)); } array_unique((array) sort($result)); + return $result; } /** * Overlay pdf on another pdf * - * @param string $documentPath - * @param string $stampPath - * @param string|null $range - * @return void * @throws Exception */ - public function applyStamp(string $documentPath, string $stampPath, string $range = null): void + public function applyStamp(string $documentPath, string $stampPath, ?string $range = null): void { // Overlay stamp on copy if (isset($range)) { $range = $this->parseRange($range, $documentPath); - $range = ['--to=' . join(',', $range)]; + $range = ['--to=' . implode(',', $range)]; } else { $range = ['--repeat=1']; } $process = new Process([ - 'qpdf', + $this->qpdfPath, $documentPath, '--overlay', $stampPath, ...$range, '--', - '--replace-input' + '--replace-input', ]); $process->run(); - if (!$this->isSuccessful($process)) { + if (! $this->isSuccessful($process)) { throw new ProcessFailedException($process); } } /** * Many PDFs are not completely valid, but can still be processed by qpdf. - * - * @param Process $process - * @return bool */ private function isSuccessful(Process $process): bool { From c4533db0a5553b6c6b3f0ca3b15dde9cfb8608ad Mon Sep 17 00:00:00 2001 From: "M. Vugteveen" Date: Tue, 28 Apr 2026 14:04:27 +0200 Subject: [PATCH 3/3] Update symfony/process version requirement --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index d54a928..0aeb3d4 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "minimum-stability": "dev", "require": { "php": "^8.1", - "symfony/process": "^6.4.2|^7.2" + "symfony/process": "^6.4.2|^7.2|^8.0" }, "require-dev": { "phpunit/phpunit": "^9.6"