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

Tc Lib Pdf Image Laravel Package

tecnickcom/tc-lib-pdf-image

Image import and embedding utilities for PDF streams. Handles PNG/JPEG and GD-based formats, transparency, palettes, caching, alternate images, and object generation for PDF embedding. Part of the TCPDF ecosystem. Requires PHP 8.2+, gd, zlib.

View on GitHub
Deep Wiki
Context7

Getting Started

This is not a PDF writer. tc-lib-pdf-image is the image-ingest half of the TCPDF ecosystem: it decodes a file, normalizes it (palette, alpha, ICC, DPI), and returns an image ID plus metadata that a PDF document object later embeds. Reaching for it directly only makes sense when you want to separate "prepare images" from "render document" — otherwise install tecnickcom/tc-lib-pdf and let it pull this in transitively.

Prerequisites and first check

  • PHP 8.2+, ext-gd, ext-zlib. Confirm with php -m | grep -E 'gd|zlib' — a missing GD fails at import time, not install time.
  • There is no Laravel service provider, facade, or config shipped. You write the wiring.
  • Read the README Quick Start first, then the generated API docs at tcpdf.org/docs/srcdoc/tc-lib-pdf-image for the real Import signature and return shapes. The README covers the happy path only.

Minimal wiring

composer require tecnickcom/tc-lib-pdf-image
$encrypt    = new \Com\Tecnick\Pdf\Encrypt\Encrypt();
$fileHelper = new \Com\Tecnick\File\File(
    allowedHosts: config('pdf-image.allowed_hosts'),   // e.g. ['cdn.example.com']
    allowedPaths: config('pdf-image.allowed_paths'),   // e.g. [storage_path('app/images')]
);

$import = new \Com\Tecnick\Pdf\Image\Import(
    kunit: 72 / 25.4,          // points-per-unit; 1.0 = points, this value = millimetres
    encrypt: $encrypt,
    fileHelper: $fileHelper,
);

$imageId = $import->add(storage_path('app/images/logo.png'));

Three things bite people on day one:

  1. kunit must match your document units. If the surrounding PDF object is built in millimetres and you pass 1.0, image dimensions and DPI-derived scaling will be wrong by a factor of ~2.83. Derive it from one place in config.
  2. File allowlists are mandatory in practice. Without entries in allowedPaths/allowedHosts, local and remote loads are refused. Populate them from config, not hard-coded strings, and use absolute paths (Storage::disk('local')->path(...)).
  3. Encrypt is required even when you are not encrypting. Pass the same Encrypt instance the document uses when encryption is enabled — a fresh one will embed image streams that don't match the document's crypto state.

First real use case: a Laravel invoice/statement job that embeds a tenant logo and a signature image. Import both once at the top of the job, keep the returned IDs, and reuse them for every page and every line item — repeated add() calls on the same instance hit the in-memory cache and cost almost nothing.

Implementation Patterns

Bind it in a service provider, scope it per document. Import's default cache lives for the lifetime of the instance. Under Octane or a long-running queue worker, a singleton binding is a memory leak: every processed image stays resident forever. Use $this->app->scoped(...) or resolve a fresh instance per job.

$this->app->singleton(\Com\Tecnick\Pdf\Encrypt\Encrypt::class);
$this->app->bind(\Com\Tecnick\Pdf\Image\Import::class, fn ($app) => new Import(
    kunit: (float) config('pdf-image.kunit'),
    encrypt: $app->make(Encrypt::class),
    fileHelper: new File(
        allowedHosts: config('pdf-image.allowed_hosts'),
        allowedPaths: config('pdf-image.allowed_paths'),
    ),
    imageCache: $app->make(ImageCacheInterface::class),
));

Wrap it in your own ImageImporter service so controllers and jobs never touch the vendor class directly. That gives you validation, metrics, placeholder fallback, and a single place to swap the package out.

Resolve remote assets locally first. S3 disks cannot be handed to add() directly, and pre-signed URLs are a cache-key disaster (see Gotchas). The reliable pattern is: copy/download to a scratch path under an allowed local directory, import, then delete the temp file. Keep the allowlist narrow — it is an SSRF and path-traversal control, not a formality.

Persistent cache: implement ImageCacheInterface with Laravel's cache, but respect the contract.

final class LaravelImageCache implements ImageCacheInterface
{
    public function get(string $key): ?array
    {
        $v = Cache::store(config('pdf-image.cache_store'))->get('pdfimg:'.hash('xxh128', $key));
        return is_array($v) ? $v : null;
    }

    public function set(string $key, array $data): void
    {
        Cache::store(config('pdf-image.cache_store'))->put(
            'pdfimg:'.hash('xxh128', $key), $data, now()->addDays(30)
        );
    }
}

Use it for brand assets reused across thousands of documents — that is the whole point of the interface. For one-off tenant uploads the in-memory cache is enough; a persistent cache just adds an invalidation problem.

Batch pipelines. Put PDF generation in a job, catch import failures per asset, log the path and exception, and substitute a bundled placeholder so one corrupt upload doesn't fail an entire nightly run. Fail fast with getimagesize() and a byte/dimension ceiling before calling add() — the library will happily decompress a 60 MP phone photo into ~240 MB of GD memory.

Share objects with the PDF layer. One Encrypt instance per document, shared between Import and the document builder. One kunit constant in config consumed by both. Treat image IDs as opaque document-scoped handles and never persist them across requests.

Testing. Storage::fake('images') plus two or three fixture files (opaque PNG, alpha PNG, baseline JPEG, one broken file). Assert: numeric ID returned, second add() of the same path returns the same ID, unsupported/corrupt input throws, and the allowlist rejects a path outside storage_path. Your CI image needs php-gd — the default php:8.x-cli has it, slim variants may not.

Extension seams are thin. ImageCacheInterface is the only designed-in extension point. Otherwise you subclass or decorate Import and File. There is no event system, no logging hooks, no plugin registry — plan for a wrapper class rather than monkey-patching.

Gotchas and Tips

  • Cache keys for local files fold in mtime and size, so in-place edits invalidate correctly. Remote URLs do not get that treatment — content changing behind a stable URL will serve stale images indefinitely. Version your CDN paths (logo-v3.png) or include a content hash in the key.
  • Pre-signed/temporary URLs are cache poison. Each signature produces a new key, so you get zero hits and unbounded cache growth. Normalize to a stable identifier, or download-then-import.
  • The cache store is a trust boundary. Bytes from it are embedded verbatim into PDFs. Never point it at user-writable storage. If you serialize, deserialize with allowed_classes => false and write atomically (LOCK_EX), exactly as the README's filesystem example does. Namespace keys with the package version so upgrades don't read stale structures.
  • Allowlist matching is literal and brittle. Trailing slashes, symlinks, .. segments, and differing case all cause misses that surface as confusing "file not found" or permission exceptions from tc-lib-file, not this package. Use realpath() when you build the list, and test with the exact strings your app produces.
  • GD only — no Imagick. WebP/AVIF availability depends on how GD was compiled; SVG is not handled here. CMYK JPEGs, 16-bit PNGs, and exotic ICC profiles can produce surprising colours or outright failures. Test with the actual files your users upload; do not assume "PNG works" covers all PNGs.
  • Warnings become exceptions. Laravel's error handler converts GD warnings to ErrorException. A malformed image can therefore throw from deep inside the import with a message that looks unrelated to your code. Catch \Throwable around add() at the asset boundary and always log the source path.
  • Memory is the real failure mode, and it is fatal, not catchable. Cap upload dimensions before import, raise memory_limit for the queue worker explicitly, and prefer downscaling with GD/Intervention before handing anything to the library.
  • Debugging: the useful values are the returned image ID plus width/height/DPI/colourspace of the imported asset. If the public API doesn't expose them, temporarily subclass Import or use reflection to dump the internal structure; log that alongside the document ID so you can correlate a bad render to an asset.
  • kunit drift between the importer and the document builder produces images that are subtly the wrong size — often only noticed in print. Keep one config value and assert it in a test.
  • Do not hold a singletonImport. Per-instance memory cache + long-lived worker = steadily climbing RSS. scoped or per-job resolution.
  • Version pinning matters more than usual. Six stars, README-level maturity, one-person maintenance. Pin ^3.14 in composer.json, read the release notes (3.14.x is a long patch line), and avoid undocumented methods — the public surface you should rely on is the constructor, add(), and ImageCacheInterface.
  • Licensing: Packagist metadata reports NOASSERTION, while the README states LGPL-3.0. Check LICENSE before shipping. For a hosted SaaS that links dynamically and doesn't modify the library, this is normally fine; get it reviewed if you redistribute binaries or patch the source.

Bottom line for a Laravel developer: treat this as a low-level dependency you configure once behind a small wrapper — allowlists from config, kunit from config, a scoped Import, an optional persistent cache for brand assets, per-asset try/catch in jobs, and a hard php-gd requirement in CI. The day-to-day value is cheap image reuse and clean separation; the day-to-day risk is memory, cache invalidation, and allowlist friction.

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.
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel