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

Posted

技术标签:

【中文标题】使用 iText 将 SVG 转换为 PDF,SVG 未在 PDF 中完全显示【英文标题】:Convert SVG to PDF with iText, SVG is not fully displayed in PDF 【发布时间】:2021-03-14 10:26:35 【问题描述】:

我正在为 PDF 页面添加 SVG 图像。

首先,我尝试了 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();

【问题讨论】:

【参考方案1】:

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

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

【讨论】:

您链接到了错误的课程。 CircleSvgNodeRenderer 确实处理 标签。 你可以调试有问题的代码,并看到 EllipseSvgNodeRenderer 做到了。 似乎相关。但是去掉svg中的'%',生成的PDF与cx和cy中带%的文件不同。 当然。因为 % 符号定义了这些值是相对的,并且必须根据父级的大小来计算。如果没有 %,这些都是以像素为单位的绝对值。 确切地说,@A.Alexander 错误是在 setParameter 方法中引发的,该方法在 CircleSvgNodeRenderer 中被覆盖【参考方案2】:

正如@A.Alexander 正确提到的,开箱即用的 iText 不支持圆形元素的相关参数。 但是,您可以为能够处理百分比的圆形标记(而不是 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 渲染一个圆时,新工厂都必须创建 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);

【讨论】:

谢谢帕维尔。它几乎完成了。除了标签比较应该适用于tag.name()

以上是关于使用 iText 将 SVG 转换为 PDF,SVG 未在 PDF 中完全显示的主要内容,如果未能解决你的问题,请参考以下文章

使用 Itext 将 Pdf 页面转换为字节数组

如何使用 iText 将 HTML 转换为 PDF [重复]

iText7 将 HTML 转换为 PDF“System.NullReferenceException”。

仿百度文库方案[openoffice.org 3+swftools+flexpaper] 使用iText将jpgjpegpng转换为pdf

仿百度文库方案[openoffice.org 3+swftools+flexpaper] 使用iText将jpgjpegpng转换为pdf

如何使用 iText 将带有图像和超链接的 HTML 转换为 PDF?