Reputation: 660
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
Generated PDF Preview
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
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 <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);
Upvotes: 3
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.
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