Zeichenzauberer

Anfang des Jahres hat Zend die neueste Version des hauseigenen PHP-Framework offiziell freigegeben. Es stellt neben einer umfangreichen Sammlung von Bibliotheken fĂĽr High-Level-Dienste eine API zum Erstellen und Bearbeiten von PDFs zur VerfĂĽgung.

vorlesen Druckansicht 6 Kommentare lesen
Lesezeit: 10 Min.
Von
  • Markus Bach
Inhaltsverzeichnis

In den Anfängen von PHP war es einigermaßen mühsam, PDFs dynamisch zu erstellen. Und zwar war das Schlimme der initiale Aufwand, bis der Rahmen stand, um überhaupt ein erstes Dokument erstellen zu können. Man konnte externe Bibliotheken verwenden. Allerdings waren die meist nicht in PHP, sondern in C programmiert und mussten erst einkompiliert werden. Für erfahrene PHP-Administratoren zwar machbar, unerfahrene Anwender konnte es aber zum Aufgeben bringen. Zudem gab es in Shared-Hosting-Umgebungen meist nicht die Option, etwas an der PHP-Installation zu ändern, da diese in der Hoheit des Hosters lag.

Das hier behandelte Zend-Framework dagegen ist vollständig in PHP implementiert, was auch den wesentlichen Unterschied zu Lösungen wie 2004 in iX beschrieben [1] ausmacht. Das bedeutet, dass PHP in seiner Grundinstallation – Version 5.1.4 oder neuer – nicht geändert werden muss, außerdem sind auf dem Server keine weiteren Programme oder Bibliotheken erforderlich. Es müssen nur die Dateien des Framework vorhanden und im Zugriff sein. Die jeweils aktuelle Version findet sich auf der Projekt-Homepage [a]. Damit das Framework funktioniert, muss es in einem Pfad liegen, der in der Konfigurationsdirektive include_path enthalten ist. Wie man diese gegebenenfalls ändern kann, ist auf der PHP-Site (siehe Onlinequellen [c]) dokumentiert. Wichtig ist, dass man das Verzeichnis library unter dem Verzeichnisnamen des eingespielten Pakets in den Pfad aufnimmt, etwa ./ZendFramework-1.5.0/library.

Diese Schritte vorausgesetzt, lädt folgender Code im PHP-Programm die Zend-API:

