iText+freemarker 生成PDF
- 生成样式图片
- 模板地址
- maven
- PDF生成工具类
- PDF生成辅助类
- base64编码工具类
- service方法调用
- 总结
生成样式图片
模板地址
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行表格会向下延申到第二页,甚至第三页,才会进行下一个表格的展示;目前本方案解决办法是将一页展示的数量变小了,给预留出足够的空白页让其展示;目前还在寻找方法中
因备注字数过长,分页控制就失效了
目前为止如果表格不够复杂,是够用的,其他的问题暂时还没碰到;