Webseiten mit wkhtmltopdf unter PHP als PDF exportieren

Internet-Ausdrucker, es gibt sie wirklich. Auch ich kann mich von dieser Krankheit nicht ganz freisprechen. Eine Unterart der Ausdrucker sind die Webseiten-Herunterlader. Am liebsten natürlich als PDF, um die Seite später vielleicht dann doch noch auszudrucken oder ganz komfortabel an den Arbeitskollegen zu versenden. Wer seine eigene Webseite als PDF exportierbar machen möchte, kann auf zahlreiche Tools zurück greifen. wkhtmltopdf ist eines davon.

Was ist wkhtmltopdf?

wkhtmltopdf ist einer HTML-Renderer, welcher Webseiten quasi 1:1 in PDF-Dokumente oder Bilder exportieren kann. Anders als Wettbewerber wie Dompdf oder TCPDF bleibt er durch die verwendete Webkit-Engine erstaunlich nah am Original. Anzegige-Fehler sind im Gegensatz zu Konkurrenz eine Seltenheit. Und wer nicht gerade das PDF von Hand setzen möchte mit Tools wie FPDF ist mit wkhtmltopdf auf jeden Fall richtig beraten.

Installation

Das Command Line Tool ist allerdings notorisch schwierig zum Laufen zu kriegen. Es gibt zwei Optionen für die Installation. Zum einen könnt Ihr das Tool direkt aus dem Quellcode selbst bauen. Mein Tipp dazu: Lasst es bleiben. Ich hatte bisher nur einen einzigen Fall, wo das überhaupt funktioniert hat und ich habe bis heute keine Ahnung was ich da anders gemacht habe. Kommen wir also direkt zur zweiten Möglichkeit. Ladet Euch eine vorkompilierte Binary von der offiziellen Webseite herunter und legt Sie in Euren bevorzugten Binaries-Ordner.

Es gibt eine dritte Möglichkeit, die ich hier nicht verschweigen möchte. Natürlich könnt wkhtmltopdf auch aus den Paketquellen von Linux installieren oder beim Mac über Homebrew. Allerdings sind diese Versionen meist ziemlich alt und haben einige Bugs. Darum würe ich wirklich dazu raten die entsprechende Binary von Hand herunter zu laden.

Benutzung

Anschließend könnt Ihr die Funktionalität überprüfen, indem Ihr einfach mal eine Webseite exportiert. Zum Beispiel diese hier:

$ wkhtmltopdf http://www.christianhanne.de christianhanne.pdf

Weitere Optionen könnt Ihr Euch entweder über wkhtmltopdf --help anzeigen lassen oder als Webseite anschauen. Beachtet dabei, dass nicht alle Optionen für jede Version von wkhtmltopdf verfügbar sind. Wir werden allerdings mit den vorhandenen Optionen in jedem Fall auskommen.

Exportieren einer Seite über PHP

Über exec() können wir unter PHP wkhtmltopdf direkt über die Command Line aufrufen. Wichtig ist dabei, dass der Apache-Benutzer auf die entsprechende Binary zugreifen kann. Wer als dritten Parameter beim exec()-Befehl eine PHP-Variable hinterlegt, der erhält im Anschluss einen Return-Wert des Shell-Aufrufs zurück. Sollte dieser nicht 0 (Null) sein, ist irgend etwas im Argen.

Wenn Ihr whtmltopdf auch unter PHP zum Laufen bekommen habt, könnt Ihr mit folgendem Skript eine PDF-Datei aus einer Webseite exportieren. Vorsicht: Falls Ihr $url oder $filepath in irgendeiner Form dynamisch durch den User bestimmen lasst, müsst Ihr die beiden Parameter bereinigen, bevor Ihr sie an exec() übergebt.

$url = 'http://www.christianhanne.de';
$filepath = 'christianhanne.pdf';

exec('wkhtmltopdf ' . $url . ' ' . $filepath, $output, $return);
if ($return !== 0) {
  throw new Exception('Unable to export website.');
}

if (!file_exists($filepath)) {
  throw new Exception('Unable to create document.');
}

Ausliefern des exportierten Dokumentes an den Client

Wer das Dokument nicht einfach nur exportieren möchte, sondern auch direkt an den Browser weiter leiten, der kann readfile() verwenden. Zusammen mit den entsprechenden Headern, wird die Datei dann direkt an den Browser ausgeliefert. Ob der Browser die Datei nun allerdings direkt im Browser anzeigt oder herunter lädt, hängt von der Konfiguration des Browsers ab.

$url = 'http://www.christianhanne.de';
$filepath = 'christianhanne.pdf';

exec('wkhtmltopdf ' . $url . ' ' . $filepath, $output, $return);
if ($return !== 0) {
  throw new Exception('Unable to call wkhtmltopdf.');
}

if (!file_exists($filepath)) {
  throw new Exception('Unable to create document.');
}

$filename = 'mein-export.pdf';
$filesize = filesize($filepath);

