JBoss Seam iText PDF custom JSF tags

Category: JBoss

10:51 PM, Tue, Dec 18 2007

JBoss Seam supports powerful features for creating PDFs, using a syntax which is similar to the output syntax for a web page. In this example, we'll create a tag which renders any java.awt.Component in the output PDF. Ability to render any Component means we can do scalable vector and character (font) output using any of the methods of Graphics2D.

We'll be using the latest version of Seam, Seam 2.0. It might be possible to apply these techniques in earlier versions, but we recommend that you upgrade to Seam 2.0 if you haven't yet. We'll start with a look at how Seam PDF works "out of the box", by creating a simple XHTML page which outputs as a PDF. Then we'll briefly look at how to create a custom JSF tag. However, rather than implementing our own generic custom tag, we'll implement it as a sub-class of one of the existing Seam PDF classes.

It's all about JSF: outputting a simple PDF
Seam is designed for Java Server Faces (JSF) and XHTML. The normal use for JSF and XHTML is to output standard HTML pages. However, this is not the only use. In fact, Seam has, since version 1.1, been able to turn JSF into PDF output. The examples directory includes files like this:

<p:document xmlns:ui="http://java.sun.com/jsf/facelets"
            xmlns:p="http://jboss.com/products/seam/pdf">

    <p:list>
        <ui:repeat value="#{lists.numbers}" var="item">
            <p:listItem><p:font style="bold">#{item}</p:font>: 
            This is some information...</p:listItem>
        </ui:repeat>
    </p:list>
</p:document>

This can be expressed more simply by using PDF as the default namespace, like this:

<document xmlns="http://jboss.com/products/seam/pdf"
            xmlns:ui="http://java.sun.com/jsf/facelets">

    <list>
        <ui:repeat value="#{lists.numbers}" var="item">
            <listItem><font style="bold">#{item}</font>: 
            This is some information...</listItem>
        </ui:repeat>
    </list>
</document>

I will use PDF as the default namespace in all the PDF examples in this post, for readability.

Our mission, in this article, is to add a new tag which allows us to draw arbitrary Graphics2D into the PDF output. We are motivated to do this because, while the Seam PDF output is very flexible and powerful, it doesn't let us do everything that a Graphics2D object can do. For example, Seam PDF has tags for drawing charts in the PDF. This is done using JFreeChart, an excellent free charting package for Java. However, JFreeChart itself supports far more charting possibilities than Seam PDF supports. We're going to add our own chart by creating the option of writing a Graphics2D directly into the chart output. Along the way, we'll drop some Swing components into the PDF output, to show that the beans accessor concepts of Seam are a great fit with Swing.

Let's look at the Seam PDF components library, and how it draws charts. All the components derive from the org.jboss.seam.pdf.ui.ITextComponent class. The charting classes all extend UIChart, which itself extends ITextComponent. We'll be replacing the work of UIChart with our own drawing code.

The real work in ITextComponent concrete classes happens in:

@Override public void createITextObject(FacesContext context) { }

and

@Override public Object getITextObject() { }

public void createITextObject(FacesContext context) does the drawing, to a com.lowagie.text.Image object. public Object getITextObject() is how iText gets the results of the drawing, by returning the Image that was created in createITextObject(). getITextObject() is declared to return Object, but it must return a com.lowagie.text.Element, or an IllegalArgumentException will be thrown.

The Element returned by getItextObject() is generated in the createITextObject(FacesContext context) method. That's where the drawing work happens. This is how we do it in GraphicsTag:

