我正在使用 Flying Saucer 将 XHTML 转换为 PDF 文档。我已经获得了仅使用基本 HTML 和内联 CSS 的代码,但是,现在我正在尝试将图像作为一种标题添加到 PDF 中。我想知道是否有任何方法可以通过读取图像文件作为 Java Image 对象来添加图像,然后以某种方式将其添加到 PDF(或 XHTML - 就像它获得一个虚拟“url”一样)代表我可以用来渲染 PDF 的 Image 对象)。有人做过这样的事吗?
预先感谢您提供的任何帮助!
我上周必须这样做,所以希望我能够立即回答你。
最简单的方法是在使用 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);
}
}
您会注意到我在这里硬编码了
/base/folder
,这是 HTML 文件所在的文件夹,因为它将是 Flying Saucer 用于解析媒体的根 URL。您可以将其更改为来自任何您想要的位置的正确位置(例如属性)。
在 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>
最后你只需要在渲染时向 Flying-Saucer 指示
ReplacedElementFactory
即可:
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();
我一直在使用 Freemarker 从模板生成 HTML,然后将结果提供给 FlyingSaucer,并取得了巨大成功。这是一个非常整洁的图书馆。
对我有用的是将其作为嵌入图像。所以先将图像转换为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());
感谢 Alex 提供详细的解决方案。我正在使用这个解决方案,发现还需要添加另一行才能使其工作。
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);
....
....
我们需要从
SharedContext
读取DPP并缩放图像以在PDF上显示渲染图像。
另一个建议: 我们可以直接扩展
ITextReplacedElement
而不是实现ReplacedElementFactory
。在这种情况下,我们可以在 ReplacedElementFactory
中设置 SharedContext
,如下所示:
renderer.getSharedContext().setReplacedElementFactory(new MediaReplacedElementFactory(renderer.getOutputDevice());
@alex 的解决方案仍然适用于
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf-openpdf</artifactId>
<version>9.1.22</version>
</dependency>
如果您将图像作为字符串,csviri 的解决方案就是您的答案。
否则,如果您的图像位于文件夹中,请将其根路径设置为
baseUrl
的第二个参数 setDocumentFromString()
,然后您将能够在 HTML 中使用相对路径(实现“虚拟 url”效果):
String baseUrl = "file:/C:/Users/jtoland/workspace/myApp/myAppWebRootFolder/";
renderer.setDocumentFromString(html, getServletContext().getResource("/").toString());
在 servlet 中你可以这样做
renderer.setDocumentFromString(html, getServletContext().getResource("/").toString());
之后,不仅像
<img src="img/image.png" />
这样的图像,像<link rel="stylesheet" href="css/style.css" />
这样的任何资源都可以使用相对路径。