Java PDF生成方案介绍及问题汇总
PDF生成目录
- 前言
- 功能
- 1. 使用前端抓取打印
- 2. 使用后端PDF模板生成
- 2.1 pdf模板的概念
- 2.2 下载链接
- 2.3 使用方法
- 1. 首先创建好我们的基础模板
- 2. 打开后就是我们的创建表单域的界面
- 3. 创建基本的文本域
- 4. 编写后端代码
- 5. 总结
- 3. 使用后端itext动态绘制模板
- 问题汇总
- 1.1 为什么前端不能使用多图打印及数据量的情况
- 1.2 什么时候适合使用前端打印,什么时候适合后端模板呢
- 3.1 PDF文件中中文无法正常显示
- 3.2 想使用自定义字体怎么办?
- 3.3 怎样给PDF中添加水印功能
- 3.4 如何给PDF表格中批量添加图片?
- 3.5 动态生成的PDF如何实现分页?
- 3.6 评论的多图片处理思路,及缩小处理时间
前言
本文用于介绍实现的方案,具体的代码层次只是大概的去涉及些,并提供开发过程中需要下载的aodbe DC工具及字体文件等,目的是为了有一个PDF的概念,方便后面开发功能能够知道怎么开发如何如去开发持续去完善,期间产生的问题,有些记不清了,等后面使用过程中慢慢去补充,也算是一些经验记录.也欢迎一块讨论一块补充,找出文中存在的Bug
这里再抛出个问题,目前尚不知道如何去实现.批量PDF中的批量图片怎样短时间内导出呢?
功能
在业务系统中生成PDF文件有三种方法,分为两个层次,一个前端打印, 一个后端打印,其中后端打印又分为两种,一种是使用模板打印,另外一种使用动态生成PDF文件
1. 使用前端抓取打印
需要打印的节点,调用方法即可使用,使用于单页或者图片数据量渲染少的情况 否则会产生问题. 见问题汇总[1.1]
windows.print();
默认print打印的是根节点,如果需要打印自定义节点就需要自己去抓取,把不想打印的地方临时remove掉或者是指定节点,这个具体再百度
2. 使用后端PDF模板生成
2.1 pdf模板的概念
后端PDF模板原理是通过提前准备好一个pdf模板,布局好pdf模板的样式,使用Adobe Acrobat Reader DC 去对模板进行表单域处理 预填好对应的属性 ,诸如下图中被红框所圈起来的地方就是表单域,这里统一使用的是文本域 左下角四个图片展示的地方使用的是图片域
这里提供下思路,对这个pdf模板进行解析下,其中的黑体文字因为是死值,所以提前在模板中就把这些值写死了,而灰色框中的文本是为了预览显示的默认值,等这里填充字段了,就会被修改.我们所需要做的就是组织好pdf的样式,然后把数据填充进去即可,想起来其实是跟前端打印一个道理,最终还是要组织好样板去打印生成
2.2 下载链接
免费版DC下载 好吧,更正下,阿里云不能下载Zip,所以这个失效了…,得自己找了
下载完成后直接点击安装即可
2.3 使用方法
这里做个大概的示范,具体的使用及出现的问题还是需要自己去排查摸索
1. 首先创建好我们的基础模板
来源可以是自己使用DC做的或者是其他方面提供的 点击签名->创建表单->在下面选择我们准备好的模板
2. 打开后就是我们的创建表单域的界面
红框所示的就是我们的工具栏,这块的操作需要慢慢摸索
3. 创建基本的文本域
这里填写的就是我们后端所需要的的属性名 举例: 这里填写 name后端: map.put("name","张三");
4. 编写后端代码
<!-- pom文件 itext 依赖 --><dependency><groupId>com.itextpdf</groupId><artifactId>itextpdf</artifactId><version>5.4.3</version></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>itext-asian</artifactId><version>5.2.0</version></dependency><dependency><groupId>fr.opensagres.xdocreport</groupId><artifactId>org.apache.poi.xwpf.converter.pdf</artifactId><version>1.0.4</version></dependency>
// pdf txt 测试OutputStream os = null;ByteArrayOutputStream baos = null;String template = "E:/test/ceshi.pdf"; //模板PDF的位置Map<String, Object> treeDto = new HashMap<>(); // 构建模板所需要的文本数据treeDto.put("titile", "标题");treeDto.put("name", "姓名");treeDto.put("sex", "性别");treeDto.put("idCard", "用户身份证号");/** 构建模板中所需要的的图片 这里value都是图片的存储路径 * 但是最终都需要解析为流所以传参什么需要自己判断* */treeDto.put("stuImg","图片地址1"); treeDto.put("img1", "图片地址2");treeDto.put("img2", "图片地址3");treeDto.put("img3", "图片地址4");try {os = response.getOutputStream();//读取模板资源文件PdfReader reader = new PdfReader(template);baos = new ByteArrayOutputStream();//获取操作对象PdfStamper stamper = new PdfStamper(reader, baos);AcroFields form = stamper.getAcroFields();//拿到pdf所存在的表单域Iterator<String> it = form.getFields().keySet().iterator();while (it.hasNext()) {// 这里就是pdf中我们编写的文本域的名称,如果相等则匹配 赋予相对应的值String name = it.next().toString();//写入图片if ("stuImg".equals(name) || "img1".equals(name) || "img2".equals(name) || "img3".equals(name)) {Image image = Image.getInstance(treeDto.get(name).toString());form.addSubstitutionFont(BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED));//设置图片应该显示的位置 这里因为只有一张pdf 所以直接get(0)int pageNo = form.getFieldPositions(name).get(0).page;// 拿到pdf所对应的表单域中的界限 x yRectangle signRect = form.getFieldPositions(name).get(0).position;float x = signRect.getLeft();float y = signRect.getBottom();PdfContentByte under = stamper.getOverContent(pageNo);image.scaleToFit(signRect.getWidth(), signRect.getHeight());//控制图片image.setAbsolutePosition(x, y);//添加图片到pdf中under.addImage(image);//写入文字} else {form.setField(name, treeDto.get(name).toString());}}stamper.setFormFlattening(true);stamper.close();Document doc = new Document();PdfCopy copy = new PdfCopy(doc, os);doc.open();PdfImportedPage importPage = copy.getImportedPage(new PdfReader(baos.toByteArray()), 1);copy.addPage(importPage);doc.close();
// response.reset(); 这里本意是清空管道,但是存在一些问题//以流的形式返回给浏览器 这样一般的浏览器就用自带的工具打开pdf或者下载 特别实用logger.info("开始返回PDF");//设置为pdf格式response.setContentType("application/pdf");response.setHeader("Content-Disposition", "attachment;fileName="+ URLEncoder.encode(fileName, "UTF-8"));response.setHeader("Access-Control-Allow-Origin", "*");response.setHeader("Access-Control-Allow-Methods", "GET");response.setHeader("Access-Control-Max-Age", "3600");response.setHeader("Access-Control-Allow-Headers", "x-requested-with");logger.info("生成预览文件成功");} catch (Exception e) {logger.info("返回PDF失败" + e.getMessage());e.fillInStackTrace();} finally {// 不管成功与否关闭流IOUtils.closeQuietly(os);}
5. 总结
只要能拿到模板对应的对象,我们就可以操作对象对文本等其他属性进行赋值,只要能想到的基本都能
够去操作.功能点很是很多的,不过基本上80%问题使用这些在模板的固定+数据的赋值+代码的配置上
都能解决了模板功能及代码编写上都能有很多其他的方法去实现更简洁更实用符合自己业务的功能,
这个就需要后续慢慢去摸索了.
3. 使用后端itext动态绘制模板
没错,这里又是使用itext来生成pdf文件,不过和模板最大的不同是模板只能在固定死的模板上进行缝缝补补的操作
,而这个功能可以实现动态的实现pdf,真正意义上的通过代码生成pdf文件
<!-- itextpdf 注意itext 的组件版本保持一致--><dependency><groupId>com.itextpdf</groupId><artifactId>itextpdf</artifactId><version>5.5.13.1</version></dependency><!-- itext-asian 语言包 可以独立出来--><dependency><groupId>com.itextpdf</groupId><artifactId>itext-asian</artifactId><version>5.2.0</version></dependency><!-- itextpdf-tool-xmlworker --><dependency><groupId>com.itextpdf.tool</groupId><artifactId>xmlworker</artifactId><version>5.5.13.1</version></dependency>
要使用动态生成pdf文件功能首先要了解几个概念
- 一个PDF 是以Document 为对象 操作的是这个对象里面的属性,可以把document对象想象成打印的一张纸,如果需要多页pdf 那就把内容向下填充
- 前期使用会出现一堆问题,如字体 及中文支持上见问题3.1~3.2
- 使用pdf创建表格的时候要有一个列的概念,这点在poi excel中也是,在脑袋中表格就是由n行n列的单元格组成,要保证整体是一个长方形
- 实现过程是从上到下,从左到右,操作document节点
- pdf填充内容的过程是先打开文件,打开执行写入程序后,才会关闭
工具类:
package com.smart.safety.util;import com.itextpdf.text.*;
import com.itextpdf.text.pdf.*;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;/*** @author MinChang* @Date 2021/8/2*/
public class PdfUtil {public static BaseFont bfChinese;private static final int defaultSize =12;public static BaseFont kaiTiFont;static {try {//指定字体 没有这个则 createChineseFont 方法无用bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H",BaseFont.NOT_EMBEDDED);} catch (DocumentException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}static {try {//指定字体 没有这个则 createChineseFont 方法无用kaiTiFont = BaseFont.createFont( "/ttf/KAITIGB2312.ttf",BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);} catch (DocumentException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}/*** @param table 表格* @param border 边框* @param rightIndent 占比* @param text 文字* @param size size* @return*/public static void createCell(PdfPTable table,int border,float rightIndent,String text,int size,int rowSpan,int colSpan){PdfPCell pdfPCell = new PdfPCell();pdfPCell.setBorder(border); //设置边框pdfPCell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);pdfPCell.setRightIndent(rightIndent);pdfPCell.setPaddingTop(2f);//把字垂直居中pdfPCell.setPaddingBottom(8f);//把字垂直居中pdfPCell.setRowspan(rowSpan);pdfPCell.setPhrase(PdfUtil.createChineseFont(text,size));pdfPCell.setColspan(colSpan);table.addCell(pdfPCell);}public static void createCell(PdfPTable table,String text,int size){int border=Rectangle.BOX;int rowSpan=1;int colSpan=1;float rightIndent=1;PdfPCell pdfPCell = new PdfPCell();pdfPCell.setBorder(border); //设置边框pdfPCell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);pdfPCell.setRightIndent(rightIndent);pdfPCell.setPaddingTop(2f);//把字垂直居中pdfPCell.setPaddingBottom(8f);//把字垂直居中pdfPCell.setRowspan(rowSpan);pdfPCell.setPhrase(PdfUtil.createChineseFont(text,size));pdfPCell.setColspan(colSpan);table.addCell(pdfPCell);}public static void createCell(PdfPTable table,String text){int size = 8;int border=Rectangle.BOX;int rowSpan=1;int colSpan=1;float rightIndent=1;PdfPCell pdfPCell = new PdfPCell();pdfPCell.setBorder(border); //设置边框pdfPCell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);pdfPCell.setRightIndent(rightIndent);pdfPCell.setPaddingTop(2f);//把字垂直居中pdfPCell.setPaddingBottom(8f);//把字垂直居中pdfPCell.setRowspan(rowSpan);pdfPCell.setPhrase(PdfUtil.createChineseFont(text,size));pdfPCell.setColspan(colSpan);table.addCell(pdfPCell);}/*** 返回中文Paragraph 默认12* @param text* @return*/public static Paragraph createChineseFont(String text){Font c = new Font(bfChinese, defaultSize, Font.NORMAL);return new Paragraph(text, c);}/*** 返回中文Paragraph* @param text* @param size* @return*/public static Paragraph createChineseFont(String text,int size){if (size==0){size=defaultSize;}Font c = new Font(bfChinese, size, Font.NORMAL);return new Paragraph(text, c);}/** @Description 蓝色背景色标题内容行添加* @Date 2019/7/12 14:56* @param table 表格* @param cell 列* @param text 文本* @return void**/public static void addTableGroupTitle(PdfPTable table, PdfPCell cell, String text) {cell = new PdfPCell(new Phrase(text,getColorFont(BaseColor.WHITE)));table.addCell(addTitleCell(cell,25,new BaseColor(69,153,241),2,false));}/*** @Description 蓝色背景色标题内容行添加* @Date 2019/7/12 14:56* @param table 表格* @param cell 列* @param text 文本* @param colspan 需要合并的列* @return void**/public static void addTableGroupTitle(PdfPTable table, PdfPCell cell, String text,int colspan) {cell = new PdfPCell(new Phrase(text,getColorFont(BaseColor.WHITE)));table.addCell(addTitleCell(cell,25,new BaseColor(69,153,241),colspan,false));}/*** @Description 核查建议* @Date 2019/7/12 14:43* @param table 表格* @param cell 列* @param suggestText 核查建议内容* @param fontColor 核查建议内容文字颜色* @return com.itextpdf.text.Element**/public static void addSuggestLine(PdfPTable table,PdfPCell cell,String suggestText,BaseColor fontColor) throws Exception {addSuggestLine(table, cell, suggestText, fontColor, 0,10f,30f);}/*** @Description 核查建议* @Date 2019/7/12 14:43* @param table 表格* @param cell 列* @param suggestText 核查建议内容* @param fontColor 核查建议内容文字颜色* @param colspan 合并的列* @param widths 列所占宽* @return com.itextpdf.text.Element**/public static void addSuggestLine(PdfPTable table,PdfPCell cell,String suggestText,BaseColor fontColor,int colspan,float...widths) throws Exception {cell = new PdfPCell(new Phrase("核查建议:",getColorFont()));cell.setColspan(1);table.addCell(addBaseCell(cell,23,new BaseColor(238,238,238),false));cell = new PdfPCell(new Phrase(suggestText,getColorFont(fontColor)));if(colspan>0){cell.setColspan(colspan);}table.addCell(addBaseCell(cell,23,new BaseColor(238,238,238),false));table.setWidths(getColumnWiths(widths));}/*** @Description 信息分组table* @Date 2019/7/12 14:43* @param groupText 文本内容* @return com.itextpdf.text.Element**/public static Element addTableGroupLine(String groupText) {PdfPTable tableBaseInfoIndex = new PdfPTable(1);tableBaseInfoIndex.setWidthPercentage(20);PdfPCell cellBaseInfo = new PdfPCell(new Phrase(groupText,getColorFont()));cellBaseInfo.setHorizontalAlignment(Element.ALIGN_CENTER);tableBaseInfoIndex.addCell(addTitleCell(cellBaseInfo,28,new BaseColor(238,238,238),2,false));tableBaseInfoIndex.addCell(addBlankLine(10,1));return tableBaseInfoIndex;}/*** @Description 指定颜色字体 默认处理中文显示* @Date 2019/7/12 14:05* @param color 字体颜色* @param fontSize 字体大小* @param fontFamily 字体* @return com.itextpdf.text.Font**/public static Font getColorFont(BaseColor color, int fontSize, String fontFamily) {Font font = new Font(getFont());font.setColor(color);if(fontSize>0&&(null!=fontFamily||!"".equals(fontFamily))){font.setSize(fontSize);font.setFamily(fontFamily);}return font;}/*** @Description 指定颜色字体 默认处理中文显示* @Date 2019/7/12 14:05* @param color 字体颜色* @return com.itextpdf.text.Font**/public static Font getColorFont(BaseColor color) {return getColorFont(color, 0, null);}/*** @Description 默认处理中文显示* @Date 2019/7/12 14:05* @return com.itextpdf.text.Font**/public static Font getColorFont() {Font font = new Font(getFont());return font;}/*** @Description 指定列宽度* @Date 2019/7/12 11:59* @param widths 一个或多个* @return float[]**/public static float[] getColumnWiths(float...widths){float[] columnWidths = new float[widths.length];for (int i = 0; i < widths.length; i++) {columnWidths[i]=widths[i];}return columnWidths;}/*** @Description 添加表头cell* @Date 2019/7/12 11:36* @param titleCell 要操作的cell* @param fixedHeight 行高度* @param baseColor 背景色* @param colspan 合并的列数* @param isBottomBorder 是否有下边框 true 有 fasle 没有* @return com.itextpdf.text.pdf.PdfPCell**/public static PdfPCell addTitleCell(PdfPCell titleCell,int fixedHeight,BaseColor baseColor,int colspan,boolean isBottomBorder){titleCell.setColspan(colspan);titleCell.setFixedHeight(fixedHeight);titleCell.setUseVariableBorders(true);titleCell.setUseAscender(true);titleCell.setUseDescender(true);titleCell.setBackgroundColor(baseColor);if(isBottomBorder){titleCell.setBorder(Rectangle.BOTTOM);titleCell.setBorderColorBottom(BaseColor.LIGHT_GRAY);}else{titleCell.setBorder(Rectangle.NO_BORDER);}titleCell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);return titleCell;}/*** @Description 添加空行* @Date 2019/7/12 11:36* @param fixedHeight 空行高度* @param colspan 合并的列数* @return com.itextpdf.text.pdf.PdfPCell**/public static PdfPCell addBlankLine(int fixedHeight,int colspan){PdfPCell blankLine = new PdfPCell();blankLine.setFixedHeight(fixedHeight);blankLine.setBorder(Rectangle.NO_BORDER);blankLine.setColspan(colspan);return blankLine;}/*** @Description 添加默认cell* @param baseCell 要操作的cell* @Date 2019/7/12 11:36* @return com.itextpdf.text.pdf.PdfPCell**/public static PdfPCell addBaseCell(PdfPCell baseCell){baseCell.setFixedHeight(23);baseCell.setUseVariableBorders(true);baseCell.setUseAscender(true);baseCell.setUseDescender(true);baseCell.setBorder(Rectangle.BOTTOM);baseCell.setBorderColorBottom(BaseColor.LIGHT_GRAY);baseCell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);return baseCell;}/*** @Description 添加cell* @param baseCell 要操作的cell* @param isBottomBorder 是否有下边框 true 有 fasle 没有* @Date 2019/7/12 11:36* @return com.itextpdf.text.pdf.PdfPCell**/public static PdfPCell addBaseCell(PdfPCell baseCell,boolean isBottomBorder){baseCell.setFixedHeight(23);baseCell.setUseVariableBorders(true);baseCell.setUseAscender(true);baseCell.setUseDescender(true);if(isBottomBorder){baseCell.setBorder(Rectangle.BOTTOM);baseCell.setBorderColorBottom(BaseColor.LIGHT_GRAY);}else{baseCell.setBorder(Rectangle.NO_BORDER);}baseCell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);return baseCell;}/*** @Description 添加cell* @param baseCell 要操作的cell* @param fixedHeight 行高* @param color 背景色* @param isBottomBorder 是否有下边框 true 有 fasle 没有* @Date 2019/7/12 11:36* @return com.itextpdf.text.pdf.PdfPCell**/public static PdfPCell addBaseCell(PdfPCell baseCell,int fixedHeight,BaseColor color,boolean isBottomBorder){baseCell.setFixedHeight(fixedHeight);baseCell.setUseVariableBorders(true);baseCell.setUseAscender(true);baseCell.setUseDescender(true);if(null!=color){baseCell.setBackgroundColor(color);}if(isBottomBorder){baseCell.setBorder(Rectangle.BOTTOM);baseCell.setBorderColorBottom(BaseColor.LIGHT_GRAY);}else{baseCell.setBorder(Rectangle.NO_BORDER);}baseCell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);return baseCell;}/*** @Description 设置中文支持* @Date 2019/7/11 10:33* @Param []* @return com.itextpdf.text.pdf.BaseFont**/public static BaseFont getFont() {BaseFont bf = null;try {bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);} catch (Exception e) {System.out.println("Exception = " + e.getMessage());}return bf;}/*** 斜角排列、全屏多个重复的花式文字水印** @param input 需要加水印的PDF读取输入流* @param output 输出生成PDF的输出流* @param waterMarkString 水印字符* @param xAmout x轴重复数量* @param yAmout y轴重复数量* @param opacity 水印透明度* @param rotation 水印文字旋转角度,一般为45度角* @param waterMarkFontSize 水印字体大小* @param color 水印字体颜色*/public static void stringWaterMark(InputStream input, OutputStream output, String waterMarkString, int xAmout, int yAmout, float opacity, float rotation, int waterMarkFontSize, BaseColor color) {try {PdfReader reader = new PdfReader(input);PdfStamper stamper = new PdfStamper(reader, output);// 添加中文字体BaseFont baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);int total = reader.getNumberOfPages() + 1;PdfContentByte over;// 给每一页加水印for (int i = 1; i < total; i++) {Rectangle pageRect = stamper.getReader().getPageSizeWithRotation(i);// 计算水印每个单位步长X,Yfloat x = pageRect.getWidth() / xAmout;float y = pageRect.getHeight() / yAmout;over = stamper.getOverContent(i);PdfGState gs = new PdfGState();// 设置透明度为gs.setFillOpacity(opacity);over.setGState(gs);over.saveState();over.beginText();over.setColorFill(color);over.setFontAndSize(baseFont, waterMarkFontSize);for (int n = 0; n < xAmout + 1; n++) {for (int m = 0; m < yAmout + 1; m++) {over.showTextAligned(Element.ALIGN_CENTER, waterMarkString, x * n, y * m, rotation);}}over.endText();}stamper.close();} catch (Exception e) {new Exception("NetAnd PDF add Text Watermark error"+e.getMessage());}}/*** 图片水印,整张页面平铺* @param input 需要加水印的PDF读取输入流* @param output 输出生成PDF的输出流* @param imageFile 水印图片路径*/public static void imageWaterMark(InputStream input, OutputStream output, String imageFile, float opacity) {try {PdfReader reader = new PdfReader(input);PdfStamper stamper = new PdfStamper(reader, output);Rectangle pageRect = stamper.getReader().getPageSize(1);float w = pageRect.getWidth();float h = pageRect.getHeight();int total = reader.getNumberOfPages() + 1;Image image = Image.getInstance(imageFile);image.setAbsolutePosition(0, 0);// 坐标image.scaleAbsolute(w, h);PdfGState gs = new PdfGState();gs.setFillOpacity(opacity);// 设置透明度PdfContentByte over;// 给每一页加水印float x, y;Rectangle pagesize;for (int i = 1; i < total; i++) {pagesize = reader.getPageSizeWithRotation(i);x = (pagesize.getLeft() + pagesize.getRight()) / 2;y = (pagesize.getTop() + pagesize.getBottom()) / 2;over = stamper.getOverContent(i);over.setGState(gs);over.saveState();//没这个的话,图片透明度不起作用,必须在beginText之前,否则透明度不起作用,会被图片覆盖了内容而看不到文字了。over.beginText();// 添加水印图片over.addImage(image);}stamper.close();} catch (Exception e) {new Exception("NetAnd PDF add image Watermark error" + e.getMessage());}}/*** @description 顶部表格卡片形式显示格式数据组装* @param tableMobileHeader 要操作的表格* @param cellMobileHeader 要操作的单元格* @param clospan 合并列 不需要合并填写0* @param fixedHeight 行高* @param padding 间距* @param border 边框* @param borderColor 边框颜色* @param backgroundColor 背景色* @param vertical 垂直对齐方式* @param horizontal 水平对齐方式* @return void**/public static void addTableHeaderData(PdfPTable tableMobileHeader, PdfPCell cellMobileHeader, int clospan, float fixedHeight, int padding, int border, BaseColor borderColor, BaseColor backgroundColor, int vertical, int horizontal) {cellMobileHeader.setUseBorderPadding(true);cellMobileHeader.setUseAscender(true);if(clospan>0){cellMobileHeader.setColspan(clospan);}cellMobileHeader.setUseDescender(true);cellMobileHeader.setFixedHeight(fixedHeight);cellMobileHeader.setPadding(padding);cellMobileHeader.setVerticalAlignment(vertical);cellMobileHeader.setHorizontalAlignment(horizontal);if(null!=backgroundColor){cellMobileHeader.setBackgroundColor(backgroundColor);}cellMobileHeader.setBorder(border);cellMobileHeader.setBorderColor(borderColor);tableMobileHeader.addCell(cellMobileHeader);}/*** @description 顶部表格卡片形式显示格式数据组装* @param tableMobileHeader 要操作的表格* @param cellMobileHeader 要操作的单元格* @param clospan 合并列 不需要合并填写0* @param backgroundColor 背景色* @return void**/public static void addTableHeaderData(PdfPTable tableMobileHeader, PdfPCell cellMobileHeader, int clospan, BaseColor backgroundColor) {addTableHeaderData(tableMobileHeader, cellMobileHeader, clospan, 100, 10, 30, BaseColor.WHITE, backgroundColor, 0, 0);}
}
生成代码:
@Asyncpublic Future<Result> createPdf(List<Map<String, Object>> userMap,String relativePath) {ByteArrayOutputStream out = null;InputStream pdfStream = null;try {Document document = new Document(PageSize.A4, 50, 50, 30, 20);out = new ByteArrayOutputStream();PdfWriter writer = PdfWriter.getInstance(document,out);document.open();for (Map<String, Object> userInfo : userMap) {CompanyStaffDto companyStaffDto = (CompanyStaffDto) userInfo.get("userInfo");// 加入水印PdfContentByte waterMar = writer.getDirectContentUnder();// 开始设置水印waterMar.beginText();// 设置水印透明度PdfGState gs = new PdfGState();// 设置填充字体不透明度为0.4fgs.setFillOpacity(0.2f);try {// 设置水印字体参数及大小 (字体参数,字体编码格式,是否将字体信息嵌入到pdf中(一般不需要嵌入),字体大小)waterMar.setFontAndSize(PdfUtil.kaiTiFont,10);// 设置透明度waterMar.setGState(gs);// 设置水印对齐方式 水印内容 X坐标 Y坐标 旋转角度for(int i=0 ; i<3; i++) {for (int j = 0; j < 6; j++) {waterMar.showTextAligned(Element.ALIGN_CENTER,"水印内容", 50.5f + i * 280, 40.0f + j * 100, -15);}}// 设置水印颜色waterMar.setColorFill(BaseColor.GRAY);//结束设置waterMar.endText();waterMar.stroke();}catch (Exception e){e.printStackTrace();}finally {waterMar = null;gs = null;}//设置标题! 这个标题不是页面内,而是文件展示的时候左上角的document.addTitle("标题");//创建中文字体,否侧不能使用 参数1 内容 参数2 内容大小Paragraph title = PdfUtil.createChineseFont("中文", 13);//设置居中title.setAlignment(Element.ALIGN_CENTER);//将内容添加的节点中document.add(title);//添加QR图片Image image = Image.getInstance((byte[]) userInfo.get("qrUrl"));//设置图片所占大小image.scaleAbsolute(60f, 60f);image.setAlignment(Element.ALIGN_RIGHT);image.setPaddingTop(-9f);document.add(image);//生成横线 linWidth 宽度LineSeparator solidLine = new LineSeparator(2f, 100, BaseColor.BLACK, Element.ALIGN_CENTER, -5f);document.add(solidLine);LineSeparator dottedLine = new LineSeparator(1f, 100, BaseColor.BLACK, Element.ALIGN_CENTER, -8f);document.add(dottedLine);//打印时间设置Paragraph printTime = PdfUtil.createChineseFont("打印时间" + DateUtil.format(new Date(), "yyyy年MM月dd日 HH:mm:ss"), 9);//设置上间距printTime.setSpacingBefore(3f);//设置下间距printTime.setSpacingAfter(3f);//设置居中方式printTime.setAlignment(Element.ALIGN_RIGHT);document.add(printTime);//设置基本情况页面Paragraph basicSituation = PdfUtil.createChineseFont("☞ 基本情况", 10);basicSituation.setAlignment(Element.ALIGN_LEFT);document.add(basicSituation);//创建基本情况表格//创建表格对象PdfPTable table = new PdfPTable(4);table.setSpacingBefore(10);//前边距table.setSpacingAfter(10);//后边距table.setWidthPercentage(100);//表格宽占比table.setHorizontalAlignment(Element.ALIGN_CENTER);table.setHeaderRows(2);table.getDefaultCell().setVerticalAlignment(Element.ALIGN_TOP);//单元格中文字垂直对齐方式table.getDefaultCell().setBorderColor(BaseColor.BLACK);//单元格线条颜色table.getDefaultCell().setMinimumHeight(30);//单元格最小高度table.getDefaultCell().setExtraParagraphSpace(5);//段落文字与表格之间的距离,底部距离table.getDefaultCell().setLeading(15, 0);//设置行间距//这里使用的是自定义的Util工具类PdfUtil.createCell(table, "姓名");PdfUtil.createCell(table,"姓名数据");PdfUtil.createCell(table, "报告月份");PdfUtil.createCell(table, nowDate);PdfUtil.createCell(table, "性别");PdfUtil.createCell(table, "性别数据");PdfUtil.createCell(table, "身份证号");PdfUtil.createCell(table, "身份证号数据");PdfUtil.createCell(table, "岗位");PdfUtil.createCell(table, "岗位数据");PdfUtil.createCell(table, "手机号");PdfUtil.createCell(table, "手机号数据");PdfUtil.createCell(table, "实际时长/计划时长");PdfUtil.createCell(table, "实际时长/计划时长数据");PdfUtil.createCell(table, "是否完成");PdfUtil.createCell(table,"已完成" );PdfUtil.createCell(table, "所属企业");PdfUtil.createCell(table, Rectangle.BOX, 1, "企业名称数据", 8, 1, 3);document.add(table);/* 这里是尾部签名及日期的展示,就不加入了 //签名Paragraph sign = PdfUtil.createChineseFont("签字:" + companyStaffDto.getName(), 8);sign.setAlignment(Element.ALIGN_RIGHT);document.add(sign);//日期Paragraph date = PdfUtil.createChineseFont(DateUtil.format(new Date(), "yyyy年MM月dd日"), 8);date.setAlignment(Element.ALIGN_RIGHT);document.add(date);*/ try{document.close();}catch (Exception e){log.debug("打印pdf无内容");}Result resultUrl =new Result();pdfStream = new ByteArrayInputStream(out.toByteArray());Result result = ossApiService.upload(pdfStream, relativePath);if(result.success){resultUrl = result;}return AsyncResult.forValue(resultUrl);}catch (Exception e){e.printStackTrace();}finally {try {if(pdfStream!=null){pdfStream.close();}} catch (IOException e) {e.printStackTrace();}try {if(out!=null){out.close();}} catch (IOException e) {e.printStackTrace();}}return null;}
最终的一个基本样式就是这个样子
问题汇总
1.1 为什么前端不能使用多图打印及数据量的情况
答: 1. 因为前端如果数据量大的话会给浏览器造成很大的压力,会产生卡死的情况,这种情况当然是不愿意去看到的2. 图片渲染量过多,会出现图片渲染丢失的情况,读取不到url而导致控制台页面报错,越往后的图片越有几率会变成一个 X 这种情况下其实也有解决办法: 将我们的图片提前在本地以base64的方式去存储 ,但是又回到第一个回答上,所以酌情使用.
1.2 什么时候适合使用前端打印,什么时候适合后端模板呢
答: 1. 什么时候可以使用前端打印功能: 1.1 像打印一般段落文字,承诺书这种,前端简单粗暴,也好布局1.2 打印表格一类的文档,因为表格后端绘制比较麻烦,所以推荐交给前端使用<table/>去布局1.3 轻量打印,数据量不大的前两种1.4 前端样式设置的很精美,后端做到同等底部太难 2. 前端打印和后端打印其实各自能做到的,各自都能做到,而且效率和布局上前端更胜一筹,但是像一些样式复杂的,随着数据量增大,前端导入的效率就无法和后端去相对应了,尤其是带有图片的情况下.而且不方便提供各种文件格式的数据,打印页面情况下就交给后端去生成固定死的页面,还能更好的处理性能上问题
3.1 PDF文件中中文无法正常显示
答: 因为字体的原因,所以如果想使用中文字体的话,需要指定对应中文的编码 这里定义了一个工具类用于返回中文字体
public static BaseFont bfChinese;private static final int defaultSize =12;public static BaseFont kaiTiFont; static {try {//指定字体 没有这个则 createChineseFont 方法无用bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H",BaseFont.NOT_EMBEDDED);} catch (DocumentException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}
/*** 返回中文Paragraph 默认12* @param text* @return*/public static Paragraph createChineseFont(String text){Font c = new Font(bfChinese, defaultSize, Font.NORMAL);return new Paragraph(text, c);}/*** 返回中文Paragraph* @param text* @param size* @return*/public static Paragraph createChineseFont(String text,int size){if (size==0){size=defaultSize;}Font c = new Font(bfChinese, size, Font.NORMAL);return new Paragraph(text, c);}
3.2 想使用自定义字体怎么办?
答: 自定义字体,不能通过简单的使用编码去调用,这点可以去看官方的Asian编码jar包,里面对中文支持少的可怜,更别说是字体,所以想使用,如楷体这类的需要自己去引用,这里提供一个楷体ttf文件,其他类型需要自行下载
代码里的文件我放在了resources文件下直接就可以通过相对路径去访问,但是excel读取字体的方式不能通过这样.excel Font用的是java的,pdf Font类是自己封装的 后面再说
楷体字体文件
public static BaseFont kaiTiFont;
static {try {//指定字体 没有这个则 createChineseFont 方法无用kaiTiFont = BaseFont.createFont( "/ttf/KAITIGB2312.ttf",BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);} catch (DocumentException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}/*** 返回中文楷体Paragraph 默认12* @param text* @return*/public static Paragraph createChineseFont(String text){Font c = new Font(kaiTiFont, defaultSize, Font.NORMAL);return new Paragraph(text, c);}
3.3 怎样给PDF中添加水印功能
具体PDF中操作水印,还是比较复杂,这个需要自己琢磨,这里给个简单的具体的可以看前面工具类中提供的水印方式
// 加入水印PdfContentByte waterMar = writer.getDirectContentUnder();// 开始设置水印waterMar.beginText();// 设置水印透明度PdfGState gs = new PdfGState();// 设置填充字体不透明度为0.4fgs.setFillOpacity(0.2f);try {// 设置水印字体参数及大小 (字体参数,字体编码格式,是否将字体信息嵌入到pdf中(一般不需要嵌入),字体大小)waterMar.setFontAndSize(PdfUtil.kaiTiFont,10);// 设置透明度waterMar.setGState(gs);// 设置水印对齐方式 水印内容 X坐标 Y坐标 旋转角度for(int i=0 ; i<3; i++) {for (int j = 0; j < 6; j++) {waterMar.showTextAligned(Element.ALIGN_CENTER,"水印内容", 50.5f + i * 280, 40.0f + j * 100, -15);}}// 设置水印颜色waterMar.setColorFill(BaseColor.GRAY);//结束设置waterMar.endText();waterMar.stroke();}catch (Exception e){e.printStackTrace();}finally {waterMar = null;gs = null;}
3.4 如何给PDF表格中批量添加图片?
答: 这里给出一个思路,把表格分为n列, 每列放置一个图片 就不用考虑 格式的问题了
for (int i=0;i< size;i++) {Image img = Image.getInstance(r.faceRecordDtoList.get(i).getFaceUrl() + "?x-oss-process=image/resize,m_fill,h_80,w_70/quality,q_70");img.scaleAbsolute(50f, 60f);img.setAlignment(Rectangle.LEFT);img.setScaleToFitHeight(false);PdfPCell imgCell = new PdfPCell(img);imgCell.setColspan(1);imgCell.setBorder(Rectangle.BOTTOM);//行对象safe.addCell(imgCell);}
3.5 动态生成的PDF如何实现分页?
两种方案
1. 使用 document.newPage(); 就能从内容结束后进行分页
2. 另外一种,换个思路生成单个的pdf文件,然后使用工具把pdf拼接成一个pdf文件
3.6 评论的多图片处理思路,及缩小处理时间
1. 处理方式
当成单元格就行,循环N,注意排列方式 方向,换行这些就行.
比如 16个图片, 一行放5个
就 从左到右排列, 每隔5个换行操作, 排列到第4行的时候,剩余1个图片,
如果按照从左到右排列,那就是最左边
这个样子:
11111
11111
1
如果想居中可以采用,前面放2个空单元
2. 缩小处理时间
说实话, 图片是处理pdf最耗时的地方,如果没有图片,速度会非常快
(要读取图片),而图片对象的生成是必不可缺少的IO操作,尽量避免
多IO. 而IText处理过程中,我们是没办法介入的, 只能从图片下手,
缩小图片的大小,像素来达到目的, 举例阿里oss 就可以给地址链
接后缀参数来处理图片,这是个思路,如果有其他办法欢迎沟通
Java PDF生成方案介绍及问题汇总相关推荐
- java PDF 生成方案
转自:http://laotu5i0.javaeye.com/?page=4&show_full=true 在此之前,先来勾画一下我心中比较理想的一个解决方案.在企业应用中,碰到的比较多的PD ...
- Java 生成各种 PDF 实战方案(图片、模板、表格)
刚接到了一个需求,生成一个pdf,一开始以为挺简单的,通过模板生成嘛,我也发过相应的文章,根据模板直接生成pdf,响应到前端或者根据模板生成pdf,直接指定下载位置,这两种方案都可以,不过这篇文章主要 ...
- java itext 导出pdf文件_【Java,PDF】使用Itext实现PDF文件生成
重要声明:本文章仅仅代表了作者个人对此观点的理解和表述.读者请查阅时持自己的意见进行讨论. 前言 有时候,业务系统要求提供一个PDF文件导出的功能,这时候我们就需要将数据库的对应数据查询出来,然后生成 ...
- java使用freemark实现word(.doc/.docx)/pdf生成和导出(附源码和模板文件)
freemark生成word/pdf 一. 背景 二.实现的技术选型以及遇到的坑 三.最终的效果 2.1 `.doc` word效果展示 2.1 `.docx` word效果展示 2.2 docx w ...
- Big Faceless Java Pdf报表生成器控件介绍
Report Generator 建立在 PDF 库之上,可将 XML 转换为 PDF,是生成复杂.多页报表的绝佳方式.现在,您可使用 JSP.ASP 或类似技术来创建动态 PDF 报表控件,与 HT ...
- java动态生成pdf文件的方法
java动态生成pdf文件 文章目录 java动态生成pdf文件 前言 一.生成pdf模板 二.使用步骤 1.使用jar包 2.pdf实现方法 总结 前言 java开发过程中难免会遇到生成文件的需求, ...
- java生成唯一字符串_java唯一字符串ID生成方案详解
工作中经常会有生成唯一字符串的需求.通常最容易想到的是UUID.UUID的唯一性毋庸置疑,但是32位的长度也容易让人退避三舍.也曾经想过参考<短网址生成方案>来生成一串ID,但是试验了一下 ...
- java 文字生成pdf,并创建自定义表单域pdf模板
目录 本文总共知识点: pom 所有的import 生成带表格的pdf 另一种方式是指定坐标生成文本域 main方法: 创建表单域做为pdf模板: 创建签名域: 根据文字获取坐标位置 完整代码: 本文 ...
- easyui treegrid获取父节点的id_超简单的分布式ID生成方案!美团开源框架介绍
目录 阐述背景 Leaf snowflake 模式介绍 Leaf segment 模式介绍 Leaf 改造支持 RPC 阐述背景 不吹嘘,不夸张,项目中用到 ID 生成的场景确实挺多.比如业务要做幂等 ...
- java静态页面我都做不出_Java高并发:静态页面生成方案
提升网站性能的方式有很多,例如有效的使用缓存,生成静态页面等等.今天要说的就是生成静态页面的方式.这个也是我近期一直在搞的一个问题,近期在做使用html + servlet做个人网站,为什么是这2个东 ...
最新文章
- Java gdal .mif/.mid文件读取
- 使用poi读取公式错误,xlsx和xls在poi3.8后都支持公式读取,读取后有计算错误公式,解决方法
- 用Python发送邮件[zt]
- 【数据结构与算法】之深入解析“Z字形变换”的求解思路和算法示例
- HDU 5131 Song Jiang's rank list
- 2009年网页设计10大趋势
- 协议类接口 - NAND
- Thread.CurrentPrincipal HttpContext.Current.User
- 包+类导入+静态导入+类放入包中+包作用域
- php内容缓存输出,PHP使用缓存即时输出内容(output buffering)的方法
- 吞吐量(TPS)、QPS、并发数、响应时间(RT)
- mysql主从配置对解决并发有用_MySQL主从配置,读写分离
- php对象合并,【面向对象的PHP】之模式:组合
- matplotlib—plt.pie绘制饼状图及参数详解
- 重庆邮电大学c语言实验报告,重庆邮电大学c语言上机实验期末实验报告.doc
- matlab5.0软件下载,MATLAB手机版
- 三菱PLC和卓岚串口服务器使用方法
- 新侨快讯:官方公布加拿大时薪最高和最低的工作,你的行业上榜吗?
- MSSQL 2000安装 图解
- Android 内容复制到剪贴板
热门文章
- 解决Idea 出现 Could not autowire.. 错误
- bert代码解读2之模型transformer的解读
- 自反ACL实验(GNS3)
- 总结AUTOCAD快捷键,持续更新~
- WebRoot到底是什么鬼?我改它的名可以吗?
- 层次化网络设计(三层网络结构)
- 计算机管理的事件id,事件查看器7035是什么意思_windows事件查看器常见ID代码含义详解...
- 接近开关NPN和PNP区别
- Vue3学习之旅--Composition-API-入门篇
- db2导出适用于mysql的数据_db2数据库导出导入数据库