Convert Svg to PDF

Convert SVG to PDF

Thanks to Adrian for showing how the Batik rasterizer API is supposed to be used. However, I needed a more lightweight solution--- I can't write to temporary files, and I want fewer dependencies. So, starting from the methods he pointed to, I found a way to access the lower-level code to do the conversion and nothing else.

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;

import org.apache.batik.transcoder.Transcoder;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.fop.svg.PDFTranscoder;

public class Test {
public static void main(String[] argv) throws TranscoderException, FileNotFoundException {
Transcoder transcoder = new PDFTranscoder();
TranscoderInput transcoderInput = new TranscoderInput(new FileInputStream(new File("/tmp/test.svg")));
TranscoderOutput transcoderOutput = new TranscoderOutput(new FileOutputStream(new File("/tmp/test.pdf")));
transcoder.transcode(transcoderInput, transcoderOutput);
}
}

The compile-and-run commands are

javac -cp batik-rasterizer.jar -d build Test.java
java -cp build:batik-rasterizer.jar Test

The important point is that TranscoderInput and TranscoderOutput can work with any InputStream and OutputStream, not just file streams. Note that one of the constructors takes a org.w3c.dom.Document, which means that you don't even need to serialize an SVG DOM into an SVG string, saving an additional step.

This version also doesn't write anything to stdout/stderr, unlike the high-level API.

For JPEG, PNG, or TIFF output, replace org.apache.fop.svg.PDFTranscoder with org.apache.batik.transcoder.image.JPEGTranscoder, PNGTranscoder, or TIFFTranscoder (note that these raster formats are in a different package).

(I'm not quite sure how Java finds the org.apache.batk.transcoder.* and org.apache.fop.svg.PDFTranscoder classes, since I don't see them in the batik-rasterizer.jar.)

Edit:

Although the simple commandline-compilation works with the batik-rasterizer.jar only, it's doing some sort of classloader magic to find all the necessary classes. In a more realistic case (building a project with Ant), you have to find the classes by hand. They can be found in batik-1.7.zip from the Batik project and fop-1.1.zip from the FOP project. From Batik, you need to compile with batik-transcoder.jar and run with

  • batik-transcoder.jar
  • batik-anim.jar
  • batik-awt-util.jar
  • batik-bridge.jar
  • batik-css.jar
  • batik-dom.jar
  • batik-ext.jar
  • batik-gvt.jar
  • batik-parser.jar
  • batik-script.jar
  • batik-svg-dom.jar
  • batik-util.jar
  • batik-xml.jar
  • xml-apis-ext.jar

From FOP, you need to compile with fop.jar and run with

  • fop.jar
  • avalon-framework-4.2.0.jar
  • xmlgraphics-commons-1.5.jar

How do I convert an SVG to PDF in C#?

I've already solved the problem.
I downloaded the svg with selenium and used iText7 to generate the PDF-file.

I used itext7, which I've installed as NuGet-packet.

using iText;
using iText.Svg.Converter;

and here the code:

FileStream svgPath = File.Open("input.svg", FileMode.Open);
FileStream pdfPath = File.Create("output.pdf");
SvgConverter.CreatePdf(svgPath, pdfPath);

To avoid errors, you should set the current location equal to the location of the svg file. (Directory.SetCurrentDirectory())

SVG to PDF. How to?

Here's the solution I ended up going with, which does rely on any libraries.

The WebKit engine can render an SVG, so you can load an SVG into a WebView:

webView.loadUrl(Uri.fromFile(svgFile).toString());

WebView also has the ability to print, so then you can continue with:

// Get a PrintManager instance
PrintManager printManager = (PrintManager) getActivity()
.getSystemService(Context.PRINT_SERVICE);

// Get a print adapter instance
PrintDocumentAdapter printAdapter = webView.createPrintDocumentAdapter();

// Create a print job with name and adapter instance
String jobName = getString(R.string.app_name) + " Document";
PrintJob printJob = printManager.print(jobName, printAdapter,
new PrintAttributes.Builder().build());

Converting from svg into pdf preserving image metadata

In the new Inkscape version 1.01, most part of the metadata is preserved when converting from .svg to .pdf, while in the previous version, 0.92.5, it was lost.

Thanks to @Moini for giving a hint in the release notes of Inkscape.

For Linux:
Install the newest Inkscape version 1.01 using flatpak

Convert SVG to PDF with iText, SVG is not fully displayed in PDF

As @A.Alexander correctly mentioned, iText out of the box doesn't support relative parameters for the circle element.
However, you can register your own renderer for the circle tag (instead of CircleSvgNodeRenderer) that is able to deal with percents.

There is the parseAbsoluteLength method in AbstractSvgNodeRenderer which can be useful for that.

Custom renderer.

import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.svg.SvgConstants;

