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 Filter Laravel Package

tecnickcom/tc-lib-pdf-filter

Pure-PHP decoder for standard PDF stream filters, including FlateDecode, LZWDecode, RunLengthDecode, ASCIIHexDecode, ASCII85Decode, CCITTFaxDecode, DCTDecode, JPXDecode and JBIG2Decode. Chain multiple filters in sequence, with typed exceptions for invalid or unknown filters. Requires PHP 8.2+ and zl...

View on GitHub
Deep Wiki
Context7

Getting Started

Install and confirm the runtime contract first

composer require tecnickcom/tc-lib-pdf-filter

Two hard requirements from the README: PHP 8.2+ and the zlib extension. On a Laravel app still on 8.1 this is a non-starter, so check composer.json's platform block and your CI matrix before promising anything. In config/app.php-era terms there's nothing to register — it's a plain library, no service provider, no config file.

The one API you need to know

use Com\Tecnick\Pdf\Filter\Filter;

$filter  = new Filter();
$decoded = $filter->decodeAll(['ASCIIHexDecode', 'FlateDecode'], $rawStreamBytes);

That is the entire public surface described in the README: pass an ordered array of filter names plus the raw bytes from a PDF stream object, get bytes back. A single filter is just a one-element array. Do not trust third-party snippets using Tecnickcom\TCPDF\Filters\* — the real namespace is \Com\Tecnick\Pdf\Filter.

Where to look first

  1. src/ — the concrete decoders (Flate, LZW, ASCIIHex, ASCII85, RunLength, CCITT, DCT, JPX, JBIG2). Skim each to see what arguments they actually consume before designing around them.
  2. https://tcpdf.org/docs/srcdoc/tc-lib-pdf-filter — generated API docs; more useful than the README for constructor signatures.
  3. The exception classes — you want to know which exception types are thrown for unknown filters before you write catch blocks.

A realistic first use case

Not "decode a whole PDF" — the package can't parse a document. Your first win is much smaller: take a PDF whose content streams are /FlateDecode, pull the stream bytes out with whatever parser you already have (smalot/pdfparser, setasign/fpdi, or your own object walker), run them through decodeAll(), and get readable content-stream operators for text extraction, redaction auditing, or debugging a broken export from a client's legacy system.

Implementation Patterns

Wrap it in a single domain service, not scattered calls

The library is stateless and cheap to instantiate, but you don't want new Filter() in fifteen controllers. Bind one class:

namespace App\Services\Pdf;

use Com\Tecnick\Pdf\Filter\Filter;

class StreamDecoder
{
    public function __construct(private Filter $filter) {}

    /** @param string[] $filters */
    public function decode(array $filters, string $bytes): string
    {
        if ($filters === []) {
            return $bytes;
        }

        return $this->filter->decodeAll($filters, $bytes);
    }
}
// AppServiceProvider::register()
$this->app->singleton(StreamDecoder::class, fn () => new StreamDecoder(new Filter()));

Everything below hangs off that one seam — swapping implementations later, or mocking it in tests, costs one binding.

The real workflow: parse → collect filters → decode

decodeAll() is only one step. The surrounding pipeline you own:

  1. Locate the stream object and read its dictionary.
  2. Read /Filter — it may be a name (/FlateDecode) or an array ([/ASCII85Decode /FlateDecode]).
  3. Read /DecodeParms alongside it (per-filter params, in the same order).
  4. Decrypt first if /Encrypt is present (see Gotchas — this package does not do that).
  5. Call decodeAll($filtersInDocumentOrder, $bytes).

Note the order: PDF lists filters in the order they were applied at encode time, and decoding walks that list front-to-back. Getting this backwards silently produces garbage rather than an error.

Queue the work, always

Anything that touches real PDFs is I/O- and CPU-heavy and prone to throwing on malformed input. Put it in a job:

class DecodePdfStreams implements ShouldQueue
{
    use Queueable;

    public function __construct(public string $disk, public string $path) {}

