Graphics: path + shading + transform primitives
At a glance
Section titled “At a glance”The Graphics module turns drawing intent into Portable Document Format (PDF) graphics operators. It covers paths, line styles, color spaces, transforms, shadings, patterns, halftones, and image loading.
Install
Section titled “Install”composer require nextpdf/core:^3Conceptual overview
Section titled “Conceptual overview”Graphics is the vector and raster drawing layer. It produces operator sequences that the ContentStream and Writer modules serialize into a PDF. A content stream encodes page content as an ordered sequence of graphics operators under International Organization for Standardization (ISO) 32000-2 §8. The module emits those operators; it does not write the file.
DrawingEngine is the primary application programming interface (API). It is a
fluent, stateful builder. Each
setter returns self, records a graphics-state change or a path-painting
operator, and appends to an internal buffer that you read with getStream().
The engine models the PDF graphics state directly: line width, line style,
stroke and fill color, alpha and blend mode, miter limit, soft mask, clipping,
overprint, flatness, smoothness, rendering intent, black generation, and
under-color removal each map to a documented operator. Color setters accept a
Color value object or an explicit ColorSpace, so device and International
Commission on Illumination (CIE)-based spaces use the same call shape.
Three families sit alongside the engine. Image input comes first. ImageLoader
decodes a file or in-memory blob into an ImageLoadResult. ImageRegistry
deduplicates and tracks decoded images with a MemoryReport, so large documents
stay within a memory budget. For vector import, SvgParser and EpsParser
translate Scalable Vector Graphics (SVG) and Encapsulated PostScript (EPS) input
into the same operator stream, with getBoundingBox() exposed for layout.
Device-color fidelity is the third family: shadings (ShadingManager, the
Type2/Type3 and mesh families), patterns (PatternFill), halftones
(Type1/Type5/Type6/Type10/Type16), transfer functions, and
International Color Consortium (ICC)-based color spaces.
TransformEngine is a focused companion for coordinate transforms. It wraps a
transform with startTransform() and stopTransform(), which emit the q and
Q save/restore pair. It provides named affine helpers: scale, translate,
rotate, skew, mirrorH, and mirrorV. Each helper accepts an optional
pivot. The transform matrix maps an internal coordinate space into the target
coordinate space. This is the same model that ISO 32000-2 applies to shading
domains — §8.7.4.
Color management follows Architectural Decision Record (ADR)-012: ICCBased and
CIE-based color spaces emit explicit cs/CS content-stream operators instead
of relying on device-color fallback. ICC profiles wrap into an ICCBased stream
with the correct component count per ISO 32000-2 §8.6.5.5.
API surface
Section titled “API surface”| Class | Key methods | Role |
|---|---|---|
DrawingEngine | getStream(), reset(), setLineWidth(), setLineStyle(), setDrawColor(), setFillColor(), setAlpha(), setSoftMask(), clip(), setOverprint(), setRenderingIntent(), line(), rect(), circle(), ellipse(), polygon(), linearGradient() | Stateful path + graphics-state operator builder |
TransformEngine | startTransform(), stopTransform(), scale(), translate(), rotate(), skew(), mirrorH(), mirrorV(), getStream() | Affine coordinate transforms |
ImageLoader | load(string $filePath), loadFromString(string $data, string $mimeType) | Decodes images to ImageLoadResult |
ImageRegistry | load(), loadFromString(), getMetadata(), memoryUsage(), reset() | Deduplicating image cache with memory reporting |
SvgParser | parse(), parseFile() | Translates SVG to the operator stream |
EpsParser | parse(), parseFile(), getBoundingBox() | Translates EPS to the operator stream |
ShadingManager | shading registration + dictionary emission | Axial, radial, and mesh shadings |
Halftone (abstract) | halftoneType(), toDict(), hasStream(), getStream() | Type 1/5/6/10/16 halftone screens |
Run composer docs:generate-api-php -- --module=Graphics to generate the full
PHPDoc table.
Code sample — Quick start
Section titled “Code sample — Quick start”Source: examples/06-colors-and-drawing.php.
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Graphics\Color;use NextPDF\Graphics\DrawingEngine;use NextPDF\Graphics\LineStyle;
$engine = new DrawingEngine();
$engine ->setLineWidth(1.5) ->setDrawColor(Color::rgb(0, 51, 102)) ->setFillColor(Color::rgb(230, 240, 250)) ->rect(20.0, 20.0, 160.0, 80.0) ->line(20.0, 110.0, 180.0, 110.0, new LineStyle(dash: [3.0, 2.0]));
$contentStreamBytes = $engine->getStream();Code sample — Production
Section titled “Code sample — Production”This example wires an image registry with memory reporting and a transform
bracket. It mirrors the structure used in examples/07-images.php and
examples/21-transforms.php.
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Graphics\DrawingEngine;use NextPDF\Graphics\ImageRegistry;use NextPDF\Graphics\TransformEngine;
$registry = new ImageRegistry();$image = $registry->load('/srv/assets/logo.png');
$report = $registry->memoryUsage();if ($report->bytes > 32 * 1024 * 1024) { // Decoded image cache exceeded the budget — reset before the next page. $registry->reset();}
$transform = new TransformEngine();$transform ->startTransform() ->translate(40.0, 700.0) ->scale(0.5, 0.5) ->stopTransform();
$engine = new DrawingEngine();$engine->reset();$page = $transform->getStream() . $engine->getStream();Edge cases & gotchas
Section titled “Edge cases & gotchas”DrawingEngineis stateful. Callreset()between independent pages so the prior graphics state does not leak into the next stream.TransformEnginerequires a matchedstartTransform()/stopTransform()pair. An unbalanced bracket leaves a danglingqand corrupts the save/restore stack downstream in the Writer.setSoftMask(),setOverprint(),setBlackGeneration(), andsetUnderColorRemoval()write extended graphics-state markers. Under a profile that rejects the feature, they are inert. Check the profile guard before you rely on the visual result.ImageRegistrydeduplicates by content. Two paths with identical bytes share one object. Do not assume one PDF image perload()call.EpsParser::getBoundingBox()returns the parsed bounding box, not the page box. Apply your own clipping if the EPS overflows the target rectangle.- Black-point compensation is advisory and marker-based. It does not transform pixels by itself.
Validation & error handling
Section titled “Validation & error handling”Two producer-side changes are breaking. Both turn previously silent corruption into an explicit failure at the call site.
Input validation now throws (migration note). Drawing input is validated
before it reaches the operator stream, and malformed values are rejected with
InvalidArgumentException. Callers that passed NaN, Infinity, or
out-of-range values previously produced corrupt operators silently; the same
input now raises an exception. The validated constraints are:
- Color alpha must be finite and within
[0, 1]. - Current transformation matrix (CTM) operands, template dimensions,
gradient-vertex coordinates, and mesh-patch coordinates must be finite — no
NaNorInfinity. - A gradient-patch edge flag must be one of
{0, 1, 2, 3}. - Type 2/3/4 function parameters and halftone parameters are bounds-checked.
- Colourant names are escaped.
- An optional-content group (OCG) layer name must be non-empty.
Audit call sites that compute coordinates or alpha from upstream data before you upgrade: a value that used to pass through is now a hard error.
ICCBased /N is fail-closed by default. Plain-PDF output rejects an
ICCBased colour space whose /N component count is outside {1, 3, 4}, and
reconciles the declared /N against the embedded profile and the /Alternate
space. This follows the ISO 32000-2 §8.6.5.5 rule for the ICCBased stream, which
carries /N alongside an /Alternate space. An N-channel ICC profile, such as a
hexachrome profile with N = 6, is retained only when a PDF/A or PDF/X profile
is active, with opt-in through IccConformancePolicy::ProfileGated. This is a
structural gate on the component count, not a claim of PDF/A or PDF/X
certification.
Performance
Section titled “Performance”Operator emission is linear in the number of drawing calls: O(n) appends to a
buffer, with no reflow. Image decoding cost comes from the codec and pixel
count, not the registry. The registry’s content-hash deduplication is the main
lever for large documents: reused assets cost one decode and one PDF object. The
performance_budget for this module’s reference workload is 1500 ms wall and 64
MB peak. Use ImageRegistry::memoryUsage() to observe the decoded-image
footprint and reset() to release it between page groups.
Security notes
Section titled “Security notes”SvgParser and EpsParser consume untrusted vector input. Treat both as
parsers of hostile data. Enforce input size limits before calling parse().
Run extraction in a constrained worker when the source is user-supplied.
EPS is a PostScript dialect. The parser translates a constrained subset and does
not execute a general interpreter, but you should still bound input size and
parse time. Image loaders decode third-party codecs. Keep the runtime image
extensions current and cap decoded dimensions. See the engine threat model in
/modules/core/security/ for the trust boundary and worker-isolation guidance.
Conformance
Section titled “Conformance”The module emits PDF graphics-operator structures consistent with ISO 32000-2
§8, ICCBased color-space dictionaries per §8.6.5.5, and shading dictionaries
whose Domain, Function, Matrix, and BBox follow §8.7.4. These are implementation
facts: src/Graphics/ produces the operator and dictionary shapes, and
tests/Unit/Graphics/ plus the
tests/Golden/PdfWriter/PdfWriterShadingGoldenBaselineSmokeTest and
PdfWriterExtGStateGoldenSmokeTest baselines exercise them. They are not a
statement of end-to-end PDF 2.0 or PDF/X conformance. Full-document conformance
is validated separately by the oracle and golden suites described in
/modules/core/conformance/. Profile behavior for ICC OutputIntents is decided
by ADR-011 and ADR-012, not by this module alone.