Svg integration in pdf using flying saucer
Asked Answered
N

5

7

I under gone a situation of converting html to pdf, Thankfully I can achieved this through flying saucer api. But My HTML consists of svg tags while converting I am unable to get the svg in pdf. It can be achieved using a Stackoverflow question and Tutorial.

What is meant by the replacedElementFactory?

ChainingReplacedElementFactory chainingReplacedElementFactory 
        = new ChainingReplacedElementFactory();
chainingReplacedElementFactory.addReplacedElementFactory(replacedElementFactory);
chainingReplacedElementFactory.addReplacedElementFactory(new SVGReplacedElementFactory());
renderer.getSharedContext().setReplacedElementFactory(chainingReplacedElementFactory);
Neslund answered 5/5, 2016 at 17:33 Comment(0)
C
7

It's just an error in the tutorial, the line with replacedElementFactory is not needed.

Here is my working example.

Java:

import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.xhtmlrenderer.pdf.ITextRenderer;

public class PdfSvg {
    public static void main(String[] args) throws Exception {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(true);
        DocumentBuilder builder = factory.newDocumentBuilder();
        Document inputDoc =  builder.parse("svg.html");

        ByteArrayOutputStream output = new ByteArrayOutputStream();

        ITextRenderer renderer = new ITextRenderer();

        ChainingReplacedElementFactory chainingReplacedElementFactory = new ChainingReplacedElementFactory();
        chainingReplacedElementFactory.addReplacedElementFactory(new SVGReplacedElementFactory());
        renderer.getSharedContext().setReplacedElementFactory(chainingReplacedElementFactory);

        renderer.setDocument(inputDoc, "");;
        renderer.layout();
        renderer.createPDF(output);

        OutputStream fos = new FileOutputStream("svg.pdf");
        output.writeTo(fos);
    }
}

HTML:

<html>
<head>
<style type="text/css">
    svg {display: block;width:100mm;height:100mm}
</style>
</head>
<body>
    <div>
        <svg xmlns="http://www.w3.org/2000/svg">
            <circle cx="50" cy="50" r="40" stroke="black" stroke-width="3"
                fill="red" />
        </svg>
    </div>
</body>
</html>

The ChainingReplacedElementFactory, SVGReplacedElement and SVGReplacedElementFactory comes from the tutorial.

Congratulation answered 6/5, 2016 at 8:23 Comment(4)
Thanks for quick reply ... Its working fine with svg tag in above html .. but i have svg generated by highchart api ... sample input is here .... jsfiddle.net/gh/get/jquery/1.9.1/highslide-software/…Neslund
The problem is to generate the SVG (or an image) from the highchart JS code, on the server. Unfortunately, I don't have a solution for that, but this page on the highchart website may help you: highcharts.com/news/52-serverside-generated-charts.Congratulation
Link is broken. I tried with xpert-framework without results. Where can I get ChainingReplacedElementFactory and SVGReplacedElementFactory?Pith
This won't work without the svg style in the html head section (correctly shown above) - don't overlook it!Shayneshays
A
1

If you wanted an in page solution, here's an alternate using @cloudformatter which is a remote formatting service. I added their Javascript to your fiddle along with some text and your Highchart chart.

http://jsfiddle.net/yk0Lxzg0/1/

var click="return xepOnline.Formatter.Format('printme', {render:'download'})";
jQuery('#buttons').append('<button onclick="'+ click +'">PDF</button>');

The above code placed in the fiddle will format the div with 'id' printme to PDF for download. That div includes your chart and some text.

http://www.cloudformatter.com/CSS2Pdf.APIDoc.Usage shows usage instructions and has many more samples of charts in SVG formatted to PDF either by themselves or as part of pages combined with text, tables and such.

Ammonal answered 6/5, 2016 at 18:25 Comment(0)
I
0

@Rajesh I hope you already found a solution to your problem. If not (or anyone having issues working with flying saucer, batik and svg tags) then you might want to consider this- removing all clip-path="url(#highcharts-xxxxxxx-xx)" from <g> tags did the trick for me.

Internecine answered 24/3, 2017 at 16:29 Comment(0)
A
0

The best result is accieved when the svg is converted to a pdf image:

class ResourceLoaderUserAgent extends ITextUserAgent {

    private final String sessionId;
    private final ITextOutputDevice outputDevice;

    public ResourceLoaderUserAgent(ITextOutputDevice outputDevice) {
        super(outputDevice, DEFAULT_DOTS_PER_PIXEL);
        this.outputDevice = outputDevice;
    }

