Wir Deutschen sind Weltmeister darin, Daten aus Anwendungen zu exportieren – besonders gern als PDF. Ob es an der gefühlten Sicherheit liegt, die uns ein PDF auf der lokalen Festplatte oder als E-Mail-Anhang gibt … Die Gründe dafür wagen wir uns hier nicht zu erörtern.

Wir wollen uns hier um die Technik kümmern und unsere Erfahrungen – die guten, aber auch die schlechten – mit Euch teilen. Denn eines haben wir gelernt – egal welche Anwendung wir bauen, irgendwann kommt die Anforderung „PDF-Export“ um die Ecke 😊.

Ein wichtiger Hinweis noch: Die Lösungen beziehen sich auf unseren favorisierten Technologie-Stack, d.h. Java und TypeScript bzw. JavaScript.

Wie immer – Kenne deine Anforderungen

Wenig überraschend ist, dass die richtige Wahl des PDF-Frameworks von den konkreten Anforderungen abhängt. Wichtig ist es, dass man vorher über die Anforderungen nachdenkt, denn ein nachträglicher Wechsel ist erfahrungsgemäß sehr aufwendig.
Einen Sieger, der in jedem Fall die richtige Wahl ist, gibt es nicht. Zu den gängigen Anforderungen zählen:

  • Styling
    Wie wichtig ist das Styling? Falls nur ein rein textuelles PDF-Dokument erzeugt werden soll, kann man sich an den simplen Technologien orientieren – man braucht ja schließlich nicht mit Kanonen auf Spatzen schießen.

  • Struktur/Länge
    Wie lang soll das PDF-Dokument werden und welche strukturellen Merkmale soll es haben? Wird ein Inhaltsverzeichnis benötigt, welches mit einem Framework generiert werden könnte? Sollen hunderte Seiten ins PDF – oder nur zwei?

  • Server vs. Client
    Es ist möglich PDFs direkt im Client zu generieren. Das kann besonders charmant sein, da die eigenen Server-Ressourcen nicht belastet werden. Außerdem wird, mit Blick auf die Regulatorik, potenziell einiges vereinfacht, wenn man weniger Daten auf dem Server verarbeitet.

  • Performance
    Selbstverständlich müssen auch die Anforderungen an die Performance beachtet werden. Viele Technologien bieten eine sehr angenehme Entwicklung an, aber benötigen dafür mehr Rechenzeit bei der Generierung der Dokumente.

Zunächst die Blacklist – Folgende Technologien würden wir nicht empfehlen

Über die Zeit haben wir uns mit verschiedenen Technologien beschäftigt. Dabei haben wir auch schwarze Schafe identifiziert, von denen wir euch raten würden: Wenn möglich, lasst die Finger davon!

  • Apache FOP (XSL, Server-Side)
    Die guten alten Zeiten … Apache FOP ist eine sehr alte und beständige Technologie. Das besondere Augenmerk liegt auf dem Wort „alte“. Die Performance, auch für große PDF-Dokumente, ist zwar akzeptabel, aber leider trifft das nicht auf die Entwicklung zu. Man ist auf alte XML-Parser angewiesen und muss komplexe PDF-Templates schreiben, die den Entwicklern graue Haare wachsen lassen.
    Es gibt kaum eine Anforderung, die diese Technologie rechtfertigt. Finger weg!
