javasvgpdf-generationflying-saucerbatik

How to add SVG image to PDF built with HTML and Flying Saucer library (and Batik)?


Im working on generation of PDFs with XHTML using the flying saucer library (old but open source). I got that working but I also want to add SVG images. Ive started working on integrating batik to try and get it to work but I'm running into issues. The SVG images are not drawn. The XHTML still renders, but it doesnt seem to show the SVG. I've gotten SVG to render on separate PDFs but never together with the flying saucer results. I've added the usual ReplacedElementFactory (which works with regular images as well but havent included that code). The only relevant method (that does get called and everything) is the following:

@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();
    if ("img".equals(nodeName)) {
        SAXSVGDocumentFactory factory = new SAXSVGDocumentFactory(XMLResourceDescriptor.getXMLParserClassName());

        SVGDocument svgImage = null;
        try {
            svgImage = factory.createSVGDocument(new File("logo.svg").toURL()
                    .toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
        Element svgElement = svgImage.getDocumentElement();
        Document htmlDoc = element.getOwnerDocument();
        Node importedNode = htmlDoc.importNode(svgElement, true);
        element.appendChild(importedNode);
        return new SVGReplacedElement(svgImage, cssWidth, cssHeight);
    }
    return this.superFactory.createReplacedElement(layoutContext, blockBox, userAgentCallback, cssWidth, cssHeight);
}

Afterwards I try to paint it with:

import java.awt.Graphics2D;
import java.awt.Point;

import org.apache.batik.bridge.BridgeContext;
import org.apache.batik.bridge.DocumentLoader;
import org.apache.batik.bridge.GVTBuilder;
import org.apache.batik.bridge.UserAgent;
import org.apache.batik.bridge.UserAgentAdapter;
import org.apache.batik.gvt.GraphicsNode;
import org.w3c.dom.svg.SVGDocument;
import org.xhtmlrenderer.css.style.CalculatedStyle;
import org.xhtmlrenderer.layout.LayoutContext;
import org.xhtmlrenderer.pdf.ITextOutputDevice;
import org.xhtmlrenderer.pdf.ITextReplacedElement;
import org.xhtmlrenderer.render.BlockBox;
import org.xhtmlrenderer.render.PageBox;
import org.xhtmlrenderer.render.RenderingContext;

import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfTemplate;

public class SVGReplacedElement implements ITextReplacedElement {

private Point location = new Point(0, 0);
private SVGDocument svg;
private int cssWidth;
private int cssHeight;

public SVGReplacedElement(SVGDocument importedNode, int cssWidth, int cssHeight) {
    this.cssWidth = cssWidth;
    this.cssHeight = cssHeight;
    this.svg = importedNode;
}

@Override
Methods....

@Override
public void paint(RenderingContext renderingContext, ITextOutputDevice outputDevice, 
        BlockBox blockBox) {

    UserAgent userAgent = new UserAgentAdapter();
    DocumentLoader loader = new DocumentLoader(userAgent);
    BridgeContext ctx = new BridgeContext(userAgent, loader);
    ctx.setDynamicState(BridgeContext.DYNAMIC);
    GVTBuilder builder = new GVTBuilder();

    blockBox.paintDebugOutline(renderingContext);

    PdfContentByte cb = outputDevice.getWriter().getDirectContent();

    float width = cssWidth / outputDevice.getDotsPerPoint();
    float height = cssHeight / outputDevice.getDotsPerPoint();

    PdfTemplate map = cb.createTemplate(width, height);

    Graphics2D g2d = map.createGraphics(width, height);

    GraphicsNode mapGraphics = builder.build(ctx, svg);
    mapGraphics.paint(g2d);
    g2d.dispose();

    PageBox page = renderingContext.getPage();
    float x = blockBox.getAbsX() + page.getMarginBorderPadding(renderingContext, CalculatedStyle.LEFT);
    float y = (page.getBottom() - (blockBox.getAbsY() + cssHeight)) + page.getMarginBorderPadding(
            renderingContext, CalculatedStyle.BOTTOM);

    cb.addTemplate(map, x, y);
}
}

Interestingly enough, the blockBox.paintDebugOutline(renderingContext); does draw the outlines of where the images should be. Eclipse debugging also revealed that the right files are connected to the IMG elements. The CSS looks as follows:

.header {
position: absolute;
display: inline-block;
right: 0;
top: 0;
width: 150px;
height: 54px;
}

I've also tried with display:block; . Examples xhtml I tried:

<img class='header' src='icon.svg' alt='Logo'/> 
<svg class='header' type='image/svg+xml' data='icon.svg' />
<object class='header' type='image/svg+xml' data='icon.svg' />

Thanks a lot for your attention and feedback (and possibly answers)

EDIT: Originally the problems was slightly different but I've resolved that. The SVGImage could not be appended to the actual document. Now it just doesnt draw. I've added the CSS to display:block etc as mentioned in guides.

EDIT: Cleaner code

EDIT: Added more on what I've tried


Solution

  • I dont know why exactly, but replacing the code in SVGReplacedElement.paint(...) fixed it.

    New code:

    @Override
    public void paint(RenderingContext renderingContext, ITextOutputDevice outputDevice, 
            BlockBox blockBox) {
        PdfContentByte cb = outputDevice.getWriter().getDirectContent();
        float width = (float) (cssWidth / outputDevice.getDotsPerPoint());
        float height = (float) (cssHeight / outputDevice.getDotsPerPoint());
    
        PdfTemplate template = cb.createTemplate(width, height);
        Graphics2D g2d = template.createGraphics(width, height);
        PrintTranscoder prm = new PrintTranscoder();
        TranscoderInput ti = new TranscoderInput(svg);
        prm.transcode(ti, null);
        PageFormat pg = new PageFormat();
        Paper pp = new Paper();
        pp.setSize(width, height);
        pp.setImageableArea(0, 0, width, height);
        pg.setPaper(pp);
        prm.print(g2d, pg, 0);
        g2d.dispose();
    
        PageBox page = renderingContext.getPage();
        float x = blockBox.getAbsX() + page.getMarginBorderPadding(renderingContext, CalculatedStyle.LEFT);
        float y = (page.getBottom() - (blockBox.getAbsY() + cssHeight)) + page.getMarginBorderPadding(
                renderingContext, CalculatedStyle.BOTTOM);
        x /= outputDevice.getDotsPerPoint(); 
        y /= outputDevice.getDotsPerPoint();
    
        cb.addTemplate(template, x, y);
    }
    

    got it from a tutorial.

    Might have had something to do with the new UserAgent and DocumentLoader etc I created that were not linked to the original document. In any case it works now. Hope it will help someone in the future. If people want to comment or add another answer as to why this works now, that might help other reading this later.