iText+freemarker 生成PDF
生成样式图片
模板地址
maven
<!--PDF start-->
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf</artifactId>
<version>9.1.5</version>
</dependency>
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf-itext5</artifactId>
<version>9.1.5</version>
</dependency>
<!--PDF end-->
<!--freemarker-->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.26-incubating</version>
</dependency>
<!--条形码-->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.3.3</version>
</dependency>
PDF生成工具类
import com.lowagie.text.DocumentException;
import freemarker.core.ParseException;
import freemarker.template.MalformedTemplateNameException;
import freemarker.template.TemplateException;
import freemarker.template.TemplateNotFoundException;
import org.xhtmlrenderer.pdf.ITextRenderer;
import java.io.IOException;
import java.io.OutputStream;
/**
* @Description PDF生成工具类
* @author GGBind
* @date 2021/12/28/10:03
*/
public class PdfUtils {
/**
* 生成PDF到输出流中(ServletOutputStream用于下载PDF)
*
* @param ftlPath ftl模板文件的路径(不含文件名)
* @param ftlName ftl模板文件的名称(不含路径)
* @param imageDiskPath 如果PDF中要求图片,那么需要传入图片所在位置的磁盘路径
* @param data 输入到FTL中的数据
* @param out HttpServletResponse
* @return
* @throws TemplateNotFoundException
* @throws MalformedTemplateNameException
* @throws ParseException
* @throws IOException
* @throws TemplateException
* @throws DocumentException
*/
public static OutputStream generateToServletOutputStream(String ftlPath, String ftlName, String imageDiskPath, Object data, OutputStream out) throws IOException, TemplateException, DocumentException {
PdfHelper pdfHelper = new PdfHelper();
String html = pdfHelper.getPdfContent(ftlPath, ftlName, data);
ITextRenderer render = null;
render = pdfHelper.getRender();
render.setDocumentFromString(html);
if (null != imageDiskPath && !"".equals(imageDiskPath)) {
//html中如果有图片,图片的路径则使用这里设置的路径的相对路径,这个是作为根路径
render.getSharedContext().setBaseURL("file:/" + imageDiskPath);
}
render.layout();
render.createPDF(out);
render.finishPDF();
return out;
}
}
PDF生成辅助类
package com.saicmotor.maxus.yuejin.partorder.common.util.pdfUtil;
import com.lowagie.text.DocumentException;
import com.lowagie.text.pdf.BaseFont;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.Version;
import lombok.extern.slf4j.Slf4j;
import org.xhtmlrenderer.pdf.ITextRenderer;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Locale;
/**
* PDF生成辅助类
* @author GGBind
* @date 2022/2/09
*/
@Slf4j
@SuppressWarnings("deprecation")
public class PdfHelper {
public ITextRenderer getRender() throws DocumentException, IOException {
ITextRenderer render = new ITextRenderer();
//添加字体,以支持中文
render.getFontResolver().addFont("/static/fonts/NSimSun.ttc", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
return render;
}
/**
* 获取要写入PDF的内容
* @param ftlPath
* @param ftlName
* @param o
* @return
* @throws IOException
* @throws TemplateException
*/
public String getPdfContent(String ftlPath, String ftlName, Object o) throws IOException, TemplateException {
return useTemplate(ftlPath, ftlName, o);
}
/**
* 使用freemarker得到html内容
* @param ftlPath
* @param ftlName
* @param o
* @return
* @throws IOException
* @throws TemplateException
*/
public String useTemplate(String ftlPath, String ftlName, Object o) throws IOException, TemplateException {
String html = null;
Template tpl = getFreemarkerConfig(ftlPath).getTemplate(ftlName);
tpl.setEncoding("UTF-8");
StringWriter writer = new StringWriter();
tpl.process(o, writer);
writer.flush();
html = writer.toString();
return html;
}
/**
* 获取Freemarker配置
*
* @param templatePath
* @return
* @throws IOException
*/
private Configuration getFreemarkerConfig(String templatePath) throws IOException {
Version version = new Version("2.3.22");
Configuration config = new Configuration(version);
config.setClassLoaderForTemplateLoading(Thread.currentThread().getContextClassLoader(), "/templates");
config.setEncoding(Locale.CHINA, "utf-8");
return config;
}
/**
* 获取类路径
*
* @return
*/
public String getPath() {
return PdfHelper.class.getResource("/").getPath().substring(1);
}
/**
* 获取前端response的输出流
* 并设置response 编码格式
* @return 输出流out
*/
public ServletOutputStream setResponse(HttpServletResponse response, String Name) throws Exception{
response.setContentType("application/pdf");
response.setCharacterEncoding("utf-8");
response.setHeader("Content-disposition", "attachment;filename="+ new String(Name.getBytes("utf-8"), "ISO8859-1") + ".pdf");
ServletOutputStream out = response.getOutputStream();
return out;
}
}
base64编码工具类
import com.google.zxing.BarcodeFormat;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import lombok.extern.slf4j.Slf4j;
import sun.misc.BASE64Encoder;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
/**
* @Description
* @Author GGBond
* @Date 2022/2/14
*/
@Slf4j
public class BarcodeUtil {
/**
* 给前端产生一个条形码
*
* @param number 编码
* @param width 宽度
* @param height 高度
*/
public static String getCode(String number, Integer width, Integer height) {
// 生成条形码
BufferedImage image = getBarCode(number, width, height);
// 使用流的方式
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
ImageIO.write(image, "png", out);
} catch (Exception e) {
log.info("generate code error! error message:{}", "出现问题!");
e.printStackTrace();
}
// 将流转成数组
byte[] bytes = out.toByteArray();
BASE64Encoder encoder = new BASE64Encoder();
// 把生成的编码返回去
return encoder.encodeBuffer(bytes).trim();
}
/**
* 产生条形码的方法
*
* @param number 编码
* @param width 宽度
* @param height 高度
*/
public static BufferedImage getBarCode(String number, Integer width, Integer height) {
try {
BitMatrix bitMatrix = new MultiFormatWriter().encode(number, BarcodeFormat.CODE_128, width, height);
return MatrixToImageWriter.toBufferedImage(bitMatrix);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 调用测试
*/
public static void main(String[] args) {
String code = getCode("1120220202", 50, 50);
System.out.println(code);
}
}
service方法调用
PdfHelper pdfHelper = new PdfHelper();
List<StockCheckDetailPdf> lists = baseMapper.getStockCheckDetailPdf(dto);
ServletOutputStream out = pdfHelper.setResponse(response, "盘点单据");
String path = pdfHelper.getPath();
Map<String,Object> map = new HashMap<>(16);
map.put("list",lists);
String code = BarcodeUtil.getCode(dto.getInventoryNumber(), 80, 20);
map.put("picture", code);
map.put("builder",builder.toString());
map.put("inWarehouse",documentCounting.getInWarehouse());
map.put("printDate",String.valueOf(DateUtil.getCurrentDate()));
//这里我们需要将生成的base64图片编码传到工具类里,进行单独设置,静态图片也是如此
PdfUtils.generateToServletOutputStream(path, "/documentCountingPDF.ftl", code, map, out);
out.flush();
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>PDF</title>
<style>
/*设置编码格式*/
body {
font-family: NSimSun
}
/*设置纸张大小*/
@page {
size: a4
}
@media print {
div.header-right {
display: block;
position: running(header-right);
}
/*设置每页结束的是表格,还是图片,还是其他的*/
/*img{page-break-inside:avoid;}*/
table {
page-break-inside: avoid;
}
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
section {
display: block;
margin: 20px 10px;
}
.preface p {
line-height: 30px;
}
section > table {
table-layout: fixed;
width: 100%;
margin: 20px 0px;
text-align:center;
word-wrap:break-word;
border-collapse: collapse;
border-style: solid;
border-width: 1px 0px 0px 1px;
}
section table td {
padding: 5px 0px;
border-style: solid;
border-width: 0px 1px 1px 0px;
margin: 0;
padding: 0;
}
.center {
width: 5.8%;
}
.part {
width: 159.1px;
word-break: keep-all;
white-space: nowrap;
}
</style>
</head>
<body>
<section>
<#--该list 是由service层的map对应,数据使用占位符去匹配${}-->
<#list list as item>
<#if item_index % 14 == 0 || item_index == 0>
<table cellSpacing="0" cellpadding="0" border="1" width="650px" align="center" >
<colgroup>
<col class="center"></col>
<col class="part"></col>
<col class="part"></col>
<col class="center"></col>
<col class="center"></col>
<col class="center"></col>
<col class="center"></col>
<col class="center"></col>
<col class="center"></col>
<col class="center"></col>
<col class="center"></col>
<col class="center"></col>
<col class="center"></col>
<col class="center"></col>
<col class="center"></col>
<col style="width: 70px;word-break: keep-all;white-space: nowrap"></col>
</colgroup>
</#if>
<#if item_index == 0>
<tr style="width: 100%;">
<td class="center" colspan="2"><img src="data:image/png;base64,${picture}"/></td>
<td class="center" colspan="7"><h4>实物盘点记录表</h4></td>
<td class="center" colspan="7"><h1>${builder}</h1></td>
</tr>
<tr style="width: 100%;">
<td class="center" colspan="4">填报单位:</td>
<td class="center" colspan="2">存货类别:</td>
<td class="center" colspan="9">仓库名称(代码): ${inWarehouse}</td>
<td style="word-break: keep-all;white-space: nowrap;">${printDate}</td>
</tr>
</#if>
<#if item_index % 14 == 0 || item_index == 0>
<tr style="width: 100%;">
<td class="center" rowspan="2">序号</td>
<td class="part" rowspan="2">件号</td>
<td class="part" rowspan="2">名称</td>
<td class="center" rowspan="2">规格</td>
<td class="center" rowspan="2">项目类</td>
<td class="center" rowspan="2">计量单位</td>
<td class="center" rowspan="2">外借量</td>
<td class="center" rowspan="2">库位</td>
<td class="center" rowspan="2">合计</td>
<td class="center" rowspan="2">货位</td>
<td class="center" colspan="3">实盘数量</td>
<td class="center" rowspan="2">盘点人</td>
<td class="center" rowspan="2">监盘人</td>
<td class="center" rowspan="2">备注</td>
</tr>
<tr style="width: 100%;">
<td class="center">合计</td>
<td class="center">企业库存</td>
<td class="center">供应商库存</td>
</tr>
</#if>
<tr style="width: 100%;">
<td class="center">${item_index +1}</td>
<td class="part">${item.accessoryItemNo}</td>
<td class="part">
<#if item.partsChineseName?length < 8 >
${item.partsChineseName}
<#else>
${item.partsChineseName?substring(0,7)}
</#if>
</td>
<td class="center"> </td>
<td class="center"> </td>
<td class="center">${item.unit}</td>
<td class="center">
${item.lendingVolume?default(0)}
</td>
<td class="center">
<#if item.atWarehouse?exists>
${item.atWarehouse!}
</#if>
</td>
<td class="center"> </td>
<td class="center"> </td>
<td class="center">${item.theoreticalInventory}</td>
<td class="center">${(item.theoreticalInventory?default(0)) - (item.lendingVolume?default(0))}</td>
<td class="center"> </td>
<td class="center"> </td>
<td class="center"> </td>
<td class="center">
<#if item.remark?exists>
${item.remark!}
</#if>
</td>
</tr>
<#--用来判断每页展示多少条数据,最后需要加上 “table” 标签-->
<#if (item_index+1) % 14 == 0 || (item_index+1) == list?size>
</table>
</#if>
</#list>
</section>
</body>
</html>
总结
需要注意的是:当前此方法不能控制表格中一行数据变长,自动向下延申后,每页的格式问题;
例如: 当前页14条数据占满本页后,如果表格中字段较长,14行表格会向下延申到第二页,甚至第三页,才会进行下一个表格的展示;目前本方案解决办法是将一页展示的数量变小了,给预留出足够的空白页让其展示;目前还在寻找方法中
因备注字数过长,分页控制就失效了
目前为止如果表格不够复杂,是够用的,其他的问题暂时还没碰到;