Skip to content
getnextpdf.com

Images, tables, and lists

Text alone rarely makes a finished document. In this tutorial you add three everyday building blocks: a picture, a table, and a bulleted list. You write two small scripts, and each one produces a real PDF (Portable Document Format) file you can open right away.

  • 01-image.php — a one-page PDF with a heading and an amber square image, placed at an exact spot on the page.
  • 02-table-and-lists.php — a one-page PDF with a small table of books and a bulleted list underneath it.

Both scripts are complete programs. Keep using the project folder from the first tutorial and save each script next to composer.json.

This script carries its own picture. A tiny 8 x 8 pixel PNG (Portable Network Graphics) image travels inside the script as a Base64 string. Base64 is a plain-text spelling of binary data, so it can sit in source code. In a real project you would skip that part, because your image file already exists on disk.

Save this as 01-image.php and run it with php 01-image.php:

<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
// A tiny 8 x 8 pixel amber PNG, stored as text inside this script.
// In a real project the image file already exists on disk, so you
// would skip this block and point image() at your own file.
$pngBase64 = 'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAAEUlEQVR42mP4Oo8bK2IYWhIAq6BngWh/dt4AAAAASUVORK5CYII=';
@mkdir(__DIR__ . '/out');
$pngPath = __DIR__ . '/out/amber-square.png';
$pngBytes = base64_decode($pngBase64, true);
if ($pngBytes === false) {
fwrite(STDERR, "The embedded image data is damaged.\n");
exit(1);
}
file_put_contents($pngPath, $pngBytes);
$document = Document::createStandalone();
$document->setTitle('Tutorial: images');
$document->addPage();
$document->setFont('helvetica', 'B', 20);
$document->cell(0, 26, 'A picture inside a PDF', newLine: true);
$document->setFont('helvetica', '', 12);
$document->cell(0, 18, 'The amber square below comes from a tiny PNG file.', newLine: true);
// Place the image 72 points (one inch) from the left edge and
// 160 points down from the top, printed as a 96 x 96 point square.
$document->image($pngPath, x: 72, y: 160, width: 96, height: 96);
$document->save(__DIR__ . '/out/image.pdf');
echo "Wrote out/image.pdf\n";

You should see Wrote out/image.pdf on the screen. Open out/image.pdf and you will find the heading, one line of text, and an amber square.

  • The script first decodes the Base64 string back into image bytes and writes them to out/amber-square.png. That is only the self-contained tutorial trick. The lesson starts at the image() call, which reads any existing PNG or JPEG (Joint Photographic Experts Group) file the same way.
  • @mkdir(__DIR__ . '/out') creates the output folder. The @ sign hides the harmless warning you would get when the folder already exists, so the script can run twice in a row.
  • image() takes a file path plus a position and a printed size. Positions and sizes are measured in points, and 72 points equal one inch. The page origin is the top-left corner, so y: 160 means 160 points down from the top.
  • Pixels and points are different things. The picture is only 8 x 8 pixels, but width: 96, height: 96 prints it as a square one and a third inches wide. You choose the printed size; the pixel count does not.
  • image() places the picture at the exact spot you name. It does not move the writing position that cell() uses. Writing the text first, then placing images, keeps the two from colliding.
  • NextPDF reads the image from a local file only, never from a web address. It checks the file is a real, supported image before embedding it. That guard protects you when file names come from users.

Tables and lists are structured content. The quickest way to describe structure is HTML (Hypertext Markup Language), the same markup web pages use. NextPDF renders a supported set of HTML straight onto the PDF page. No browser is involved.

Save this as 02-table-and-lists.php and run it with php 02-table-and-lists.php:

<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
@mkdir(__DIR__ . '/out');
$document = Document::createStandalone();
$document->setTitle('Tutorial: tables and lists');
$document->addPage();
$document->setFont('helvetica', 'B', 20);
$document->cell(0, 26, 'A reading list, two ways', newLine: true);
$document->ln(8);
$html = <<<'HTML'
<h2>The books as a table</h2>
<table border="1" cellpadding="6" style="width: 100%;">
<thead>
<tr style="background-color: #1E3A8A; color: #FFFFFF;">
<th style="width: 50%;">Title</th>
<th style="width: 20%; text-align: center;">Year</th>
<th style="width: 30%; text-align: right;">Pages</th>
</tr>
</thead>
<tbody>
<tr>
<td>The Paper Office</td>
<td style="text-align: center;">2019</td>
<td style="text-align: right;">312</td>
</tr>
<tr style="background-color: #F8FAFC;">
<td>Ink and Pixels</td>
<td style="text-align: center;">2023</td>
<td style="text-align: right;">208</td>
</tr>
</tbody>
</table>
<h2>Why these two books</h2>
<ul>
<li>Short chapters that fit into a lunch break</li>
<li>Worked examples on every page</li>
<li>No jargon without an explanation</li>
</ul>
HTML;
$document->writeHtml($html);
$document->save(__DIR__ . '/out/table-and-lists.pdf');
echo "Wrote out/table-and-lists.pdf\n";

You should see Wrote out/table-and-lists.pdf. The PDF holds a heading, a three-column table with a dark header row, and a bulleted list.

  • The script mixes both writing styles on one page. cell() prints the big heading at the current writing position. writeHtml() then flows the table and the list down the page below it.
  • ln(8) adds a small vertical gap, eight points tall, between the heading and the HTML content. Without it, the two would sit tightly together.
  • The table uses ordinary table markup: <thead> for the header row, <tbody> for the data rows, <th> for header cells, and <td> for data cells. border="1" draws the grid lines, and cellpadding="6" adds breathing room inside every cell.
  • Column widths are percentages, such as width: 50%. They divide up the table’s full width, so the layout survives a page-size change. The Lay out an HTML table recipe covers headers, footers, and column sizing in depth.
  • The style attributes use CSS (Cascading Style Sheets) declarations, the styling language of the web. Colors, alignment, and widths work as shown here. Before you rely on other properties, check the CSS support matrix.
  • The bulleted list is just <ul> with one <li> per item. NextPDF draws the bullet marks and the indentation for you.
  • An error naming ImageProcessingException means the image file could not be read or decoded. Check the path, and check the file is a supported format such as PNG or JPEG.
  • An error naming PageLayoutException means a position or size was rejected. Width and height must be greater than zero, and the path must point to a local file, not a web address.
  • Both errors are described in the engine errors reference.
  • If the table renders oddly, check the markup first. Every <tr> row and <td> cell needs its closing tag.
  • For anything else, start at the troubleshooting guide.

Your pages can now carry pictures, tables, and lists. In the next tutorial you let a document grow past one page and keep it tidy with headers, footers, and page numbers. For deeper dives into today’s topics, the Embed images in a document recipe covers image formats and sizing rules.