Wer kennt es nicht – das PDF?
Wir haben es alle bestimmt schon mal benutzt. Doch wie erstellen wir ein solches Dokument? Ich möchte euch in diesem kleinem Tutorial zeigen, wie ihr mit der Zend-PDF-Komponente eure eigenen PDF-Dateien erstellen könnt. Der Clou dabei: wir verwenden Zeilenumbrüche (mehrzeilige Texte).
Zum Schluss möchten wir in unserem Controller nur wenig Code sehen. Dieser könnte so aussehen:
<?php
class PdfController
extends Zend_Controller_Action
{
public function init()
{
$this->_helper
->viewRenderer
->setNoRender(true);
$this->_helper
->layout()
->disableLayout();
}
public function createPdfAction()
{
// @todo hier evtl. Daten aus einem Model/Service holen und an My_Example_Pdf übergeben
// My_Example_Pdf könnte genauso gut auch gleich ein Service sein.
$pdf = new My_Example_Pdf();
$pdf->renderToOutput();
}
}
Hierzu benötigen wir jedoch zunächst die Klasse My_Example_Pdf.
<?php
class My_Example_Pdf
extends Zend_Pdf
{
public function renderToOutput()
{
}
}
So kommen wir unserem Ziel schon näher – langsam ;D Fangen wir hier also mit der Ausgabe der PDF-Datei an. Dafür müssen bestimmte Header gesetzt werden. Darunter zB. das PDF als Body und die Content-Length.
$response->setHeader('Content-Disposition', 'attachment; filename="My_Example_PDF_' . date('d-m-Y') . '.pdf"');
$response->setHeader('Content-Type', 'application/pdf', true);
$response->setHeader('Content-length', strlen($binaryPdf));
$response->setBody($binaryPdf);
$binaryPdf entspricht hier dem Inhalt des gerenderten PDF-Dokumentes, den wir durch Aufruf der render-Methode der Zend_Pdf-Klasse erhalten.
$binaryPdf = $pdf->render();
Wir können ein PDF natürlich komplett ohne irgendwelche Templates erstellen. Ich für meinen Fall, habe jedoch eine Example.pdf-Datei, welche bereits feste Formularfeldpositionen hat, die später ausgefüllt werden sollen. Es gäbe nun verschiedene Ansätze unsere PDF-Datei mit Daten zu füttern. Ich entscheide mich hier für den Weg, bereits vorab eine Instanz von Zend_Pdf zu erzeugen und diese an meine My_Example_Pdf-Klasse zu übergeben. Je nachdem in welchem Kontext wir uns gerade befinden und wie spezifisch das PDF aufgebaut werden muss, könnten sich auch andere Ansätze erschließen.
Erzeugen wir also eine Instanz von Zend_PDF und laden unser Tempalte Example.pdf, welches wir nun auch mit Daten füllen werden:
$pdf = Zend_Pdf::load(APPLICATION_PATH . '/../data/Example.pdf');
$firstPage = $pdf->pages[0];
$firstPage->setFont(Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_HELVETICA_BOLD), 10);
$firstPage->drawText('foo', 105, 764.3, 'UTF-8');
$firstPage->drawText('bar', 181.8, 740.3, 'UTF-8');
$firstPage->drawText('baz', 365, 740.3, 'UTF-8');
$firstPage->setFont(Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_HELVETICA), 9);
$this->drawMultilineText($firstPage, $something, 75, 685);
Huch, was haben wir denn hier? Die Funktion drawMultilineText haben wir bisher noch gar nicht erwähnt. Das holen wir nun nach. Diese Funktion erwartet als ersten Parameter eine Pdf-Seite, auf welcher mehrzeiliger Text eingefügt werden soll. Als zweiten Parameter übergeben wir den Text, als dritten und vierten Parameter die x- und y-Startpositionen von unten links ausgehend, als 5ten Parameter können wir die Zeilenhöhe und als 6ten das Charset angeben. Durch Angabe der Zeilenhöhe können wir also – aha das ist unser Geheimnis – mehrere Zeilen berechnen und entsprechend den Text hinein zeichnen. Die Zeilenhöhe sollte daher entsprechend der Schritgröße oder etwas größer gewählt werden.
Nun zu unserer Funktion:
public function drawMultilineText(Zend_Pdf_Page $pdfPage, $text, $xStart, $yStart, $lineHeight=10, $charset='UTF-8')
{
$i = 0;
$string_a = explode("n", $text);
$outputString = '';
foreach ($string_a as $part) {
$temp = wordwrap($part, $this->_maxLen, "n", true);
$temp_a = explode("n", $temp);
foreach ($temp_a as $line) {
$i++;
if ($i > $this->maxLines) {
break;
}
// Nun Zeichnen wir eine Zeile von unserem Text.
$pdfPage->drawText($this->_spaces . $line, $xStart, $yStart, $charset);
$yStart -= $lineHeight;
}
}
}
Wir teilen unseren Text anhand der bereits vorhandenen Zeilenumbrüche, iterieren über den Text, brechen die Zeilen nochmals anhand der maximalen Zeichenlänge um und iterieren weiter – nur diesmal über die zerstückelten Satzteile bis wir endlich eine neue Zeile in unsere PDF-Seite dazu zeichen. Wir haben also nachstehende Klasse erzeugt:
class My_Example_Pdf
extends Zend_Pdf
{
/**
* You can add space before every line for advanced usage.
*
* @var string
*/
private $_spaces = '';
/**
* The maximum char count per line.
*
* @var integer
*/
private $_maxLen = 110;
/**
* The maximum of rows to draw.
*
* @var integer
*/
private $_maxLines = 40;
public function __construct($options = null)
{
// Nothing to do here for now.
}
public function renderToOutput()
{
$binaryPdf = $pdf->render();
$response->setHeader('Content-Disposition', 'attachment; filename="My_Example_PDF_' . date('d-m-Y') . '.pdf"');
$response->setHeader('Content-Type', 'application/pdf', true);
$response->setHeader('Content-length', strlen($binaryPdf));
$response->setBody($binaryPdf);
$response->sendResponse();
}
public function drawMultilineText(Zend_Pdf_Page $pdfPage, $text, $xStart, $yStart, $lineHeight=10, $charset='UTF-8')
{
$i = 0;
$string_a = explode("n", $text);
$outputString = '';
foreach ($string_a as $part) {
$temp = wordwrap($part, $this->_maxLen, "n", true);
$temp_a = explode("n", $temp);
foreach ($temp_a as $line) {
$i++;
if ($i > $this->maxLines) {
break;
}
// Nun Zeichnen wir eine Zeile von unserem Text.
$pdfPage->drawText($this->_spaces . $line, $xStart, $yStart, $charset);
$yStart -= $lineHeight;
}
}
}
}
Wir können nun das $firstPage-Objekt an My_Example_Pdf->drawMultilineText($firstPage, …. übergeben und danach unser PDF rendern und zB. zum Download anbieten.