Конвертируйте SVG в PDF с помощью iText, SVG не полностью отображается в PDF

#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 не поддерживает проценты, только пиксели.

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 получают
значения 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() .