Skip to content

Graphics: path + shading + transform primitives

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.

Terminal window
composer require nextpdf/core:^3

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.

ClassKey methodsRole
DrawingEnginegetStream(), reset(), setLineWidth(), setLineStyle(), setDrawColor(), setFillColor(), setAlpha(), setSoftMask(), clip(), setOverprint(), setRenderingIntent(), line(), rect(), circle(), ellipse(), polygon(), linearGradient()Stateful path + graphics-state operator builder
TransformEnginestartTransform(), stopTransform(), scale(), translate(), rotate(), skew(), mirrorH(), mirrorV(), getStream()Affine coordinate transforms
ImageLoaderload(string $filePath), loadFromString(string $data, string $mimeType)Decodes images to ImageLoadResult
ImageRegistryload(), loadFromString(), getMetadata(), memoryUsage(), reset()Deduplicating image cache with memory reporting
SvgParserparse(), parseFile()Translates SVG to the operator stream
EpsParserparse(), parseFile(), getBoundingBox()Translates EPS to the operator stream
ShadingManagershading registration + dictionary emissionAxial, 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.

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();

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();
  • DrawingEngine is stateful. Call reset() between independent pages so the prior graphics state does not leak into the next stream.
  • TransformEngine requires a matched startTransform()/stopTransform() pair. An unbalanced bracket leaves a dangling q and corrupts the save/restore stack downstream in the Writer.
  • setSoftMask(), setOverprint(), setBlackGeneration(), and setUnderColorRemoval() 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.
  • ImageRegistry deduplicates by content. Two paths with identical bytes share one object. Do not assume one PDF image per load() 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.

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 NaN or Infinity.
  • 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.

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.

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.

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.