Die iText-Bibliothek zur Erstellung von PDF-Dateien

Java meets PDF

Cord Jastram

Mit iText existiert eine leistungsfähige Bibliothek für die Erstellung von PDF-Dateien. Die Bibliothek steht unter der LGPL- oder MPL-Lizenz zur Verfügung und gibt dem Java-Entwickler die Möglichkeit, die vielfältigen Möglichkeiten des PDF-Formats für eigene Anwendungen zu nutzen.

Im Java Magazin 5.2003 wurde iText bereits im Rahmen der Open Source-Perlen besprochen. Dieser Beitrag wirft einen genaueren Blick auf diese Bibliothek. Um iText einsetzen zu können, lädt man zuerst die Quellen und eine kompilierte Fassung der Bibliothek, das zugehörige Tutorial und eine umfangreiche Menge an Beispielen aus dem Internet. Verwendet man die bei der Erstellung des Artikels aktuelle Version 1.0, müssen die Dateien iText-1.00.jar und iTextXml-1.00.jar über den Classpath erreichbar sein.

Das Füllmodel von iText

Bevor die Möglichkeiten von iText genauer betrachtet werden, wird zunächst das der Bibliothek zugrunde liegende Modell für die Seitenerzeugung vorgestellt.

Prinzipiell kann man die einzelnen Bausteine einer PDF-Datei wie z.B. Texte und Grafiken zur Laufzeit im Speicher halten. Erst am Ende der Bearbeitung findet die eigentliche Formatierung statt, d.h. die Aufteilung der Elemente auf die einzelnen Seiten. Bei diesem Modell kann auch nachträglich Text gelöscht oder editiert werden. iText hingegen verwendet ein lineares Füllmodel, bei dem die Seiten nacheinander aufgebaut werden und jeweils nur die aktuelle Seite mit Text oder anderen Elemente gefüllt wird. Ein späteres Löschen oder Editieren von Elementen ist nicht möglich. Die beiden Modelle erinnern ansatzweise an den DOM- und den SAX Ansatz für XML-Parser, wobei das von iText verwendete lineare Füllmodel dem SAX-Ansatz entspricht. Das Erzeugen einer PDF-Datei erfolgt in 6 Schritten:

  • Dokument anlegen
  • PdfWriter zuordnen
  • Metadaten schreiben
  • Dokument öffnen
  • Seiten erzeugen
  • Dokument schließen

Die Einschränkung, dass Metainformationen nur vor dem Öffnen des Dokumentes geschrieben werden können, ist eine Designentscheidung der iText-Entwickler. Das Beispiel eines einfachen Java-Programms, das die 6 Schritte durchführt, ist auf der Heft-CD enthalten (Beispiel-Listing iText). Das erzeugte PDF-Dokument zeigt sich in Acrobat mit dem Dialog für die Dokumenteneigenschaften. Dokumente werden unabhängig von dem zu erzeugenden Dateiformat durch Objekte der Klasse com.lowagie.itext.Document repräsentiert. Sie ermöglicht es z.B. die Größe und die Ränder der Seiten festzulegen. Die Ränder der Seiten definieren auch automatische den Kopf- und den Fußbereich des Dokumentes. iText verwendet als Einheit den typografischen Punkt, wobei 72 Punkte einem Inch (2,54 cm) entsprechen. Eine A4-Seite besitzt somit eine Breite von 595 pt und eine Höhe von 842 pt. Der Ursprung des Koordinatensystems befindet sich in der unteren linken Ecke einer Seite (Abb. 1).

 Abb. 1: Das Koordinatensystem

 In der Klasse com.lowagie.text.PageSize sind eine Vielzahl von Seitengrößen definiert, die dem Dokument zugewiesen werden können. Reichen die vorhandenen Seitengrößen nicht aus, dann kann über ein Objekte der Klasse com.lowagie.text.Rectangle eine beliebige rechteckige Seite erzeugt werden, bei der man über die Methoden setGrayFill(float) und setBackgroundColor(java.awt.Color) auch die Hintergrundfarbe setzen kann (Listing 1). Die Seitengröße kann innerhalb eines Dokumentes geändert werden, wobei die Änderung nicht sofort, sondern erst mit der nächsten neuen Seite wirksam wird.

Listing 1

Rectangle rect = new Rectangle((float) 500.0, (float) 800.0);
rect.setBackgroundColor(java.awt.Color.orange);
document.setPageSize(rect);
document.newPage();
// jetzt hat man eine Seite mit neuen Abmessungen und neuer Hintergrundfarbe
...

