123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510 |
- package com.ruoyi.common.utils;
- import com.itextpdf.text.*;
- import com.itextpdf.text.pdf.BaseFont;
- import com.itextpdf.text.pdf.PdfPCell;
- import com.itextpdf.text.pdf.PdfPTable;
- import com.itextpdf.text.pdf.PdfWriter;
- import org.apache.commons.codec.binary.Base64;
- import org.apache.commons.collections4.CollectionUtils;
- import org.apache.logging.log4j.LogManager;
- import org.apache.logging.log4j.Logger;
- import javax.servlet.ServletOutputStream;
- import javax.servlet.http.HttpServletResponse;
- import java.lang.reflect.Method;
- import java.net.URL;
- import java.net.URLEncoder;
- import java.util.ArrayList;
- import java.util.List;
- /**
- * @Author: tjf
- * @Date: 2024/3/18 15:34
- * @Describe:
- */
- public class PDFUtil {
- private static final Logger logger = LogManager.getLogger(PDFUtil.class);
- /**
- * fontSize_normal : (正文字体大小11号).
- */
- public static final float FONTSIZE_NORMAL = 11f;
- /**
- * fontSize_titile : (标题字体大小14号).
- */
- public static final float FONTSIZE_TITILE = 14f;
- /**
- * FONTSIZE_COVER : (封面字体大小32号).
- */
- public static final float FONTSIZE_COVER = 32f;
- /**
- * normalFont : (通用字体样式:宋体、11号).
- */
- private static Font normalFont = null;
- /**
- * titleFont : (通用标题字体样式:宋体、14号、加粗).
- */
- private static Font titleFont = null;
- /**
- * coverFont : (通用封面字体样式:宋体、28号、加粗).
- */
- private static Font coverFont = null;
- /**
- * 标题是否居中,true-居中、false-默认居左
- */
- private static final Boolean titleCenter = true;
- /**
- * getBaseFont : (获取可以解析中文的字体:使用宋体). <br/>
- *
- * @author
- * @return
- * @since JDK 1.8
- */
- public static BaseFont getBaseFontChinese()
- {
- try
- {
- // 宋体资源文件路径,可以从C://Windows//Fonts//simsun.ttc拷贝到相应目录下
- URL path = PDFUtil.class.getResource("/config/fonts/simsun.ttc");
- return BaseFont.createFont(path + ",0", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
- // 本地测试:使用windows自带的宋体文件
- //return BaseFont.createFont("C://Windows//Fonts//simsun.ttc,0", BaseFont.IDENTITY_H, false);
- }
- catch (Exception e)
- {
- logger.error("设置字体样式失败", e);
- return null;
- }
- }
- /**
- * getNormalFont : (获取普通字体样式). <br/>
- *
- * @author
- * @return
- * @since JDK 1.8
- */
- public static Font getNormalFont()
- {
- if (normalFont == null)
- {
- BaseFont bfChinese = getBaseFontChinese();
- normalFont = new Font(bfChinese, FONTSIZE_NORMAL, Font.NORMAL);
- }
- return normalFont;
- }
- /**
- * getTitleFont : (获取标题通用字体). <br/>
- *
- * @author
- * @return
- * @since JDK 1.8
- */
- public static Font getTitleFont()
- {
- if (titleFont == null)
- {
- BaseFont bfChinese = getBaseFontChinese();
- titleFont = new Font(bfChinese, FONTSIZE_TITILE, Font.BOLD);
- }
- return titleFont;
- }
- /**
- * getTitleFont : (获取封面通用字体). <br/>
- *
- * @author
- * @return
- * @since JDK 1.8
- */
- public static Font getCoverFontFont()
- {
- if (coverFont == null)
- {
- BaseFont bfChinese = getBaseFontChinese();
- coverFont = new Font(bfChinese, FONTSIZE_COVER, Font.BOLD);
- }
- return coverFont;
- }
- /**
- * getfieldValue : (通过反射,根据方法名,执行方法,最终返回行结果的toString值). <br/>
- *
- * @author
- * @param <T> 方法执行的入参
- * @param object 对象
- * @param methodName 方法名
- * @param args 方法执行参数
- * @since JDK 1.8
- */
- private static <T> String getfieldValue(T object, String methodName, Object... args)
- {
- try
- {
- Method method = object.getClass().getMethod(methodName);
- Object value = method.invoke(object, args);
- return value == null ? "" : value.toString();
- }
- catch (Exception e)
- {
- logger.error("getfieldValue error", e);
- return "";
- }
- }
- /**
- * analysisPicBase64Info : (解析base64图片信息). <br/>
- *
- * @author
- * @param picBase64Info 图片base64信息,或前台echart通过调用getDataURL()方法获取的图片信息
- * @return 图片经过base64解码后的信息
- * @since JDK 1.8
- */
- public static Element analysisPicBase64Info(String picBase64Info)
- {
- if (StringUtils.isEmpty(picBase64Info))
- {
- // 空段落
- return new Paragraph();
- }
- // 1.获取图片base64字符串信息:若入参是通过前台echarts调用getDataURL()方法获取的,则该字符串包含逗号,且则逗号后面的内容才是图片的信息
- String pictureInfoStr = picBase64Info.indexOf(",") == -1 ? picBase64Info : picBase64Info.split(",")[1];
- // 2.将图片信息进行base64解密
- byte[] imgByte = Base64.decodeBase64(pictureInfoStr);
- // 对异常的数据进行处理
- /**
- * .图片的原始表达ascii码范围是0-255,
- * .这里面有一些不可见的编码。然后为了图片正确传输才转成编码base64的0-63,
- * .当从base64转成byte时,byte的范围是[-128,127],那么此时就会可能产生负数,而负数不是在ascii的范围里,所以需要转换一下
- */
- for (int i = 0; i < imgByte.length; i++)
- {
- if (imgByte[i] < 0)
- {
- imgByte[i] += 256;
- }
- }
- try
- {
- return Image.getInstance(imgByte);
- }
- catch (Exception e)
- {
- logger.error("analysisPicBase64Info error", e);
- return new Paragraph();
- }
- }
- /**
- * analysisPicBase64Info_batch : (批量解析base64加密的图片信息,生成Image对象). <br/>
- *
- * @author
- * @param picBase64Infos 经过base64加密的图片信息
- * @return
- * @since JDK 1.8
- */
- public static List<Element> analysisPicBase64Info_batch(List<String> picBase64Infos)
- {
- List<Element> images = new ArrayList<Element>();
- for (String li : picBase64Infos)
- {
- Element image = analysisPicBase64Info(li);
- images.add(image);
- }
- return images;
- }
- /**
- * createImage : (根据图片的base64加密文件创建pdf图片). <br/>
- *
- * @author
- * @param picBase64Info base64加密后的图片信息(支持台echart通过调用getDataURL()方法获取的图片信息)
- * @param title 段落标题
- * @param percentX 图片缩放比例X轴
- * @param percentY 图片缩放比例Y轴
- * @param titleCenter 标题是否居中,true-居中、false-默认居左
- * @return 返回图片段落
- * @since JDK 1.8
- */
- public static Paragraph createImageFromEncodeBase64(String picBase64Info, String title, float percentX,
- float percentY, boolean titleCenter)
- {
- // 1.获取图片
- Element element = analysisPicBase64Info(picBase64Info);
- // 2.创建段落,并添加标题
- Paragraph paragraph = new Paragraph(title, getTitleFont());
- // 空行
- paragraph.add(Chunk.NEWLINE);
- paragraph.add(Chunk.NEWLINE);
- if (!(element instanceof Image))
- {
- // 图片解析失败
- return paragraph;
- }
- Image image = (Image) element;
- // 3.设置图片缩放比例
- image.scalePercent(percentX, percentY);
- // 4.图片放入该段落
- paragraph.add(image);
- return paragraph;
- }
- /**
- * createImageFromEncodeBase64_batch : (批量创建). <br/>
- *
- * @author
- * @param picBase64Infos 图片base64加密后的信息(支持台echart通过调用getDataURL()方法获取的图片信息)
- * @param titles 段落标题
- * @param percentXs X轴缩放比例
- * @param percentYs Y轴缩放比例
- * @return
- * @since JDK 1.8
- */
- public static Paragraph createImageFromEncodeBase64_batch(List<String> picBase64Infos, List<String> titles,
- List<Float> percentXs, List<Float> percentYs)
- {
- Paragraph paragraphs = new Paragraph();
- for (int i = 0; i <= picBase64Infos.size(); i++)
- {
- Paragraph imagePara = createImageFromEncodeBase64(picBase64Infos.get(i), titles.get(i), percentXs.get(i),
- percentYs.get(i), titleCenter);
- if (!imagePara.isEmpty())
- {
- paragraphs.add(imagePara);
- // 空行
- paragraphs.add(Chunk.NEWLINE);
- paragraphs.add(Chunk.NEWLINE);
- }
- }
- return paragraphs;
- }
- /**
- * createTable : (创建table段落). <br/>
- *
- * @author
- * @param <T>
- * @param list 构建table的数据
- * @param title 该段落取的名字
- * @param methodNames 需要调用的方法名,用来获取单元格数据。通常是某个属性的get方法
- * @return
- * @since JDK 1.8
- */
- public static <T> Paragraph createTable(List<T> list, String title, String[] tableHead, List<String> methodNames)
- {
- return createTable(list, FONTSIZE_NORMAL, FONTSIZE_TITILE, title, tableHead, methodNames, false);
- }
- /**
- * createTableByList : (创建table段落). <br/>
- *
- * @author
- * @param <T>
- * @param listData
- * @param normalFontSize 正文字体大小
- * @param titleFontSize 标题字体大小
- * @param title 段落名称
- * @param methodNames 获取表格属性的方法名
- * @param b
- * @return
- * @since JDK 1.8
- */
- public static <T> Paragraph createTable(List<T> listData, float normalFontSize, float titleFontSize,
- String title, String[] tableHead, List<String> methodNames, boolean b)
- {
- // 1.创建一个段落
- Paragraph paragraph = new Paragraph(title, getTitleFont());
- // 空行
- paragraph.add(Chunk.NEWLINE);
- paragraph.add(Chunk.NEWLINE);
- // 3.创建一个表格
- PdfPTable table = new PdfPTable(methodNames.size());// 列数
- paragraph.add(table);
- // 4.构造表头
- for (String head : tableHead)
- {
- head = StringUtils.isEmpty(head) ? "" : head;
- PdfPCell cell = new PdfPCell(new Paragraph(head, getNormalFont()));
- cell.setBackgroundColor(
- new BaseColor(Integer.parseInt("124"), Integer.parseInt("185"), Integer.parseInt("252")));// 背景色
- cell.setMinimumHeight(Float.parseFloat("15"));// 单元格最小高度
- cell.setHorizontalAlignment(Element.ALIGN_CENTER);// 水平居中
- table.addCell(cell);
- }
- if (CollectionUtils.isEmpty(listData))
- {
- // 没有数据,添加一行空单元格,并返回
- for (int i = 0; i < methodNames.size(); i++)
- {
- table.addCell(new Paragraph(" "));// 有一个空格,否则添加不了
- }
- return paragraph;
- }
- // 5.构造table数据
- for (T li : listData)
- {
- for(String name : methodNames)
- {
- String valueStr = getfieldValue(li, name);
- PdfPCell cell = new PdfPCell(new Paragraph(valueStr, getNormalFont()));
- cell.setHorizontalAlignment(Element.ALIGN_CENTER);// 水平居中
- table.addCell(cell);
- }
- }
- // 5.返回
- return paragraph;
- }
- /**
- * addToTable : (从段落中找到第一个table,向该table中追加数据). <br/>
- * (). <br/>
- *
- * @author
- * @param <T>
- * @param paragraph
- * @param listData
- * @param methodNames
- * @since JDK 1.8
- */
- public static <T> void addToTable(Paragraph paragraph, List<T> listData, List<String> methodNames)
- {
- for (Element ele : paragraph)
- {
- if (!(ele instanceof PdfPTable))
- {
- // 不是table元素,直接跳过
- continue;
- }
- // 找到第一个table元素
- PdfPTable table = (PdfPTable) ele;
- for (T data : listData)
- {
- for (String name : methodNames)
- {
- String valueStr = getfieldValue(data, name);
- PdfPCell cell = new PdfPCell(new Paragraph(valueStr, getNormalFont()));
- cell.setHorizontalAlignment(Element.ALIGN_CENTER);// 水平居中
- table.addCell(cell);
- }
- }
- break;
- }
- }
- /**
- * exportDocument : (生成并下载PDF文档). <br/>
- * (). <br/>
- *
- * @author
- * @param document 文档对象
- * @param cover 封面:若不是null,则会先添加封面,并另起新页面添加段落
- * @param paragraphs 需要组成PDF文件的段落
- * @param response 请求的响应对象
- * @param fileName 生成的文件名称,不需要加pdf后缀
- * @since JDK 1.8
- */
- public static void exportDocument(Document document, Paragraph cover, List<Paragraph> paragraphs,
- HttpServletResponse response, String fileName)
- {
- try (ServletOutputStream out = response.getOutputStream())
- {
- response.setContentType("application/binary;charset=UTF-8");
- response.setHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(fileName + ".pdf", "UTF-8"));
- PdfWriter.getInstance(document, out);
- // 打开文档
- document.open();
- if (cover != null)
- {
- document.add(cover);
- // 起新页面
- document.newPage();
- }
- StringBuilder errorMsg = new StringBuilder();
- for (int i = 0; i < paragraphs.size(); i++)
- {
- try
- {
- // 将段落添加到文档
- document.add(paragraphs.get(i));
- // 空行
- document.add(Chunk.NEWLINE);
- document.add(Chunk.NEWLINE);
- }
- catch (DocumentException e)
- {
- errorMsg.append("PDF文件生成出错,请检查第:").append(i).append("个段落");
- }
- }
- if (!StringUtils.isEmpty(errorMsg.toString()))
- {
- logger.error(errorMsg);
- }
- // 关闭文档
- document.close();
- out.flush();
- out.close();
- }
- catch (Exception e)
- {
- logger.error("生成PDF文档并下载,出错:", e);
- }
- }
- /**
- * setDefaultIndentationLeft : (设置段落默认左边距). <br/>
- *
- * @author
- * @param paragraph
- * @since JDK 1.8
- */
- public static void setDefaultIndentationLeft(Paragraph paragraph)
- {
- paragraph.setIndentationLeft(Float.parseFloat("30"));
- }
- /**
- * addBlankLine : (添加空行). <br/>
- *
- * @author
- * @param paragraph 需要添加空行的段落
- * @param lineNum 需要添加空行的个数
- * @since JDK 1.8
- */
- public static void addBlankLine(Paragraph paragraph, int lineNum)
- {
- if (paragraph == null)
- {
- return;
- }
- for (int i = 0; i < lineNum; i++)
- {
- paragraph.add(Chunk.NEWLINE);
- }
- }
- }
|