<fo:block padding-top="5mm">
   <xsl:for-each select="./subCategories">
      <xsl:choose>
         <xsl:when test="./warning = 'true'">
            <fo:table table-layout="fixed" width="160mm">
               <fo:table-column column-width="30mm" />
               <fo:table-column column-width="proportional-column-width(1)" />
               <fo:table-column column-width="129mm" />
               <fo:table-body>
                  <fo:table-row>
                     <fo:table-cell column-number="1">
                        <fo:block font-size="8pt" text-align="left" margin-top="2mm">
                           <xsl:value-of select="./categoryName" />
                           <xsl:text>:</xsl:text>
                        </fo:block>
                     </fo:table-cell>
                     <fo:table-cell column-number="3">
                        <xsl:for-each select="./messages">
                           <fo:block font-size="8pt" text-align="left" margin-top="2mm" margin-bottom="1mm">
                              <xsl:value-of select="./text" />
                           </fo:block>
                           <xsl:for-each select="./values">
                              <fo:block text-align="left">
                                 <xsl:value-of select="./domain"></xsl:value-of>
                              </fo:block>
  • PDFBox aka „PDF Assembler“ (Server-Side)
    Wie ihr wisst, werden Hochsprachen (wie z.B. Java) in Maschinencode übersetzt. Diesen Maschinencode kann man auch selbst in Form von Assembler-Code schreiben.
    Mit Assembler kann man unglaublich performante Anwendungen bauen – aber dafür ist der Aufwand enorm. Genau deswegen nennen wir PDFBox scherzhaft „PDF Assembler“, denn PDFBox ist sehr ähnlich zu Assembler.
    Mithilfe von PDFBox erzielen wir mit Abstand die beste Performance, aber dafür müssen wir mit unglaublich komplexen Java-Code leben. Besonders ansprechendes Styling ist sehr schwer umsetzen. Dadurch erzeugen umfangreiche PDF-Dokumente bei Entwicklern Frust und nur wenige trauen sich damit zu arbeiten.
    Wenn eine sehr hohe Performance keine wichtige Anforderung ist, dann lasst auch hiervon die Finger.

  • Browser-Druck-Funktionalität (Server-Side via Puppeteer)
    Diese Variante ist sehr fancy. Wir alle benutzen Webbrowser und wie wir alle wissen, kann man sich Internetseiten als PDF exportieren. Warum definieren wir also nicht einfach unser PDF als eine Internetseite? Unser Backend bekommt einen Webbrowser, den er via Puppeteer steuern kann und löst immer wieder die PDF-Export-Funktion aus.
    Der große Vorteil hieran: Wir können Frameworks wie Vue oder Angular einsetzen und Entwickler können in Echtzeit ihre Änderungen sehen.
    Leider ist der große Nachteil: Die Performance lässt zu wünschen übrig.
    Außerdem ist der Aufwand recht hoch, weil plötzlich ein Webbrowser in unserer Anwendung benötigt wird. Dieser ist meistens recht groß, verbraucht viele Ressourcen und sollte immer aktuell gehalten werden.
    Gepaart mit der langsamen Performance ist das der Grund, weshalb diese Variante in unsere Blacklist gerutscht ist.
    Auch hier gilt: Finger weg!
    Hinweis: Man kann das Ganze auch client-seitig betreiben und es gibt Fälle, wo es dann sogar nützlich sein kann.

  • Html2pdf.js (Client-Side)
    In der oberen Variante haben wir festgestellt, dass die Entwicklung als Internetseite sehr angenehm ist. Warum also nicht ein Framework, welches genau das kann?
    Um mal auf der Client-Seite zu bleiben: Man muss nicht alles in Java machen, sondern kann das auch mit JavaScript.
    Mithilfe von html2pdf.js ist dies möglich. Leider ist das resultierende PDF stark eingeschränkt, sodass wir für diese Technologie keine Empfehlung aussprechen können. Beispielweise kann man Bilder und Text aus den PDF-Dateien nicht kopieren. Außerdem ist der Styling-Aufwand viel höher als bei den anderen Technologien.
    Deswegen sprechen wir auch hier die Warnung aus: Finger weg!

Unser Favorit

Damit kommen wir zu unserem großen Favoriten: OpenHtml2Pdf

Natürlich passt diese Technologie nicht zu jedem Use-Case, aber wenn möglich, dann versuchen wir diese Technik zu nutzen.

Wir bleiben dabei, dass „Internetseiten“ zu entwickeln sehr angenehm ist. Jeder kann HTML/CSS und man kann schnell sichtbare und anpassbare Ergebnisse erzielen.

