java 使用 Flying Saucer 在内存中将图像渲染为 PDF
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/11477065/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Using Flying Saucer to Render Images to PDF In Memory
提问by JToland
I'm using Flying Saucer to convert XHTML to a PDF document. I've gotten the code to work with just basic HTML and in-line CSS, however, now I'm attempting to add an image as a sort of header to the PDF. What I'm wondering is if there is any way whatsoever to add the image by reading in an image file as a Java Image object, then adding that somehow to the PDF (or to the XHTML -- like it gets a virtual "url" representing the Image object that I can use to render the PDF). Has anyone ever done anything like this?
我正在使用 Flying Saucer 将 XHTML 转换为 PDF 文档。我已经让代码只使用基本的 HTML 和内联 CSS,但是,现在我试图将图像作为一种标题添加到 PDF。我想知道的是是否有任何方法可以通过读取图像文件作为 Java Image 对象来添加图像,然后以某种方式将其添加到 PDF(或 XHTML - 就像它获得一个虚拟的“url”一样)表示可用于渲染 PDF 的 Image 对象)。有没有人做过这样的事情?
Thanks in advance for any help you can provide!
在此先感谢您提供的任何帮助!
回答by Alex
I had to do that last week so hopefully I will be able to answer you right away.
我上周不得不这样做,所以希望我能马上回答你。
Flying Saucer
飞碟
The easiest way is to add the image you want as markup in your HTML template before rendering with Flying Saucer. Within Flying Saucer you will have to implement a ReplacedElementFactory
so that you can replace any markup before rendering with the image data.
最简单的方法是在使用 Flying Saucer 渲染之前在 HTML 模板中添加您想要的图像作为标记。在 Flying Saucer 中,您必须实现 ,ReplacedElementFactory
以便您可以在使用图像数据渲染之前替换任何标记。
/**
* Replaced element in order to replace elements like
* <tt><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);
}
}
You will notice that I have hardcoded here /base/folder
which is the folder where the HTML file is located as it will be the root url for Flying Saucer for resolving medias. You may change it to the correct location, coming from anywhere you want (Properties for example).
您会注意到我在这里硬编码/base/folder
了 HTML 文件所在的文件夹,因为它将是 Flying Saucer 用于解析媒体的根 url。您可以将其更改为正确的位置,来自您想要的任何位置(例如属性)。
HTML
HTML
Within your HTML markup you indicate somewhere a <div class="media" data-src="somefile.png" />
like so:
在您的 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>
Rendering
渲染
And finally you just need to indicate your ReplacedElementFactory
to Flying-Saucer when rendering:
最后,您只需要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();
I have been using Freemarker to generate the HTML from a template and then feeding the result to FlyingSaucer with great success. This is a pretty neat library.
我一直在使用 Freemarker 从模板生成 HTML,然后将结果提供给 FlyingSaucer 并取得了巨大成功。这是一个非常整洁的图书馆。
回答by Suresh Garrepalli
Thanks Alex for detailed solution. I'm using this solution and found there is another line to be added to make it work.
感谢亚历克斯的详细解决方案。我正在使用此解决方案,并发现要添加另一行以使其工作。
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);
....
....
We need to read the DPP from SharedContext
and scale the image to display render the image on PDF.
我们需要从中读取 DPPSharedContext
并缩放图像以在 PDF 上显示渲染图像。
Another suggestion:
We can directly extend ITextReplacedElement
instead of implementing ReplacedElementFactory
. In that case we can set the ReplacedElementFactory
in the SharedContext
as follows:
另一个建议:我们可以直接扩展ITextReplacedElement
而不是实现ReplacedElementFactory
. 在这种情况下,我们可以设置ReplacedElementFactory
在SharedContext
如下:
renderer.getSharedContext().setReplacedElementFactory(new MediaReplacedElementFactory(renderer.getOutputDevice());
回答by csviri
what worked for me is putting it as a embedded image. So converting image to base64 first and then embed it:
对我有用的是将其作为嵌入式图像。所以首先将图像转换为 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());