header('Content-Description: File Transfer');
header('Cache-Control: public, must-revalidate, max-age=0');
header('Pragma: public');
header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); 
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="' . $filename . '";');
header('Content-Transfer-Encoding: binary');
header('Content-Length: ' . $filesize);

readfile($filepath);
exit;

Bonus: Rudimentäres Caching

Da wkhtmltopdf einiges an System-Ressourcen frisst, sollten wir den Prozess nur dann abfeuern, wenn es auch wirklich notwendig ist. Ein rudimentäres Caching erreichen wir, indem wir zuerst prüfen, ob das exportierte Dokument bereits vorhanden ist. Nur wenn das nicht der Fall ist, exportieren wir es durch wkhtmltopdf. Das erweiterte Skript sieht dann so aus:

$url = 'http://www.christianhanne.de';
$filepath = 'christianhanne.pdf';

if (!file_exists($filepath)) {
  exec('wkhtmltopdf ' . $url . ' ' . $filepath, $output, $return);
  if ($return !== 0) {
    throw new Exception('Unable to call wkhtmltopdf.');
  }

  if (!file_exists($filepath)) {
    throw new Exception('Unable to create document.');
  }
}

$filename = 'mein-export.pdf';
$filesize = filesize($filepath);

header('Content-Description: File Transfer');
header('Cache-Control: public, must-revalidate, max-age=0');
header('Pragma: public');
header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); 
header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="'.$filename.'";');
header('Content-Transfer-Encoding: binary');
header('Content-Length: ' . $filesize);

readfile($filepath);
exit;

So können wir natürlich nicht mehr aus Änderungen der Seite reagieren. Besser wäre es den Timestamp der exportierten Datei mit dem letzten Änderungsdatum der Seite zu vergleichen. Wie das von statten gehen kann, ist allerdings von CMS zu CMS und zu Framework verschieden. Darum überlasse ich es Euch eine entsprechende Logik für Euer System umzusetzen.

Bonus: FPDI benutzen, um ein Wasserzeichen einzufügen

Wenn Euch das noch nicht genug ist, könnt Ihr Eure exportierten Dokumente auch noch mit Wasserzeichen bzw. Hintergründen versehen. Das kann zum Beispiel sinnvoll sein, wenn Ihr wkhtmltopdf nutzt, um Rechnungen zu exportieren. Zu diesem Zweck ladet Ihr Euch am besten FPDI herunter und bindet dies in Eure Applikation ein. Wasserzeichen kann man dann wie folgt platzieren:

$page_width = 210;
$page_height = 296;

$watermark = 'path/to/watermark.pdf'

$pdf = new FPDI();

$pdf->setSourceFile($watermark);
$background = $pdf->importPage(1, '/MediaBox');

$pages = $pdf->setSourceFile($filepath);
for ($i = 1; $i <= $pages; $i++) {
  $pdf->addPage();
  $pdf->useTemplate($background, 0, 0, $page_width, $page_height);

  $page = $pdf->importPage($i, '/MediaBox');
  $pdf->useTemplate($page, 0, 0, $page_width, $page_height);
}

$pdf->Output($filepath, 'F');

$watermark ist dabei der Pfad zum jeweiligen Hintergrund-PDF. Idealerweise hat es die selbe Größe wie Eure exportierten Dokumente. Ihr müsst die Seitengröße explizit angeben. Hier gehe ich bei $page_width und $page_height von einem Dokument im DIN A4 Format aus. Die Werte verstehen sich dabei als Millimeter-Angaben. Über FPDI laden wir dann die Quelldatei und iterieren über alle Seiten. Dort platzieren wir jeweils zuerst das Wasserzeichen und anschließend darüber die eigentliche Seite des Dokumentes. Das Eure Webseite einen transparenten Hintegrund haben sollte, versteht sich hoffentlich von selbst.

Bonus: Zusätzliche Fonts installieren

Einen kleinen Wehrmutstropfen gibt es in Zusammenhang mit wkhtmltopdf leider. Webfonts werden, auch wenn die Webseite anderes behauptet, nicht unterstützt. Statt dessen müsste Ihr Schriften erst auf dem Server installieren, wenn Ihr diese in wkhtmltopdf benutzen möchtet. Dies könnt Ihr tun, indem Ihr neue Schriften in den entsprechenden Ordner platziert. Unter Ubuntu sind das entweder /usr/share/fonts oder /usr/local/share/fonts.

Anschließend müsst Ihr den Font-Cache leeren. Dies könnt Ihr einfach über die Command Line machen:

$ sudo fc-cache 

Sollten die neuen Schriften trotzdem nicht registriert werden, schaut Euch am besten die Dokumentation auf ubuntuusers.de an.


Kommentare


Wichtig: Durch den Click auf die obige Checkbox stimmst Du ausdrücklich der Übertragung von Daten an Facebook zu. Die Zustimmung erfolgt einmalig für die Kommentare dieses Artikels und wird nicht gespeichert. Weitere Details kannst Du dem Punkt Social Plugins der Datenschutzerklärung entnehmen.