| @ -0,0 +1,388 @@ | |||||
| package com.hxhq.business.utils; | |||||
| import com.hxhq.common.core.utils.DateUtils; | |||||
| import com.itextpdf.text.*; | |||||
| import com.itextpdf.text.Font; | |||||
| import com.itextpdf.text.pdf.*; | |||||
| import com.itextpdf.text.pdf.draw.LineSeparator; | |||||
| import lombok.SneakyThrows; | |||||
| import org.slf4j.Logger; | |||||
| import org.slf4j.LoggerFactory; | |||||
| import org.springframework.stereotype.Component; | |||||
| import java.io.File; | |||||
| import java.io.FileOutputStream; | |||||
| import java.io.IOException; | |||||
| import java.nio.file.Paths; | |||||
| import java.util.*; | |||||
| import java.util.List; | |||||
| /** | |||||
| * OpenPDF工具类 - 支持保存到本地文件 | |||||
| */ | |||||
| @Component | |||||
| public class PdfUtil { | |||||
| private static final Logger log = LoggerFactory.getLogger(PdfUtil.class); | |||||
| public static void main(String[] args) { | |||||
| String fileDir="d:/"; | |||||
| exportForm("张三 2025-12-12 14:52:52",fileDir); | |||||
| exportTable("张三 2025-12-12 14:52:52",fileDir); | |||||
| } | |||||
| /** | |||||
| * 导出form | |||||
| * @param qm | |||||
| * @param fileDir | |||||
| * @return | |||||
| */ | |||||
| public static String exportForm(String qm,String fileDir) { | |||||
| Document document = null; | |||||
| FileOutputStream fos = null; | |||||
| String filePath = ""; | |||||
| try { | |||||
| // 1. 生成文件名 | |||||
| String fileName = generateFileName(); | |||||
| // 2. 确保目录存在 | |||||
| File dir = new File(fileDir); | |||||
| if (!dir.exists()) { | |||||
| dir.mkdirs(); | |||||
| } | |||||
| // 3. 完整文件路径 | |||||
| filePath = Paths.get(fileDir, fileName).toString(); | |||||
| // 4. 创建PDF文档 | |||||
| document = new Document(PageSize.A4); | |||||
| fos = new FileOutputStream(filePath); | |||||
| PdfWriter writer = PdfWriter.getInstance(document, fos); | |||||
| // 使用完整的页脚处理器 | |||||
| writer.setPageEvent(new CompletePdfFooter()); | |||||
| // 5. 设置PDF属性 | |||||
| document.addTitle("华西海圻"); | |||||
| document.addAuthor("华西海圻"); | |||||
| document.addCreator("华西海圻"); | |||||
| document.addCreationDate(); | |||||
| document.open(); | |||||
| // 6. 设置中文字体 | |||||
| BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED); | |||||
| Font titleFont = new Font(bfChinese, 16, Font.BOLD); | |||||
| // 7. 添加签名 | |||||
| Paragraph title = new Paragraph(qm, titleFont); | |||||
| title.setAlignment(Element.ALIGN_CENTER); | |||||
| title.setSpacingAfter(20); | |||||
| document.add(title); | |||||
| //横线 | |||||
| LineSeparator line = new LineSeparator(); | |||||
| line.setLineWidth(0.5f); | |||||
| // 设置100%宽度 | |||||
| line.setPercentage(100f); // 百分比宽度 | |||||
| // 设置对齐方式 | |||||
| line.setAlignment(Element.ALIGN_CENTER); // Element.ALIGN_CENTER, Element.ALIGN_LEFT等 | |||||
| // 设置颜色 | |||||
| line.setLineColor(BaseColor.BLACK); | |||||
| // 添加到文档 | |||||
| document.add(line); | |||||
| Map<String, String> formData = new LinkedHashMap<>(); | |||||
| formData.put("姓名","张三"); | |||||
| formData.put("性别", "男"); | |||||
| formData.put("手机","15882062878"); | |||||
| // 生成table | |||||
| addFormTableColumns(document, formData,3); | |||||
| log.info("生成成功:{}", filePath); | |||||
| } catch (Exception e) { | |||||
| log.error("生成失败", e); | |||||
| throw new RuntimeException("生成失败: " + e.getMessage()); | |||||
| } finally { | |||||
| if (document != null) { | |||||
| document.close(); | |||||
| } | |||||
| if (fos != null) { | |||||
| try { | |||||
| fos.close(); | |||||
| } catch (IOException e) { | |||||
| log.error("关闭文件流失败", e); | |||||
| } | |||||
| } | |||||
| } | |||||
| return filePath; | |||||
| } | |||||
| /** | |||||
| * 导出table | |||||
| * @param qm | |||||
| * @param fileDir | |||||
| * @return | |||||
| */ | |||||
| public static String exportTable( String qm,String fileDir) { | |||||
| Document document = null; | |||||
| FileOutputStream fos = null; | |||||
| String filePath = ""; | |||||
| try { | |||||
| // 1. 生成文件名 | |||||
| String fileName = generateFileName(); | |||||
| // 2. 确保目录存在 | |||||
| File dir = new File(fileDir); | |||||
| if (!dir.exists()) { | |||||
| dir.mkdirs(); | |||||
| } | |||||
| // 3. 完整文件路径 | |||||
| filePath = Paths.get(fileDir, fileName).toString(); | |||||
| // 4. 创建PDF文档 | |||||
| document = new Document(PageSize.A4); | |||||
| fos = new FileOutputStream(filePath); | |||||
| PdfWriter writer = PdfWriter.getInstance(document, fos); | |||||
| // 使用完整的页脚处理器 | |||||
| writer.setPageEvent(new CompletePdfFooter()); | |||||
| // 5. 设置PDF属性 | |||||
| document.addTitle("华西海圻"); | |||||
| document.addAuthor("华西海圻"); | |||||
| document.addCreator("华西海圻"); | |||||
| document.addCreationDate(); | |||||
| document.open(); | |||||
| // 6. 设置中文字体 | |||||
| BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED); | |||||
| Font titleFont = new Font(bfChinese, 18, Font.BOLD); | |||||
| Font headerFont = new Font(bfChinese, 12, Font.BOLD); | |||||
| Font contentFont = new Font(bfChinese, 10, Font.NORMAL); | |||||
| // 7. 添加签名 | |||||
| Paragraph title = new Paragraph(qm, titleFont); | |||||
| title.setAlignment(Element.ALIGN_CENTER); | |||||
| title.setSpacingAfter(20); | |||||
| document.add(title); | |||||
| //横线 | |||||
| LineSeparator line = new LineSeparator(); | |||||
| line.setLineWidth(0.5f); | |||||
| // 设置100%宽度 | |||||
| line.setPercentage(100f); // 百分比宽度 | |||||
| // 设置对齐方式 | |||||
| line.setAlignment(Element.ALIGN_CENTER); // Element.ALIGN_CENTER, Element.ALIGN_LEFT等 | |||||
| // 设置颜色 | |||||
| line.setLineColor(BaseColor.BLACK); | |||||
| // 添加到文档 | |||||
| document.add(line); | |||||
| // 9. 创建表格 | |||||
| PdfPTable table = new PdfPTable(3); | |||||
| table.setWidthPercentage(100); | |||||
| table.setSpacingBefore(10); | |||||
| // 10. 表头 | |||||
| String[] headers = {"签名人", "签名意义", "签名时间"}; | |||||
| for (String header : headers) { | |||||
| PdfPCell cell = new PdfPCell(new Phrase(header, headerFont)); | |||||
| cell.setHorizontalAlignment(Element.ALIGN_CENTER); | |||||
| cell.setBackgroundColor(BaseColor.WHITE); | |||||
| cell.setPadding(8); | |||||
| cell.setBorderWidth(1); | |||||
| table.addCell(cell); | |||||
| } | |||||
| // 11. 表格数据 | |||||
| int rowNum = 0; | |||||
| for (int i = 0; i < 3; i ++) { | |||||
| // 交替行颜色 | |||||
| if (rowNum % 2 == 0) { | |||||
| table.getDefaultCell().setBackgroundColor(BaseColor.WHITE); | |||||
| } else { | |||||
| table.getDefaultCell().setBackgroundColor(BaseColor.WHITE); | |||||
| } | |||||
| table.addCell(createCell("1", contentFont)); | |||||
| table.addCell(createCell("2", contentFont)); | |||||
| table.addCell(createCell("3", contentFont)); | |||||
| rowNum++; | |||||
| } | |||||
| document.add(table); | |||||
| log.info("生成成功:{}", filePath); | |||||
| } catch (Exception e) { | |||||
| log.error("生成失败", e); | |||||
| throw new RuntimeException("生成失败: " + e.getMessage()); | |||||
| } finally { | |||||
| if (document != null) { | |||||
| document.close(); | |||||
| } | |||||
| if (fos != null) { | |||||
| try { | |||||
| fos.close(); | |||||
| } catch (IOException e) { | |||||
| log.error("关闭文件流失败", e); | |||||
| } | |||||
| } | |||||
| } | |||||
| return filePath; | |||||
| } | |||||
| /** | |||||
| * 创建表单 | |||||
| */ | |||||
| public static void addFormTableColumns(Document document, Map<String, String> data, Integer count) | |||||
| throws DocumentException, IOException { | |||||
| // 创建3列表格 | |||||
| PdfPTable table = new PdfPTable(count); | |||||
| table.setWidthPercentage(100); | |||||
| table.setSpacingBefore(10); | |||||
| table.setSpacingAfter(10); | |||||
| // 设置等宽列 | |||||
| table.setWidths(new float[]{1, 1, 1}); | |||||
| // 创建字体 | |||||
| BaseFont baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED); | |||||
| Font labelFont = new Font(baseFont, 10, Font.NORMAL, BaseColor.BLACK); | |||||
| Font valueFont = new Font(baseFont, 10, Font.NORMAL, BaseColor.BLACK); | |||||
| // 将数据转换为List | |||||
| List<Map.Entry<String, String>> entries = new ArrayList<>(data.entrySet()); | |||||
| // 每3个字段一组,每组一行 | |||||
| for (int i = 0; i < entries.size(); i += count) { | |||||
| // 创建一行 | |||||
| for (int j = 0; j < count; j++) { | |||||
| int index = i + j; | |||||
| if (index < entries.size()) { | |||||
| Map.Entry<String, String> entry = entries.get(index); | |||||
| // 创建单元格内容 | |||||
| Phrase cellContent = new Phrase(); | |||||
| cellContent.add(new Chunk(entry.getKey() + ":", labelFont)); | |||||
| cellContent.add(new Chunk(com.hxhq.common.core.utils.StringUtils.isBlank(entry.getValue()) ? "" : entry.getValue(), valueFont)); | |||||
| PdfPCell cell = new PdfPCell(cellContent); | |||||
| cell.setBorder(Rectangle.NO_BORDER); // 无边框 | |||||
| cell.setPadding(4); | |||||
| cell.setHorizontalAlignment(Element.ALIGN_LEFT); | |||||
| cell.setVerticalAlignment(Element.ALIGN_MIDDLE); | |||||
| cell.setMinimumHeight(20); // 最小高度 | |||||
| table.addCell(cell); | |||||
| } else { | |||||
| // 添加空单元格保持布局 | |||||
| PdfPCell emptyCell = new PdfPCell(); | |||||
| emptyCell.setBorder(Rectangle.NO_BORDER); | |||||
| emptyCell.setMinimumHeight(20); | |||||
| table.addCell(emptyCell); | |||||
| } | |||||
| } | |||||
| } | |||||
| document.add(table); | |||||
| } | |||||
| /** | |||||
| * 创建表格 | |||||
| */ | |||||
| private static PdfPCell createCell(String content, Font font) { | |||||
| PdfPCell cell = new PdfPCell(new Phrase(content, font)); | |||||
| cell.setHorizontalAlignment(Element.ALIGN_CENTER); | |||||
| cell.setVerticalAlignment(Element.ALIGN_MIDDLE); | |||||
| cell.setPadding(6); | |||||
| cell.setMinimumHeight(25); | |||||
| return cell; | |||||
| } | |||||
| /** | |||||
| * 生成文件名 | |||||
| */ | |||||
| private static String generateFileName() { | |||||
| Random random= new Random(); | |||||
| String timestamp = DateUtils.dateTimeNow("yyyyMMddHHmmss"); | |||||
| return String.format("%s_%s.pdf", timestamp,random.nextInt(1000)); | |||||
| } | |||||
| /** | |||||
| * 页脚 | |||||
| */ | |||||
| public static class CompletePdfFooter extends PdfPageEventHelper { | |||||
| private Font footerFont; | |||||
| private PdfTemplate total; | |||||
| private int totalPages = 0; | |||||
| public CompletePdfFooter() { | |||||
| try { | |||||
| BaseFont baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED); | |||||
| footerFont = new Font(baseFont, 10, Font.NORMAL, BaseColor.GRAY); | |||||
| } catch (Exception e) { | |||||
| footerFont = new Font(Font.FontFamily.HELVETICA, 10, Font.NORMAL, BaseColor.GRAY); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public void onOpenDocument(PdfWriter writer, Document document) { | |||||
| total = writer.getDirectContent().createTemplate(100, 16); | |||||
| } | |||||
| @SneakyThrows | |||||
| @Override | |||||
| public void onEndPage(PdfWriter writer, Document document) { | |||||
| PdfContentByte cb = writer.getDirectContent(); | |||||
| int currentPage = writer.getPageNumber(); | |||||
| float y = document.bottom() - 20; | |||||
| // 在每一页都重新计算,确保位置准确 | |||||
| String pageText = "第 " + currentPage + " 页 / 共 "; | |||||
| BaseFont baseFont = footerFont.getBaseFont(); | |||||
| // 计算页面文本宽度 | |||||
| float pageTextWidth = baseFont.getWidthPoint(pageText, footerFont.getSize()); | |||||
| // 临时用估计的总页数计算位置(等文档关闭后会被替换) | |||||
| String tempTotal = String.valueOf(writer.getPageNumber() + 5); // 估计值 | |||||
| float totalWidth = baseFont.getWidthPoint(tempTotal, footerFont.getSize()); | |||||
| // 计算起始位置 | |||||
| float totalTextWidth = pageTextWidth + totalWidth + | |||||
| baseFont.getWidthPoint(" 页", footerFont.getSize()); | |||||
| float startX = (document.getPageSize().getWidth() - totalTextWidth) / 2; | |||||
| // 写入当前页信息 | |||||
| ColumnText.showTextAligned(cb, Element.ALIGN_LEFT, | |||||
| new Phrase(pageText, footerFont), startX, y, 0); | |||||
| // 添加总页数模板 | |||||
| cb.addTemplate(total, startX + pageTextWidth, y); | |||||
| // 写入"页"字 | |||||
| ColumnText.showTextAligned(cb, Element.ALIGN_LEFT, | |||||
| new Phrase(" 页", footerFont), startX + pageTextWidth + totalWidth, y, 0); | |||||
| } | |||||
| @Override | |||||
| public void onCloseDocument(PdfWriter writer, Document document) { | |||||
| totalPages = writer.getPageNumber(); | |||||
| String totalPagesStr = String.valueOf(totalPages); | |||||
| // 重新创建合适宽度的模板 | |||||
| BaseFont baseFont = footerFont.getBaseFont(); | |||||
| total.beginText(); | |||||
| total.setFontAndSize(baseFont, footerFont.getSize()); | |||||
| total.setTextMatrix(0, 0); | |||||
| total.showText(totalPagesStr); | |||||
| total.endText(); | |||||
| } | |||||
| } | |||||
| } | |||||