java使用freemarker模板生成html,再生成pdf
1.freemarker模板生成html
- 添加Maven依赖
在pom.xml文件中添加以下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
- 创建Freemarker模板
新建一个HTML文件,例如table.ftl,然后在其中编写HTML模板,包括表格的头部、内容和尾部等部分。具体可以参考下面这个例子:
<table border="1">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Age</th>
</tr>
</thead>
<tbody>
<#list users as user>
<tr>
<td>${user.id}</td>
<td>${user.name}</td>
<td>${user.age}</td>
</tr>
</#list>
</tbody>
</table>
上面的模板中使用了Freemarker的指令语法来实现动态生成表格内容,包括使用<#list>
标签来遍历用户列表,并使用${}
语法来输出用户信息。
- 创建Controller方法
在Controller中编写一个方法来获取用户列表,然后渲染上面的模板并返回HTML内容。示例代码如下:
@Controller
public class UserController {
@GetMapping("/users")
public String userList(Model model) {
List<User> users = new ArrayList<>();
users.add(new User(1, "Tom", 18));
users.add(new User(2, "Jerry", 20));
users.add(new User(3, "John", 22));
model.addAttribute("users", users);
return "table";
}
}
上面的方法使用@GetMapping
注解来处理请求,然后创建一个用户列表,并将其添加到模型中。最后返回table
字符串,代表要使用的HTML模板文件。
- 运行项目
运行Spring Boot应用程序,然后使用浏览器访问http://localhost:8080/users
,即可看到动态生成的HTML表格。
注意:上面的例子仅供参考,实际应用中需要根据自己的需求进行修改扩展。
2.利用iText将生成的HTML转换为PDF文件
理解了freemarker生成html的步骤以后,就可以利用iText把html生成pdf文件了。
- 编写转换代码
添加Maven依赖
在pom.xml文件中添加以下依赖:
<dependency>
<groupId>com.itextpdf.tool</groupId>
<artifactId>xmlworker</artifactId>
<version>5.5.1</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
<!-- 支持css样式渲染 -->
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf-itext5</artifactId>
<version>9.0.9</version>
</dependency>
- 编写转换代码
在SpringBoot应用程序中创建一个Service或者Controller类,然后编写HTML转PDF的代码。
freeMarker转换为html的方法:
public class HtmlGenerator {
public static String generate(String template, Map<String, Object> variables) throws IOException, TemplateException, IOException {
Configuration config = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
// 指定FreeMarker模板文件的位置
config.setClassForTemplateLoading(HtmlGenerator.class, "/filePath");
//读取模板文件地址
config.setDefaultEncoding("UTF-8");
//获取模板文件
Template tp = config.getTemplate(template);
StringWriter stringWriter = new StringWriter();
BufferedWriter writer = new BufferedWriter(stringWriter);
tp.setEncoding("UTF-8");
//把map数据写入
tp.process(variables, writer);
String htmlStr = stringWriter.toString();
writer.flush();
writer.close();
return htmlStr;
}
}
/filePath为项目中的ftl文件相对路径。
html生成pdf的方法:
public class PdfDocumentGenerator {
private static final Logger logger = LoggerFactory.getLogger(PdfDocumentGenerator.class);
/**
* Output a pdf to the specified outputstream
*
* @param htmlStr
* the htmlstr
* @param out
* the specified outputstream
* @throws Exception
*/
public static void generate(String htmlStr, OutputStream out)
throws Exception {
DocumentBuilderFactory df = DocumentBuilderFactory.newInstance();
df.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); // Compliant
df.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); // compliant
DocumentBuilder builder = df.newDocumentBuilder();
org.w3c.dom.Document doc = builder.parse(new ByteArrayInputStream(htmlStr
.getBytes()));
ITextRenderer renderer = new ITextRenderer();
renderer.setDocument(doc, null);
renderer.layout();
renderer.createPDF(out);
out.close();
}
public static void generatePlus(String htmlStr, OutputStream out) throws IOException, DocumentException {
final String charsetName = "UTF-8";
Document document = new Document(PageSize.A4, 30, 30, 30, 30);
document.setMargins(30, 30, 30, 30);
PdfWriter writer = PdfWriter.getInstance(document, out);
document.open();
// html内容解析
HtmlPipelineContext htmlContext = new HtmlPipelineContext(
new CssAppliersImpl(new XMLWorkerFontProvider() {
@Override
public Font getFont(String fontname, String encoding,
float size, final int style) {
if (fontname == null) {
fontname = getChineseFont();
}
return super.getFont(fontname, encoding, size,
style);
}
})) {
@Override
public HtmlPipelineContext clone()
throws CloneNotSupportedException {
HtmlPipelineContext context = super.clone();
try {
ImageProvider imageProvider = this.getImageProvider();
context.setImageProvider(imageProvider);
} catch (NoImageProviderException e) {
}
return context;
}
};
// 图片解析
htmlContext.setImageProvider(new AbstractImageProvider() {
String rootPath = PdfDocumentGenerator.class.getResource("/").getPath();
@Override
public String getImageRootPath() {
return rootPath;
}
@Override
public Image retrieve(String src) {
if (StringUtils.isEmpty(src)) {
return null;
}
try {
Image image = Image.getInstance(new File(rootPath, src).toURI().toString());
// 图片显示位置
image.setAbsolutePosition(400, 400);
if (image != null) {
store(src, image);
return image;
}
} catch (Throwable e) {
e.printStackTrace();
}
return super.retrieve(src);
}
});
htmlContext.setAcceptUnknown(true).autoBookmark(true).setTagFactory(Tags.getHtmlTagProcessorFactory());
// css解析
CSSResolver cssResolver = XMLWorkerHelper.getInstance().getDefaultCssResolver(true);
cssResolver.setFileRetrieve(new FileRetrieve() {
@Override
public void processFromStream(InputStream in,
ReadingProcessor processor) throws IOException {
try (
InputStreamReader reader = new InputStreamReader(in, charsetName)) {
int i = -1;
while (-1 != (i = reader.read())) {
processor.process(i);
}
} catch (Throwable e) {
}
}
// 解析href
@Override
public void processFromHref(String href, ReadingProcessor processor) throws IOException {
InputStream is = PdfDocumentGenerator.class.getResourceAsStream("/" + href);
try (InputStreamReader reader = new InputStreamReader(is,charsetName)) {
int i = -1;
while (-1 != (i = reader.read())) {
processor.process(i);
}
} catch (Throwable e) {
e.printStackTrace();
}
}
});
HtmlPipeline htmlPipeline = new HtmlPipeline(htmlContext, new PdfWriterPipeline(document, writer));
Pipeline<?> pipeline = new CssResolverPipeline(cssResolver, htmlPipeline);
XMLWorker worker = null;
worker = new XMLWorker(pipeline, true);
XMLParser parser = new XMLParser(true, worker, Charset.forName(charsetName));
try (InputStream inputStream = new ByteArrayInputStream(htmlStr.getBytes())) {
parser.parse(inputStream, Charset.forName(charsetName));
}
document.close();
}
/**
* 获取中文字体位置
* @return
*/
public static String getChineseFont() {
String chineseFont = null;
chineseFont = Object.class.getResource("/").getPath() + "font/simsun.ttc";
if(!new File(chineseFont).exists()){
throw new RuntimeException("字体文件不存在!"+chineseFont);
}
return chineseFont;
}
}
运行测试方法:
public class Pdfdest {
public static void main(String[] args) throws Exception {
String outputFile = "d:/test1.pdf";
Map<String, Object> map = new HashMap<>();
map.put("XXX", "测试");
//生成工具,下面有代码
String htmlStr = HtmlGenerator.generate("test.ftl", map);
//生成工具,下面有代码
OutputStream out = new FileOutputStream(outputFile);
PdfDocumentGenerator.generatePlus(htmlStr,out);
}
}
准备一个test.ftl放到resource/filePath下,当然字体最好也放到resource/font下,运行时需要使用。
test.ftl的代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"></meta>
<style>
@page {
@top-center { content: element(header) }
}
@page {
@bottom-center { content: element(footer) }
}
.apply {
margin: 0 auto;
padding: 0 30px;
}
.title {
margin-top: 40px ;
text-align: center;
font-weight: bold;
//字体需要和后台对应上
font-family: SimSun;
font-weight: bold;
font-size: 20px;
color: #333333;
letter-spacing: 0;
}
.table {
border-collapse: collapse;
width: 100%;
margin-top: 30px;
font-family: SimSun;
font-size: 14px;
color: #111111;
letter-spacing: 0.54px;
}
.label {
background-color: #E6E6E6;
width: 20%;
}
.normaltd {
padding: 10px 0;
}
.maxtd {
height: 250px;
}
.value {
width: 30%;
padding-left: 10px;
}
.apply {
margin: 0 auto;
padding: 0 30px;
}
.title {
margin-top: 40px ;
text-align: center;
font-weight: bold;
//字体需要和后台对应上
font-family: SimSun;
font-weight: bold;
font-size: 20px;
color: #333333;
letter-spacing: 0;
}
.table {
width: 100%;
margin-top: 30px;
font-family: SimSun;
font-size: 14px;
color: #111111;
letter-spacing: 0.54px;
}
.label {
background-color: #E6E6E6;
width: 20%;
}
.normaltd {
padding: 10px 0;
}
.maxtd {
height: 250px;
}
.value {
width: 30%;
padding-left: 10px;
}
tr {
page-break-inside: avoid;
page-break-after: auto;
}
</style>
</head>
<body style="font-family: SimSun">
<div class="apply">
<p class="title">申请单</p>
<table border="1" cellspacing="0" class="table">
<tr>
<td class="label normaltd" align="center">XXX</td>
<td class="normaltd value">${XXX}</td>
<td class="label normaltd" align="center">XXX</td>
<td class="normaltd value">${XXX}</td>
</tr>
<tr>
<td class="label normaltd" align="center">XXX</td>
<td class="normaltd value">${XXX}</td>
<td class="label normaltd" align="center">XXX</td>
<td class="normaltd value">${XXX}</td>
</tr>
<tr>
<td class="label" align="center">XXX</td>
<td class="normaltd value">${XXX}</td>
<td class="label normaltd" align="center">XXX</td>
<td class="normaltd value">${XXX}</td>
</tr>
<tr>
<td class="label normaltd" align="center">XXX</td>
<td class="normaltd value">XXX</td>
<td class="label normaltd" align="center">XXX</td>
<td class="normaltd value">${XXX}</td>
</tr>
<tr>
<td class="label normaltd" align="center">XXX</td>
<td class="normaltd value">${XXX}</td>
<td class="label normaltd" align="center">XXX</td>
<td class="normaltd value">${XXX}</td>
</tr>
<tr >
<td valign="middle" colspan="1" class="label maxtd" align="center">XXX</td>
<td valign="middle" colspan="3" class="maxtd value">${XXX}</td>
</tr>
<tr>
<td colspan="1" class="label normaltd" align="center">XXX</td>
<td colspan="3" class="normaltd value">${XXX}</td>
</tr>
<tr>
<td colspan="1" class="label normaltd" align="center">XXX</td>
<td colspan="3" class="normaltd value">${XXX}</td>
</tr>
<tr>
<td colspan="1" class="label normaltd" align="center">XXX</td>
<td colspan="3" class="normaltd value">${XXX}</td>
</tr>
<tr>
<td class="label normaltd" align="center">XXX</td>
<td class="normaltd value">${XXX}</td>
<td class="label normaltd" align="center">XXX</td>
<td class="normaltd value">${XXX}</td>
</tr>
<tr>
<td class="label normaltd" align="center">XXX</td>
<td class="normaltd value">${XXX}</td>
<td class="label normaltd" align="center">XXX</td>
<td class="normaltd value">${XXX}</td>
</tr>
<tr>
<td class="label" align="center">XXX</td>
<td class="normaltd value">${XXX}</td>
<td class="label normaltd" align="center">XXX</td>
<td class="normaltd value">${XXX}</td>
</tr>
<tr>
<td class="label normaltd" align="center">XXX</td>
<td class="normaltd value">XXX</td>
<td class="label normaltd" align="center">XXX</td>
<td class="normaltd value">${XXX}</td>
</tr>
<tr>
<td class="label normaltd" align="center">XXX</td>
<td class="normaltd value">${XXX}</td>
<td class="label normaltd" align="center">XXX</td>
<td class="normaltd value">${XXX}</td>
</tr>
<tr >
<td valign="middle" colspan="1" class="label maxtd" align="center">XXX</td>
<td valign="middle" colspan="3" class="maxtd value">${XXX}</td>
</tr>
<tr>
<td colspan="1" class="label normaltd" align="center">XXX</td>
<td colspan="3" class="normaltd value">${XXX}</td>
</tr>
<tr>
<td colspan="1" class="label normaltd" align="center">XXX</td>
<td colspan="3" class="normaltd value">${XXX}</td>
</tr>
<tr>
<td colspan="1" class="label normaltd" align="center">XXX</td>
<td colspan="3" class="normaltd value">${XXX}</td>
</tr>
<tr>
<td class="label normaltd" align="center">XXX</td>
<td class="normaltd value">${XXX}</td>
<td class="label normaltd" align="center">XXX</td>
<td class="normaltd value">${XXX}</td>
</tr>
<tr>
<td class="label normaltd" align="center">XXX</td>
<td class="normaltd value">${XXX}</td>
<td class="label normaltd" align="center">XXX</td>
<td class="normaltd value">${XXX}</td>
</tr>
<tr>
<td class="label" align="center">XXX</td>
<td class="normaltd value">${XXX}</td>
<td class="label normaltd" align="center">XXX</td>
<td class="normaltd value">${XXX}</td>
</tr>
<tr>
<td class="label normaltd" align="center">XXX</td>
<td class="normaltd value">XXX</td>
<td class="label normaltd" align="center">XXX</td>
<td class="normaltd value">${XXX}</td>
</tr>
<tr>
<td class="label normaltd" align="center">XXX</td>
<td class="normaltd value">${XXX}</td>
<td class="label normaltd" align="center">XXX</td>
<td class="normaltd value">${XXX}</td>
</tr>
</table>
</div>
</body>
</html>
执行就可以看到生成的pdf文件了,文件路径在d:/test1.pdf。