javaimagexhtmlpdf-generationflying-saucer

Using Flying Saucer to Render Images to PDF In Memory


I'm using Flying Saucer to convert XHTML to a PDF document. I've gotten the code to work with just basic HTML and in-line CSS, however, now I'm attempting to add an image as a sort of header to the PDF. What I'm wondering is if there is any way whatsoever to add the image by reading in an image file as a Java Image object, then adding that somehow to the PDF (or to the XHTML -- like it gets a virtual "url" representing the Image object that I can use to render the PDF). Has anyone ever done anything like this?

Thanks in advance for any help you can provide!


Solution

  • I had to do that last week so hopefully I will be able to answer you right away.

    Flying Saucer

    The easiest way is to add the image you want as markup in your HTML template before rendering with Flying Saucer. Within Flying Saucer you will have to implement a ReplacedElementFactory so that you can replace any markup before rendering with the image data.

    /**
     * Replaced element in order to replace elements like 
     * <tt>&lt;div class="media" data-src="image.png" /></tt> with the real
     * media content.
     */
    public class MediaReplacedElementFactory implements ReplacedElementFactory {
        private final ReplacedElementFactory superFactory;
    
        public MediaReplacedElementFactory(ReplacedElementFactory superFactory) {
            this.superFactory = superFactory;
        }
    
        @Override
        public ReplacedElement createReplacedElement(LayoutContext layoutContext, BlockBox blockBox, UserAgentCallback userAgentCallback, int cssWidth, int cssHeight) {
            Element element = blockBox.getElement();
            if (element == null) {
                return null;
            }
            String nodeName = element.getNodeName();
            String className = element.getAttribute("class");
            // Replace any <div class="media" data-src="image.png" /> with the
            // binary data of `image.png` into the PDF.
            if ("div".equals(nodeName) && "media".equals(className)) {
                if (!element.hasAttribute("data-src")) {
                    throw new RuntimeException("An element with class `media` is missing a `data-src` attribute indicating the media file.");
                }
                InputStream input = null;
                try {
                    input = new FileInputStream("/base/folder/" + element.getAttribute("data-src"));
                    final byte[] bytes = IOUtils.toByteArray(input);
                    final Image image = Image.getInstance(bytes);
                    final FSImage fsImage = new ITextFSImage(image);
                    if (fsImage != null) {
                        if ((cssWidth != -1) || (cssHeight != -1)) {
                            fsImage.scale(cssWidth, cssHeight);
                        }
                        return new ITextImageElement(fsImage);
                    }
                } catch (Exception e) {
                    throw new RuntimeException("There was a problem trying to read a template embedded graphic.", e);
                } finally {
                    IOUtils.closeQuietly(input);
                }
            }
            return this.superFactory.createReplacedElement(layoutContext, blockBox, userAgentCallback, cssWidth, cssHeight);
        }
    
        @Override
        public void reset() {
            this.superFactory.reset();
        }
    
        @Override
        public void remove(Element e) {
            this.superFactory.remove(e);
        }
    
        @Override
        public void setFormSubmissionListener(FormSubmissionListener listener) {
            this.superFactory.setFormSubmissionListener(listener);
        }
    }
    

    You will notice that I have hardcoded here /base/folder which is the folder where the HTML file is located as it will be the root url for Flying Saucer for resolving medias. You may change it to the correct location, coming from anywhere you want (Properties for example).

    HTML

    Within your HTML markup you indicate somewhere a <div class="media" data-src="somefile.png" /> like so:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
       "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
        <head>
            <title>My document</title>
            <style type="text/css">
            #logo { /* something if needed */ }
            </style>
        </head>
        <body>
            <!-- Header -->
            <div id="logo" class="media" data-src="media/logo.png" style="width: 177px; height: 60px" />
            ...
        </body>
    </html>
    

    Rendering

    And finally you just need to indicate your ReplacedElementFactory to Flying-Saucer when rendering:

    String content = loadHtml();
    ITextRenderer renderer = new ITextRenderer();
    renderer.getSharedContext().setReplacedElementFactory(new MediaReplacedElementFactory(renderer.getSharedContext().getReplacedElementFactory()));
    renderer.setDocumentFromString(content.toString());
    renderer.layout();
    final ByteArrayOutputStream baos = new ByteArrayOutputStream();
    renderer.createPDF(baos);
    // baos.toByteArray();
    

    I have been using Freemarker to generate the HTML from a template and then feeding the result to FlyingSaucer with great success. This is a pretty neat library.