Wednesday, December 19, 2012

Generating Barcodes in PDFs with Flying-Saucer

Flying-Saucer is a nice library to generate PDF documents from within Java applications. Just generate a bunch of XHTML, throw it into the renderer and let it produce the desired document utilizing iText.

When it comes to barcodes however, Flying-Saucer cannot access the built in barcode functionality of iText (at least I didn't find any documentation for it).

However, being OpenSource and well designed, one only needs to create one subclass to achieve the task: Flying-Saucer relies on a factory named ReplacedElementFactory, which can replace elements by custom objects. This is also used to embed images, as the class ITextReplacedElementFactory shows. Now we can simply create a subclass, which replaces images with an appropriate barcode: 


<img src="0123456789" type="code128" style="height: 1cm" />

One simply needs to override the createReplacedElement method like this (the whole code can be found here: BarcodeReplacedElementFactory.java (GitHub)):

    @Override
    public ReplacedElement createReplacedElement(

            LayoutContext c, 
            BlockBox box,
            UserAgentCallback uac, 

            int cssWidth, 
            int cssHeight) 
    {

        Element e = box.getElement();
        if (e == null) {
            return null;
        }

        String nodeName = e.getNodeName();
        if (nodeName.equals("img")) {
            if ("code128".equals(e.getAttribute("type"))) {
                try {
                    Barcode128 code = new Barcode128();
                    code.setCode(e.getAttribute("src"));
                    FSImage fsImage = new ITextFSImage(

                                       Image.getInstance(
                                         code.createAwtImage(

                                           Color.BLACK, 
                                           Color.WHITE
                                         ),
                                         Color.WHITE

                                       ));
                    if (cssWidth != -1 || cssHeight != -1) {
                        fsImage.scale(cssWidth, cssHeight);
                    }
                    return new ITextImageElement(fsImage);
                } catch (Throwable e1) {
                    return null;
                }

            }
        }

        return super.createReplacedElement(

                        c, box, uac, cssWidth, cssHeight);
    }


Granted, "type" is no valid XHTML-Element for <img /> but as you can see in the code above, you could easily replace it with data-type or any other attribute. Flying-Saucer doesn't seem to care about this anyway.

Note: The code above can only handle Code128-Barcodes, but can easily be extended to handle EAN and the like (iText supports a whole bunch of barcodes by default).

In order to make our factory work, we need to pass it to the renderer - which is pretty darn easy:
      
        ITextRenderer renderer = new ITextRenderer();
        renderer.getSharedContext().setReplacedElementFactory(
                new BarcodeReplacedElementFactory(

                        renderer.getOutputDevice()
                ));
        renderer.setDocumentFromString(inputAsString);
        renderer.layout();
        renderer.createPDF(outputAsStream);