Vor dem Öffnen des neuen Dokumentes wird die Verschlüsselung des Dokumentes gewählt und es können zwei Passwörter gesetzt werden. Acrobat ab der Version 5.0 akzeptiert eine Schlüssellänge von 128 Bit. Das Userpassword wird für das Öffnen des Dokumentes benötigt, das Ownerpassword für das Ändern der gesetzten Sicherheitseinstellungen. Über diese Einstellungen kann z.B. das Drucken und das Editieren eines Dokumentes erlaubt werden. Die entsprechenden Optionen sind in der Klasse com.lowagie.itext.pdf.PdfWriter deklariert und werden für den Methodenaufruf verodert:

String userPassword = "java", ownerPassword = "geheim";
int permissions = PdfWriter.AllowPrinting | PdfWriter.AllowModifyContents;
writer.setEncryption(PdfWriter.STRENGTH40BITS, userPassword, ownerPassword, permissions);
Text und Events

Text wird über die Klassen Chunk, Phrase, Paragraph, Chapter und Section aus dem Paket com.lowagie.text realisiert, welche im Java Magazin 5.2003 bereits vorgestellt wurden. Konstanten für die Textausrichtung sind in der Klasse com.lowagie.text.ElementTags definiert. Für die einzelnen Blöcke kann zudem noch die Hintergrundfarbe gesetzt werden. Die Klassen Chapter und Section werden für die Gliederung von Text verwendet und erzeugen zusätzlich noch automatisch Lesezeicheneinträge. Für die besonders einfache Erstellung von Tabellen existiert die Klasse com.lowagie.text.pdf.PdfPTable. Die Klassen com.lowagie.text.List und com.lowagie.text.ListItem ermöglichen die Erstellung von Listen.

Die einzelnen Bausteine werden im Programm erzeugt und dem Dokument mit der Funktion add hinzugefügt. Der dem Dokument zugeordnete PdfWriter nimmt die Bausteine und formatiert den Text entsprechend der gesetzten Parameter. Dieser Vorgang läuft im Verborgenen ab und funktioniert als Blackbox, in deren interne Mechanismen nicht eingegriffen werden kann. Detaillierte Einblicke in den Vorgang der Formatierung erhält man dadurch, dass man einen Eventhandler bei dem PdfWriter-Objekt registriert. Für Events wie z.B. das Öffnen und Schließen des Dokumentes, das Anlegen und das Beenden einer Seite sowie für das Anlegen und Beenden der Textbausteine existieren Funktionen, die entsprechend aufgerufen werden. Der Handler implementiert das Interface com.lowagie.text.pdf.PdfPageEvent, in dem die Handler-Funktionen definiert sind. Implementiert beispielsweise die Klasse DemoPageEvent das Interface, so wird der Handler durch folgende Zeilen registriert:

DemoPageEvent dpe = new DemoPageEvent();
writer.setPageEvent(dpe);

Das Interface PdfPageEvent definiert Funktionen, die bei dem Öffnen und dem Schließen des Dokumentes aufgerufen werden:

public void onOpenDocument(PdfWriter writer, Document doc);
public void onCloseDocument(PdfWriter writer, Document doc);

Vor dem Anlegen einer Seite und vor dem Beenden einer Seite werden die Funktionen

public void onStartPage(PdfWriter writer, Document document);
public void onEndPage(PdfWriter writer, Document document);

aufgerufen. Die Funktionen aus Listing 2 erlauben es dem Entwickler, jeweils vor und nach dem Einfügen von Textbausteinen weitere Aktionen durchzuführen.

Listing 2

public void onParagraph(PdfWriter writer, Document document, float paragraphPosition);
public void onParagraphEnd(PdfWriter writer,Document document,float paragraphPosition);
public void onChapter(PdfWriter writer,Document document,float paragraphPosition, Paragraph title);
public void onChapterEnd(PdfWriter writer,Document document,float paragraphPosition);
public void onSection(PdfWriter writer,Document document,float paragraphPosition, int depth, Paragraph title);
public void onSectionEnd(PdfWriter writer,Document document,float paragraphPosition);
public void onGenericTag(PdfWriter writer, Document document, Rectangle rect, String text);
Konvertierung XML nach PDF

Die Erzeugung von PDF-Dateien über die im vorherigen Abschnitt beschriebenen Klassen kann insbesondere bei umfangreichen Texten sehr mühselig und schlecht wartbar sein. Liegen die in das PDF-Format zu konvertierenden Daten jedoch in XML vor, dann bietet iText über die Klasse com.lowagie.text.xml.XmlParser einen komfortablen Konvertierungsmechanismus. Man benötigt dann nur eine zu konvertierende XML-Datei (Listing 3) und eine Datei, die die Abpictureung der Tags dieser Datei auf die iText-spezifischen Tags ermöglicht (Listing 4).