    @Override
    public ImageResource getImageResource(String uriStr) {
        if (uriStr.contains(".svg")) {
            return getSVGImageAsPDF(uriStr);
        } else {
            return super.getImageResource(uriStr);
        }
    }

    @Override
    public byte[] getBinaryResource(String uri) {
        if (uri.contains(".svg")) {
            try(var is = resolveAndOpenStream(uri)) {
                Transcoder transcoder = new PDFTranscoder();
                TranscoderInput input = new TranscoderInput(is);
                var os = new ByteArrayOutputStream();
                TranscoderOutput output = new TranscoderOutput(os);
                transcoder.transcode(input, output);
                return os.toByteArray();
            } catch (Exception e) {
                LOG.error("Could not get svg {} as pdf", uri, e);
                return new byte[]{};
            }
        } else {
            return super.getBinaryResource(uri);
        }
    }

    private ImageResource getSVGImageAsPDF(String uriStr) {
        try {
            // Code from ITextUserAgent.getImageResource(String)
            URI uri = new URI(uriStr);
            PdfReader reader = outputDevice.getReader(uri);
            PDFAsImage image = new PDFAsImage(uri);
            Rectangle rect = reader.getPageSizeWithRotation(1);
            image.setInitialWidth(rect.getWidth() * outputDevice.getDotsPerPoint());
            image.setInitialHeight(rect.getHeight() * outputDevice.getDotsPerPoint());
            return new ImageResource(uriStr, image);
        } catch (URISyntaxException | IOException e) {
            throw new RuntimeException(e);
        }
    }
}

Dependencies:

 <dependency>
            <groupId>org.xhtmlrenderer</groupId>
            <artifactId>flying-saucer-pdf</artifactId>
            <version>9.5.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.xmlgraphics</groupId>
            <artifactId>batik-transcoder</artifactId>
            <version>1.17</version>
        </dependency>
        <dependency>
            <groupId>org.apache.xmlgraphics</groupId>
            <artifactId>fop</artifactId>
            <version>2.9</version>
        </dependency>
Adamson answered 1/3 at 8:52 Comment(0)
I
-1

My code is referring to the missing code part "SVGReplacedElementFactory".

And I use it like this:

renderer
.getSharedContext()
.setReplacedElementFactory( new B64ImgReplacedElementFactory() );
import com.itextpdf.text.BadElementException;
import com.itextpdf.text.Image;
import com.itextpdf.text.pdf.codec.Base64;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.image.JPEGTranscoder;
import org.apache.batik.transcoder.image.PNGTranscoder;
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;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

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
    {
        if(srcAttr.startsWith("data:image/"))
        {
            // BASE64Decoder decoder = new BASE64Decoder();
            // byte[] decodedBytes = decoder.decodeBuffer(b64encoded);
            // byte[] decodedBytes = B64Decoder.decode(b64encoded);
            byte[] decodedBytes = Base64.decode(srcAttr.substring(srcAttr.indexOf("base64,") + "base64,".length(), srcAttr.length()));
            return new ITextFSImage(Image.getInstance(decodedBytes));
        }
        FSImage fsImage = uac.getImageResource(srcAttr).getImage();
        if(fsImage == null)
        {
            return convertToPNG(srcAttr);
        }
        return null;
    }

    private FSImage convertToPNG(String srcAttr) throws IOException, BadElementException
    {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        PNGTranscoder t = new PNGTranscoder();
//        t.addTranscodingHint(JPEGTranscoder.KEY_PIXEL_UNIT_TO_MILLIMETER, (25.4f / 72f));
        t.addTranscodingHint(JPEGTranscoder.KEY_WIDTH, 4000.0F);
        t.addTranscodingHint(JPEGTranscoder.KEY_HEIGHT, 4000.0F);
        try
        {
            t.transcode(
                    new TranscoderInput(srcAttr),
                    new TranscoderOutput(byteArrayOutputStream)
            );
        }
        catch(TranscoderException e)
        {
            e.printStackTrace();
        }
        byteArrayOutputStream.flush();
        byteArrayOutputStream.close();
        return new ITextFSImage(Image.getInstance(byteArrayOutputStream.toByteArray()));
    }

    public void remove(Element e)
    {
    }

    @Override
    public void setFormSubmissionListener(FormSubmissionListener formSubmissionListener)
    {

    }

    public void reset()
    {
    }
} 
Insouciant answered 8/1, 2020 at 9:19 Comment(1)
Thank you for this code snippet, which might provide some limited short-term help. A proper explanation would greatly improve its long-term value by showing why this is a good solution to the problem, and would make it more useful to future readers with other, similar questions. Please edit your answer to add some explanation, including the assumptions you've made.Usher

© 2022 - 2024 — McMap. All rights reserved.