Render image from servlet in flyingsaucer generated pdf
Asked Answered
A

1

13

I'm using flyingsaucer to render an xhtml document to pdf through a servlet which returns the generated pdf document. The xhtml document features an image which is requested from another servlet. The image servlet checks who is logged in before returning the appropriate image. The code below shows how the image is requested:

<img height="140" width="140" src="http://localhost:8080/myapp/servlet/DisplayPic" />

My problem is that the http request for the image is from the pdf renderer and not the logged in user so the image servlet doesn't know who's logged in and therefore the desired image is not returned.

I'm currently using the code below to render the xhtml document:

ITextRenderer renderer = new ITextRenderer();
renderer.setDocumentFromString(xhtmlDocumentAsString);
renderer.layout();
os = response.getOutputStream();
renderer.createPDF(os);

I need to either maintain the user's session when the image servlet is requested or provide the renderer with the image to use for that specific xhtml element. I think the latter can be done using a ReplacedElementFactory but I haven't been able to dig out any example code that can help me.

Armoury answered 25/4, 2012 at 13:22 Comment(2)
This is awesome! Any idea how to make it work for SVG files?Hit
There is a feature request to make data-url for images work directly in Flying Saucer: code.google.com/p/flying-saucer/issues/detail?id=202Constitutionally
A
25

I've got this working very nicely now. Here's the code.

In my xhtml document i have:

<div class="profile_picture" style="display:block;width:140px;height:140px;" />

(I'm using a div element instead of img as the factory is only used for block level elements)

I render my document using:

ITextRenderer renderer = new ITextRenderer();
renderer.getSharedContext().setReplacedElementFactory(new ProfileImageReplacedElementFactory(renderer.getSharedContext().getReplacedElementFactory()));
renderer.setDocumentFromString(xhtmlDocumentAsString);
renderer.layout();
os = response.getOutputStream();
renderer.createPDF(os);

And i have my own ReplacedElementFactory as below:

public class ProfileImageReplacedElementFactory implements ReplacedElementFactory {

    private final ReplacedElementFactory superFactory;

    public ProfileImageReplacedElementFactory(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");
        if ("div".equals(nodeName) && className.contains("profile_picture")) {

            InputStream input = null;
            try {
                input = ...;
                byte[] bytes = IOUtils.toByteArray(input);
                Image image = Image.getInstance(bytes);
                FSImage fsImage = new ITextFSImage(image);

                if (fsImage != null) {
                    if ((cssWidth != -1) || (cssHeight != -1)) {
                        fsImage.scale(cssWidth, cssHeight);
                    }
                    return new ITextImageElement(fsImage);
                }
            } catch (IOException e) {
                getLogger().error(ExceptionUtils.getStackTrace(e));
            } catch (BadElementException e) {
                getLogger().error(ExceptionUtils.getStackTrace(e));
            } finally {
                IOUtils.closeQuietly(input);
            }
        }

        return superFactory.createReplacedElement(layoutContext, blockBox, userAgentCallback, cssWidth, cssHeight);
    }

    @Override
    public void reset() {
        superFactory.reset();
    }

    @Override
    public void remove(Element e) {
        superFactory.remove(e);
    }

    @Override
    public void setFormSubmissionListener(FormSubmissionListener listener) {
        superFactory.setFormSubmissionListener(listener);
    }
}
Armoury answered 26/4, 2012 at 15:4 Comment(5)
Will this work for an embedded image? one that looks like <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEU... ?Guildsman
It needs to be a block level element so the simple answer is no. You can do something like <div><img/></div> and do the replacement on the div element instead of the img elementArmoury
Sorry... I just realised that I completely misunderstood your question. I don't see any reason why that wouldn't work, you just need to read the embedded image in your ReplacedElementFactory and return itArmoury
Why the fsImage != null check right after the initialization?Swanskin
@Swanskin I don't think it's needed... should be safe to leave outArmoury

© 2022 - 2024 — McMap. All rights reserved.