Listing 3

<?xml version="1.0" ?>
<DOKUMENT>
<KAPITEL>
<TITLE>Kapitel</TITLE>
<NEWLINE />
<TEXT>Dieser Text wurde mit <BOLDTEXT>iText </BOLDTEXT> in das PDF-Format konvertiert.</TEXT>
</KAPITEL>
<KAPITEL>
<TITLE>Kapitel</TITLE>
<NEWLINE />
<TEXT>Noch mehr Text ...</TEXT>
</KAPITEL>
</DOKUMENT>

Listing 4

<tagmap>
<tag name="paragraph" alias="TEXT">
<attribute name="leading" value="14" />
<attribute name="size" value="10" />
</tag>
<tag name="chunk" alias="BOLDTEXT">
<attribute name="style" value="bold" />
</tag>
<tag name="itext" alias="DOKUMENT" />
<tag name="newline" alias="NEWLINE" />
<tag name="title" alias="TITLE">
<attribute name="size" value="24" />
</tag>
<tag name="chapter" alias="KAPITEL">
<attribute name="numberdepth" value="2" />
</tag>
</tagmap>

Die genaue Definition dieser Tags findet sich in der zugehörigen DTD unter www.lowagie.com/iText/itext.dtd. Listing 5 zeigt ein einfaches Programm für die Konvertierung von XML nach PDF, wobei die ganze Arbeit von der Funktion XmlParser.parse geleistet wird. Für das eben gezeigte Beispiel erhält man durch die Konvertierung die in Abpictureung 2 gezeigte PDF-Datei, wobei iText z.B. auch automatisch die Lesezeichen für die Kapitel erzeugt hat.

Listing 5

import java.io.FileOutputStream;
import com.lowagie.text.Document;
import com.lowagie.text.PageSize;
import com.lowagie.text.pdf.PdfWriter;
import com.lowagie.text.xml.XmlParser;
public class Xml2Pdf {
public static void main(String[] args) {
// 1. Dokument erzeugen
Document document = new Document(PageSize.A4, 80, 60, 60, 60);
try {
// 2. PdfWriter zuordnen
PdfWriter.getInstance(document, new FileOutputStream("xml2pdf.pdf"));
XmlParser.parse(document, "xml2pdf.xml", "tagmap.xml");
}
catch(Exception e) {
//...
}
}
}


 Abb. 2: Die Ausgabe von Xml2Pdf.java in Adobe Acrobat

Zeichensätze

Das PDF-Format kennt die mit dem Acrobat Reader mitgelieferten Typ 1 Zeichensätze (Courier, Arial, Times, Symbol, Zapf Dingbats). Zunächst wird ein Font-Objekt der Klasse com.lowagie.text.Font über eine Factory erzeugt, dann wird ein Textbaustein mit dem Font als Parameter im Konstruktor erzeugt und dem Dokument hinzugefügt:

Font courier = FontFactory.getFont(FontFactory.COURIER, 10);
document.add(new Paragraph(text, courier));

Soll ein TrueType-Font verwendet werden, wird zuerst eine Objekt der Klasse com.lowagie.text.BaseFont erzeugt und aus diesem dann das zugehörige Font-Objekt erzeugt. Damit die PDF-Datei auf Rechnern, auf denen der Zeichensatz nicht zur Verfügung steht, angezeigt und gedruckt werden können, sollte der Zeichensatz durch den Parameter BaseFont.EMBEDDEDimmer in das Dokument eingebunden werden:

BaseFont bfVerdana =
BaseFont.createFont("c:windowsfontsVerdana.ttf", BaseFont.CP1252, BaseFont.EMBEDDED);
Font verdana = new Font(bfVerdana, 22);
document.add(new Paragraph(text, courier));
Externe und interne Links

Interne Links innerhalb eines PDF-Dokuments lassen sich auf zwei Wegen anlegen. Einzelne Textelemente der Klasse Chunk lassen sich durch die Funktion setLocalDestination zum Sprungziel machen, während durch die Funktion setLocalGoto der entsprechende Chunk als Link auf das Sprungziel verweist. Verknüpft werden der Link und das Sprungziel über einen eindeutigen Bezeichner, der als Parameter an die Funktionen übergeben wird (Listing 6).

Listing 6

...
// der Link
Chunk localGoto = new Chunk("Siehe Erklärung");
localLink.setLocalGoto( "LINK01" );
document.add( localGoto );
// hier wird weiterer Text erzeugt
...
// das Sprungziel
Chunk localDestination = new Chunk("Erklärung");
localDestination.setLocalDestination( "LINK01" );
document.add( localDestination );

