PDFUtil.java 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  1. package com.ruoyi.common.utils;
  2. import com.itextpdf.text.*;
  3. import com.itextpdf.text.pdf.BaseFont;
  4. import com.itextpdf.text.pdf.PdfPCell;
  5. import com.itextpdf.text.pdf.PdfPTable;
  6. import com.itextpdf.text.pdf.PdfWriter;
  7. import org.apache.commons.codec.binary.Base64;
  8. import org.apache.commons.collections4.CollectionUtils;
  9. import org.apache.logging.log4j.LogManager;
  10. import org.apache.logging.log4j.Logger;
  11. import javax.servlet.ServletOutputStream;
  12. import javax.servlet.http.HttpServletResponse;
  13. import java.lang.reflect.Method;
  14. import java.net.URL;
  15. import java.net.URLEncoder;
  16. import java.util.ArrayList;
  17. import java.util.List;
  18. /**
  19. * @Author: tjf
  20. * @Date: 2024/3/18 15:34
  21. * @Describe:
  22. */
  23. public class PDFUtil {
  24. private static final Logger logger = LogManager.getLogger(PDFUtil.class);
  25. /**
  26. * fontSize_normal : (正文字体大小11号).
  27. */
  28. public static final float FONTSIZE_NORMAL = 11f;
  29. /**
  30. * fontSize_titile : (标题字体大小14号).
  31. */
  32. public static final float FONTSIZE_TITILE = 14f;
  33. /**
  34. * FONTSIZE_COVER : (封面字体大小32号).
  35. */
  36. public static final float FONTSIZE_COVER = 32f;
  37. /**
  38. * normalFont : (通用字体样式:宋体、11号).
  39. */
  40. private static Font normalFont = null;
  41. /**
  42. * titleFont : (通用标题字体样式:宋体、14号、加粗).
  43. */
  44. private static Font titleFont = null;
  45. /**
  46. * coverFont : (通用封面字体样式:宋体、28号、加粗).
  47. */
  48. private static Font coverFont = null;
  49. /**
  50. * 标题是否居中,true-居中、false-默认居左
  51. */
  52. private static final Boolean titleCenter = true;
  53. /**
  54. * getBaseFont : (获取可以解析中文的字体:使用宋体). <br/>
  55. *
  56. * @author
  57. * @return
  58. * @since JDK 1.8
  59. */
  60. public static BaseFont getBaseFontChinese()
  61. {
  62. try
  63. {
  64. // 宋体资源文件路径,可以从C://Windows//Fonts//simsun.ttc拷贝到相应目录下
  65. URL path = PDFUtil.class.getResource("/config/fonts/simsun.ttc");
  66. return BaseFont.createFont(path + ",0", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
  67. // 本地测试:使用windows自带的宋体文件
  68. //return BaseFont.createFont("C://Windows//Fonts//simsun.ttc,0", BaseFont.IDENTITY_H, false);
  69. }
  70. catch (Exception e)
  71. {
  72. logger.error("设置字体样式失败", e);
  73. return null;
  74. }
  75. }
  76. /**
  77. * getNormalFont : (获取普通字体样式). <br/>
  78. *
  79. * @author
  80. * @return
  81. * @since JDK 1.8
  82. */
  83. public static Font getNormalFont()
  84. {
  85. if (normalFont == null)
  86. {
  87. BaseFont bfChinese = getBaseFontChinese();
  88. normalFont = new Font(bfChinese, FONTSIZE_NORMAL, Font.NORMAL);
  89. }
  90. return normalFont;
  91. }
  92. /**
  93. * getTitleFont : (获取标题通用字体). <br/>
  94. *
  95. * @author
  96. * @return
  97. * @since JDK 1.8
  98. */
  99. public static Font getTitleFont()
  100. {
  101. if (titleFont == null)
  102. {
  103. BaseFont bfChinese = getBaseFontChinese();
  104. titleFont = new Font(bfChinese, FONTSIZE_TITILE, Font.BOLD);
  105. }
  106. return titleFont;
  107. }
  108. /**
  109. * getTitleFont : (获取封面通用字体). <br/>
  110. *
  111. * @author
  112. * @return
  113. * @since JDK 1.8
  114. */
  115. public static Font getCoverFontFont()
  116. {
  117. if (coverFont == null)
  118. {
  119. BaseFont bfChinese = getBaseFontChinese();
  120. coverFont = new Font(bfChinese, FONTSIZE_COVER, Font.BOLD);
  121. }
  122. return coverFont;
  123. }
  124. /**
  125. * getfieldValue : (通过反射,根据方法名,执行方法,最终返回行结果的toString值). <br/>
  126. *
  127. * @author
  128. * @param <T> 方法执行的入参
  129. * @param object 对象
  130. * @param methodName 方法名
  131. * @param args 方法执行参数
  132. * @since JDK 1.8
  133. */
  134. private static <T> String getfieldValue(T object, String methodName, Object... args)
  135. {
  136. try
  137. {
  138. Method method = object.getClass().getMethod(methodName);
  139. Object value = method.invoke(object, args);
  140. return value == null ? "" : value.toString();
  141. }
  142. catch (Exception e)
  143. {
  144. logger.error("getfieldValue error", e);
  145. return "";
  146. }
  147. }
  148. /**
  149. * analysisPicBase64Info : (解析base64图片信息). <br/>
  150. *
  151. * @author
  152. * @param picBase64Info 图片base64信息,或前台echart通过调用getDataURL()方法获取的图片信息
  153. * @return 图片经过base64解码后的信息
  154. * @since JDK 1.8
  155. */
  156. public static Element analysisPicBase64Info(String picBase64Info)
  157. {
  158. if (StringUtils.isEmpty(picBase64Info))
  159. {
  160. // 空段落
  161. return new Paragraph();
  162. }
  163. // 1.获取图片base64字符串信息:若入参是通过前台echarts调用getDataURL()方法获取的,则该字符串包含逗号,且则逗号后面的内容才是图片的信息
  164. String pictureInfoStr = picBase64Info.indexOf(",") == -1 ? picBase64Info : picBase64Info.split(",")[1];
  165. // 2.将图片信息进行base64解密
  166. byte[] imgByte = Base64.decodeBase64(pictureInfoStr);
  167. // 对异常的数据进行处理
  168. /**
  169. * .图片的原始表达ascii码范围是0-255,
  170. * .这里面有一些不可见的编码。然后为了图片正确传输才转成编码base64的0-63,
  171. * .当从base64转成byte时,byte的范围是[-128,127],那么此时就会可能产生负数,而负数不是在ascii的范围里,所以需要转换一下
  172. */
  173. for (int i = 0; i < imgByte.length; i++)
  174. {
  175. if (imgByte[i] < 0)
  176. {
  177. imgByte[i] += 256;
  178. }
  179. }
  180. try
  181. {
  182. return Image.getInstance(imgByte);
  183. }
  184. catch (Exception e)
  185. {
  186. logger.error("analysisPicBase64Info error", e);
  187. return new Paragraph();
  188. }
  189. }
  190. /**
  191. * analysisPicBase64Info_batch : (批量解析base64加密的图片信息,生成Image对象). <br/>
  192. *
  193. * @author
  194. * @param picBase64Infos 经过base64加密的图片信息
  195. * @return
  196. * @since JDK 1.8
  197. */
  198. public static List<Element> analysisPicBase64Info_batch(List<String> picBase64Infos)
  199. {
  200. List<Element> images = new ArrayList<Element>();
  201. for (String li : picBase64Infos)
  202. {
  203. Element image = analysisPicBase64Info(li);
  204. images.add(image);
  205. }
  206. return images;
  207. }
  208. /**
  209. * createImage : (根据图片的base64加密文件创建pdf图片). <br/>
  210. *
  211. * @author
  212. * @param picBase64Info base64加密后的图片信息(支持台echart通过调用getDataURL()方法获取的图片信息)
  213. * @param title 段落标题
  214. * @param percentX 图片缩放比例X轴
  215. * @param percentY 图片缩放比例Y轴
  216. * @param titleCenter 标题是否居中,true-居中、false-默认居左
  217. * @return 返回图片段落
  218. * @since JDK 1.8
  219. */
  220. public static Paragraph createImageFromEncodeBase64(String picBase64Info, String title, float percentX,
  221. float percentY, boolean titleCenter)
  222. {
  223. // 1.获取图片
  224. Element element = analysisPicBase64Info(picBase64Info);
  225. // 2.创建段落,并添加标题
  226. Paragraph paragraph = new Paragraph(title, getTitleFont());
  227. // 空行
  228. paragraph.add(Chunk.NEWLINE);
  229. paragraph.add(Chunk.NEWLINE);
  230. if (!(element instanceof Image))
  231. {
  232. // 图片解析失败
  233. return paragraph;
  234. }
  235. Image image = (Image) element;
  236. // 3.设置图片缩放比例
  237. image.scalePercent(percentX, percentY);
  238. // 4.图片放入该段落
  239. paragraph.add(image);
  240. return paragraph;
  241. }
  242. /**
  243. * createImageFromEncodeBase64_batch : (批量创建). <br/>
  244. *
  245. * @author
  246. * @param picBase64Infos 图片base64加密后的信息(支持台echart通过调用getDataURL()方法获取的图片信息)
  247. * @param titles 段落标题
  248. * @param percentXs X轴缩放比例
  249. * @param percentYs Y轴缩放比例
  250. * @return
  251. * @since JDK 1.8
  252. */
  253. public static Paragraph createImageFromEncodeBase64_batch(List<String> picBase64Infos, List<String> titles,
  254. List<Float> percentXs, List<Float> percentYs)
  255. {
  256. Paragraph paragraphs = new Paragraph();
  257. for (int i = 0; i <= picBase64Infos.size(); i++)
  258. {
  259. Paragraph imagePara = createImageFromEncodeBase64(picBase64Infos.get(i), titles.get(i), percentXs.get(i),
  260. percentYs.get(i), titleCenter);
  261. if (!imagePara.isEmpty())
  262. {
  263. paragraphs.add(imagePara);
  264. // 空行
  265. paragraphs.add(Chunk.NEWLINE);
  266. paragraphs.add(Chunk.NEWLINE);
  267. }
  268. }
  269. return paragraphs;
  270. }
  271. /**
  272. * createTable : (创建table段落). <br/>
  273. *
  274. * @author
  275. * @param <T>
  276. * @param list 构建table的数据
  277. * @param title 该段落取的名字
  278. * @param methodNames 需要调用的方法名,用来获取单元格数据。通常是某个属性的get方法
  279. * @return
  280. * @since JDK 1.8
  281. */
  282. public static <T> Paragraph createTable(List<T> list, String title, String[] tableHead, List<String> methodNames)
  283. {
  284. return createTable(list, FONTSIZE_NORMAL, FONTSIZE_TITILE, title, tableHead, methodNames, false);
  285. }
  286. /**
  287. * createTableByList : (创建table段落). <br/>
  288. *
  289. * @author
  290. * @param <T>
  291. * @param listData
  292. * @param normalFontSize 正文字体大小
  293. * @param titleFontSize 标题字体大小
  294. * @param title 段落名称
  295. * @param methodNames 获取表格属性的方法名
  296. * @param b
  297. * @return
  298. * @since JDK 1.8
  299. */
  300. public static <T> Paragraph createTable(List<T> listData, float normalFontSize, float titleFontSize,
  301. String title, String[] tableHead, List<String> methodNames, boolean b)
  302. {
  303. // 1.创建一个段落
  304. Paragraph paragraph = new Paragraph(title, getTitleFont());
  305. // 空行
  306. paragraph.add(Chunk.NEWLINE);
  307. paragraph.add(Chunk.NEWLINE);
  308. // 3.创建一个表格
  309. PdfPTable table = new PdfPTable(methodNames.size());// 列数
  310. paragraph.add(table);
  311. // 4.构造表头
  312. for (String head : tableHead)
  313. {
  314. head = StringUtils.isEmpty(head) ? "" : head;
  315. PdfPCell cell = new PdfPCell(new Paragraph(head, getNormalFont()));
  316. cell.setBackgroundColor(
  317. new BaseColor(Integer.parseInt("124"), Integer.parseInt("185"), Integer.parseInt("252")));// 背景色
  318. cell.setMinimumHeight(Float.parseFloat("15"));// 单元格最小高度
  319. cell.setHorizontalAlignment(Element.ALIGN_CENTER);// 水平居中
  320. table.addCell(cell);
  321. }
  322. if (CollectionUtils.isEmpty(listData))
  323. {
  324. // 没有数据,添加一行空单元格,并返回
  325. for (int i = 0; i < methodNames.size(); i++)
  326. {
  327. table.addCell(new Paragraph(" "));// 有一个空格,否则添加不了
  328. }
  329. return paragraph;
  330. }
  331. // 5.构造table数据
  332. for (T li : listData)
  333. {
  334. for(String name : methodNames)
  335. {
  336. String valueStr = getfieldValue(li, name);
  337. PdfPCell cell = new PdfPCell(new Paragraph(valueStr, getNormalFont()));
  338. cell.setHorizontalAlignment(Element.ALIGN_CENTER);// 水平居中
  339. table.addCell(cell);
  340. }
  341. }
  342. // 5.返回
  343. return paragraph;
  344. }
  345. /**
  346. * addToTable : (从段落中找到第一个table,向该table中追加数据). <br/>
  347. * (). <br/>
  348. *
  349. * @author
  350. * @param <T>
  351. * @param paragraph
  352. * @param listData
  353. * @param methodNames
  354. * @since JDK 1.8
  355. */
  356. public static <T> void addToTable(Paragraph paragraph, List<T> listData, List<String> methodNames)
  357. {
  358. for (Element ele : paragraph)
  359. {
  360. if (!(ele instanceof PdfPTable))
  361. {
  362. // 不是table元素,直接跳过
  363. continue;
  364. }
  365. // 找到第一个table元素
  366. PdfPTable table = (PdfPTable) ele;
  367. for (T data : listData)
  368. {
  369. for (String name : methodNames)
  370. {
  371. String valueStr = getfieldValue(data, name);
  372. PdfPCell cell = new PdfPCell(new Paragraph(valueStr, getNormalFont()));
  373. cell.setHorizontalAlignment(Element.ALIGN_CENTER);// 水平居中
  374. table.addCell(cell);
  375. }
  376. }
  377. break;
  378. }
  379. }
  380. /**
  381. * exportDocument : (生成并下载PDF文档). <br/>
  382. * (). <br/>
  383. *
  384. * @author
  385. * @param document 文档对象
  386. * @param cover 封面:若不是null,则会先添加封面,并另起新页面添加段落
  387. * @param paragraphs 需要组成PDF文件的段落
  388. * @param response 请求的响应对象
  389. * @param fileName 生成的文件名称,不需要加pdf后缀
  390. * @since JDK 1.8
  391. */
  392. public static void exportDocument(Document document, Paragraph cover, List<Paragraph> paragraphs,
  393. HttpServletResponse response, String fileName)
  394. {
  395. try (ServletOutputStream out = response.getOutputStream())
  396. {
  397. response.setContentType("application/binary;charset=UTF-8");
  398. response.setHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(fileName + ".pdf", "UTF-8"));
  399. PdfWriter.getInstance(document, out);
  400. // 打开文档
  401. document.open();
  402. if (cover != null)
  403. {
  404. document.add(cover);
  405. // 起新页面
  406. document.newPage();
  407. }
  408. StringBuilder errorMsg = new StringBuilder();
  409. for (int i = 0; i < paragraphs.size(); i++)
  410. {
  411. try
  412. {
  413. // 将段落添加到文档
  414. document.add(paragraphs.get(i));
  415. // 空行
  416. document.add(Chunk.NEWLINE);
  417. document.add(Chunk.NEWLINE);
  418. }
  419. catch (DocumentException e)
  420. {
  421. errorMsg.append("PDF文件生成出错,请检查第:").append(i).append("个段落");
  422. }
  423. }
  424. if (!StringUtils.isEmpty(errorMsg.toString()))
  425. {
  426. logger.error(errorMsg);
  427. }
  428. // 关闭文档
  429. document.close();
  430. out.flush();
  431. out.close();
  432. }
  433. catch (Exception e)
  434. {
  435. logger.error("生成PDF文档并下载,出错:", e);
  436. }
  437. }
  438. /**
  439. * setDefaultIndentationLeft : (设置段落默认左边距). <br/>
  440. *
  441. * @author
  442. * @param paragraph
  443. * @since JDK 1.8
  444. */
  445. public static void setDefaultIndentationLeft(Paragraph paragraph)
  446. {
  447. paragraph.setIndentationLeft(Float.parseFloat("30"));
  448. }
  449. /**
  450. * addBlankLine : (添加空行). <br/>
  451. *
  452. * @author
  453. * @param paragraph 需要添加空行的段落
  454. * @param lineNum 需要添加空行的个数
  455. * @since JDK 1.8
  456. */
  457. public static void addBlankLine(Paragraph paragraph, int lineNum)
  458. {
  459. if (paragraph == null)
  460. {
  461. return;
  462. }
  463. for (int i = 0; i < lineNum; i++)
  464. {
  465. paragraph.add(Chunk.NEWLINE);
  466. }
  467. }
  468. }