Friday, February 29, 2008

Export Swing components to PDF

Use JFreeChart and iText to draw charts

Suppose you've written an application with a GUI using Swing components such as JTable or JTextPane. All these components are derived from the abstract class javax.swing.JComponent, which includes the print(Graphics g) method: You can use this method to let the Swing component print itself to iText's PdfGraphics2D object.

(Note: This article excerpts Chapter 12, "Drawing to Java Graphics2D," from iText in Action, Bruno Lowagie (Manning Publications, December 2006; ISBN: 1932394796): http://www.manning.com/lowagie.)

Figure 1 shows a simple Java application with a JFrame. It contains a JTable found in Sun's Java tutorial on Swing components. If you click the first button, the contents of the table are added to a PDF using createGraphicsShapes() (the upper PDF in the screenshot). If you click the second button, the table is added using createGraphics() (the lower PDF, using the standard Type 1 font Helvetica). Notice the subtle differences between the fonts used for both variants.



Figure 1. A Swing application with a JTable that is printed to PDF two different ways. Click on thumbnail to view full-sized image.

If you run this example, try changing the content of the JTable; the changes are reflected in the PDF. If you select a row, the background of the row is shown in a different color in the Java applications as well as in the PDF.

The code to achieve this is amazingly simple:

/* chapter12/MyJTable.java */
public void createPdf(boolean shapes) {
Document document = new Document();
try {
PdfWriter writer;
if (shapes)
writer = PdfWriter.getInstance(document,
new FileOutputStream("my_jtable_shapes.pdf"));
else
writer = PdfWriter.getInstance(document,
new FileOutputStream("my_jtable_fonts.pdf"));
document.open();
PdfContentByte cb = writer.getDirectContent();
PdfTemplate tp = cb.createTemplate(500, 500);
Graphics2D g2;
if (shapes)
g2 = tp.createGraphicsShapes(500, 500);
else
g2 = tp.createGraphics(500, 500);
table.print(g2);
g2.dispose();
cb.addTemplate(tp, 30, 300);
} catch (Exception e) {
System.err.println(e.getMessage());
}
document.close();
}


The next example was posted to the iText mailing list by Bill Ensley (bearprinting.com), one of the more experienced iText users on the mailing list. It's a simple text editor that allows you to write text in a JTextPane and print it to PDF.

Figure 2 shows this application in action.



Figure 2. A simple editor with a JTextPane that is drawn onto a PDF file. Click on thumbnail to view full-sized image.

The code is a bit more complex than the JTable example. This example performs an affine transformation before the content of the JTextPane is painted:

/* chapter12/JTextPaneToPdf.java */
Graphics2D g2 = cb.createGraphics(612, 792, mapper, true, .95f);
AffineTransform at = new AffineTransform();
at.translate(convertToPixels(20), convertToPixels(20));
at.scale(pixelToPoint, pixelToPoint);
g2.transform(at);
g2.setColor(Color.WHITE);
g2.fill(ta.getBounds());
Rectangle alloc = getVisibleEditorRect(ta);
ta.getUI().getRootView(ta).paint(g2, alloc);
g2.setColor(Color.BLACK);
g2.draw(ta.getBounds());
g2.dispose();


Numerous applications use iText this way. Let me pick two examples; one free/open source software (FOSS) product and one proprietary product:

JasperReports, a free Java reporting tool from JasperSoft, allows you to deliver content onto the screen; to the printer; or into PDF, HTML, XLS, CSV, and XML files. If you choose to generate PDF, iText's PdfGraphics2D object is used behind the scenes.
ICEbrowser is a product from ICEsoft. ICEbrowser parses and lays out advanced Web content (XML/HTML/CSS/JS); PDF is generated by rendering the parsed documents to the PdfGraphics2D object.
It's not my intention to make a complete list of products that use iText. The main purpose of these two examples is to answer the following question: Can I build iText into my commercial product? Lots of people think open source is the opposite of commercial, but that's a misunderstanding. It's not because iText is FOSS that it can only be used in other free products. It's not because iText is free that it isn't a "commercial" product. As long as you respect the license, you can use iText in your closed-source or proprietary software.

Another useful aspect of iText's Graphics2D functionality is that it opens the door to using iText in combination with other libraries with graphical output—for instance, Apache Batik, a library that is able to parse SVG; or JFreeChart, a library that will be introduced in the next section.

Drawing charts with JFreeChart
Suppose you need to make charts showing demographic information. You take the student population of the Technological University of Foobar and graph the number of students per continent.

To make these charts, you'll combine iText with JFreeChart, an interesting library developed by David Gilbert and Thomas Morgner. The Website jfree.org explains that JFreeChart is "a free Java class library for generating charts, including pie charts (2D and 3D), bar charts (regular and stacked, with an optional 3D effect), line and area charts, scatter plots and bubble charts, time series, high/low/open/close charts and candle stick charts, combination charts, Pareto charts, Gantt charts, wind plots, meter charts and symbol charts, and wafer map charts."

These charts can be rendered on an AWT (Abstract Window Toolkit) or Swing component, they can be exported to JPEG or PNG, and you can combine JFreeChart with Apache Batik to produce SVG or with iText to produce PDF.

Figure 3 shows PDFs with a pie chart and a bar chart created using JFreeChart and iText. In JFreeChart, you construct a JFreeChart object using the ChartFactory. One of the parameters passed to one of the methods to create the chart is a dataset object.



Figure 3. Foobar statistics represented in a pie chart and a bar chart. Click on thumbnail to view full-sized image.

The code to create the charts shown in Figure 3 is simple:

/* chapter12/FoobarCharts.java */
public static JFreeChart getBarChart() {
DefaultCategoryDataset dataset = new DefaultCategoryDataset();
dataset.setValue(57, "students", "Asia");
dataset.setValue(36, "students", "Africa");
dataset.setValue(29, "students", "S-America");
dataset.setValue(17, "students", "N-America");
dataset.setValue(12, "students", "Australia");
return ChartFactory.createBarChart("T.U.F. Students",
"continent", "number of students", dataset,
PlotOrientation.VERTICAL, false, true, false);
}
public static JFreeChart getPieChart() {
DefaultPieDataset dataset = new DefaultPieDataset();
dataset.setValue("Europe", 302);
dataset.setValue("Asia", 57);
dataset.setValue("Africa", 17);
dataset.setValue("S-America", 29);
dataset.setValue("N-America", 17);
dataset.setValue("Australia", 12);
return ChartFactory.createPieChart("Students per continent",
dataset, true, true, false);
}


The previous code snippet creates two JFreeChart objects. The following code snippet shows how to create a PDF file per chart:

/* chapter12/FoobarCharts.java */
public static void convertToPdf(JFreeChart chart,
int width, int height, String filename) {
Document document = new Document(new Rectangle(width, height));
try {
PdfWriter writer;
writer = PdfWriter.getInstance(document, new FileOutputStream(filename));
document.open();
PdfContentByte cb = writer.getDirectContent();
PdfTemplate tp = cb.createTemplate(width, height);
Graphics2D g2d = tp.createGraphics(width, height, new DefaultFontMapper());
Rectangle2D r2d = new Rectangle2D.Double(0, 0, width, height);
chart.draw(g2d, r2d);
g2d.dispose();
cb.addTemplate(tp, 0, 0);
}
catch(Exception e) {
e.printStackTrace();
}
document.close();
}


The chart is drawn on a PdfTemplate. This object can easily be wrapped in an iText Image object if you want to add it to the PDF with document.add().

No comments: