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 :
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) {
}
}
Now, in the code where we call the Renderer, we used :
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 }Now, we can embed images in the form of data URLs in our GSPs using base64 encoded version of the image bytes.
