使用 iText 将 SVG 转换为 PDF,SVG 在 PDF 中未完全显示

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

我正在努力将 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();
}

问题是圆在 cx 和 cy 属性中包含百分比。 itextpdf 库中的 EllipseSvgNodeRenderer class 不支持百分比,仅支持像素。

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 两者都值。

正如@A.Alexander 正确提到的,开箱即用的 iText 不支持圆形元素的相关参数。 但是,您可以为能够处理百分比的 circle 标记(而不是 CircleSvgNodeRenderer)注册自己的渲染器。

在 AbstractSvgNodeRenderer 中有 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;
    }

}

然后需要自定义DefaultSvgNodeRendererFactory.

每当 SvgConverter 渲染一个圆时,New Factory 必须创建 CustomCircleSvgNodeRenderer 实例。 例如

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 使用 CustomRendererFactory

您应该将此工厂添加到 ConverterProperties 实例,然后使用 SvgConverter 中具有 ISvgConverterProperties 类型参数的方法。

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