使用Flying Saucer将图像渲染为PDF格式的内存

JTo*_*and 13 java xhtml pdf-generation image flying-saucer

我正在使用Flying Saucer将XHTML转换为PDF文档.我已经得到了仅使用基本HTML和内联CSS的代码,但是,现在我正在尝试将图像作为一种标题添加到PDF中.我想知道的是,如果有任何方式通过读取图像文件作为Java Image对象添加图像,然后以某种方式添加到PDF(或XHTML - 就像它获取虚拟"url"表示我可以用来渲染PDF的Image对象.有没有人做过这样的事情?

提前感谢您提供的任何帮助!

Ale*_*lex 31

我上周必须这样做,希望我能马上回答你.

飞碟

最简单的方法是在使用Flying Saucer渲染之前,将您想要的图像添加到HTML模板中.在Flying Saucer中,您必须实现一个,ReplacedElementFactory以便在使用图像数据渲染之前替换任何标记.

/**
 * Replaced element in order to replace elements like 
 * <tt>&lt;div class="media" data-src="image.png" /></tt> with the real
 * media content.
 */
public class MediaReplacedElementFactory implements ReplacedElementFactory {
    private final ReplacedElementFactory superFactory;

    public MediaReplacedElementFactory(ReplacedElementFactory superFactory) {
        this.superFactory = superFactory;
    }

    @Override
    public ReplacedElement createReplacedElement(LayoutContext layoutContext, BlockBox blockBox, UserAgentCallback userAgentCallback, int cssWidth, int cssHeight) {
        Element element = blockBox.getElement();
        if (element == null) {
            return null;
        }
        String nodeName = element.getNodeName();
        String className = element.getAttribute("class");
        // Replace any <div class="media" data-src="image.png" /> with the
        // binary data of `image.png` into the PDF.
        if ("div".equals(nodeName) && "media".equals(className)) {
            if (!element.hasAttribute("data-src")) {
                throw new RuntimeException("An element with class `media` is missing a `data-src` attribute indicating the media file.");
            }
            InputStream input = null;
            try {
                input = new FileInputStream("/base/folder/" + element.getAttribute("data-src"));
                final byte[] bytes = IOUtils.toByteArray(input);
                final Image image = Image.getInstance(bytes);
                final FSImage fsImage = new ITextFSImage(image);
                if (fsImage != null) {
                    if ((cssWidth != -1) || (cssHeight != -1)) {
                        fsImage.scale(cssWidth, cssHeight);
                    }
                    return new ITextImageElement(fsImage);
                }
            } catch (Exception e) {
                throw new RuntimeException("There was a problem trying to read a template embedded graphic.", e);
            } finally {
                IOUtils.closeQuietly(input);
            }
        }
        return this.superFactory.createReplacedElement(layoutContext, blockBox, userAgentCallback, cssWidth, cssHeight);
    }

    @Override
    public void reset() {
        this.superFactory.reset();
    }

    @Override
    public void remove(Element e) {
        this.superFactory.remove(e);
    }

    @Override
    public void setFormSubmissionListener(FormSubmissionListener listener) {
        this.superFactory.setFormSubmissionListener(listener);
    }
}
Run Code Online (Sandbox Code Playgroud)

您会注意到我在这里有硬编码,/base/folder这是HTML文件所在的文件夹,因为它将成为Flying Saucer解析媒体的根网址.您可以将其更改为正确的位置,来自您想要的任何位置(例如属性).

HTML

在您的HTML标记中,您可以在某处指明<div class="media" data-src="somefile.png" />:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>My document</title>
        <style type="text/css">
        #logo { /* something if needed */ }
        </style>
    </head>
    <body>
        <!-- Header -->
        <div id="logo" class="media" data-src="media/logo.png" style="width: 177px; height: 60px" />
        ...
    </body>
</html>
Run Code Online (Sandbox Code Playgroud)

渲染

最后你只需要ReplacedElementFactory在渲染时向Flying-Saucer 表明:

String content = loadHtml();
ITextRenderer renderer = new ITextRenderer();
renderer.getSharedContext().setReplacedElementFactory(new MediaReplacedElementFactory(renderer.getSharedContext().getReplacedElementFactory()));
renderer.setDocumentFromString(content.toString());
renderer.layout();
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
renderer.createPDF(baos);
// baos.toByteArray();
Run Code Online (Sandbox Code Playgroud)

我一直在使用Freemarker从模板生成HTML,然后将结果提供给FlyingSaucer取得了巨大的成功.这是一个非常整洁的图书馆.

  • 很好的答案,对于所有想要从资源文件夹中获取图像的人来说,只需将 FileInputStream 替换为: input = this.getClass().getClassLoader().getResourceAsStream(element.getAttribute("data-src")); (2认同)

csv*_*iri 6

对我有用的是将其作为嵌入式图像。所以首先将图像转换为 base64 然后嵌入它:

    byte[] image = ...
    ITextRenderer renderer = new ITextRenderer();
    renderer.setDocumentFromString("<html>\n" +
                                   "    <body>\n" +
                                   "        <h1>Image</h1>\n" +
                                   " <div><img src=\"data:image/png;base64," + Base64.getEncoder().encodeToString(image) + "\"></img></div>\n" +
                                   "    </body>\n" +
                                   "</html>");
    renderer.layout();
    renderer.createPDF(response.getOutputStream());
Run Code Online (Sandbox Code Playgroud)


小智 5

感谢亚历克斯的详细解决方案。我正在使用此解决方案,并发现要添加另一行以使其工作。

public ReplacedElement createReplacedElement(LayoutContext layoutContext, BlockBox blockBox, UserAgentCallback userAgentCallback, int cssWidth, int cssHeight) {
  Element element = blockBox.getElement();
  ....
  ....
  final Image image = Image.getInstance(bytes);
  final int factor = ((ITextUserAgent)userAgentCallback).getSharedContext().getDotsPerPixel(); //Need to add this line
  image.scaleAbsolute(image.getPlainWidth() * factor, image.getPlainHeight() * factor) //Need to add this line
  final FSImage fsImage = new ITextFSImage(image);
  ....
  ....
Run Code Online (Sandbox Code Playgroud)

我们需要从中读取 DPPSharedContext并缩放图像以在 PDF 上显示渲染图像。

另一个建议:我们可以直接扩展ITextReplacedElement而不是实现ReplacedElementFactory. 在这种情况下,我们可以设置ReplacedElementFactorySharedContext如下:

renderer.getSharedContext().setReplacedElementFactory(new MediaReplacedElementFactory(renderer.getOutputDevice()); 
Run Code Online (Sandbox Code Playgroud)