PDF ist vermutlich das wichtigste interoperable Dokumentenformat zwischen Betriebssystemen und Geräten wie Computer, Smartphone und Tablet. Die wichtigste Eigenschaft für PDF ist die Unveränderlichkeit der Dokumente. Natürlich sind auch hier Grenzen gesetzt und man kann heutzutage problemlos mit entsprechenden Programmen Seiten aus einem PDF Dokument entfernen oder hinzufügen. Dennoch lassen sich die Inhalte der einzelnen Seiten nicht ohne Weiteres verändern. Wo vor Jahren noch kostspielige Spezialsoftware wie beispielsweise Acrobat von Adobe nötig war, um mit PDF umzugehen, sind mittlerweile viele kostenfreie Lösungen auch für den kommerziellen Einsatz verfügbar. So beherrschen die gängigen Office Suiten den Export des Dokumentes als PDF.
Gerade im Geschäftsbereich ist PDF eine hervorragende Wahl, um Rechnungen oder Berichte zu generieren. Hier setzt auch dieser Artikel an. Ich beschreibe, wie einfache PDF Dokumente für die Rechnungslegung etc. genutzt werden können. Komplizierte Layouts, wie sie beispielsweise für Magazine und Zeitschriften in Verwendung kommen, sind nicht Teil dieses Artikels.
Technisch kommt die frei verfügbare Bibliothek openPDF zum Einsatz. Das Konzept ist, aus einem gültigen HTML Code, welcher in einem Template enthalten ist, eine PDF zu generieren. Da wir uns im JAVA Umfeld bewegen, ist die Template Engine der Wahl Velocity. Alle diese Abhängigkeiten nutzt TP-CORE in der Funktionalität PdfRenderer, welche als Fassade implementiert ist. Dies soll die Komplexität der PDF-Generierung in der Funktionalität kapseln und einen Austausch der Bibliothek ermöglichen.
Auch wenn ich nun kein Spezialist der PDF-Generierung bin, habe ich im Laufe der Jahre einige Erfahrungen speziell mit dieser Funktionalität in Bezug auf die Wartbarkeit von Softwareprojekten sammeln können. Dazu gibt es sogar einen Konferenzvortrag. Als ich vor sehr vielen Jahren den Entschluss gefasst hatte, PDF in TP-CORE zu unterstützen, gab es nur die iText-Bibliothek, die zu dieser Zeit mit der Version 5 frei verfügbar war. Ähnlich wie Linus Torvalds mit seinem ursprünglich genutzten Source Control-Management-System, erging es mir. Itext wurde kommerziell und ich brauchte eine neue Lösung. Nun, ich bin nicht Linus, der mal fix GIT aus dem Boden stampft, also habe ich nach einiger Wartezeit openPDF entdeckt. OpenPDF ist ein Fork von iText5. Jetzt musste ich meinen bestehenden Code entsprechend anpassen. Das erforderte einiges an Zeit, war aber dank meiner Kapselung eine überschaubare Aufgabe. In diesem Anpassungsprozess habe ich allerdings Probleme entdeckt, die mir in meiner kleinen Welt bereits das Leben schwer gemacht haben, also habe ich TP-CORE Version 3.0 veröffentlicht, um in der Funktionalität Stabilität zu erreichen. Wer also TP-CORE in einer 2.X Version verwendet, findet dort als PDF Lösung noch iText5. Das soll aber jetzt genug über die Entstehungsgeschichte sein. Kümmern wir uns darum, wie wir aktuell mit TP-CORE 3.1 PDF generieren können. Auch hier gilt: Mein größtes Ziel ist es, eine möglichst hohe Kompatibilität zu erreichen.
Bevor wir beginnen können, müssen wir TP-CORE als Abhängigkeit in unser Java Projekt einbinden. Das Beispiel demonstriert die Verwendung von Maven als Build Werkzeug, kann aber leicht auf Gradle umgestellt werden.
<dependency>
<groupId>io.github.together.modules</groupId>
<artifactId>core</artifactId>
<version>3.1.0</version>
</dependency>
Um etwa eine Rechnung zu generieren, brauchen wir verschiedene Schritte. Zu erst benötigen wir ein HTML Template, das wir mit Velocity erzeugen. Das Template kann natürlich Platzhalter für Namen und Adresse enthalten, wodurch Serienbriefe und Batchverarbeitung möglich sind. Der folgende Code zeigt, wie wir damit umgehen können.
<h1>HTML to PDF Velocity Template</h1>
<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet.</p>
<p>Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.</p>
<h2>Lists</h2>
<ul>
<li><b>bold</b></li>
<li><i>italic</i></li>
<li><u>underline</u></li>
</ul>
<ol>
<li>Item</li>
<li>Item</li>
</ol>
<h2>Table 100%</h2>
<table border="1" style="width: 100%; border: 1px solid black;">
<tr>
<td>Property 1</td>
<td>$prop_01</td>
<td>Lorem ipsum dolor sit amet,</td>
</tr>
<tr>
<td>Property 2</td>
<td>$prop_02</td>
<td>Lorem ipsum dolor sit amet,</td>
</tr>
<tr>
<td>Property 3</td>
<td>$prop_03</td>
<td>Lorem ipsum dolor sit amet,</td>
</tr>
</table>
<img src="path/to/myImage.png" />
<h2>Links</h2>
<p>here we try a simple <a href="https://together-platform.org/tp-core/">link</a></p>template.vm
In dem Template sind drei Properties enthalten: $prop_01, $prop_02 und $prop_03, die wir mit Werten füllen müssen. Das gelingt uns mit einer einfachen HashMap, die wir mit dem TemplateRenderer einfügen, wie das folgende Beispiel zeigt.
Map<String, String> properties = new HashMap<>();
properties.put("prop_01", "value 01");
properties.put("prop_02", "value 02");
properties.put("prop_03", "value 03");
TemplateRenderer templateEngine = new VelocityRenderer();
String directory = Constraints.SYSTEM_USER_HOME_DIR + “/“;
String htmlTemplate = templateEngine
.loadContentByFileResource(directory, "template.vm", properties);Der TemplateRenderer benötigt drei Argumente:
- directory – Der vollständige Pfad vo das Template zu finden ist.
- template – den Namen des Velocity-Templates
- properties – Die HashMap mit den zu ersetzenden Variablen
Das Ergebnis ist ein gültiger HTML Code, aus dem wir nun über den PdfRenderer das PDF generieren können. Eine Besonderheit ist die Konstante SYSTEM_USER_HOME_DIR, welche auf das Nutzerverzeichnis des aktuell angemeldeten Benutzers zeigt und die Unterschiede einzelner Betriebssysteme wie Linux und Windows abfängt.
PdfRenderer pdf = new OpenPdfRenderer();
pdf.setTitle("Title of my own PDF");
pdf.setAuthor("Elmar Dott");
pdf.setSubject("A little description about the PDF document.");
pdf.setKeywords("pdf, html, openPDF");
pdf.renderDocumentFromHtml(directory + "myOwn.pdf", htmlTemplate);Das Listing zum Erstellen der PDF ist überschaubar und recht selbsterklärend. Es ist natürlich möglich, das HTML hardcodiert zu übergeben. Die Nutzung des Templates erlaubt hier eine Trennung von Code und Design, was auch spätere Anpassungen flexibel erlaubt.
Das Standardformat ist A4 mit den Dimensionen size: 21cm 29.7cm; margin: 20mm 20mm; und ist als inline CSS definiert. Über die Methode setFormat(String format); lässt sich dieser Wert individuell festlegen. Der erste Wert steht hierbei für die Breite und der zweite Wert für die Höhe.
Der PDF Renderer hat auch die Möglichkeit, einzelne Seiten aus einem Dokument zu löschen.
PdfRenderer pdf = new OpenPdfRenderer();
PdfDocument original = pdf.loadDocument( new File("document.pdf") );
PdfDocument reduced = pdf.removePage(original, 1, 5);
pdf.writeDocument(reduced, "reduced.pdf");
Wir sehen also, dass bei der Implementierung der PDF Funktionalität sehr viel Wert auf eine einfache Handhabung gelegt wurde. Dennoch gibt es viele Möglichkeiten, individuelle PDF Dokumente zu erstellen. Daher hat die Lösung, über HTML das Layout zu definieren, einen besonderen Reiz. Trotz, dass der PdfRenderer mit einer sehr überschaubaren Funktionalität aufwartet, ist es sehr einfach möglich, durch den Vererbungsmechanismus von Java das Interface und die zugehörige Implementierung durch eigene Lösungen zu erweitern. Diese Möglichkeit besteht auch für alle anderen Implementierungen aus TP-CORE.
TP-CORE ist auch für den kommerziellen Gebrauch frei und hat dank der Apache 2 Lizenz keine Einschränkungen. Der Source Code ist unter https://git.elmar-dott.com zu finden und die Dokumentation inklusive Security und Testcoverage findet sich unter https://together-platform.org/tp-core/.