Skip to content

Internals Architecture

Dragon edited this page Jun 3, 2026 · 2 revisions

Internals: Architecture

This page describes the core architecture of phppdf for contributors and library maintainers. Read the Home page and the public API pages first.

Architecture

Fluent builder, deferred serialization. Document accumulates pages, fonts, images, encryption settings, and metadata in memory. Nothing is written to bytes until output() (or save()) is called; that single call serializes everything in one deterministic pass via Writer/PdfWriter and the indirect-object infrastructure in Writer/Object/.

The serialization pass produces a PDF 1.7 file with:

  • A cross-reference table (xref) listing every indirect object by byte offset.
  • A trailer pointing at the catalog and the metadata.
  • Compressed content streams (FlateDecode).
  • Deterministic object numbering: the same input always produces the same bytes (which is what makes the golden-tests harness possible).

Units. All public coordinates default to millimetres; switch to PDF points via new Document(Unit::PT). Font sizes always stay in points (typographic convention). Internally, every measurement is converted to points and stored as $xxxPt floats. The Y axis is top-down in the public API; conversion to PDF native (bottom-up) happens at serialization time.

Stateful Page, single-pass content stream. Page owns a ContentStream, a font / size / leading state machine, an (x, y) cursor, and the inHeaderRender flag. cell() is the workhorse: it normalizes newlines, optionally triggers auto-page-break via Document::addPage(), then delegates rendering to Page/CellRenderer. The cursor is driven by the NextPosition enum (RIGHT / NEWLINE / BELOW).

Page lifecycle. Document::setMargins, setHeader, setFooter, setAutoPageBreak. Headers fire eagerly at addPage(); footers fire deferred at output() to allow correct (pageNumber, totalPages) substitution. Footers are guarded by $footersRendered so repeated output() calls do not double-emit. The $inHeaderRender flag on Page doubles as auto-break suppressor during header rendering and as a one-shot recursion guard for cells larger than the drawable area.

Conventions used throughout the codebase

  • PHP 8.4 features in use: final readonly class, typed class constants (private const string FOO = '...'), constructor property promotion, Closure parameter types, named arguments at call sites.
  • Value objects are final readonly with named constructors (Color::hex(), Border::all(), CellPadding::symmetric(), etc.). Validation is eager in the constructor with messages that include the offending value: "Cell padding top cannot be negative, got -1".
  • Public API exposes float coordinates in the document unit. Internal helpers ending in Pt operate in points. Never mix.
  • Error handling is fail-fast: throw PdfException (or subclass) at the boundary.
  • PHPStan runs at level: max on src/ and tests/. New code must pass with zero errors (no @phpstan-ignore); prefer adding a real accessor over silencing an onlyWritten property.
  • PHPUnit is configured with failOnWarning="true" and failOnRisky="true": a warning is a failure.
  • ASCII only in code, comments, and commit messages (no em-dash, no curly quotes).

See also

Clone this wiki locally