Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Ghostscript Laravel Package

org_heigl/ghostscript

A PHP wrapper around Ghostscript for processing PDF and PostScript files from your application. It offers an object-oriented API to convert, merge, split, and render documents, making tasks like generating thumbnails and manipulating PDFs easier without writing raw shell commands.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a thin, framework-agnostic wrapper around the Ghostscript CLI binary. There is no Laravel service provider, no config file, and no facade — you install the binary, composer require the wrapper, and wire it up yourself. Budget an hour, not a sprint.

Steps

  1. Install the underlying binary first. The PHP package does nothing without it:
    # Debian/Ubuntu image
    apt-get update && apt-get install -y ghostscript
    gs --version   # must print, e.g. 10.02.1
    
    Add this to your Dockerfile/sail/Forge provisioning. On Windows it's gswin64c, which the wrapper historically does not pick up automatically.
  2. composer require org_heigl/ghostscript
  3. Read src/Ghostscript.php before anything else. It is the whole API surface: an input file, an output file, a device, a resolution, and createImage(). Everything else lives in src/Device/ (Jpeg, Png, Tiff, Pdf, Text) and src/Exception/. Five minutes there beats any documentation.
  4. First use case: PDF thumbnail for an admin list view. Someone uploads a 40-page specification PDF; you show page 1 as a 200px JPEG in the table.
use Org_Heigl\Ghostscript\Ghostscript;

$gs = new Ghostscript();
$gs->setInputFile(storage_path('app/inbox/spec.pdf'));
$gs->setOutputFile(storage_path('app/tmp/spec.jpg'));
$gs->setDevice('jpeg');          // or a Device\Jpeg instance, depending on version
$gs->setResolution(150);
$gs->createImage();              // invoke gs and write the file

The wrapper's job is building the gs command line and calling it. If createImage() returns without throwing, check the file exists and is non-zero bytes — a gs run can "succeed" with an unusable output.

Implementation Patterns

Wrap it in one service class, once. You will otherwise repeat binary-path, temp-file, and cleanup logic across five controllers. Bind a PdfPreviewer in a service provider and inject it:

final class PdfPreviewer
{
    public function thumbnail(string $localPdfPath, int $dpi = 150): string
    {
        $out = tempnam(sys_get_temp_dir(), 'gs_') . '.jpg';

        (new Ghostscript())
            ->setInputFile($localPdfPath)
            ->setOutputFile($out)
            ->setDevice('jpeg')
            ->setResolution($dpi)
            ->createImage();

        return $out; // caller moves it to a disk and unlinks
    }
}

Always convert from a local path, never a stream. Ghostscript is a subprocess: it opens files by path. Storage::disk('s3')->get() into a tmpfile(), convert, then Storage::disk('s3')->put() the result and unlink() the temp files in a finally. Keep temp paths free of spaces and non-ASCII characters.

Do it in a queued job. PDF rasterisation is CPU-bound and unbounded in duration. implements ShouldQueue, set $timeout = 120, $tries = 2, and add a size/page guard before dispatching so a 300MB upload doesn't pin a worker for ten minutes:

dispatch(new GeneratePdfThumbnail($document))->onQueue('media');

Choose the device by intent, not by habit:

Intent Device Notes
Thumbnail / preview jpeg or png16m 72–150 dpi is plenty; 300 dpi for print proofs
Normalise / shrink / merge pdfwrite Ghostscript re-distils; good for flattening before storage
Text extraction / search index txtwrite Cheaper than a full PDF parser for plain text
Multi-page archive tiff Big files; rarely what you want in a web app

Multi-page output requires a %d placeholder in the output filename. Without it you get one file (last page wins) or a Ghostscript error:

$gs->setOutputFile(storage_path('app/tmp/page-%03d.jpg')); // page-001.jpg, page-002.jpg …

For a first-page-only preview, either pass %d and keep page-001, or drop to a raw -dFirstPage=1 -dLastPage=1 flag if the wrapper version you have exposes extra arguments.

Pair with Intervention Image / your existing pipeline for the resize-and-store step rather than fighting Ghostscript's own -r and -g flags. Let Ghostscript produce a clean 150 dpi raster; let your image library handle cropping and WebP conversion.

Testing: don't shell out in CI unless the binary is present. Bind a fake PdfPreviewer that copies a fixture JPEG, and keep exactly one integration test guarded by if (! shell_exec('command -v gs')) { $this->markTestSkipped(...); }. Storage::fake() for the disk side as usual.

Gotchas and Tips

  • The binary is not vendored. The single most common failure is a green local machine and a sh: gs: not found in production. Make gs --version part of your container healthcheck or a deploy smoke test.
  • Filename extension must match the device. jpeg writing to .png produces a JPEG that browsers, Intervention, and your MIME sniffer will all disagree about. Keep device and extension derived from a single config value.
  • No Laravel package discovery. You own the binding, the config, and the shell_exec trust boundary. With 40 stars and zero dependents, assume you are the maintainer of last resort: pin the version in composer.json rather than floating ^.
  • Untrusted PDFs. PostScript is a programming language. Ghostscript 9.50+ defaults to -dSAFER, but a malicious or malformed PDF can still hang or OOM the process. Sanitise by re-distilling through pdfwrite before you trust a file, cap the page count, and run conversions in a queue worker with a hard $timeout — not in the request cycle.
  • Check the artefact, not the exit code. Verify file_exists() and filesize() > 0 after createImage(). Silent empty output is the classic symptom of a wrong device/extension pair or an unwritable target directory — very common when the temp dir is owned by root but PHP-FPM runs as www-data.
  • Long-lived workers and shared state. If you use a singleton accessor (Ghostscript::getInstance()), instance state can leak between jobs in a queue worker. Prefer new Ghostscript() per conversion, or explicitly reset input/output/device each time.
  • Version sensitivity. Ghostscript flag behaviour changes across major versions (notably -dSAFER in 9.50), and the wrapper exposes a version setter for a reason. Pin the Ghostscript version in your image alongside the PHP dependency so an OS package bump doesn't silently change output.
  • Spaces and shell injection. File paths with spaces, quotes, or ; are the wrapper's problem only if it escapes them. Namespace your uploads to UUID filenames and store them under storage/app/tmp/ — this sidesteps the issue entirely.
  • Consider the escape hatch. If you need page ranges, password-protected PDFs, -dPDFSETTINGS=/ebook, or progress reporting, this wrapper will run out of API. A 20-line Symfony Process call gives you full flag control and is a legitimate replacement for a 40-star dependency; spatie/pdf-to-image (Imagick-backed) is the other common route if you already have Imagick compiled with Ghostscript support.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons