PDF Generation
Office App has built-in PDF generation using TCPDF. You can define multiple PDF templates per model, preview them in the browser, download them, or auto-attach them to a record's file field.
Step 1: Define PDF Types
Override the static getPdfs() method to declare available PDFs:
| use System\Pdf\PdfDefinition;
public static function getPdfs(): array
{
return [
new PdfDefinition('invoice', 'Invoice'),
new PdfDefinition('summary', 'Summary Report'),
(new PdfDefinition('pass', 'Access Pass'))->attachTo('document'), // (1)
];
}
|
attachTo('document') automatically saves the generated PDF into the document PdfProperty field when the user clicks "Attach".
Each definition creates a button in the record's toolbar under a "PDF" group with options to Preview, Download, and Attach (if configured).
Step 2: Implement the Builder
Override getPdf() to dispatch to your builder methods:
| use System\Pdf\PdfContext;
use System\Pdf\PdfDocument;
public function getPdf(string $key, PdfContext $ctx): PdfDocument
{
return match ($key) {
'invoice' => $this->buildInvoicePdf(),
'summary' => $this->buildSummaryPdf(),
'pass' => $this->buildPassPdf(),
default => throw new \InvalidArgumentException("Unknown PDF: $key"),
};
}
|
Step 3: Build the PDF
A builder method returns a PdfDocument with a closure that receives a Pdf instance (extends TCPDF):
| use System\Pdf\Pdf;
use System\Pdf\PdfDocument;
use System\UI\Lang;
private function buildInvoicePdf(): PdfDocument
{
$record = $this;
return (new PdfDocument(function (Pdf $pdf) use ($record) {
// Heading
$pdf->heading(Lang::t('Invoice'));
// Key-value table
$pdf->keyValueTable([
[Lang::t('Title'), $record->title ?? '—'],
[Lang::t('Reference'), $record->reference ?? '—'],
[Lang::t('Value'), $record->value ?? '—'],
[Lang::t('Date'), $record->start_date ?? '—'],
[Lang::t('Status'), $record->_state ?? '—'],
]);
// Spacer
$pdf->spacer(6);
// Body text
$pdf->body(
Lang::t('Thank you for your business.'),
11 // font size
);
}))
->title(Lang::t('Invoice') . ': ' . ($record->title ?? ''))
->orientation('P') // 'P' = portrait, 'L' = landscape
->pageSize('A4'); // 'A4', 'Letter', etc.
}
|
PDF Helper Methods
The Pdf class (extends TCPDF) provides these convenience methods:
heading(string $text, int $size = 14)
Prints a bold heading.
| $pdf->heading('Contract Summary');
$pdf->heading('Section Title', 12); // smaller heading
|
body(string $text, int $size = 10)
Prints body text with automatic line wrapping.
| $pdf->body('This is a paragraph of text.');
$pdf->body($longText, 9); // smaller font
|
keyValueTable(array $rows)
Prints a two-column table. Each row is [label, value].
| $pdf->keyValueTable([
['Name', $this->name],
['Email', $this->email],
['Status', $this->status],
]);
|
spacer(int $mm)
Adds vertical whitespace.
| $pdf->spacer(10); // 10mm gap
|
imageFromDataUri(string $dataUri, float $w, float $h, string $type)
Embed an image from a data URI (useful for QR codes).
| $qr = new QRCodeProperty('qr');
$dataUri = $qr->getDataUri(url: $recordUrl, size: 80);
$pdf->imageFromDataUri($dataUri, 25, 25, 'PNG');
|
TCPDF Native Methods
Since Pdf extends TCPDF, you have access to all TCPDF methods:
| $pdf->SetFont('dejavusans', '', 10);
$pdf->Cell(0, 8, 'Cell text', 1, 1);
$pdf->MultiCell(0, 5, 'Multi-line text', 1);
$pdf->Ln(5);
$pdf->SetFillColor(240, 240, 240);
$pdf->write1DBarcode('ABC-123', 'C128', '', '', 60, 15);
|
PdfDocument Options
| (new PdfDocument($closure))
->title('Document Title') // shown in browser tab
->orientation('P') // 'P' portrait, 'L' landscape
->pageSize('A4') // 'A4', 'Letter', 'Legal', etc.
|
Complete Example: Contract Summary with QR Code
| use System\Pdf\Pdf;
use System\Pdf\PdfContext;
use System\Pdf\PdfDefinition;
use System\Pdf\PdfDocument;
use System\QRCodeProperty;
use System\UI\Lang;
class Contract extends DefaultModel
{
public static function getPdfs(): array
{
return [
(new PdfDefinition('summary', 'Contract Summary'))
->attachTo('document'),
new PdfDefinition('cover_letter', 'Cover Letter'),
];
}
public function getPdf(string $key, PdfContext $ctx): PdfDocument
{
return match ($key) {
'summary' => $this->buildSummaryPdf(),
'cover_letter' => $this->buildCoverLetterPdf(),
default => throw new \InvalidArgumentException("Unknown PDF: $key"),
};
}
private function buildSummaryPdf(): PdfDocument
{
$contract = $this;
return (new PdfDocument(function (Pdf $pdf) use ($contract) {
$pdf->heading(Lang::t('Contract Summary'));
$pdf->keyValueTable([
[Lang::t('Title'), $contract->title ?? '—'],
[Lang::t('Reference'), $contract->reference ?? '—'],
[Lang::t('Value'), $contract->value ?? '—'],
[Lang::t('Start'), $contract->start_date ?? '—'],
[Lang::t('End'), $contract->end_date ?? '—'],
[Lang::t('Status'), $contract->_state ?? '—'],
]);
$pdf->spacer(6);
// QR code linking to this record
$url = rtrim(APP_URL, '/') . '?model=contract&id=' . $contract->getId();
$qr = new QRCodeProperty('qr');
$dataUri = $qr->getDataUri(url: $url, size: 80);
if ($dataUri !== '') {
$pdf->body(Lang::t('Scan to open record:'), 8);
$pdf->imageFromDataUri($dataUri, 25, 25, 'PNG');
}
}))
->title(Lang::t('Contract Summary') . ': ' . ($this->title ?? ''))
->orientation('P')
->pageSize('A4');
}
private function buildCoverLetterPdf(): PdfDocument
{
$contract = $this;
return (new PdfDocument(function (Pdf $pdf) use ($contract) {
$pdf->heading(Lang::t('Cover Letter'), 16);
$pdf->body(date('d.m.Y'));
$pdf->spacer(8);
$pdf->body(
Lang::t('Dear Sir or Madam,') . "\n\n" .
Lang::t('Please find enclosed the contract') .
' "' . ($contract->title ?? '') . '".' . "\n\n" .
Lang::t('Yours sincerely,'),
11
);
}))
->title(Lang::t('Cover Letter') . ': ' . ($this->title ?? ''))
->orientation('P')
->pageSize('A4');
}
}
|