Case studies

Sprungmarken und Links

FPDF unterstützt sowohl interne Links (Sprungmarken) also auch externe Verweise (Weblinks), die sich mit wenigen Codezeilen realisieren lassen:

$link = $pdf->AddLink();
$pdf->SetLink($link,0,2);
$pdf->Write(5, "Seite 1\n");
$pdf->SetTextColor(0,0,200);
$pdf->Write(5, "Gehe zu Seite 2", $link);

$pdf->AddPage();

$pdf->SetTextColor(0,0,0);
$pdf->Write(5,"Seite 2\n");
$pdf->SetTextColor(0,0,200);
$pdf->Write(5,"http://www.trilos.de", "http://www.trilos.de");

Um eine Sprungmarke zu setzen, müssen wir sie zunächst via AddLink() instanzieren. SetLink() setzt sie in das Dokument ein; dafür werden bis zu drei Paramter benötigt - die Sprungmarke selbst, vertikale Zielposition und Nummer der Zielseite (die letzten beiden Parameter haben jeweils die Defualtwerte 0 und können auch weggelassen werden.

Für externe Links brauchen wir lediglich in den Ausgabefunktionen Write(), Cell() oder MultiCell() die gewünschte URL als letzten Parameter zu übergeben. Das Beispiel verändert zusätzlich noch die Textfarbe, um die Links/Sprungmarken deutlicher hervorzuheben.

Die Wrapper-Klasse PDFDOC

Zunächst erstellen wir uns eine eigene Wrapper-Klasse namens PDFDOC, um die FDPD ein wenig zu erweitern und den durch uns zu erstellenden Quellcode möglichst kurz zu halten. Der Wrapper wird für alle folgenden Beispiele benötigt.

// Abgeleitete PDFDOC-Klasse
class PDFDoc extends PDF {

  // Übergeordnetes Objekt
  var $pdf;

  // PDF im Konstruktor erstellen
  function PDFDoc() {
    $this->pdf = new PDF("P", "mm", "A4");
    $this->pdf->SetTitle("PDF-Vortrag");
    $this->pdf->AliasNbPages();
    $this->pdf->Open();
    $this->pdf->SetMargins(25,25,20);
    $this->pdf->AddPage();
  }

  function Display() {
    header("Content-type: application/pdf");
    $this->pdf->Output();
  }
}

PDFDOC besitzt lediglich einen Konstruktor, der uns ein PDF mit den gängigen Eigenschaften vorbereitet, sowie eine Methode Display(), die das Erzeugte ausgibt.

Dokument aus Bausteinen zusammenstellen

Beginnen wir damit, uns ein längeres Dokument aus Bausteinen zusammenzustellen. Der Einfachheit halber definieren wir uns zwei Typen von Bausteinen: Überschriften (groß und fett) sowie Absätze (normal und mehrzeilige Fliesstexte). PDFDOC soll beliebig viele dieser Bausteine entgegennehmen können. Wir erweitern den Wrapper also um zwei Methoden:

// Überschrift hinzufügen
function AddHeading($text) {
  // Font stzen
  $this->pdf->SetFont("Times", "B", 14);
  // Überschrift ausgeben
  $this->pdf->Write(5, $text);
  // Abstand
  $this->pdf->Ln(10);
}

// Absatz hinzufügen
function AddParagraph($text) {
  $this->pdf->SetFont("Times", "", 11);
  $this->pdf->Write(5, $text);
  $this->pdf->Ln(10);
}

Eine kurze Demo überprüft das ordnungsgemäße Funktionieren:

$d = new PDFDOC();
for($i=1;$i<5;$i++) {
  $d->AddHeading($i.". Kapitel");
  $d->AddParagraph(str_repeat("Gallia est omnia divisa in partes tres... ", 4+($i*2))); }
$d->Display();

Grafik-Ausgabe

Als nächstes betrachten wir die grafischen Fähigkeiten der FPDF; mit Hilfe von Line() und SetDrawColor() lassen sich schöne Effekte erzielen - auch hier wieder als neue Methode innerhalb von PDFDOC realisiert:

// Grafische Spielerei
function fancyGraphic() {
  for($x=10;$x<=200;$x++) {
    $y = 60+49*sin($x/20);
    // Malfarbe verändern
    $this->pdf->SetDrawColor(100,200-$x,$x);
    // Linie zeichen
    $this->pdf->Line($x, $y, $y, 100-$y/2);
    $this->pdf->Line($y, (100-$y/2)+100, $y+100, $x);
  }
}
Grafische Spielerei (Ergebnis)
Grafische Spielerei (Ergebnis)

Einfaches Diagramm

Oftmals steht man vor der Aufgabe, Daten grafisch dazustellen. Mit Hilfe der Gdlib ist das in PHP auch ohne weiteres möglich - und in PDF geht das natürlich auch. Unsere neue Methode simpleDiagram() liefert uns auf komfortable Art ein Balkendiagramm. Als Eingabeparameter erwartet sie eine Überschrift für das Diagramm, die darzustellenden Werte in Form eines einfachen assoziativen Arrays, sowie eine Farbe für die Balken in HTML-konformer Hexadezimal-Notation.

Hier lernen wir eine weitere Methode der FPDF kennen: Cell(), die mit bis zu acht Parametern sehr mächtig ist. Cell() erzeugt uns eine rechteckige Fläche (Zelle) mit einer optionalen Umrandung, einer Hintergrundfarbe und einem Ausgabetext. Die linke obere Ecke der Zelle entsprecht der aktuellen Position des "virtuellen Cursors". Cell() kennt die folgenden Parameter:

  1. Breite der Zelle (wenn 0, dann erstreckt sich die Zelle bis zum rechten Rand)
  2. Höhe (Standardwert: 0)
  3. Ausgabetext (String)
  4. Umrandung (0=ohne, 1=mit Rahmen) oder alternativ eine Kombination aus L, T, R und B für left/top/right/bottom
  5. Neue Position des Cursors nach der Ausgabe: 0=nach rechts, 1=an den Anfang der nächsten Zeile, 2=darunter
  6. Ausrichtung des Ausgabetextes: L (oder leer)=linksbündig, R=rechtsbündig, C=zentriert
  7. Füllung: 0=transparent, 1=mit Füllfarbe
  8. Link

Hinweis: Für die Ausgabe von längeren Texten (mit Zeilenumbrüchen) ist die Methode MultiCell() vorgesehen. Hier werden die Texte entweder automatisch umgebrochen - d.h. wenn sie den rechten Seitenrand erreichen - oder aber manuell durch das Steuerzeichen "\n". Insgesamt werden so viele Zeilen wie nötig untereinander dargestellt. Weitere Möglichkeiten für die Textausgabe stehen auf der FPDF-Homepage zum Download bereit.

// Einfaches Diagramm
function simpleDiagram($title, $data, $color="0000e0") {
  // Font setzen
  $this->pdf->SetFont("Times", "B", 14);
  // Titel ausgeben
  $this->pdf->Write(5, $title);
  $this->pdf->Ln(8);
  $this->pdf->SetFont("Times", "", 11);
  // Malfarbe setzen
  $this->pdf->SetFillColor(hexdec(substr($color,0,2)), hexdec(substr($color,2,2)), hexdec(substr($color,4,2)));
  // Werte analysieren
  while(list($key, $value) = each($data)) {
    // Breitestes Label
    if($this->pdf->GetStringWidth($key)>$kmax) $kmax = $this->pdf->GetStringWidth($key)+5;
    // Größter Datenwert
    if($value>$vmax) $vmax = $value;
  }
  $faktor = (150-$kmax)/$vmax;
  // Daten ausgeben
  reset($data);
  while(list($key, $value) = each($data)) {
    // Label
    $this->pdf->Cell($kmax,5,$key,0,0,"R");
    // Balken
    $this->pdf->Cell($value*$faktor,5," ",0,0,"C",1);
    // Beschriftung
    $this->pdf->Cell(0,5,number_format($value,0,",","."),0,1,"L");
  $this->pdf->Ln(1);
  }
  // Abstand
  $this->pdf->Ln(5);
}

$d = new PDFDoc();
$d->simpleDiagram("Zusammensetzung des Deutschen Bundestages (2002-2004)", array("SPD"=>251, "CDU/CSU"=>247, "B90/Die Grünen"=>55, "FDP"=>47, "fraktionslos"=>3),"ffa07a");

$d->simpleDiagram("Flächen einiger Bundesländer (Quadratkilometer)", array("Baden-Württemberg"=>35751, "Bayern"=>70554, "Brandenburg"=>29061, "Hessen"=>21114, "Niedersachsen"=>47344, "Rheinland-Pfalz"=>19849, "Thüringen"=>16254),"2e8b57");

$d->Display();

Nach dem Aufruf von simpleDiagram() legt die Methode die gewünschten PDF-Eigenschaften fest, gibt den Titel aus und analysiert die übergebenen Werte, damit die Ausgabe auch von der Größe her passt. Die FPDF-Methode Ln() fügt einen frei definierbaren Zeilenabstand ein.

Ausgabe von simpleDiagram()
Ausgabe von simpleDiagram()

Übrigens: weitere Lösungen für die Erzeugung von Diagrammen stehen zum Download auf der FPDF-Homepage bereit.

mySQL-Operationen

Zum Abschluss der Case studies zeige ich ein Beispiel, um mySQL-Tabellen in ansprechender Form zu dokumentieren. Ziel soll die Auflistung aller wichtigen Eigenschaften einer mySQL-Tabelle sein.

Für das Beispiel wird eine beliebige mySQL-Datenbank mit mindestens einer Tabelle beliebiger Struktur benötigt. Im Code-Beispiel stellen wir als erstes eine Verbindung zum Datenbankserver her und rufen unsere neue PDFDOC-Methode mit zwei Parametern auf, nämlich dem Datenbankhandle und dem Namen der zu dokumentierenden Tabelle.

Die Methode erstellt uns eine aus mehreren Cell()-Blöcken bestehende Tabellenkopfreihe und gibt anschließend alle Tabellenfelder mit ihren Eigenschaften untereinander aus. Die Funktionsweise ist in etwa dem bekannten phpMyAdmin vergleichbar.

Es sei noch einmal darauf hingewiesen: unsere "Tabelle" hat mit einer herkömmlichen HTML-Tabelle nicht viel mehr als die Bezeichnung gemein. Da unser PDF-Dokument eine variable Zeichenfläche ist (vergl. Kapitel 4.3), könnten die Zelleninhalte die Umrahmungen (aus der Kopfzeile) jederzeit überschreiben. Entweder nimmt man dieses (unschöne) Verhalten schweigend in Kauf, oder man schreibt sich Routinen, welche die zu erwartenden Stringlängen berücksichtigen und ggf. die Kopfzeilen variabel gestalten - ganz ähnlich wurde ja bereits beim Balkendiagramm aus dem vorhergehenden Kapitel 5.4) verfahren.

