php2pdf

Documents and pages

Creating a document

use Php2Pdf\Core\FixedClock;
use Php2Pdf\Core\Output\MemoryOutput;
use Php2Pdf\Core\Output\StreamOutput;
use Php2Pdf\Document\Document;
use Php2Pdf\Document\DocumentOptions;

$doc = new Document('out.pdf');                       // a file path
$doc = new Document(new StreamOutput($resource));     // any writable stream, e.g. php://output
$doc = new Document($memory = new MemoryOutput());    // in memory; $memory->contents() after close()

DocumentOptions fixes behaviour for the document’s lifetime:

Option Default Meaning
version '1.7' The PDF version in the header
compress, compressionLevel true, 6 Flate-compress content streams
clock SystemClock Supplies creation dates; inject FixedClock for reproducible output
producer 'Php2Pdf' Written to the Info dictionary and XMP
xmp true Write an XMP metadata packet
topDown false Default coordinate origin of pages
pdfx null Write to a PDF/X level (see navigation-and-pdfx.md)
rgbProfile null An ICC profile RGB colours are interpreted in
glyphCheck Warn (Error under PDF/X) What a character missing from its font does (see fonts-and-text.md)
scriptCheck Error What text in a script the engine cannot shape yet does (see fonts-and-text.md)
limits Limits::default() Resource limits for the PDFs, images and SVG the document reads

Limits caps what reading a hostile or broken file can cost: maxDecodedBytes (256 MiB per decompressed stream), maxImagePixels (100 million), maxPages (100,000 per imported PDF), maxXrefEntries (10 million), maxObjectNesting (256 levels of PDF arrays and dictionaries, page tree levels, reference chains and nested resources), maxNesting (256 levels of SVG elements), maxSvgDepth (512 levels of SVG drawing, counting nested elements and the references being expanded within them) and maxSvgWork (10,000 <use>, mask, pattern and marker expansions per SVG). A file over a limit fails with a ParseException, except that a PDF object nested too deeply is treated like any other broken object: it reads as missing, or makes the reader rebuild the cross-reference. SVG that refers back to something it is still expanding (a clip path clipped by itself, a mask drawn through a <use> of the element it masks, and so on) also fails with a ParseException naming the loop. The static constructors (PdfFile::fromData(), SvgFile::fromData(), ImageReader::read()) take a Limits too; lower the limits for files from untrusted sources.

Handling untrusted files

The library parses these inputs, and any of them may come from a user upload:

The limits stop a small file from asking for unbounded work, but PHP cannot recover from running out of memory or time: those are fatal errors, not exceptions, and no catch sees them. For files from untrusted sources:

Errors

Everything the library throws implements Php2Pdf\Exception\ExceptionInterface: catch that to handle them all. Each is also one of PHP’s own exceptions, so it can be caught by kind:

Exception Is a Thrown when
InvalidArgumentException \InvalidArgumentException an argument is out of range or the wrong kind
StateException \LogicException a call the object’s state does not allow: drawing on a finished page, using a closed document, restore() without save()
PdfException \RuntimeException something fails at run time; its kinds below say what
ParseException PdfException an input file or markup is malformed, unsupported, or over a resource limit
IoException PdfException a file cannot be read or written, or a tool cannot be run
ConformanceException PdfException the document would break the rules of the standard it declares (PDF/X-4)
LayoutException PdfException content cannot be laid out where it was asked to go

Methods that read files say so with @throws ParseException and @throws IoException, and the static analysis checks that every caller in the library declares them too.

Deterministic output

With a FixedClock identical input produces byte-identical files: object numbers, the document ID (an MD5 over the file’s content) and XMP identifiers are all derived deterministically. Golden-file tests compare whole PDFs this way.

Pages

$page = $doc->addPage(PageSize::A4);
$page = $doc->addPage(Dimensions::mm(148, 210), topDown: true);

Adding a page finishes and writes the previous one, so draw on a page before adding the next. Document::currentPage() returns the open page; pageCount() the number of pages so far.

Every page carries the five PDF boxes. The media box is the page size; the others default to it:

$page->setTrimBox(Box::fromMm(10, 10, 190, 277));
$page->setBleedBox(Box::fromMm(7, 7, 196, 283));
$page->setCropBox(...); $page->setArtBox(...);
$page->setRotation(90);            // display rotation, multiples of 90
$page->trimBox(); $page->mediaBox(); $page->width(); $page->height();

Boxes are given in the page’s coordinate mode; toPdfBox() converts a top-down box to PDF coordinates when you need to talk to the file directly.

Metadata

$doc->info()->setTitle('Brochure')->setAuthor('Studio')->setSubject('...')->setKeywords('a, b')->setCreator('My app');
$doc->addXmpDescription('<rdf:Description ...>...</rdf:Description>');  // extra RDF for the XMP packet
$doc->setOutputIntent(IccProfile::fromFile('ISOcoated_v2.icc'), 'FOGRA39', 'Coated FOGRA39');

The Info dictionary and the XMP packet are written at close(); a /Metadata stream is attached to the catalog unless xmp is disabled.

Streaming and closing

Pages stream to the output as they finish. Fonts, templates and other deferred objects are written when close() runs, together with the page tree, catalog, Info dictionary and cross-reference table. A document that was never given a page cannot be closed.

onClose($callback) runs a callback when close() is called, before the last page is finished: the page count is final, templates placed on every page can still be drawn on (a “Page X of Y” footer; see the cookbook), and so can the last page.

Shared objects

$doc->objects() holds the document’s PDF objects. add($object) writes one and returns its reference; shared($key, $factory) writes it once per document and returns the same reference for the same key, as the library does for ICC streams, shadings and extended graphics states. Images are deduplicated by content hash, imported PDFs by path, SVG files by path and converter.

Artwork of your own (see fit-engine.md) can write its own resources there with Dictionary, Stream, Name and Reference from Php2Pdf\Core\Object, and draw a form or image XObject with Canvas::drawXObject($reference, $matrix):

$form = $doc->objects()->shared('logo-mark', fn () => new Stream(
    Dictionary::of(['Type' => new Name('XObject'), 'Subtype' => new Name('Form'), 'BBox' => [0, 0, 10, 10]]),
    '0 0 10 10 re f',
));
$canvas->drawXObject($form, $matrix);