leoleozhu
leoleozhu

Reputation: 660

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

I'm working on adding SVG image to PDF pages.

Firstly, I tried SvgConverter.createPDF to check if iText works with SVG.

Some svgs are fine. Unfortunately, the following SVG (using percentage position) is not correctly displayed / positioned in PDF.

My Conversion code

    String svgImage = resourceFile("svg/circle-sRGB-rgb.svg");
    String destination = targetFile("svg-itext_SVG2PDF.pdf");

    try(InputStream svgStream = new FileInputStream(new File(svgImage))) {
        try(OutputStream pdfStream = new FileOutputStream(new File(destination))) {
            SvgConverter.createPdf(svgStream, pdfStream);
        }
    }

SVG file

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 80 60">
    <circle cx="50%" cy="50%" r="30" fill="rgb(10, 200, 200)"/>
</svg>

SVG Preview

enter image description here

Generated PDF Preview

enter image description here

If I change the position (cx, cy) in SVG to absolute values, output seems to be good.

I also tried converting the SVG to an xObject, but it also didn't help.

private void addSvgImageToPdfPage(PdfPage page, String svgContent, float x, float y, float w, float h) {
    // convert svg to xObject
    PdfFormXObject xObject = SvgConverter.convertToXObject(svgContent, page.getDocument());

    // create page canvas
    PdfCanvas pdfCanvas = new PdfCanvas(page);

    // create AT
    AffineTransform at = AffineTransform.getTranslateInstance(x, y);
    at.concatenate(AffineTransform.getScaleInstance(w / xObject.getWidth(), h / xObject.getHeight()));

    float[] matrix = new float[6];
    at.getMatrix(matrix);

    // add svg xObject to canvas
    pdfCanvas.addXObjectWithTransformationMatrix(xObject, matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);

    pdfCanvas.release();
}

Upvotes: 2

Views: 1367

Answers (2)

Pavel Chermyanin
Pavel Chermyanin

Reputation: 391

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 &lt;circle&gt; 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);

Upvotes: 3

A.Alexander
A.Alexander

Reputation: 598

The problem is that the circle contains percents in cx and cy attributes. EllipseSvgNodeRenderer class from itextpdf lib does not support percents, only pixels.

https://git.itextsupport.com/projects/I7J/repos/itextcore/browse/svg/src/main/java/com/itextpdf/svg/renderers/impl/EllipseSvgNodeRenderer.java#64

So when you run your code the following error appears in the console:

ERROR [main] CssUtils - Unknown absolute metric length parsed "%".

The "%" symbol is ignored and instead of 40 and 30 pixels cx and cy get 50px values both.

Upvotes: 4

Related Questions