使用 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 不支持百分比,仅支持像素。
因此,当您 运行 您的代码时,控制台中会出现以下错误:
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 <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 渲染一个圆时,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);
我正在努力将 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 不支持百分比,仅支持像素。
因此,当您 运行 您的代码时,控制台中会出现以下错误:
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 <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 渲染一个圆时,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);