import com.itextpdf.svg.renderers.ISvgNodeRenderer;
import com.itextpdf.svg.renderers.SvgDrawContext;
import com.itextpdf.svg.utils.DrawUtils;

/**
* {@link ISvgNodeRenderer} implementation for the <circle> tag.
*/
public class CustomCircleSvgNodeRenderer extends AbstractSvgNodeRenderer {

private float cx;
private float cy;
float r;

@Override
protected void doDraw(SvgDrawContext context) {
PdfCanvas cv = context.getCurrentCanvas();
cv.writeLiteral("% ellipse\n");
if (setParameters(context)) {
// Use double type locally to have better precision of the result after applying arithmetic operations
cv.moveTo((double) cx + (double) r, cy);
DrawUtils.arc((double) cx - (double) r, (double) cy - (double) r, (double) cx + (double) r,
(double) cy + (double) r, 0, 360, cv);
}
}

private boolean setParameters(SvgDrawContext context) {
cx = 0;
cy = 0;
if (getAttribute(SvgConstants.Attributes.CX) != null) {
cx = parseAbsoluteLength(getAttribute(SvgConstants.Attributes.CX), context.getCurrentViewPort().getWidth(), 0.0f, context);
}
if (getAttribute(SvgConstants.Attributes.CY) != null) {
cy = parseAbsoluteLength(getAttribute(SvgConstants.Attributes.CY), context.getCurrentViewPort().getHeight(), 0.0f, context);
}
if (getAttribute(SvgConstants.Attributes.R) != null
&& parseAbsoluteLength(getAttribute(SvgConstants.Attributes.CY), context.getCurrentViewPort().getHeight(), 0.0f, context) > 0) {
r = parseAbsoluteLength(getAttribute(SvgConstants.Attributes.CY), context.getCurrentViewPort().getHeight(), 0.0f, context);
} else {
return false; //No drawing if rx is absent
}
return true;
}

@Override
public ISvgNodeRenderer createDeepCopy() {
CustomCircleSvgNodeRenderer copy = new CustomCircleSvgNodeRenderer();
deepCopyAttributesAndStyles(copy);
return copy;
}

}

Then you need to customize DefaultSvgNodeRendererFactory.

New Factory must create instance of CustomCircleSvgNodeRenderer every time SvgConverter renders a circle.
e.g

public class CustomRendererFactory extends DefaultSvgNodeRendererFactory {

@Override
public ISvgNodeRenderer createSvgNodeRendererForTag(IElementNode tag, ISvgNodeRenderer parent) {
if (SvgConstants.Tags.CIRCLE.equals(tag.name())) {
return new CustomCircleSvgNodeRenderer();
}
return super.createSvgNodeRendererForTag(tag, parent);
}
}

Enforce SVGConverter to use CustomRendererFactory

You should add this factory to ConverterProperties instance and then use methods from SvgConverter that have a parameter of type ISvgConverterProperties.

    SvgConverterProperties properties = new SvgConverterProperties();
properties.setRendererFactory(new RendererFactory());
// xObject
SvgConverter.convertToXObject(svg, pdfDoc, properties);
// or pdf
SvgConverter.createPdf(svgStream, pdfStream, properties);

Strange output when converting SVG to PDF javascript mermaid.js example

I suspect there is some rendering bug in the print rendering module since the SVG call and Mermaid response appears perfect, plus native read and print SVG also looks good, and proper. So your initial code (with a small necessary @media line looks like this as a PDF.

Sample Image

Likewise the Evo sample is perfectly rendered using the same method but allowing in that case the media default of system paper e.g. 8.5x11(Letter)

"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" --headless --run-all-compositor-stages-before-draw --print-to-pdf-no-header --print-to-pdf="C:\Users\WDAGUtilityAccount\desktop\svg-example.pdf" https://www.evopdf.com/DemoAppFiles/HTML_Files/SVG_Examples.html & timeout 5 & svg-example.pdf

Sample Image

To reduce the media size from Chrome's default I had to add in your <head>

<head>
<meta http-equiv="Content-Style-Type" content="text/css">
<style>@media print { @page { margin: 0; size: 125px 238px ; } body { margin: 0; } }</style>
</head>

and you will note due to a slight rounding error in the media maths the size needs to be slightly larger than the SVG viewBox="0 0 124.63749694824219 231.20001220703125" oddly on this occasion just round up width but 6 extra units in height !

Thus I suggest replace the final printout method to the more normal JS methods to use the browsers native print function, should be only a few lines more than my one line method.

How to programmatically convert SVG to PDF on Windows?

There are two options:

  • Batik (Java, open source)
  • Inkscape (native binary, open source)

Batik will give you more control. With Inkscape, you can use the command line parameters (--export-pdf IIRC) to do the conversion without firing up the UI.



Related Topics



Leave a reply



Submit