Java实现生成pdf并解决表格分割的问题
作者:苦瓜不苦077
这篇文章主要为大家详细介绍了如何利用Java实现生成pdf,并解决表格分割的问题,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
Maven依赖
<dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.31</version> </dependency> <dependency> <groupId>org.xhtmlrenderer</groupId> <artifactId>flying-saucer-pdf</artifactId> <version>9.1.16</version> </dependency>
自定义ftl模板
该模板是由html页面直接后缀而成,模版名称定为template-01.ftl
注意事项
中文乱码问题
需要在模板中添加font-family: SimSun, serif;
标签,可解决中文乱码问题
body { /*解决中文乱码*/ font-family: SimSun, serif; /*自动换行*/ word-break: break-all; }
页眉和页脚
其实页眉和页脚可以通过定义的ftl模板来实现
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"/> <title>Title</title> <style> /*页眉的上下左右边距*/ @page { margin: 30mm 20mm 30mm 20mm; } @page { /*页眉*/ @top-center { content: element(header) } /*页脚*/ @bottom-center { content: element(footer) } } /*页眉*/ #header { position: running(header); margin-top: 10mm; } /*页脚*/ #footer { position: running(footer); } /*分页*/ #page-number:before { content: counter(page); } /*分页*/ #page-count:before { content: counter(pages); } </style> </head> <body> <!--页眉--> <div id="header"> 深圳市xxx有限公司 <hr/> </div> <!--页脚--> <div id="footer"> 页码<span id="page-number"></span>/<span id="page-count"></span> </div> </body> </html>
完整ftl模板页面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"/> <title>Title</title> <style> /*页眉的上下左右边距*/ @page { margin: 30mm 20mm 30mm 20mm; } @page { /*页眉*/ @top-center { content: element(header) } /*页脚*/ @bottom-center { content: element(footer) } } /*页眉*/ #header { position: running(header); margin-top: 10mm; } /*页脚*/ #footer { position: running(footer); } /*分页*/ #page-number:before { content: counter(page); } /*分页*/ #page-count:before { content: counter(pages); } * { padding: 0; margin: 0; } body { /*解决中文乱码*/ font-family: SimSun, serif; /*自动换行*/ word-break: break-all; } .main { width: 100%; height: auto; margin: 0 auto; text-align: center; } table { width: 100%; border-collapse: collapse; } td, th { line-height: 20px; padding: 7px 5px; border: 1px solid #999999; } </style> </head> <body> <!--页眉--> <div id="header"> 深圳市xxx有限公司 <hr/> </div> <!--页脚--> <div id="footer"> 页码<span id="page-number"></span>/<span id="page-count"></span> </div> <div class="main"> <h1>深圳市xxx有限公司</h1> <p style="margin: 30px 0 50px 0;text-align: left;"> 人在世俗的世界中行走着,在慢慢流逝的时间里静静等待着成年那一刻的全速奔跑。可漫长的等待过后却发现,形形色色的欲望与世俗观念像橡皮泥一样粘在身上,越积越重,最后竟无限膨胀,束缚了我们的双腿,减缓了我们的步伐。我们不能轻松上路,也不能全速奔跑。它们甚至遮蔽住我们的双眼,遮掩住我们纯真的心,让我们的脚步开始凌乱,旋转在灯红酒绿的花花世界里…… </p> <table> <thead> <tr> <th>姓名</th> <th>年龄</th> <th>性别</th> </tr> </thead> <tbody> <#if !data?? || (data?size==0)> <tr> <td colspan="3">无</td> </tr> <#else> <#list data as item> <tr> <td>${item.name}</td> <td>${item.age}</td> <td>${item.sex}</td> </tr> </#list> </#if> </tbody> </table> </div> </body> </html>
PDF工具类
字体包SimSun.ttc、ArialUni.ttf自行下载
package org.example; import com.lowagie.text.pdf.BaseFont; import freemarker.template.Configuration; import freemarker.template.Template; import org.xhtmlrenderer.pdf.ITextFontResolver; import org.xhtmlrenderer.pdf.ITextRenderer; import java.io.IOException; import java.io.StringWriter; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Locale; /** * @author 苦瓜不苦 * @date 2023/11/28 18:33 **/ public class PDFUtil { /** * 模板生成器 * * @param createFile 生成文件的路径 * @param ftlName 模板名称 * @param object 数据 */ public static void processTemplate(String createFile, String ftlName, Object object) { Configuration configuration = null; StringWriter writer = null; ByteArrayOutputStream outputStream = null; try { // 初始化模版 configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS); writer = new StringWriter(); outputStream = new ByteArrayOutputStream(); // 加载模板目录 configuration.setClassForTemplateLoading(MainApi.class, "/module"); configuration.setClassicCompatible(true); ITextRenderer renderer = new ITextRenderer(); // 设置字体 ITextFontResolver fontResolver = renderer.getFontResolver(); fontResolver.addFont("fonts/SimSun.ttc", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); fontResolver.addFont("fonts/ArialUni.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); configuration.setEncoding(Locale.CHINA, "UTF-8"); // 读取模板文件 Template template = configuration.getTemplate(ftlName, "UTF-8"); // 写入数据到模板中 template.process(object, writer); writer.flush(); // 获取填充好数据的html页面 String html = writer.toString(); renderer.setDocumentFromString(html); renderer.layout(); // 通过html页面字符串转换成pdf文件 renderer.createPDF(outputStream); renderer.finishPDF(); return outputStream.toByteArray(); } catch (Exception e) { throw new RuntimeException(e); } finally { try { if (Objects.nonNull(outputStream)) { outputStream.close(); } if (Objects.nonNull(writer)) { writer.close(); } if (Objects.nonNull(configuration)) { configuration.clone(); } } catch (IOException e) { throw new RuntimeException(e); } } } }
调用测试
public class Main { public static void main(String[] args) { String fromFile = "./" + System.currentTimeMillis() + ".pdf"; String toFile = "template-01.ftl"; List<JSONObject> data = new ArrayList<>(); for (int i = 0; i < 60; i++) { JSONObject object = new JSONObject(); object.set("name", "张三"); object.set("sex", "男"); object.set("age", "18"); data.add(object); } JSONObject object = new JSONObject(); object.set("data", data); byte[] bytes = PDFUtil.processTemplate(toFile, object); File file = FileUtil.writeBytes(bytes, fromFile); System.err.println(file); } }
扩展情况
以上代码即可生成好一份PDF文档了,但是会存在一些问题,
表格的形式会被自动切割,出现以下情况
按照不同的需求,可以使用不同的方式来处理。
一是,当被分页时,每页都需要一个标题的存在。
二是,分页的头部和尾部需要闭合起来
还有扩展于图片水印或者文字水印的需求
图片水印方法
/** * 添加图片水印 * * @param bytes pdf字节 * @return */ public static byte[] appendImageWatermark(byte[] bytes) { try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { PdfReader reader = new PdfReader(bytes); PdfStamper stamper = new PdfStamper(reader, byteArrayOutputStream); // 加载水印图片 URL url = PDFUtil.class.getClassLoader().getResource("fonts/bg.png"); Image image = Image.getInstance(url); // 设置等比缩放 图片大小 image.scalePercent(20); // 自定义大小 // image.scaleAbsolute(200,100); // 设置旋转弧度 image.setRotation(0); // 设置旋转角度 image.setRotationDegrees(0); // 创建PdfGState对象并设置透明度 PdfGState gState = new PdfGState(); // 填充透明度 gState.setFillOpacity(0.3f); // 描边透明度 gState.setStrokeOpacity(0.3f); // PDF总页数 int total = reader.getNumberOfPages() + 1; for (int i = 1; i < total; i++) { Rectangle pageRect = reader.getPageSizeWithRotation(i); PdfContentByte content = stamper.getOverContent(i); content.saveState(); content.setGState(gState); // 设置图片水印 // 获取pdf每页的长宽 float width = pageRect.getWidth(); float top = pageRect.getTop(); // 获取缩放之后水印图片的长宽 float scaledWidth = image.getScaledWidth(); float scaledHeight = image.getScaledHeight(); // 通过计算将水印添加到中间 float x = (width - scaledWidth) / 2; float y = (top - scaledHeight) / 2; content.addImage(image, scaledWidth, 60, 0, scaledHeight, x, y); content.restoreState(); } stamper.close(); reader.close(); return byteArrayOutputStream.toByteArray(); } catch (Exception e) { throw new RuntimeException(e); } }
文字水印方法
/** * 添加文字水印 * * @param bytes pdf字节 * @param text 水印文字 * @param size 文字大小 * @return */ public static byte[] appendTextWatermark(byte[] bytes, String text, Integer size) { try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) { PdfReader reader = new PdfReader(bytes); PdfStamper stamper = new PdfStamper(reader, byteArrayOutputStream); // 创建PdfGState对象并设置透明度 PdfGState gState = new PdfGState(); // 填充透明度 gState.setFillOpacity(0.3f); // 描边透明度 gState.setStrokeOpacity(0.3f); // 加载字体 BaseFont baseFont = BaseFont.createFont("fonts/ArialUni.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); // PDF总页数 int total = reader.getNumberOfPages() + 1; for (int i = 1; i < total; i++) { Rectangle pageRect = reader.getPageSizeWithRotation(i); PdfContentByte content = stamper.getOverContent(i); content.saveState(); // 获取pdf每页的长宽 float width = pageRect.getWidth(); float top = pageRect.getTop(); // 通过计算将水印添加到中间 float x = (width - (size * text.length())) / 2; float y = (top - size) / 2; // 设置字体水印 content.beginText(); content.setGState(gState); // 字体 content.setFontAndSize(baseFont, size); // 颜色 content.setColorFill(Color.BLACK); // 水印位置 content.showTextAligned(Element.ALIGN_LEFT, text, x, y, 30); content.endText(); content.restoreState(); } stamper.close(); reader.close(); return byteArrayOutputStream.toByteArray(); } catch (Exception e) { throw new RuntimeException(e); } }
表格分页被切割问题-方式一
在ftl模板中的style标签中添加css样式
tr { page-break-inside: avoid; page-break-after: auto; }
同一个表格在分页时,会被自动添加上下边框
表格分页被切割问题-方式二
在ftl模板中的style标签中添加css样式,需要注意的是表格的标题需要使用thead标签包裹,表格其他行用tbody标签包裹
<style> table { page-break-inside: auto; -fs-table-paginate: paginate; border-spacing: 0; } tr { page-break-inside: avoid; page-break-after: auto; } </style> <body> <table> <thead> <tr> <th>姓名</th> <th>年龄</th> <th>性别</th> </tr> </thead> <tbody> <#if !data?? || (data?size==0)> <tr> <td colspan="3">无</td> </tr> <#else> <#list data as item> <tr> <td>${item.name}</td> <td>${item.age}</td> <td>${item.sex}</td> </tr> </#list> </#if> </tbody> </table> </body>
被分页时,表格的标题也会携带下来
以上就是Java实现生成pdf并解决表格分割的问题的详细内容,更多关于Java生成pdf的资料请关注脚本之家其它相关文章!