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:
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:
$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():
$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:
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
/BaseFontentry 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:
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:
$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:
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:
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:
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:
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:
$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:
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:
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:
$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
$fontStyleargument everyaddText()call resolves against a registered font or style - Creating Documents — where a document's fonts live alongside its pages and metadata