UIDocument doc = (UIDocument) findITextParent(getParent(), UIDocument.class);
if (doc != null) {
    final PdfWriter writer = (PdfWriter) doc.getWriter();

Now we have a PdfWriter, and we can use that to get generate an Element. Note that we only create the Image; we don't actually render it into the PDF.

Element has many sub-classes. There are four categories of Element sub-classes: classes for handling bits of text, classes for handling bitmap images (PNGs, JPEGs, etc), classes for handling generic drawing data (vectors), and classes which compose other elements.

We use a PdfTemplate to get a Graphics2D:

final PdfContentByte cb = writer.getDirectContent();
final PdfTemplate tp = cb.createTemplate(getWidth(), getHeight()); 
final Graphics2D g2 = tp.createGraphics(getWidth(),getHeight());

Now we have a regular Graphics2D and can draw on it in any of the usual ways. This Graphics2D is actually a iText internal class, which translates our drawing commands into PDF commands, preserving the drawing actions as scalable high-quality vectors. When we're done with it, we need to save what we have done as an Element so that getITextObject() can make use of it:

try { component = new ImgTemplate(tp); }
catch(Exception e) {
  // note: use better exception handling in a production system
  System.out.println(e.getMessage());
}

and then the getITextObject() works like this:

private Image component = null;

@Override
public Object getITextObject() {
   return component;
}

And that's it! We now have the ability to drop arbitrary graphics into a PDF. We'll create a simple testing tag called RedOval and package it, and test it out. Take a look at the RedOval source. We need to package this into a tag library. There are several different ways that JSF can discover JSF tag libraries. We'll use the way that is most practical: create a JAR file with two particular XML files in the META-INF directory.

The first to create is META-INF/chiral-pdf.taglib.xml. This file defines the tag library itself. The name of this file is not important, except that it must end with .taglib.xml. This is what it looks like:

<?xml version="1.0"?>
<!DOCTYPE facelet-taglib PUBLIC
   "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
   "facelet-taglib_1_0.dtd">
   
<facelet-taglib>
    <namespace>http://chiralsoftware.com/products/chiralpdf</namespace>

    <tag>
        <tag-name>redOval</tag-name>
        <component>
            <component-type>chiralsoftware.pdf.ui.RedOval</component-type>
        </component>
    </tag>

</facelet-taglib>

The file's declaration specifies that it is a facelet-taglib file. Note the namespace declaration. This allows us to make reference to the taglib in the XHTML file, like this:

<document xmlns="http://jboss.com/products/seam/pdf"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:s="http://jboss.com/products/seam/taglib"
    xmlns:chiralpdf="http://chiralsoftware.com/products/chiralpdf">

The URL is used in the faclet-taglib file, and is used in the xmlns attribute, binding the taglib all the way to the chiralpdf: namespace prefix in the XHTML file.

The second file to create in the META-INF directory is the META-INF/faces-config.xml file, where we connect the component-type, as specified in chiral-pdf.taglib.xml, with the actual Java class. This is what our META-INF/faces-config.xml looks like:

<?xml version="1.0"?>
<faces-config version="1.2"
              xmlns="http://java.sun.com/xml/ns/javaee"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
              http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd">

    <component>
            <component-type>chiralsoftware.pdf.ui.RedOval</component-type>
            <component-class>chiralsoftware.pdf.ui.RedOval</component-class>
    </component>

</faces-config>

These are all put into a jar, which has the following files:

% jar tf dist/chiral-pdf.jar
META-INF/MANIFEST.MF
META-INF/chiral-pdf.taglib.xml
META-INF/faces-config.xml
chiralsoftware/pdf/ui/RedOval.class

Try it out with a simple test page:

<document xmlns="http://jboss.com/products/seam/pdf"
    xmlns:chiralpdf="http://chiralsoftware.com/products/chiralpdf">

    <font size="24"><paragraph spacingAfter="50">PDF test</paragraph>
</font>

    <paragraph>Simple PDF paragraph</paragraph>
    
    <chiralpdf:redOval width="340" height="120"/>
    
</document>

And voilĂ :

PDF generated by iText in JBoss Seam

We have created our own subclass of ITextComponent, which draws on a Graphics2D in scalable vector form, and we have packaged this tag into a tag library JAR file and deployed it.

Now let's go beyond red ovals and do something wild. We'll create a tag which is capable of displaying any java.awt.Component. All the Swing components (JLabel, etc) are sub-classes of java.awt.Component, and they are all beans. We'll draw Swing components directly into the PDF, just by defining them in components.xml, like any other Seam component. This leads to the possibility of creating a Swing interface, and then re-using the same display components in a web application to generate good-looking printable PDFs.

We'll create a class called GraphicsTag that accepts a java.awt.Component as a value parameter.

Before we go any further: why don't we use the Seam pdf:image tag, instead of writing a new tag? Looking at the Seam documentation, there is indeed a tag called pdf:image. The documentation says "p:image inserts an image into the document. Images can be be loaded from the classpath or from the web application context using the value attribute." That sounds like what we want, except the value attribute is limited in which types of values it can accept. It can accept a binding to a BufferedImage, which is a bitmap. That's perfect for displaying bitmaps, but is not acceptable for displaying vector graphics, such as text or charts. If the value attribute is not a BufferedImage object, it is passed on as the argument to

org.jboss.seam.ui.graphicImage.Image.setInput(Object o)

Looking at Image.setInput(Object o), it accepts arguments of the following types, with the following usage:

In every case, the pdf:image tag is only able to display bitmap images. The ability to display an image from the classloader or a file is very convenient, but is not suitable for displaying vector graphics (text, charts, SVG).

There are no other tags within the standard Seam PDF tag library that can help us achieve our goal: display high-quality output using arbitrary Graphics2D operations. We must write our own tag, so we continue.

GraphicsTag is a modification of RedOval, to accept a value binding for a Component. Look at the GraphicsTag.java source to see how it works. One key point: tag attributes should be value bindings. In the case of the chiralpdf:graphics tag, the component attribute would need to be a value binding to work. For example:

<chiralpdf:graphics 
        width="310" height="120"
        component="#{sampleButton}"/<

When I first wrote the code, it looked like this:

public void createITextObject(FacesContext context) {
    if(component == null) return;

This didn't work. setComponent() was never called. In a value binding, the property setter (setComponent() in this case) is never called, so the component variable is always null. The key is to use getComponent() instead. This is what getComponent() does:

public Component getComponent() {
    return (Component) valueBinding(FacesContext.getCurrentInstance(), 
			"component",component);
}

GraphicsTag is added to META-INF/chiral-pdf.taglib.xml and META-INF/faces-config.xml just like RedOval was.

Now, the Component, which is the value binding used in chiralpdf:graphics, must be created. One obvious way would be to use the @Name annotation:

@Name("myGraphics") public class MyGraphics extends Component {
  public void paint(Graphics g) { ... }

Try it out to confirm how it works.

Another alternative is to realize that all the existing Swing components are beans, and all extend java.awt.Component, so we can handle them in components.xml just like any other beans.

Starting simple, put this in components.xml:

<component name="sampleButton" class="javax.swing.JButton">
   <property name="label">This is a Swing button</property>
</component>

We get:

PDF generated by iText in JBoss Seam

Nice! A Swing component just showed up in the PDF output. And the PDF is based on vectors, fonts and strings, so the output, including the text in the JButton, will be visible to search engines. And this component was made visible with a single tag in the XHTML file, and three simple lines in components.xml. One interesting thing to notice is that this button is using the Linux look-and-feel, because, in this case, the server was running Linux. If this server had been running on some other operating system, the button would, by default, take that other operating system's look-and-feel. This is something to be aware of when using Swing components. All Swing components have system-dependent look-and-feel.

You wouldn't normally want a plain JButton in a PDF output. Other features such as a carefully constructed JPanel or JTablewhich displays the user interface in a desktop application would be more logical. You can, of course, use all the ordinary Seam annotations:

@Name("myLabel") public class MyLabel extends JLabel {

  @In public void setUserName(String userName) {
    setText(userName);
  }

We have a Swing Component being used, with bijection annotations, as a Seam component! Given that the Swing components are highly configurable using bean accessors, we can do more in components.xml:

<factory name="remoteAddr"  auto-create="true" 
   value="#{facesContext.externalContext.request.remoteAddr}" />

<component name="rightNow" class="java.util.Date"/>

<factory name="sampleButtonLabel" 
value="&lt;html&gt;IP address: &lt;i&gt;#{remoteAddr}&lt;/i&gt;&lt;br/&gt;Time: &lt;i&gt;#{rightNow}&lt;/i&gt;"/>

<component name="sampleButton" class="javax.swing.JButton">
    <property name="label">#{sampleButtonLabel}</property>
</component>

Now, with the Chiral PDF tag library, one line in the front-end code, and a dozen lines in components.xml, we've added a Swing component, with dynamic HTML, into the PDF output:

PDF generated by iText in JBoss Seam

Again, the text in the button is real text in the PDF, meaning it is scalable and accessible by search engines, text-to-speech, etc.

One of the most obvious uses of this new tag is to display JFreeChart output. The Seam PDF library already comes with some charting tags, but JFreeChart itself has far more capabilities than Seam PDF will ever have. Using this custom tag, it's easy to display any JFreeChart you can create in a ready-to-print PDF. I'll edit this blog entry later to post some examples as we get this integrated into some web applications. Our initial motivation was to be able to insert arbitrary JFreeCharts into PDF output.

Future enhancements could include adding support for child nodes to perform transformations on the Graphics2D space, such as affine transformations.

Conclusion

We've introduced the Seam PDF library. We've shown how to write a custom tag, based on extending one of the tags in this library. We've shown how to package this tag in the JAR file so it is automatically discovered by JSF. We've created a tag which takes a java.awt.Component object as its value binding, and then renders that object into the PDF, in harmony with the other Seam PDF tags. We've shown how Swing components' bean properties make them a great fit with the rest of Seam, including setting properties with bijection and using Expression Language and components.xml. With a simple custom tag, we have created the possibility of using tools like NetBeans Matisse to design PDF output.

The chiral-pdf.jar tag library is ready for use and available for download. The chiral-pdf.jar tag library buildable source is also available. I hope that this tag will be included in a future release of JBoss Seam. Update: I just saw a post from Norman Richards of JBoss asking if they can include this tag in the next release. Yes! Now there is a JIRA entry #JBSEAM-2414 for this.

If your company or government entity is looking for expert help in adding advanced web application features, contact us. We have the expertise in enterprise web applications to not only use the best technology available today, but to go beyond it.