#java #svg #itext #itext7
#java #svg #itext #itext7
Вопрос:
Я работаю над добавлением 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-файла
Если я изменю позицию (cx, cy) в SVG на абсолютные значения, результат будет хорошим.
Я также попытался преобразовать 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. Класс EllipseSvgNodeRenderer из библиотеки itextpdf не поддерживает проценты, только пиксели.
Поэтому при запуске вашего кода в консоли появляется следующая ошибка:
ERROR [main] CssUtils - Unknown absolute metric length parsed "%".
Символ «%» игнорируется, и вместо 40 и 30 пикселей cx и cy получают
значения 50 пикселей оба.
Комментарии:
1. Вы ссылались на неправильный класс. CircleSvgNodeRenderer обрабатывает тег <circle> .
2. Вы можете отладить рассматриваемый код и увидеть, что EllipseSvgNodeRenderer это делает.
3. Это кажется взаимосвязанным. Но при удалении ‘%’ в svg сгенерированный PDF-файл отличается от файла с% в cx и cy.
4. конечно. поскольку знак% определяет, что эти значения являются относительными и должны вычисляться в зависимости от размера родительского элемента. Без% это абсолютные значения в пикселях.
5. если быть точным, @A.Alexander ошибка возникает в методе setParameter, который переопределяется в CircleSvgNodeRenderer
Ответ №2:
Как правильно заметил @A.Alexander, готовый iText не поддерживает относительные параметры для элемента circle . Однако вы можете зарегистрировать свой собственный рендерер для тега CircleSvgNodeRenderer (вместо 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 amp;<circleamp;> 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("% ellipsen");
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
amp;amp; 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.
Новая фабрика должна создавать экземпляр CustomCircleSvgNodeRenderer каждый раз, когда SVGConverter отображает круг. например
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);
Комментарии:
1. Спасибо Павлу. Это почти сделано. за исключением того, что сравнение тегов должно применяться к
tag.name()
.