Die Klasse com.itext.text.Anchor ist von der Klasse Phrase abgeleitet und kann auch externe Links realisieren:

Anchor anchor = new Anchor("Ein Verweis zum Java Magazin");
anchor.setReference("http://www.javamagazin.de");
anchor.setName("");
document.add( anchor );

Mit Hilfe der Klasse com.lowagie.text.Annotation können einerseits die Acrobat-typischen Anmerkungen im PostIt-Stil erstellt werden, andererseits können Links zu externen PDF-Dateien oder externen Anwendungen erstellt werden.

Grafik

In den bisherigen Beispielen wurden Objekte dem Dokument hinzugefügt und der interne Formatierungsmechanismus sorgte für die Positionierung. In einigen Fällen möchte man jedoch selber für die Positionierung verantwortlich zeigen. Für diese Fälle existiert die Klasse com.lowagie.text.pdf.PdfContentByte, die auch intern für die Positionierung von Elementen verwendet wird. Für eine Seite existieren vier PdfContentByte-Objekte, die jeweils einen Layer repräsentieren. Zwei der Layer werden intern von iText verwendet, die beiden anderen Layer stehen dem Programmierer über die Funktionen getDirectContent() und getDirectContentUnder() der Klasse PdfWriter zur Verfügung (Abb. 3).

Abb. 3: Die Layer einer Seite

Einfache Liniengrafik wird durch die Klasse com.lowagie.text.Graphic, die von der Klasse PdfContentByte abgeleitet ist, realisiert. Listing 7 zeichnet den Funktionsgraphen zweier mathematische Funktionen in unterschiedlichen Farben und Linienmustern in ein Rechteck.

Listing 7

Graphic gr = new Graphic();
gr.rectangle(100, 590, 400, 120); // Rechteck malen
// den ersten Linienzug erzeugen
gr.moveTo(100, 650);
float x, y;
for (int i = 0; i < 400; i++) {
x = (float) (100 + i);
y = (float) (650 + 50 * java.lang.Math.sin(i * 0.1));
gr.lineTo(x, y);
}
gr.stroke(); // und plotten
// Farbe und Linienmuster umsetzen
gr.setColorStroke(java.awt.Color.red);
gr.setLineDash(4, 2, 6);
// den zweiten Linenzug erzeugen
gr.moveTo(100, 700);
for (int i = 0; i < 400; i++) {
x = (float) (100 + i);
y = (float) (650 + 50 * java.lang.Math.cos(i * 0.1));
gr.lineTo(x, y);
}
gr.stroke(); // und plotten
document.add(gr); // dem Dokument hinzufügen


Möchte man eine grafische Anwendung, die auf der Klasse java.awt.Graphics2D basiert, PDF-Dateien generieren lassen, erzeugt man mit Hilf der Klasse PdfContentByte ein java.awt.Graphics2D Objekt, dessen erzeugte Grafik automatisch in eine PDF-Repräsentation umgesetzt wird:

PdfContentByte cb = writer.getDirectContent();
cb.saveState();
java.awt.Graphics2D g2 = cb.createGraphics(100, 300);
// hier wird dann Grafik erzeugt
...
g2.dispose();
cb.restoreState();

Intern erzeugt createGraphics ein Objekt der von java.awt.Graphics2D abgeleiteten Klasse com.lowagie.text.pdf.PdfGraphics2D, in der die Umsetzung in die PDF-Darstellung implementiert ist.

Bitmaps

Bitmaps werden in iText durch die abstrakte Klasse com.lowagie.text.Image repräsentiert. Für das Erzeugen von Bitmaps in PDF-Dateien stellt die Klasse zwei statische Factory-Methoden zur Verfügung:

public static Image getInstance(URL url)throws BadElementException, MalformedURLException, IOException 
public static Image getInstance(String filename)throws BadElementException, MalformedURLException, IOException

Beide Methoden versuchen den Typ des Bildes zu ermitteln und geben dann ein entsprechendes Objekt einer von Image abgeleiteten Klasse zurück. Unterstützt werden z.Zt. unter anderem die Formate WMF, GIF, JPEG und PNG durch die Klassen ImgWMF, Gif, JPEG und PNG. Kann der Typ der Grafik nicht bestimmt werden oder wird die Datei nicht gefunden, werden entsprechend definierten Exceptions geworfen. Bitmaps werden mit der Methode setAlignment(int alignment) links- oder rechtsbündig bzw. zentriert ausgegeben. Für das absolute Positionieren existiert die Methode setAbsolutePosition(int absoluteX, int absoluteY) wobei die untere linke Ecke des Bildes positioniert wird. Die Größe kann z.B. über die Methoden

