Using Data URLs for embedding images in Flying Saucer generated PDFs
We extensively use Flying Saucer to generate PDFs from GSPs in our grails applications. However, there is always the issue of embedding images from within the application because the URLs are usually relative to the environment and as such, embedding them in PDFs with a URL in the src attribute is cumbersome.
To get around this, we decided to write our own implementation of the ReplacedElementFactory taking some help from this excellent snippets of code. However, we didn’t find a need to go with our custom implementation of Base64 encoding and as such, we ended up using the sun.misc.BASE64Decoder. The resulting class looked like this :
[java]
import com.lowagie.text.BadElementException
import com.lowagie.text.Image
import org.w3c.dom.Element
import org.xhtmlrenderer.extend.FSImage
import org.xhtmlrenderer.extend.ReplacedElement
import org.xhtmlrenderer.extend.ReplacedElementFactory
import org.xhtmlrenderer.extend.UserAgentCallback
import org.xhtmlrenderer.layout.LayoutContext
import org.xhtmlrenderer.pdf.ITextFSImage
import org.xhtmlrenderer.pdf.ITextImageElement
import org.xhtmlrenderer.render.BlockBox
import org.xhtmlrenderer.simple.extend.FormSubmissionListener
public class B64ImgReplacedElementFactory implements ReplacedElementFactory {
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")) {
String attribute = e.getAttribute("src");
FSImage fsImage;
try {
fsImage = buildImage(attribute, uac);
} catch (BadElementException e1) {
fsImage = null;
} catch (IOException e1) {
fsImage = null;
}
if (fsImage != null) {
if (cssWidth != -1 || cssHeight != -1) {
fsImage.scale(cssWidth, cssHeight);
}
return new ITextImageElement(fsImage);
}
}
return null;
}
protected FSImage buildImage(String srcAttr, UserAgentCallback uac) throws IOException, BadElementException {
FSImage fsImage;
if (srcAttr.startsWith("data:image/")) {
String b64encoded = srcAttr.substring(srcAttr.indexOf("base64,") + "base64,".length(), srcAttr.length());
byte[] decodedBytes = new sun.misc.BASE64Decoder().decodeBuffer(b64encoded);
fsImage = new ITextFSImage(Image.getInstance(decodedBytes));
} else {
fsImage = uac.getImageResource(srcAttr).getImage();
}
return fsImage;
}
public void remove(Element e) {
}
public void reset() {
}
@Override
public void setFormSubmissionListener(FormSubmissionListener listener) {
}
}
[/java]
Now, in the code where we call the Renderer, we used :
[java] byte[] generatePdf(String content) { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); byte[] bytes = null ITextRenderer renderer = new ITextRenderer(); SharedContext sharedContext = renderer.getSharedContext(); sharedContext.setPrint(true); sharedContext.setInteractive(false); sharedContext.setReplacedElementFactory(new B64ImgReplacedElementFactory()); sharedContext.getTextRenderer().setSmoothingThreshold(0); try { renderer.setDocumentFromString(content); renderer.layout(); renderer.createPDF(byteArrayOutputStream); bytes = byteArrayOutputStream.toByteArray() } catch (Throwable e) { log.error("Error while generating pdf ${e.message}", e) } return bytes } [/java]
Now, we can embed images in the form of data URLs in our GSPs using base64 encoded version of the image bytes.
Thank you very much, Your solution resolved my issue.
I’m having the same problem with tiny images – how did you set the CSS width explicitly on the Image? I can only see the methods – scaleAbsolute(), scaleToFit() etc. Many thanks.
Easiest way to make a URL small
Thanks. It solves my problem. One more help needed.
I have a image as byte array. I do not want to get this image as base64 and decode it to get the byte array again for FSImage. Is there a way to directly pass byte array to createReplacedElement method and do the image replacement?
Thanks.
Thank you very much, this solution was very useful.
George; I also had those tiny images, but the problem went away when I explicitly set CSS width and height on the image elements:
Hope this helps.
HI
how did you solve the isseu with the width an height of the page , because in my pdf the image appears small