// Verbindung zur Datenbank herstellen
$dbh = mysql_connect("localhost", "root", "");
mysql_select_db("demo");

// mySQL-Tabellenstruktur ausgeben
function mySQLTable($dbh, $table) {
  // Felder der Tabelle auslesen
  $query = mysql_query("show fields from ".$table, $dbh);
  if(mysql_affected_rows($dbh)>0) {
    // Tabellenlayot vorbereiten
    $this->pdf->SetFont("Helvetica", "B",14);
    $this->pdf->SetFillColor(200,200,200);
    $this->pdf->Write(5, "Tabellenstruktur von \"$table\"");
    $this->pdf->SetFont("","",11);
    $this->pdf->Ln(8);

    // Kopfzeilen schreiben
    $this->pdf->Cell(55,5,"Field",1,0,"C",1);
    $this->pdf->Cell(30,5,"Type",1,0,"C",1);
    $this->pdf->Cell(20,5,"Null",1,0,"C",1);
    $this->pdf->Cell(35,5,"Extra",1,1,"C",1);

    // Datensätze der Feldzeilen ausgeben
    while($row = mysql_fetch_array($query)) {
      $this->pdf->Cell(55,5,$row["Field"],1,0,"",0);
      $this->pdf->Cell(30,5,$row["Type"],1,0,"",0);
      $this->pdf->Cell(20,5,($row["Null"]=="") ? "No":"Yes",1,0,"",0);
      $this->pdf->Cell(35,5,$row["Extra"],1,1,"",0);
    }
    // Abstand
    $this->pdf->Ln(10);
  }
}
mySQL-Tabellenanalyse
Egebnis der Tabellenanalyse

