Skip to content

Contracts / Document

The document domain holds the contracts you use to build Portable Document Format (PDF) output: PdfDocumentInterface for content, DocumentFactoryInterface for worker-safe creation, the font and image registry contracts, and the three delivery and layout enums. All are stable since 1.0.0 or 1.7.0.

Terminal window
composer require nextpdf/core:^3

PdfDocumentInterface is the primary application programming interface (API) surface. It defines page management, font selection, cell and multi-cell text layout, Hypertext Markup Language (HTML) rendering, image embedding, and final output. Every method returns static, so you can chain calls. Document::createStandalone() returns a concrete instance that satisfies the interface. Type-hint the interface in your own services so the engine internals stay swappable.

Document creation has two paths. In a classic PHP FastCGI Process Manager (PHP-FPM) request, createStandalone() builds a self-contained document with private registries. Long-running workers, including RoadRunner, Swoole, and Laravel Octane, use the other path. There, DocumentFactoryInterface::create() returns a fresh, disposable Document. The document reads from process-lifetime registries but never mutates them. The factory holds the FontRegistryInterface and ImageRegistryInterface singletons. Each document gets its own rendering context and writer. This contains failures: one document cannot corrupt shared state that another document depends on.

The registry contracts keep workers fast. FontRegistryInterface parses a font file once and caches the parsed metadata for the life of the process. You can lock it after warmup so production traffic cannot mutate it. ImageRegistryInterface caches decoded image binary data under a bounded least-recently-used policy. Image metadata stays resident even after the binary is evicted. Both registries expose memoryUsage() for capacity planning. ImageRegistryInterface extends ResettableService, which evicts cached data without destroying structural metadata. A worker can drop image caches under memory pressure and keep serving.

Three enums complete the domain. OutputDestination selects inline display, forced download, filesystem write, or raw string return. Orientation selects portrait or landscape. Alignment selects left, center, right, or justified text. Each enum uses the legacy TCPDF code as its enum value, so the compat-tcpdf bridge maps cleanly. The backward-compatibility promise for these enums is additive. No case is removed. New cases may arrive in a minor release.

TypeKindKey membersStabilitySince
PdfDocumentInterfaceinterfaceaddPage(), setMargins(), setFont(), cell(), multiCell(), writeHtml(), image(), output(), save()stable1.0.0
DocumentFactoryInterfaceinterfacecreate(?Config): Documentstable1.7.0
ResettableServiceinterfacereset(): voidstable1.7.0
FontRegistryInterfaceinterfaceregister(), get(), warmup(), lock(), isLocked(), registerFromBinary(), memoryUsage()stable1.7.0
ImageRegistryInterfaceinterfaceload(), loadFromString(), getMetadata(), memoryUsage() (extends ResettableService)stable2.0.0
OutputDestinationenum (string)Inline, Download, File, Stringstable1.0.0
Orientationenum (string)Portrait, Landscapestable1.0.0
Alignmentenum (string)Left, Center, Right, Justifystable1.0.0

The typography page documents FontRegistryInterface and ImageRegistryInterface in full. This page covers their role in the creation lifecycle.

examples/01-hello-world.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('Hello World');
$doc->addPage();
$doc->setFont('helvetica', '', 24);
$doc->cell(0, 15, 'Hello, NextPDF!', newLine: true);
$doc->setFont('helvetica', '', 12);
$doc->cell(0, 10, 'This is a minimal PDF generated with NextPDF.', newLine: true);
$doc->save(__DIR__ . '/output/01-hello-world.pdf');
examples/02-pdf-factory.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\PdfFactory;
use NextPDF\ValueObjects\{Margin, PageSize};
$factory = PdfFactory::new()
->withPageSize(PageSize::A4())
->withMargins(new Margin(15.0, 15.0, 15.0, 15.0))
->withCompress(true)
->withLang('en');
// The same configured factory creates independent documents.
$doc = $factory->create();
$doc->setTitle('PdfFactory Example');
$doc->setAuthor('NextPDF');
$doc->addPage();
$doc->setFont('helvetica', '', 16);
$doc->cell(0, 12, 'Created via PdfFactory', newLine: true);
$doc2 = $factory->create();
$doc2->addPage();
$doc2->setFont('helvetica', '', 12);
$doc2->cell(0, 10, 'Second document from the same factory.');
$doc->save(__DIR__ . '/output/02-pdf-factory.pdf');

PdfFactory is the immutable builder. Each with*() call returns a new instance. Under the hood, it composes a DocumentFactoryInterface, so the worker registry model from the overview applies without extra wiring.

  • createStandalone() builds private registries. In a worker loop, that re-parses every font on every request. Use DocumentFactoryInterface with shared registries instead.
  • A Document is disposable by design. Reusing one instance across logical documents leaks state. Call create() for each document and let garbage collection reclaim it.
  • FontRegistryInterface::lock() makes register(), addFontDirectory(), and warmup() throw LogicException. Lock after warmup, never during request handling.
  • OutputDestination::File writes to the server filesystem and returns the raw bytes. save() is the explicit file path. Do not mix the two for the same document.
  • cell() accepts bool|string for the border argument for TCPDF compatibility. An empty string is not the same as false. Pass the typed value you mean.

The font and image registries make the document domain a memory-bounded system, not a per-request one. First-request font parsing dominates. The performance_budget is 1500 ms wall and 64 MB peak across three documents in the worker example. Almost all of that budget is the first font parse. After warmup, contract-attributable work per document is O(1): a registry lookup and a context allocation. memoryUsage() on either registry returns a MemoryReport for live capacity planning. ResettableService::reset() bounds peak memory under sustained load.

The document contracts carry no cryptographic surface, but two operational risks apply. First, image() accepts a path or Uniform Resource Locator (URL). In untrusted-input scenarios, constrain remote fetching through ExternalResourcePolicyInterface (see the security-policy page) rather than passing user-controlled URLs directly. Second, writeHtml() is the entry point for the HTML pipeline. Untrusted markup must pass an HtmlSecurityPolicyInterface before rendering. The document layer itself does not sanitize. That is the security-policy domain’s job, and because it is a contract, you can supply a stricter policy without forking.

The document contracts implement PDF 2.0 document structure as defined in ISO 32000-2. Output, page, and font handling produce indirect objects and a cross-reference stream per ISO 32000-2 §7. The writer layer emits content per the engine layer contract, architecture decision record (ADR-010). This page asserts no clause-level claim beyond structural conformance. The extraction and accessibility pages document PDF/A and PDF/UA conformance and carry the normative tables.