tc-lib-pdf-filter

Technical overview and integration notes for tc-lib-pdf-filter

Overview

tc-lib-pdf-filter decodes and applies PDF stream filters defined by the PDF specification.

A stream can carry a chain of filters rather than a single one, so both the generation and the parsing side run their bytes through this package.

Repository and API Docs

Project Metadata

ItemValue
Namespace\Com\Tecnick\Pdf\Filter
LicenseGNU LGPL v3

Installation

composer require tecnickcom/tc-lib-pdf-filter

Where It Fits

When you are reading or writing PDF streams directly and need the decode chain to behave exactly as the specification describes it.

Features

PDF Filters

  • FlateDecode, LZWDecode, RunLengthDecode
  • ASCIIHexDecode, ASCII85Decode
  • CCITTFaxDecode, DCTDecode, JPXDecode, JBIG2Decode
  • Crypt (Identity and None)

API Design

  • Decode a single filter or apply a chain of filters in order
  • DecodeParms as a single dictionary or as an array parallel to Filter
  • TIFF (predictor 2) and PNG predictors applied by FlateDecode and LZWDecode from their DecodeParms
  • Pure-PHP implementation suitable for parser integration
  • Fallback decoding paths for FlateDecode streams that are truncated or carry a damaged header
  • Typed exceptions for unknown filters and malformed streams

Typed Enums

\Com\Tecnick\Pdf\Filter\FilterType is a backed enum of the supported filter names; each backing value is the exact filter name validated by Filter::decode(). A filter can be named by an enum case, by its full name, or by the inline-image abbreviation (AHx, A85, LZW, Fl, RL, CCF, DCT), optionally with a leading /.

use Com\Tecnick\Pdf\Filter\FilterType;

// decode() accepts a FilterType case, the plain filter name or its abbreviation.
$decoded = $filter->decode(FilterType::FlateDecode, $data);
$decoded = $filter->decode('Fl', $data);

Decode Parameters

DecodeParms entries are passed to decode() and decodeAll() as an array. Beyond the parameters defined by the PDF specification, three filters accept one more:

ParameterFiltersMeaning
MaxOutputSizeFlateDecode, LZWDecode, RunLengthDecodeDecoded-size cap in bytes; 0 (default) means unlimited

FlateDecode reaches compression ratios above 1000:1, so callers decoding untrusted documents should set MaxOutputSize to bound decompression bombs.

CCITTFaxDecode derives the image height from Rows. When Rows is absent the height is estimated from the encoded length, which under-estimates it and truncates the image, so pass Rows (the image dictionary /Height).

Integration Notes

Set MaxOutputSize on anything you did not generate yourself. It is the only defence here against a decompression bomb, and the default is unlimited.

LZWDecode is worth avoiding on the writing side: ISO 19005 forbids it, so a stream that uses it cannot go into a PDF/A document without being re-encoded.

Keep byte-level fixtures for the documents you care about. Filter output is exact, which makes a regression easy to catch and hard to notice any other way.

Requirements

  • PHP 8.2 or later
  • Extensions: pcre, zlib
  • Optional extension: imagick (required for the JPXDecode and CCITTFaxDecode filters)
  • Optional CLI tool: jbig2dec (required for the JBIG2Decode filter)
  • Composer

Without the optional dependencies those three filters throw; the others are unaffected.

Example

<?php

require_once __DIR__ . '/vendor/autoload.php';

$filter = new \Com\Tecnick\Pdf\Filter\Filter();
$decoded = $filter->decodeAll(['ASCIIHexDecode', 'FlateDecode'], $data);

// Bound the decoded size of an untrusted stream and apply a PNG predictor.
$decoded = $filter->decodeAll(
    ['FlateDecode'],
    $data,
    [
        'MaxOutputSize' => 16 * 1024 * 1024,
        'Predictor' => 12,
        'Colors' => 1,
        'BitsPerComponent' => 8,
        'Columns' => 4,
    ],
);

Development and Packaging

  • QA and local checks: make deps, make help, make qa
  • Coverage report: make qa-coverage
  • Packaging: make rpm, make deb

Support and Contribution