    public function handle(StreamDecoder $decoder): void
    {
        $bytes = Storage::disk($this->disk)->get($this->path);
        // ...walk objects, decode, emit results
        event(new PdfStreamsDecoded($this->disk, $this->path));
    }
}

Laravel's filesystem layer keeps large blobs out of your request lifecycle, and the event gives downstream steps (OCR, indexing, e-signature prep) a clean hook.

Expose it as an Artisan command for ops

php artisan pdf:decode storage/app/in/legacy.pdf --filter=FlateDecode

Batch/forensic use cases are almost always driven from the CLI, not the UI. One command with --dump-filters (list filters found, don't decode) is the single most valuable debugging tool you can build here.

Abstract over it if PDF work is core to your product

If decoding is load-bearing, define an interface (PdfStreamDecoder) with a tc-lib implementation and a qpdf-via-Process implementation behind it. You get an escape hatch without a rewrite, and you can A/B the pure-PHP path against the C tool on your own corpus.

Gotchas and Tips

The description overpromises on encryption

Packagist says "compression and encryption filters" but the README's feature list is decoders only, and PDF encryption is not a stream filter — it's the security handler (RC4/AES-128/AES-256) applied around the stream before /Filter runs. Assume you must decrypt elsewhere (qpdf, setasign/fpdi, or your own implementation) and validate this on a password-protected sample before it lands in an estimate. The Crypt class referenced in circulating AI-generated assessments is not in the documented API.

/DecodeParms, especially /Predictor, is the classic silent corruption

Flate/LZW streams for images and xref tables frequently carry /Predictor 12 (PNG Up) or /Predictor 2 (TIFF). If predictor post-processing isn't applied, the inflate succeeds and the bytes still look like noise. The README's decodeAll($filters, $data) signature shows no parameter channel for this — read the source before assuming the decoders accept params. If they don't, you own the predictor un-filtering step (/Columns, /Colors, /BitsPerComponent, /Predictor) as a post-pass.

Image codecs are a trap

DCTDecode (JPEG), JPXDecode (JPEG2000), and JBIG2Decode are image compression formats, not byte transforms. Pure PHP will either pass them through or reject them. Treat a stream with these filters as "already decoded pixels in a container" and hand it to GD/Imagick rather than expecting bytes from this library.

It's a decoder, not a PDF reader

It cannot find the streams for you, cannot resolve object references, cannot decompress an object stream's contents into usable objects without your parser feeding it. Budget for the parsing layer separately.

Binary safety around the decoded output

The return value is arbitrary bytes. Never log it raw, never mb_* it, never put it in a JSON column, and never let Laravel's default Strict-mode string handling touch it. Write straight to disk or a LONGBLOB; compare with hash_equals/md5 rather than string equality. When debugging, hex-dump the first 64 bytes rather than echoing.

No streaming API means a memory ceiling

decodeAll() takes and returns whole strings. A 200 MB Flate stream can inflate to several GB. Enforce an input size cap, raise memory_limit explicitly in the job (not globally), and fail fast with a domain exception when a stream exceeds your threshold. Never run this in a web request.

Wrap every call — malformed PDFs are the norm

Unknown or invalid filter names throw typed exceptions per the README. Catch narrowly around the decode step, log the filter name and object ID, and continue with the next stream. One bad object should not kill a 400-page document.

Validate output, don't trust it

qpdf --check or pdftk on reconstructed output, plus a known-good fixture pair (raw stream → expected bytes) in your test suite. Regression tests here are worth more than coverage metrics, because a decoding bug looks exactly like a rendering bug three layers up.

Licensing and bus factor

Packagist reports NOASSERTION; the README states LGPL-3.0. That matters if you link it into a proprietary product — get it reviewed rather than assuming permissive. And with ~6 stars and effectively no dependents, you are the support team: pin ^2.11, commit the lockfile, watch release notes, and keep the interface abstraction described above so a swap is a day's work, not a quarter's.

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.
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels