Pop PDF
Build

Fonts

A Document draws no text until a font is registered with it — addFont() and addFonts() register a standard PDF font; a font file goes through embedFont() instead, covered on Embedding Fonts. Fonts live with the Document itself, not on any one Page — register a font once and every page that references it by name draws with it.

Adding a Font#

addFont() takes either a standard font name or a Document\Font instance, and addFonts() takes an array of either:

PHP
use Pop\Pdf\Document;
use Pop\Pdf\Document\Page;
use Pop\Pdf\Document\Font;

$document = new Document(new Page(Page::LETTER));
$document->addFonts([Font::HELVETICA, Font::TIMES_BOLD]);

$document->getAvailableFonts(); // ['Helvetica', 'Times-Bold']

A bare string is wrapped in new Font($name) for you, so addFont(Font::HELVETICA) and addFont(new Font(Font::HELVETICA)) register the same thing. Registering the same name twice is a no-op — the original Font instance is kept, and a distinct object built separately for that name is discarded:

PHP
$first = new Font(Font::HELVETICA);
$document->addFont($first);
$document->addFont(new Font(Font::HELVETICA)); // discarded, 'Helvetica' is already registered

$document->getFont('Helvetica') === $first; // true — the original instance, untouched

Safe to repeat across a codebase without checking isFontAvailable() first. Each successful registration also becomes the document's current font — see Selecting the Current Font.

addFont() also accepts a path to a font file in place of a standard name, and hands it straight to embedFont():

PHP
$document->addFont(__DIR__ . '/fonts/DejaVuSans.ttf');
$document->getAvailableFonts(); // ['DejaVuSans']

Embedding Fonts covers that path in full — formats, Unicode and glyph coverage. Everything below concerns the standard fonts, which never touch a file.

A name that is neither a standard font nor a valid file path raises InvalidArgumentException:

PHP
try {
    $document->addFont('NotARealFont');
} catch (\InvalidArgumentException $exception) {
    echo $exception->getMessage();
    // Error: The font 'NotARealFont' is not valid. It must be a standard PDF font or a font file.
}

The Standard Fonts#

A standard font is one every PDF reader already supports, so a document references it by name and no font file travels with it. Font::standardFonts() returns every name Pop PDF accepts:

Constant Value
Font::COURIER Courier
Font::COURIER_OBLIQUE Courier-Oblique
Font::COURIER_BOLD Courier-Bold
Font::COURIER_BOLD_OBLIQUE Courier-BoldOblique
Font::HELVETICA Helvetica
Font::HELVETICA_OBLIQUE Helvetica-Oblique
Font::HELVETICA_BOLD Helvetica-Bold
Font::HELVETICA_BOLD_OBLIQUE Helvetica-BoldOblique
Font::TIMES_ROMAN Times-Roman
Font::TIMES_BOLD Times-Bold
Font::TIMES_ITALIC Times-Italic
Font::TIMES_BOLD_ITALIC Times-BoldItalic
Font::SYMBOL Symbol
Font::ZAPF_DINGBATS ZapfDingbats
Font::ARIAL Arial
Font::ARIAL_ITALIC Arial,Italic
Font::ARIAL_BOLD Arial,Bold
Font::ARIAL_BOLD_ITALIC Arial,BoldItalic
Font::COURIER_NEW CourierNew
Font::COURIER_NEW_ITALIC CourierNew,Italic
Font::COURIER_NEW_BOLD CourierNew,Bold
Font::COURIER_NEW_BOLD_ITALIC CourierNew,BoldItalic
Font::TIMES_NEW_ROMAN TimesNewRoman
Font::TIMES_NEW_ROMAN_ITALIC TimesNewRoman,Italic
Font::TIMES_NEW_ROMAN_BOLD TimesNewRoman,Bold
Font::TIMES_NEW_ROMAN_BOLDITALIC TimesNewRoman,BoldItalic

The Benefits of Standard Fonts#

  • A standard font is referenced by name only
  • Render text from a common set of fonts without having to reach for and mange font files
  • Nothing goes into the PDF beyond a /BaseFont entry naming it — no heavy font file travels with it
  • This can help keep the filesize very low

No font file has to be present at build time either — each name backs a class in Build\Font\Standard\ with its own glyph widths, so new Font(Font::HELVETICA) works with nothing installed. The tradeoff: a standard name always builds and costs the document nothing, but draws in whatever the reader's viewer supplies for that name — see Embedding Fonts.

Font::standardFonts() and the instance method getStandardFonts() return the same array — the static form needs no Font object in hand, and it's what backs the table above.

How a Standard Font Draws Text#

A standard font's content stream carries WinAnsiEncoding (Windows-1252) bytes, one byte per character, not the UTF-8 bytes a PHP string arrives in. café set in Font::ARIAL reaches the page as the four bytes 63 61 66 E9 — not UTF-8's two-byte C3 A9 for é — and Pdf::extractTextFromFile() reads it straight back as café, since the encoding round trips both ways:

PHP
use Pop\Pdf\Document;
use Pop\Pdf\Document\Page;
use Pop\Pdf\Document\Page\Text;
use Pop\Pdf\Document\Font;
use Pop\Pdf\Pdf;

$document = new Document(new Page(Page::LETTER));
$document->addFont(Font::ARIAL);
$document->getPage(1)->addText(new Text('café', size: 24), 'Arial', x: 72, y: 700);

Pdf::writeToFile($document, filename: __DIR__ . '/winansi.pdf');
Pdf::extractTextFromFile(__DIR__ . '/winansi.pdf'); // 'café'

WinAnsiEncoding covers Latin-1 but not every script — a character the font's glyph table has no entry for stops the document at compile time, rather than reaching the page as a missing or substitute glyph:

PHP
$document->getPage(1)->addText(new Text('中', size: 24), 'Arial', x: 72, y: 730);

try {
    Pdf::writeToFile($document, filename: __DIR__ . '/fail.pdf');
} catch (\Pop\Pdf\Build\Font\Exception $exception) {
    echo $exception->getMessage();
    // Error: The font 'Arial' does not contain a glyph for character '中' (U+4E2D).
}

This is the same hasGlyph()/requireGlyphCoverage() machinery Embedding Fonts covers for embedded fonts — a standard Font answers both, checked against its own built-in glyph table rather than a parsed file's cmap.

Selecting the Current Font#

getCurrentFont() reports whichever font was most recently registered, and setCurrentFont() moves that pointer to a font already on the document:

PHP
use Pop\Pdf\Document;
use Pop\Pdf\Document\Font;

$document = new Document();
$document->addFonts([Font::HELVETICA, Font::TIMES_BOLD]);
$document->getCurrentFont(); // 'Times-Bold' — the last one added

$document->setCurrentFont('Helvetica');
$document->getCurrentFont(); // 'Helvetica'

Naming a font that has not been registered raises Document\Exception:

PHP
try {
    $document->setCurrentFont('Arial'); // never registered on $document
} catch (\Pop\Pdf\Document\Exception $exception) {
    echo $exception->getMessage();
    // Error: The font 'Arial' has not been added to the PDF document.
}

A document with no fonts registered answers getCurrentFont() with null, not a fallback name.

Neither method changes what addText() draws with — every call still takes its own $fontStyle, with no implicit fallback to the current font. getCurrentFont() exists to avoid a repeated literal: register once, then pass $document->getCurrentFont() into addText() instead of retyping the name.

Measuring a String#

getStringWidth($string, $size) reports how wide a string renders in a Font, in points, at the given size — call it on the Font object a document already holds, not on the constant:

PHP
use Pop\Pdf\Document;
use Pop\Pdf\Document\Page;
use Pop\Pdf\Document\Font;

$document = new Document(new Page(Page::LETTER));
$document->addFont(Font::HELVETICA);
$font = $document->getFont('Helvetica');

$font->getStringWidth('Centered Heading', 24); // 194.76

Width scales linearly with size — the same string measures 97.38 points at 12pt and 389.52 at 48pt, each exactly double the size before it. A width measured once can be scaled rather than remeasured, though remeasuring at the real size costs about the same and never drifts out of sync.

Centering that heading on a 612-point letter page is a matter of subtracting the measured width from the page width and halving what's left:

PHP
use Pop\Pdf\Document\Page\Text;

$width = $font->getStringWidth('Centered Heading', 24);
$x     = (612 - $width) / 2; // 208.62

$page = $document->getPage(1);
$page->addText(new Text('Centered Heading', size: 24), 'Helvetica', x: $x, y: 700);

The same measurement fits a string to a box by shrinking the size until it stops overflowing, rather than by picking a size and hoping:

PHP
$string   = 'Quarterly Revenue Summary';
$maxWidth = 200;
$size     = 24;

while ($font->getStringWidth($string, $size) > $maxWidth && $size > 1) {
    $size--;
}
// $size is 15, where the string measures 194.22 points wide

Checking What Is Available#

isFontAvailable() and its alias hasFont() both answer whether a name has been registered on the document, and getAvailableFonts() lists every one that has:

PHP
use Pop\Pdf\Document;
use Pop\Pdf\Document\Font;

$document = new Document();
$document->addFonts([Font::HELVETICA, Font::TIMES_BOLD]);

$document->isFontAvailable('Helvetica'); // true
$document->hasFont('Helvetica');         // true, same check
$document->isFontAvailable('Arial');     // false — never registered
$document->getAvailableFonts();          // ['Helvetica', 'Times-Bold']

Those three ask what the document has registered. isStandard(), on a Font object itself, asks a different question — whether that particular font is one of the standard names rather than a file loaded from disk:

PHP
use Pop\Pdf\Document\Font;

(new Font(Font::HELVETICA))->isStandard(); // true

Combining the two answers what a document is actually carrying — every registered name alongside whether it embeds anything:

PHP
$document->addFont(__DIR__ . '/fonts/DejaVuSans.ttf');

foreach ($document->getAvailableFonts() as $name) {
    $font = $document->getFont($name);
    echo $name . ': ' . ($font->isStandard() ? 'standard' : 'embedded') . "\n";
}
// Helvetica: standard
// Times-Bold: standard
// DejaVuSans: embedded

See Also#

  • Embedding Fonts — loading a TrueType, OpenType or Type1 file instead of a standard name
  • Styles — binding a font, a size and a color to one name addText() can take
  • Adding Text — the $fontStyle argument every addText() call resolves against a registered font or style
  • Creating Documents — where a document's fonts live alongside its pages and metadata