Würde man zuvor via PHP alle Tabellen einer gegebenen Datenbank herausfinden und die PDFDOC-Methode mySQLTable() in einer Schleife aufrufen, wäre das Ergebnis eine ordentliche Datenbankdokumentation, die man sogar einem Kunden aushändigen könnte.

Kopf- und Fußzeilen

Für mehrseitige PDF-Dokumente bietet es sich an, regelmäßig wiederkehrende Kopf- und Fußzeilen zu implementieren. FPDF bringt dafür bereits zwei Methoden mit; Header() und Footer(), die in der ursprünglichen Mutterklasse jedoch leer sind. Aufgerufen werden sie übrigens automatisch. Um sie einzusetzen, sollte man sie in der eigenen Unterklasse implementieren - in unserem Falle wäre das unsere altgediente Wrapper-Klasse PDFDOC.

In der Kopfzeile wollen wir unser Firmenlogo inklusive Schriftzug unterbringen, während in der Fußzeile die jeweils aktuelle Seitenzahl plus der Gesamtseitenzahl erscheinen soll. Für letzteres bietet FDPD die Methode PageNo(), welche die gerade aktuelle Seitenzahl liefert, sowie einen Alias {nb}, der beim Abschluss des Dokumentes ersetzt wird. Der Alias kann via AliasNbPages() im übrigen auch umdefiniert werden.

// Kopfzeile
function Header() {
  // Logo einfügen
  $this->pdf->SetFont("Arial","B",12);
  $this->pdf->Cell(0,5,"www.trilos.de",0,0,"R");
  $this->pdf->Image("trilos_logo.png",10,8,46);
  $this->pdf->Ln(10);
}

function Footer() {
  // Position auf 1.5 cm vom unteren Rand entfernt setzen
  $this->pdf->SetY(-15);
  $this->pdf->SetFont('Arial','I',8);
  // Seitennumer
  $this->Cell(0,10,"Seite ".$this->PageNo()." von {nb}",0,0,"C");
}