<?php
require_once(,Zend/Pdf.php');
?>

Das Zend-Framework unterstützt den PDF-Standard in der Version 1.4, entsprechend Adobes Acrobat 5. In den Dokumenten lassen sich Seiten hinzufügen oder entfernen, außerdem kann man die Sortierung und Dokumenteigenschaften (Autor, Titel, …) ändern. In die Dokumente lassen sich Texte, einfache Grafikelemente (Linien, Rechtecke, Polygone, Kreise und Ellipsen) sowie Bilder im JPG-, PNG- oder TIFF-Format zeichnen.

Objekte der Klasse Zend_Pdf repräsentieren Dokumente, deshalb kann das Erstellen und Laden über den Konstruktor oder durch statische Methoden erfolgen, die sich entweder auf den Pfad zu einer PDF-Datei oder das komplette Dokument in einer String-Variablen beziehen:

<?php 
// Neues PDF erzeugen
$pdf1 = new Zend_Pdf();
// PDF-Datei laden
$pdf2 = Zend_Pdf::load($pathToPdf);
// Dokument aus einem String erzeugen
$pdf3 = Zend_Pdf::parse($pdfAsString);
?>

Um Änderungen im PDF-Dokument zu speichern, können Entwickler die Methoden save und render nutzen. Der Unterschied: save schreibt das Dokument in eine Datei, render gibt es als String zurück – etwa wenn es gleich zur Darstellung an einen Browser als Response geschickt werden soll:

<?php
//PDF in Datei speichern $pdf->save($pathToPdf);
// PDF erzeugen und an Browser schicken
$pdfAsString = $pdf->render();
header(,Content-type: application/pdf');
header(,Content-Disposition: attachment;
filename=\"zend.pdf\"');
echo $pdfAsString;
?>

Die Metadaten eines Dokuments sind nicht Teil des Inhalts, sie dienen vielmehr dazu, Informationen zum Dokument selbst anzugeben. Folgende Attribute können optional ausgewertet und gesetzt werden:

  • Title: Titel des Dokuments
  • Author: Name des Erstellers
  • Subject: kurze Beschreibung des Inhalts
  • Creator und Producer: Angaben darĂĽber, mit welchen Anwendungen das Dokument erstellt beziehungsweise mit welcher Anwendung das Rohdokument in PDF konvertiert wurde
  • CreationDate: Datum der Erzeugung des Dokuments im Format D:YYYYMMDDHHmm wobei D: konstant ist und die restlichen Parameter denen der PHP-Funktion date entsprechen
  • ModDate: Datum der letzten Ă„nderung, das Format entspricht dem von CreationDate

Die einzelnen Attribute liegen im PDF-Objekt als assoziatives Array vor und sind über die Schlüsselwörter einfach ansprechbar:

<?php
$oldAuthor = $pdf->properties[,Author'];
$pdf->properties[,Author'] = $oldAuthor .",
Weiterer Autor";
?>

PDF-Dokumente sind seitenorientiert aufgebaut, das heißt, alle Seiten bestehen unabhängig voneinander und sind deshalb einzeln änderbar. Anders formatierte Seiten als schon im Dokument vorhandene lassen sich einfügen. Innerhalb der API repräsentiert die Klasse Zend_Pdf_Page Seiten im Dokument. Organisiert sind sie innerhalb der Objekte vom Typ Zend_Pdf als öffentliches Array, deshalb lassen sich Seiten leicht ansprechen, entfernen, umsortieren und mit allen Array-Funktionen bearbeiten:

<?php
// 5. Seite des Dokuments laden
$pageFive = $pdf->pages[4];
// 10. Seite entfernen
unset($pdf->pages[9]);
// 5. und 6. Seite vertauschen
$pdf->pages[4] = $pdf->pages[5];
$pdf->pages[5] = $pageFive;
// Seiten verwĂĽrfeln
shuffle($pdf->pages);
?>

Neue Seiten lassen sich auf zwei Arten zu einem Dokument hinzufĂĽgen: Zum einen ĂĽber die statische Methode newPage der Klasse Zend_Pdf, zum anderen ĂĽber den Konstruktor der Klasse Zend_Pdf_Page. Der Unterschied ist, dass newPage eine schon gebundene Seite erzeugt und der Konstruktor eine ungebundene. Jedoch mĂĽssen beide Seiten dem Page-Array des PDF-Objekts explizit zugewiesen werden, damit sie Teil des Dokuments sind. Ungebundene Seiten kann man in mehrere PDF-Objekte eingefĂĽgen, ohne sie neu erstellen zu mĂĽssen. Dies ist nĂĽtzlich, wenn eine Seite als Vorlage fĂĽr mehrere gleichartige Seiten dienen soll.

<?php
// Gebundene Seite einfĂĽgen
$pageBound = $pdf1->newPage(Zend_Pdf_Page::SIZE_A4);
$pdf1->pages[] = $pageBound;
// Ungebundene Seite erzeugen
$pageUnbound = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_A4);
// Seite an zwei unabhängige PDFs anhängen
$pdf1->pages[] = $pageUnbound;
$pdf2->pages[] = $pageUnbound;
?>

Die Methode newPage und der Konstruktor erwarten jeweils als Parameter die Seitengröße, als Konstanten sind die Felder SIZE_A4, SIZE_A4_LANDSCAPE, SIZE_LETTER oder SIZE_LETTER_LANDSCAPE der Klasse Zend_Pdf_Page verwendbar. Die Seitengröße kann man auch selbst definieren, indem man Höhe und Breite in Pixel als Parameter übergibt.

Um eine bestehende Seite als Vorlage für eine neue Seite zu verwenden, übergibt man die Template-Seite dem Konstruktor, der so eine ungebundene Seite erstellt, die sich dem PDF wieder anhängen lässt:

<?php
//1. Seite des Dokuments als Template laden
$templatePage = $pdf->pages[0];
// Neue Seite mit dem Template erzeugen
$newPage = new Zend_Pdf_Page($templatePage);
// Neue Seite hinzufĂĽgen
$pdf->pages[] = $newPage;
?>

Da jetzt die Grundlagen geschaffen sind, um mit Dokumenten umzugehen, können die Seiten mit Inhalt gefüllt werden. Um einen Text einzufügen, verwendet man die Methode drawText der Klasse Zend_Pdf_Page. Sie erwartet als Parameter den Text und die Position des Textes als XY-Koordinaten (Nullpunkt in der linken unteren Ecke). Optional kann man das Encoding des Textes angeben, falls es zu Problemen mit Sonderzeichen kommt:

<?php
$page = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_A4);
// Text in Latin1-Codierung schreiben
$page->drawText(,Hello PDF world!', 100, 100, ,Latin1');
?>

Leider geben diese Code-Zeilen nur eine Fehlermeldung aus, denn es ist nur festgelegt, was und wo dargestellt werden soll, aber es fehlen Angaben über Schriftart, -stil und -farbe, die in Objekten der Klasse Zend_Pdf_Style stehen. Pro PDF können mehrere Styles zum Einsatz kommen, jeweils gesetzt vor dem Aufruf der draw-Methoden. Ein Style bleibt solange gültig, bis ein neuer angegeben wird. Ein Beispiel zeigt Listing 1 (ein Beispielprogramm mit – fast – allen besprochene Befehlen ist über den iX-Listing-Service erhältlich. [d]

Schriftart und Schriftgröße werden dem Style mit der Methode setFont zugewiesen, wobei Schriftarten mit statischen Methoden der Klasse Zend_Pdf_Font zunächst zu laden sind. Das Framework definiert bereits sechs Schriftarten, ladbar über ihren Namen. Zend_Pdf_Font enthält zum einfachen Umgang entsprechende Konstanten. Andere Schriftarten können über fontWithPath aus einer Datei mit der Truetype-Definition geladen werden.

Die Schriftfarbe legt setFillColor fest, mit einem Farb-Objekt als Parameter. Die Farb-Objekte lassen sich auf vier verschiedene Arten definieren. Der Konstruktor der Klasse Zend_Pdf_Color_Html erwartet ein Farbschema, wie es auch in HTML-Dateien zum Einsatz kommt – das Zeichen # gefolgt vom RGB-Wert in hexadezimaler Angabe. Zend_Pdf_Color_Rgb legt RGB-Werte als Floats im Wertebereich von 0 bis 1 fest, Graustufen unterstützen die Methoden Zend_Pdf_Color_Greyscale, das CMYK-Schema Zend_Pdf_Color_Cmyk.

Bilder (JPG, PNG, TIFF) lassen sich aus Dateien laden, primitive Grafiken auch mit dem Framework selbst zeichnen. Die Klasse Zend_Pdf_Page enthält Methoden für Kreise, Ellipsen, Polygone, Rechtecke und Linien oder Teile davon. Um etwa nur ein Kreissegment zu zeichnen, kann man der Methode drawCircle zusätzlich Start- und Endwinkel übergeben. Für die Primitive sind optional die Darstellungsmodi wie Füllung und Umrandung einstellbar (Listing 2).

Beim Laden eines Bildes ermittelt die statische Methode imageWithPath der Klasse Zend_Pdf_Image automatisch den Typ des Bildes. Danach kann man das Bild samt Koordinaten an die drawImage-Methode von Zend_Pdf_Pag ĂĽbergeben:

<?php
// Bild aus Datei laden
$image = Zend_Pdf_Image::imageWithPath(,/tmp/testbild.jpg');
// Bild in Orginalgröße zeichnen
$x1 = 100;
$y1 = 300;
$x2 = $x1 + $image->getPixelWidth();
$y2 = $y1 + $image->getPixelHeight()¬ ;
$page->drawImage($image, $x1, $y1, $x2, $x2);
?>

Das Bild lässt sich beim Zeichnen gleich skalieren, wenn für die Koordinaten $x2 und $y2 nicht die genaue Höhe und Breite berücksichtigt werden, sondern entsprechend transformierte Werte.

Das Zend-Framework erlaubt es, ohne größere Verrenkungen PDF-Dokumente zu generieren und zu ändern. Zwar lassen sich nicht alle Features des PDF-Standards nutzen (Formulare, Signaturen et cetera), dennoch dürften sich die meisten Anwendungsfälle lösen lassen. Es bleibt aber zu hoffen, dass die Implementierung der PDF-API vollständiger und aktueller wird. Trotzdem arbeitet die API performant und ist in ihren Methoden leicht verständlich. Das Jonglieren mit Pixeln und Koordinaten erweist sich zwar manchmal als etwas lästig, aber das liegt weniger an der API als an PDF selbst. (JS)

Markus Bach
beschäftigt sich seit 1999 mit PHP. Sein Aufgabenschwerpunkt liegt momentan in der Java-Entwicklung, außerdem ist er als Softwarearchitekt tätig.

[1] Rolf Assfalg; PDF; Auf Wunsch Druck; Professionelle Druckausgaben fĂĽr Internet-Anwendungen; iX 1/2004, S. 126

ix-Link

Mehr Infos

Listing 1

<?php
// Style fĂĽr Ăśberschriften festlegen
$headlineStyle = new Zend_Pdf_Style();
$headlineStyle->setFont(Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_HELVETICA_BOLD), 14);
$headlineStyle->setFillColor(new Zend_Pdf_Color_Html(,#ff0000'));
// Style fĂĽr Text festlegen
$bodyStyle = new Zend_Pdf_Style();
$bodyStyle->setFont(Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_HELVETICA), 10);
$bodyStyle->setFillColor(new Zend_Pdf_Color_Rgb(0, 0, 0.9));
// Style setzen und Ăśberschrift schreiben:
$page->setStyle($headlineStyle);
$page->drawText(,Ăśberschrift', 10, 500);
// Style setzen und Body schreiben:
$page->setStyle($bodyStyle);
$page->drawText(,Text Text Text', 10, 450);
?>
Mehr Infos

Listing 2

<?php // Zunächst wird der Style definiert $style = new Zend_Pdf_Style(); $style->setFillColor
(new Zend_Pdf_Color_Html(,#ff0000’)); $style->setLineColor(new Zend_Pdf_Color_Html
(,#00ff00’)); $style->setLineWidth(3); $page->setStyle($style);
// Zeichne einen Kreis an // Position x = 50 und y = 300 mit Radius 30 Pixel $page->drawCircle
(50, 300, 30);
// Zeichne ein Kreissegment // von Winkel PI/4 bis 3*PI/4 $page->drawCircle(100, 300, 30,
(M_PI / 4), (3 * M_PI / 4));
// Zeichne ein gefĂĽlltes Rechteck mit AuĂźenlinie mit den Koordinaten
// der Eckpunkte 100 / 300 und 200 / 400 $page->drawRectangle(100, 300, 200, 400,
Zend_Pdf_Page::SHAPE_DRAW_FILL_AND_STROKE);
// Zeichne nur die AuĂźenlinie $page->drawRectangle(300, 300, 400, 400,
Zend_Pdf_Page::SHAPE_DRAW_STROKE);
// Zeichne nur die FĂĽllung $page->drawRectangle(100, 100, 200, 200,
Zend_Pdf_Page::SHAPE_DRAW_FILL); ?>

(js)