I have recently been trying to do some printing from a Java application. The Java printing API appears to be extremely powerful but it has, in my opinion, two significant problems. First, it doesn't seem to have a default set of bits to plug together and produce printout. Instead you get some useful components and a set of interfaces. The second problem is that you effectively paint every component yourself, something that I really didn't fancy doing for the rather complex layouts that I had to work with.
I began to think about how easy the output would be in something like HTML which led to me deciding to unearth my memories of XSL-FO. By digging around it seemed like it was possible to use Fop (a Java XSL-FO toolkit) to render data straight to the printer utilising some of the support classes from the Java print API. Sadly I struggled to find any documentation of how to do it so I pieced together the scraps I could find and the result is shown below.
The code below borrows heavily from the example on the FOP site but I have made the changes that were needed to use a print selection dialog and to allow the xml source to be passed as a parameter.
package uk.co.artran.print;
import java.awt.print.PrinterJob;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamSource;
import org.apache.fop.apps.*;
import org.apache.fop.render.print.PrintRenderer;
import org.w3c.dom.Document;
public class FopPrint {
private FopFactory fopFactory;
public FopPrint() {
// Step 1: Construct a FopFactory
fopFactory = FopFactory.newInstance();
}
public void printXML(Document xml){
// Step 2: Set up the PrinterJob using its dialog
PrinterJob printerJob = PrinterJob.getPrinterJob();
try {
if (printerJob.printDialog()) {
// Create a PrintRenderer from our PrinterJob and use it to override the default renderer
PrintRenderer printRenderer = new PrintRenderer(printerJob);
FOUserAgent userAgent = fopFactory.newFOUserAgent();
userAgent.setRendererOverride(printRenderer);
// Step 3: Construct fop with desired output format
Fop fop = fopFactory.newFop(MimeConstants.MIME_POSTSCRIPT, userAgent);
// Step 4: Setup JAXP using transformer
TransformerFactory factory = TransformerFactory.newInstance();
Source xslt = new StreamSource(this.getClass().getResourceAsStream("test.xsl"));
Transformer transformer = factory.newTransformer(xslt);
// Step 5: Setup input and output for XSLT transformation
// Setup input document
Source src = new DOMSource(xml);
// Resulting SAX events (the generated FO) must be piped through to FOP
Result res = new SAXResult(fop.getDefaultHandler());
// Step 6: Start XSLT transformation and FOP processing
transformer.transform(src, res);
}
} catch(Exception ex) {
ex.printStackTrace();
}
}
}
-
Step 1: Create a new
FopFactoryinstance. TheFopFactoryinstance holds references to configuration information and cached data. It's important to reuse this instance if you plan to render multiple documents during a JVM's lifetime. -
Step 2: Set up the
PrinterJobthat the generated document will be written to. ThePrinterJobhas a method calledprintDialog()that will create a print dialog to allow the user to specify the printer, paper size, number of copies and so on, if possible the dialog will use the native dialog for the OS. The dialog is modal and returnstrueif the user clicks OK. After returning from the dialog thePrinterJobwill be configured with the users selections and can be passed to thePrintRendererwhich is then used to configure theFOUserAgent. -
Step 3: Create a new Fop instance through one of the factory
methods on the FopFactory. You tell the FopFactory what your desired output format
is. This is done by using the MIME type of the desired output format (ex. "application/pdf").
The example uses one of the
MimeConstants.*constants to specify postscript which should work well for most printers. The second parameter is theFOUserAgentsetup up in step 2. -
Step 4 Setup a JAXP
Transformerto perform the transform of the input xml to the intermediate xsl-fo. At this point we only create the transformer and pass in the xsl file. In this case the xsl is a resource stored in the same package as theFopPrintclass. -
Step 5: Set up the input and output for the XSLT
transformation. The Source object is set up to load the xml
Documentwhich was passed in as a parameter. TheResultis set up so the output of the XSLT transformation is sent to FOP. The FO file is sent to FOP in the form of SAX events which is the most efficient way. Saving intermediate results to a file or a memory buffer affects performance negatively. -
Step 6: Finally, we start the XSLT transformation by starting
the JAXP Transformer. As soon as the JAXP Transformer starts to send its output
to FOP, FOP itself starts its processing in the background. When the
transform()method returns FOP will also have finished producing its output.
My references for the article were the Java API documentation, the FOP API documentation and the FOP embedding documentation.

