Merging Multiple Photos and PDFs into a Single PDF
I've built a method that merges multiple photos and PDFs into a single large PDF file using the following libraries:
use Imagick;
use Spatie\PdfToImage\Pdf;
I'm developing this on a Windows 11 machine.
Issue
When I hardcode the source and destination paths as follows:
$sourcePath = "C:\\Users\\test\\testPhotoCreation\\sourcePathForPdf\\";
$destinationPdfPath = "C:\\Users\\test\\testPhotoCreation\\pdfCreatedDestination\\merged.pdf";
everything works as expected.
However, when I modify the implementation to accept a user-inputted path (retrieved via an HTML form), I get the following error when executing this line:
$pdf = new Pdf($file);
Error Message
FailedToExecuteCommand `"gs" -sstdout=%stderr -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 "-sDEVICE=pamcmyk32" -dTextAlphaBits=4 -dGraphicsAlphaBits=4 "-r72x72" -dPrinted=false "-sOutputFile=C:/Users/test/AppData/Local/Temp/magick-gzSUuBgN1oiEKEsxRVP20rd1XIgpCdtM%d" "-fC:/Users/test/AppData/Local/Temp/magick-3ZbYt3YgbMobScyYalUK5TNBTNwjgoPn`
Debugging Steps Taken
- Ensured Proper Path Formatting: Used
\\in paths, but the issue persists. - Reverted Formatting Changes: Still encountering the same error.
- Only Difference: The failing scenario dynamically receives paths from user input instead of being hardcoded.
Question
What could be causing this issue when using a dynamically received file path? Could it be related to:
- Path encoding issues on Windows?
- Permissions or security settings?
- Ghostscript (gs) not handling dynamically set paths correctly?
I've attached my code below, with the original hardcoded paths commented out.
Full PHP Code
<?php
namespace App\Custom_class;
use Imagick;
use Spatie\PdfToImage\Pdf;
use Illuminate\Support\Str;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
class Utilities
{
public static function createPdfFromFiles($sourcePath,$destinationPdfPath)
{
// $sourcePath = "C:\\Users\\test\\testPhotoCreation\\sourcePathForPdf\\";
// $destinationPdfPath = "C:\\Users\\test\\testPhotoCreation\\pdfCreatedDestination\\merged.pdf";
$sourcePath = trim($sourcePath);
$destinationPdfPath = trim($destinationPdfPath);
if (!Str::endsWith($sourcePath, '\\')) {
$sourcePath .= '\\'; // Append backslash if missing
}
if (!Str::endsWith($destinationPdfPath, '\\')) {
$destinationPdfPath .= '\\'; // Append backslash if missing
}
// Temporary directory to store modified files before merging
$sourcePathTempFiles = $sourcePath.'tempFiles';
// Copy all files
$files = glob($sourcePath."*");
if (!is_dir($sourcePathTempFiles)) {
mkdir($sourcePathTempFiles, 0777, true);
}
foreach ($files as $file) {
if (is_file($file)) {
copy($file, $sourcePathTempFiles . DIRECTORY_SEPARATOR . basename($file));
}
}
// Ensure destination folder exists
if (!is_dir(dirname($destinationPdfPath))) {
mkdir(dirname($destinationPdfPath), 0777, true);
}
// Get all image and PDF files from source directory
$files = glob($sourcePathTempFiles . '/*.{jpg,jpeg,png,gif,bmp,pdf}', GLOB_BRACE);
$images = []; // Array to store image paths
foreach ($files as $file) {
$extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
if (in_array($extension, ['jpg', 'jpeg', 'png', 'gif', 'bmp'])) {
// It's an image
$images[] = $file;
} elseif ($extension === 'pdf') {
// Convert PDF to images
$file = str_replace('/', '\\', trim($file));
$filename = pathinfo($file, PATHINFO_FILENAME);
$pdf = new Pdf($file);
$pdfOutputPath = $sourcePathTempFiles. '/temp_pdf_images_' . $filename;
if (!is_dir($pdfOutputPath)) {
mkdir($pdfOutputPath, 0777, true);
}
// Convert each page in PDF to an image
foreach (range(1, $pdf->getNumberOfPages()) as $pageNumber) {
$imagePath = "$pdfOutputPath/page_$pageNumber.jpg";
$pdf->setPage($pageNumber)->saveImage($imagePath);
$images[] = $imagePath;
}
}
}
if (empty($images)) {
throw new \Exception("No valid images or PDFs found to merge.");
}
// Sort images based on file number
$newImageArray = [];
foreach ($images as $image) {
if (Str::contains($image, 'temp_pdf_images')) {
$orderNumber = Str::before(Str::after($image, 'temp_pdf_images_'), '/');
$pageNumber = Str::beforeLast(Str::after($image, 'page_'), '.');
$tempImageInfo = [
'path' => $image,
'orderNumber' => $orderNumber,
'pageNumber' => $pageNumber
];
$newImageArray[] = $tempImageInfo;
} else {
$temp = Str::afterLast($image, '/');
$orderNumber = Str::beforeLast($temp, '.');
$tempImageInfo = [
'path' => $image,
'orderNumber' => $orderNumber,
];
$newImageArray[] = $tempImageInfo;
}
}
$collectionImage = collect($newImageArray);
$sortedImageArray = $collectionImage->sortBy([
['orderNumber', 'asc'],
['pageNumber', 'asc'],
])->values();
// Create a new Imagick instance
$imagick = new Imagick();
// Read images into Imagick
foreach ($sortedImageArray as $image) {
$img = new Imagick($image['path']);
$img->setImageFormat('pdf'); // Ensure each image is treated as a PDF page
$imagick->addImage($img);
}
// Merge images into a single PDF
$imagick->setImageFormat('pdf');
$imagick->writeImages($destinationPdfPath, true);
// Delete the temporary folder
if (File::exists($sourcePathTempFiles)) {
File::deleteDirectory($sourcePathTempFiles);
}
}
}
Summary
This function:
- Copies all images and PDFs into a temporary folder.
- Processes PDFs by converting each page into an image.
- Sorts files by order number and page number.
- Merges all images into a single PDF using
Imagick. - Deletes the temporary folder after processing.
Any insights or suggestions to resolve the issue with dynamically received file paths would be greatly appreciated!
Please or to participate in this conversation.