void scaleAbsolute(int newWidth, int newHeight) 
void scalePercent(int percent) 
public void scalePercent(int percentX, int percentY) 
public void scaleToFit(int fitWidth, int fitHeight)

manipuliert werden, wobei alle Methoden nur die Darstellung der Grafik und nicht ihre physikalische Auflösung ändern. Für das Drehen ist die Methode void setRotation(double r) zuständig. Weitere Möglichkeiten, wie z.B. das automatische Umfließen von Bitmaps durch den Text oder das Erzeugen eines Wasserzeichens für das Dokument durch die Klasse com.lowagie.text.Watermark sind in dem Tutorial beschrieben.

PDF und Servlets

iText unterstützt auch die Erzeugung von PDF-Dateien in Browser-basierten Anwendungen, d.h. die PDF-Dateien können auch in Servlets erzeugt werden. Auf Grund eines Bugs im Internet Explorer muss der Inhalt der PDF-Datei zuerst in einem ByteArrayOutputStream zwischengespeichert werden und dann komplett mit korrekter Längeangabe ausgegeben werden. Listing 8 zeigt eine Schablone für die doGet-Methode eines HttpServlet.

Listing 8

public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 
try { 
Document document = new Document(); 
// ByteArrayOutputStream erzeugen 
ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
PdfWriter.getInstance(document, baos); 
// hier wird die PDF-Datei erzeugt 
... 
document.close(); 
// Laenge und Inhalt des ByteArrayOutputStream festlegen 
response.setContentType("application/pdf"); 
response.setContentLength(baos.size()); 
// ByteArrayOutputStream schreiben 
ServletOutputStream out = response.getOutputStream(); 
baos.writeTo(out); 
out.flush(); 

catch (Exception e) { 
// Behandlung der Exception 

}
Formulare mit PDF

PDF bietet für die Erstellung von Formularen ähnliche Möglichkeiten wie HTML. Es werden folgende Typen von Eingabefeldern unterstützt:

  • Einzeilige Textfelder
  • Einzeilige Passwort-Felder
  • Mehrzeilige Textfelder
  • Listfelder
  • Radiobuttons
  • Checkboxen

Die Funktionalität für die Erstellung der Eingabefelder fasst iText in der Klasse com.lowagie.text.pdf.PdfAcroForm zusammen. Über die Methode addHtmlPostButton kann zusätzlich ein Button eingefügt werden, über den das PDF-Formular wie ein HTML-Formular verschickt werden kann, d.h. man kann ein HTML-basiertes Formular ohne Änderungen an der zugehörigen serverseitigen Anwendung gegen ein PDF-basiertes Formular austauschen.

Fazit

Mit iText existiert in der Java-Welt eine leistungsfähige und stabile Bibliothek für die Erzeugung von PDF-Dateien. Die Bibliothek kann sowohl für die einfache Konvertierung von XML-basierten Daten nach PDF als auch für komplexe Aufgaben im Bereich der PDF-Erstellung genutzt werden. Das Tutorial und die Vielzahl der mitgelieferten Beispiele ermöglichen einen schnellen Einstieg in die Programmierung mit iText.

Links und Literatur
Geschrieben von
Cord Jastram
Kommentare
  1. Broly2015-03-25 23:46:47

    hi,

    ich verwende diese lib gerade in einem Projekt und wollte fragen, wie man die Bruchstellen einer Tabelle, die nicht auf eine din a4 Seite passt, definieren kann. Die Darstellung meiner pdf ist folgt:
    Erst kommt eine Überschrift, dann 2*2 Felder die immer mit den Gleichen Elementen gefüllt sind, anschließend folgt eine Tabelle, welche nach Größe der Informationen dynamisch wächst. Wenn die Informationen der Tabelle so gering sind, sodass alles auf eine Seite passt dann liegt ein Problem vor. Allerdings ändert sich dies, wenn die Informationen eine din a4 Seite sprängen. So werden nämlich auf der ersten Seite die Überschrift mit den erwähnten 2*2 Felder angezeigt, dann eine zu 75% leere Seite und auf der zweiten Seite erscheint dann die Tabelle. Umfasst die Tabelle mehr als zwei Seiten, so wird die Tabelle automatisch auf der dritten Seite gebrochen usw. Ich hätte gerne das die Tabelle immer auf der ersten Seite
    erscheint. Wäre super wenn ihr mir helfen könnt.

    Mfg

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.