使用itext将svg转换为pdf,svg不能完全显示在pdf中

tpgth1q7  于 2021-07-07  发布在  Java
关注(0)|答案(2)|浏览(1107)

我正在将svg图像添加到pdf页面。
首先,我尝试了svgconverter.createpdf来检查itext是否可以与svg一起工作。
一些SVG很好。遗憾的是,以下svg(使用百分比位置)在pdf中没有正确显示/定位。
我的转换代码

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文件

<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预览

生成的pdf预览

如果我将svg中的位置(cx,cy)改为绝对值,输出似乎很好。
我还尝试将svg转换为xobject,但也没有用。

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();
}
ejk8hzay

ejk8hzay1#

问题是圆包含cx和cy属性中的百分比。itextpdf lib中的ellipsesvgnoderender类不支持百分比,仅支持像素。
https://git.itextsupport.com/projects/i7j/repos/itextcore/browse/svg/src/main/java/com/itextpdf/svg/renderers/impl/ellipsesvgnoderenderer.java#64
因此,当您运行代码时,控制台中会出现以下错误:

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

忽略“%”符号,而不是40和30像素cx和cy都获得50px值。

dgtucam1

dgtucam12#

正如@a.alexander正确提到的,itext开箱即用不支持circle元素的相关参数。但是,您可以为能够处理百分比的circle标记(而不是circlesvgnoderender)注册自己的渲染器。
abstractsvgnoderender中的parseabsolutelength方法对此非常有用。
自定义渲染器。

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;
    }

}

然后需要自定义DefaultsVgnoderEnderFactory。
每当svgconverter渲染一个圆时,新工厂必须创建customCircleSVGNodeRender的示例。例如

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);
    }
}

强制svgconverter使用CustomRenderFactory
您应该将此工厂添加到converterproperties示例,然后使用svgconverter中具有isvgconverterproperties类型参数的方法。

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

相关问题