OpenHtml2Pdf ist eine Java-Bibliothek, die – wie der Name es vielleicht schon erahnen lässt – aus HTML eine PDF-Datei generiert.

Zusätzlich wird eine HTML-Templating-Engine benötigt, damit sich generische PDF-Dokumente erstellen lassen. Dazu empfehlen wir Pebble (https://pebbletemplates.io/), denn damit können die reingereichten Java-Objekte erfasst werden und dadurch ist eine Autovervollständig für unsere Java-Klassen möglich.

Die Templates lassen sich in Echtzeit bearbeiten und können mit modernen IDEs (wir nutzen IntelliJ) problemlos dargestellt werden.

 

Abbildung 1: Autovervollständigung via Peeble in IntelliJ

Es wird zwar nicht dieselbe Performance erzielt, wie beim „PDF-Assembler“, aber wir finden es akzeptabel (mit etwa 0,2s/Seite auf einer schwachen CPU).

Ein großer Vorteil: Da OpenHtml2PDF OpenSource ist, können wir an Verbesserungen mitarbeiten! Sollten z.B. unsere Performance-Anforderungen steigen, so können wir Verbesserung anbieten wie z.B. die Umsetzung von Multi-Threading, Caching- oder Preload-Systeme.

override fun createPDF(template: TemplateData): ByteArrayOutputStream {
    val out = ByteArrayOutputStream()

    val builder = PdfRendererBuilder().apply {
        useCacheStore(
            PdfRendererBuilder.CacheStore.PDF_IMAGES, imageCacheService.cache
        )
        useCacheStore(PdfRendererBuilder.CacheStore.PDF_FONT_METRICS, FSDefaultCacheStore())
        useFastMode()
        useSVGDrawer(
            BatikSVGDrawer(
            )
        )
        withProducer("cysmo")
        withHtmlContent(
            templatingService.createTemplate(template),
            Paths.get(basepath).toUri().toASCIIString()
        )
        toStream(out)
    }

    AutoFont.toBuilder(builder, cssFontList)

    builder.run()

    return out
}

Codebeispiel: Kotlin-Methode um PDF zu generieren.

Zusammenfassend lässt sich sagen, dass wir mit OpenHtml2Pdf die Developer-Experience wie beim Entwickeln von Web-Anwendungen haben und damit auch schöne Dokumente mit komplexen Diagrammen einfach erzeugen können. Die Performance ist für alle unsere Usecases aktuell total in Ordnung und kann auch noch verbessert werden, sollte dies erforderlich sein. Daher ist OpenHtml2Pdf unsere Empfehlung.

Weitere gute Lösungen für Spezialfälle

Aber natürlich gibt es auch Fälle, wo die Anforderung so speziell sind, dass andere Lösungen besser passen. Für unsere bisherigen Produkte gibt es drei Fälle, die eine Abweichung vom Favoriten rechtfertigen.

  • Dokumente mit hunderten von Seiten – Performance
    Manchmal ist eine schnelle Performance essenziell und leider lassen sich Dokumente nicht immer vorgenerieren. Wenn jede Millisekunde zählt, dann empfehlen wir klar den PDF-Assembler. Wir raten jedoch davon ab, dass man mit wenigen Klassen die gesamte PDF-Datei definiert. Es dauert nicht lange und man verliert den Überblick.
    Stattdessen lautet unser Rat: Nutzt selbstgebaute Komponenten für quasi alles. Text, Bilder, besondere Kästen, Überschriften, etc. – im Grunde kann man sich an HTML orientieren.
    Wir hätten uns viel Ärger erspart, wenn wir stärker mit Komponenten gearbeitet hätten.

  • Keine Datenverarbeitung im Backend
    Es kann auch nötig sein, dass keine Daten im Backend verarbeitet werden dürfen. Diesen Fall kann es geben, wenn das Frontend Daten aus mehreren Quellen sammelt, die jedoch aus Datenschutzgründen nicht vom Backend verarbeitet werden dürfen.
    Ein anderer Grund kann sein, dass man die Rechenzeit im Backend reduzieren möchte, z.B. aus Kostengründen.
    In diesem Fall empfehlen wir, dass das PDF als „klassische Internetseite“ entwickelt wird. Mittels Druck-Funktionalität können sich die Anwender selbst ein PDF generieren.
    Für diesen Workflow gibt es schon einige Techniken, wie die CSS-Page-Optionen. Es gibt auch bessere Frameworks (z.B. Page.js), die die Entwicklung vereinfachen.

  • Einfache Formulare, die „nur“ befüllt werden müssen
    Manchmal sind die Dokumente bzgl. Länge, Struktur und Styling immer gleich – nur einzelne Werte unterscheiden sich. In diesen Fällen empfehlen wir, ein befüllbares Formular außerhalb der Anwendung vorzubereiten und dann nur noch befüllen. Dies ist sehr einfach mit PDFBox möglich und ermöglicht auch ein „Abschließen“ des Formulars, sodass die Felder im Nachgang nicht mehr bearbeitbar sind. Mit diesem Ansatz haben wir uns sehr viel Aufwand in Projekten gespart.

Zusammenfassung

Es gibt viele Lösungen am Markt und unser Lernprozess war nicht schmerzfrei. Dennoch freuen wir uns, die Erfahrungen gemacht zu haben und die Learnings mitgenommen zu haben, wo die Vorteile von unterschiedlichen Frameworks liegen.

Natürlich halten wir weiterhin unsere Augen offen nach neuen, noch besseren Lösungen. Sobald wir da was finden, geben wir ein Update bzgl. unserer Erfahrung ab. Wir freuen uns aber natürlich auch über Feedback zu unserem Artikel – vielleicht habt ihr ja Erfahrungen mit anderen Technoligen gemacht. Lasst es uns gern wissen!

Zum Schluss gibt es noch eine Übersicht der PDF-Technologien mit einer kurzen Bewertung.

Technologien

Vor- und Nachteile

Bewertung

OpenHtml2Pdf

+ stabil und gute Performance

+ PDF/A-Support

+ Minimaler Codeaufwand für HTML → PDF (~200 Zeilen)

+ HTML-Templates mit beispielsweise Pepple ermöglicht dynamische Generierung des Inhalts

– Kein Support für JavaScript

Ja!

 

Browser-Druck-Funktionalität (Client-Side)

+ JavaScript-Support

+ Gut entwickelbar

– Multi-Browser Kompatibilität muss sichergestellt werden

 

 

Ja, es gibt Fälle, bei denen diese Technologie nötig ist.

Browser-Druck-Funktionalität (Server-Side via Puppeteer)

 

+ Selbe Vorteile wie Client-Side

+ Nur Chrome muss kompatibel sein

– Langsamste Performance

Finger weg!

PDFBox aka PDF-Assembler

+ Beste Performance

– viel Entwicklungsaufwand

Ja, es gibt Fälle, bei denen diese Technologie nötig ist.

 

Apache FOP (XSL)

 

+ funktioniert schon seit einigen Jahren

– alt (dadurch schlecht entwickelbar)

Finger weg!

html2pdf.js (nutzt jsPDF, Client-Side)

 

+ kein Drucker-Dialog nötig

– hoher Styling-Aufwand

– Resultat hat einige Einschränkungen (z.B. Text nicht markierbar)

Finger weg!

Gastautor:

Andre Köpke | IT Consultant
Andre ist als Entwickler bei PPI.X tätig und unterstützt verschiedene Projekte in den Bereichen DevOps, Java- und Typescript-Entwicklung. Zudem ist er Teil-Kopf der DevOps-Zelle und berät Inhouse-Projekte beim Aufbau eines automatisierten CI/CD-Prozesses.

 

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Verwandte Artikel