Skip to content

Embed images in a document

Place a raster image at an absolute position with an explicit width. Omit the height, and NextPDF derives it from the source aspect ratio. This recipe follows examples/07-images.php. You can use JPEG, PNG, GIF, BMP, WebP, and AVIF files.

NextPDF embeds the image as an ISO 32000-2 image XObject. The image dictionary states the width, height, and bits per component explicitly.

Terminal window
composer require nextpdf/core:^3

The image() API is part of Core. You need nothing else to embed an existing file. The backing example generates a test image with GD, so it needs ext-gd. The API has been stable since 1.0.0 and runs on the 8.1–8.4 backport matrix.

image($file, x, y, width, height) loads the file through the image registry, decodes it, and places it. The registry validates the path before decoding. It rejects any path that contains a NUL byte or looks like a Uniform Resource Locator (URL) scheme (scheme://…). image() reads local files only, never a remote URL. The registry also rejects a non-positive width or height.

Placement uses the current transformation matrix to map the image’s unit square into the target rectangle. ISO 32000-2 §8.8 specifies the cm operator inside a q/Q pair for this placement, so the transform stays isolated. The decoded raster becomes an image XObject. Its BitsPerComponent is required and must be one of 1, 2, 4, 8, or 16 (§8.9.5).

The API surface is generated from PHPDoc. This recipe uses one method:

  • image(string $file, ?float $x = null, ?float $y = null, ?float $width = null, ?float $height = null): static — embeds and places a local raster image. Omit height to preserve the source aspect ratio. It rejects URL-scheme paths, NUL-byte paths, and non-positive dimensions with a PageLayoutException.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->addPage();
// Absolute position; height inferred from the source aspect ratio.
$doc->image(__DIR__ . '/logo.png', x: 15, y: 30, width: 80);
$doc->save(getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: __DIR__ . '/images.pdf');

This complete, harness-ready example generates a deterministic test image with GD, so the recipe stays self-contained. It honours NEXTPDF_COOKBOOK_OUTPUT and pins no entropy of its own.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
if (!\extension_loaded('gd')) {
fwrite(STDERR, "ext-gd required for the self-contained test image\n");
exit(1);
}
// Build a deterministic test image (fixed pixels — no entropy).
$imgPath = \tempnam(\sys_get_temp_dir(), 'npf') . '.png';
$img = \imagecreatetruecolor(200, 100);
if ($img === false) {
fwrite(STDERR, "GD imagecreatetruecolor failed\n");
exit(1);
}
$bg = (int) \imagecolorallocate($img, 30, 58, 138);
$fg = (int) \imagecolorallocate($img, 255, 255, 255);
\imagefilledrectangle($img, 0, 0, 199, 99, $bg);
\imagestring($img, 5, 40, 40, 'NextPDF Image', $fg);
\imagepng($img, $imgPath);
\imagedestroy($img);
try {
$doc = Document::createStandalone();
$doc->setTitle('Image Embedding');
$doc->addPage();
$doc->setFont('helvetica', 'B', 18);
$doc->cell(0, 12, 'Image Embedding', newLine: true);
$doc->ln(5);
// Absolute placement; width fixed, height from aspect ratio.
$doc->image($imgPath, x: 15, y: 50, width: 80);
// Same image, narrower — scaled down by the CTM, not re-decoded.
$doc->image($imgPath, x: 15, y: 105, width: 40);
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: __DIR__ . '/images.pdf';
$doc->save($out);
echo "Created images.pdf\n";
} finally {
@\unlink($imgPath);
}
  • No remote URLs. A path matching scheme://… is rejected with a PageLayoutException. Download the asset to a local file first, then pass that path. The loader does not fetch over the network for you. This is a deliberate Server-Side Request Forgery (SSRF) guard.
  • Non-positive dimensions throw. A width <= 0 or height <= 0 raises a PageLayoutException. To use the natural size, omit the argument instead of passing 0.
  • NUL-byte path rejected. A path that contains \0 is rejected. This guards against poison-null-byte paths. Validate user-supplied filenames.
  • Aspect ratio. Pass only width, or only height, to scale proportionally. Passing both values can distort the image.
  • The same file embedded twice. Reuse one path to scale through the transformation matrix. The engine does not re-decode identical bytes. Favour reusing the path over duplicating files.

Decode cost is proportional to the source pixel count, so the budget is 96 MB to allow a moderate photo. Placing the same path again costs little: one extra Do plus a cm, not a second decode. Scaling down by width does not reduce the stored pixels. Pre-resize large source images if document size matters.

The URL-scheme and NUL-byte rejections guard against SSRF and path injection. image() cannot be tricked into fetching http://… or traversing past a null byte. Even so, treat user-supplied filenames as untrusted. Resolve them against an allowed base directory before you call image(). When the file is user-uploaded, image decoding runs on attacker-influenced bytes. Cap the input size and consider decoding in an isolated worker.

StatementSpecClausereference_id
An image dictionary specifies width, height, and bits per component explicitly.ISO 32000-2§8.9.5
BitsPerComponent is required and is 1, 2, 4, 8, or 16.ISO 32000-2§8.9.5
An image is placed by the cm operator inside q/Q.ISO 32000-2§8.8

Reproducibility profile — structural. Image bytes are deterministic for a fixed source. Every saved document still carries a trailer /ID and date atoms, so the harness strips those and compares the qpdf-normalised structure. This recipe describes how NextPDF produces the structure. It does not make a blanket ISO 32000-2 conformance claim.

Not applicable. Raster image embedding is a Core capability, with no Premium gate.