使用 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 确实处理正如@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 <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;
那你需要自定义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 将 HTML 转换为 PDF [重复]
iText7 将 HTML 转换为 PDF“System.NullReferenceException”。
仿百度文库方案[openoffice.org 3+swftools+flexpaper] 使用iText将jpgjpegpng转换为pdf
仿百度文库方案[openoffice.org 3+swftools+flexpaper] 使用iText将jpgjpegpng转换为pdf