Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multi-page PDF generation from SVG with Java and Apache Batik

I have two simple SVG documents that I want to convert to a PDF such that each document is on one page in the PDF.

My first SVG document has two rectangles that look as follow:

1st SVG doc

and the second one is a black circle.

The code looks as follow:

import java.io.*;
import org.apache.batik.anim.dom.*;
import org.apache.batik.transcoder.*;
import org.w3c.dom.*;

public class MultiPagePdf {

  public static void main(String[] args) {

    MultiPagePDFTranscoder transcoder = new MultiPagePDFTranscoder();
    try {
      final DOMImplementation impl = SVGDOMImplementation.getDOMImplementation();
      SVGOMDocument doc1 = (SVGOMDocument) impl.createDocument(SVGDOMImplementation.SVG_NAMESPACE_URI, "svg", null);

      // 1st rectangle in doc1
      Element el = doc1.createElementNS(SVGDOMImplementation.SVG_NAMESPACE_URI, "rect");
      el.setAttributeNS(null, "width", "60");
      el.setAttributeNS(null, "height", "60");
      el.setAttributeNS(null, "fill", "none");
      el.setAttributeNS(null, "stroke", "blue");
      SVGOMSVGElement docEl = (SVGOMSVGElement) doc1.getDocumentElement();
      docEl.appendChild(el);

      // 2nd rectangle in doc1
      Element ell = doc1.createElementNS(SVGDOMImplementation.SVG_NAMESPACE_URI, "rect");
      ell.setAttributeNS(null, "x", "50");
      ell.setAttributeNS(null, "y", "50");
      ell.setAttributeNS(null, "width", "25");
      ell.setAttributeNS(null, "height", "25");
      ell.setAttributeNS(null, "fill", "green");
      docEl.appendChild(ell);

      final DOMImplementation impl2 = SVGDOMImplementation.getDOMImplementation();
      SVGOMDocument doc2 = (SVGOMDocument) impl2.createDocument(SVGDOMImplementation.SVG_NAMESPACE_URI, "svg", null);
      // circle in doc2
      Element el2 = doc2.createElementNS(SVGDOMImplementation.SVG_NAMESPACE_URI, "circle");
      el2.setAttributeNS(null, "cx", "130");
      el2.setAttributeNS(null, "cy", "100");
      el2.setAttributeNS(null, "r", "50");
      SVGOMSVGElement docEl2 = (SVGOMSVGElement) doc2.getDocumentElement();
      docEl2.appendChild(el2);

      OutputStream outputStream = new FileOutputStream(new File("/C:/Users/ah/Documents/simpleMulti.pdf"));
      TranscoderOutput transcoderOutput = new TranscoderOutput(outputStream);

      Document[] doccs = { doc1, doc2 };
      transcoder.transcode(doccs, null, transcoderOutput); // generate PDF doc
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

I've printed both SVG documents and they look as they should:

1st SVG Document:

<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentScriptType="text/ecmascript" zoomAndPan="magnify" contentStyleType="text/css" preserveAspectRatio="xMidYMid meet" version="1.0">
    <rect fill="none" width="60" height="60" stroke="blue"/>
    <rect fill="green" x="50" width="25" height="25" y="50"/>
</svg>

2nd SVG Document:

<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentScriptType="text/ecmascript" zoomAndPan="magnify" contentStyleType="text/css" preserveAspectRatio="xMidYMid meet" version="1.0">
    <circle r="50" cx="130" cy="100"/>
</svg>

I found a code that should be doing what I'm after using Apache FOP; I have the following code to generate the PDF:

import java.io.*;
import java.util.*;
import org.apache.batik.transcoder.*;
import org.apache.fop.*;
import org.apache.fop.svg.*;
import org.w3c.dom.*;

public class MultiPagePDFTranscoder extends AbstractFOPTranscoder {

  protected PDFDocumentGraphics2D graphics = null;
  protected Map<String, Object> params = null;

  public MultiPagePDFTranscoder() {
    super();
  }

  protected void transcode(Document[] documents, String uri, TranscoderOutput output) throws TranscoderException {
    graphics = new PDFDocumentGraphics2D(isTextStroked());
    graphics.getPDFDocument().getInfo().setProducer("Apache FOP Version " + Version.getVersion() + ": PDF Transcoder for Batik");

    try {
      OutputStream out = output.getOutputStream();
      if (!(out instanceof BufferedOutputStream)) {
        out = new BufferedOutputStream(out);
      }
      for (int i = 0; i < documents.length; i++) {
        Document document = documents[i];
        super.transcode(document, uri, null);
        int tmpWidth = 300;
        int tmpHeight = 300;
        if (i == 0) {
          graphics.setupDocument(out, tmpWidth, tmpHeight);
        } else {
          graphics.nextPage(tmpWidth, tmpHeight);
        }

        graphics.setGraphicContext(new org.apache.xmlgraphics.java2d.GraphicContext());
        graphics.transform(curTxf);
        this.root.paint(graphics);
      }
      graphics.finish();
    } catch (IOException ex) {
      throw new TranscoderException(ex);
    }
  }
}

The PDF file is generated, but I have two problems with it.

  1. The translation and scale of the SVG elements are not correct.

  2. On the second page, the first document is present (I've tried with multiple pages, and all the previous documents are present on the current page).

The generated PDF document.

I use Apache FOP 2.1 with Apache Batik 1.8.

Any help with either problem would be highly appreciated. I'm also open to other solutions to my overall task (converting SVGs to multi-paged PDF).

like image 584
artofdoe Avatar asked Oct 31 '22 04:10

artofdoe


1 Answers

I had a similar problem. The way I went about it was to use the SVGConverter app in Batik (org.apache.batik.apps.rasterizer.SVGConverter) to convert the single SVG to PDF then stick them together into one file using PDFBox (org.apache.pdfbox.multipdf.PDFMergerUtility).

Convert SVG to single page PDF(s):

File outputFile = new File(pdfPath);
SVGConverter converter = new SVGConverter();
converter.setDestinationType(DestinationType.PDF);
converter.setSources(new String[] { svgPath });
converter.setDst(outputFile);
converter.execute();

Join the PDFs together:

File pdffile = new File(multipagepdfPath);
PDFMergerUtility pdf = new PDFMergerUtility();
pdf.setDestinationFileName(pdffile.getPath());
pdf.addSource(page1pdffile);
pdf.addSource(page2pdffile);
pdf.mergeDocuments(MemoryUsageSetting.setupMainMemoryOnly());
//The memory settings depend on if you want to use RAM or Temp files.

If you find a better solution please let me know. The main problem I had was that the converter app is the only one in Batik that converts to pdf and keeps the sizes correctly.

like image 109
Kim Avatar answered Nov 15 '22 05:11

Kim