新增pdf页码

master
yangzhe123 2025-09-04 10:45:11 +08:00
parent 1739cfadef
commit b8c07682b9
1 changed files with 151 additions and 66 deletions

View File

@ -7,6 +7,7 @@ import com.gunshi.project.hsz.mapper.ForecastProjectMapper;
import com.gunshi.project.hsz.model.ForecastProject;
import com.gunshi.project.hsz.model.ForecastResults;
import com.itextpdf.io.font.PdfEncodings;
import com.itextpdf.kernel.colors.Color;
import com.itextpdf.kernel.colors.DeviceRgb;
import com.itextpdf.kernel.events.Event;
import com.itextpdf.kernel.events.IEventHandler;
@ -19,6 +20,7 @@ import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.kernel.pdf.colorspace.PdfColorSpace;
import com.itextpdf.layout.Canvas;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Cell;
@ -32,6 +34,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.awt.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
@ -92,93 +95,175 @@ public class ForecastProjectService extends ServiceImpl<ForecastProjectMapper, F
}
public void generatePdf(ForecastProject project, ZipOutputStream outputStream) {
// 第一次生成:获取总页码
int totalPages = calculateTotalPages(project);
// 使用ByteArrayOutputStream作为缓冲
// 第二次生成正式生成带页码的PDF
generatePdfWithPageNumbers(project, outputStream, totalPages);
}
// 第一次生成:计算总页码
private int calculateTotalPages(ForecastProject project) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PdfDocument pdfDoc = null;
Document document = null;
try{
PdfWriter writer = new PdfWriter(baos);
pdfDoc = new PdfDocument(writer);
document = new Document(pdfDoc,PageSize.A4.rotate());
// 设置字体
String fontPath = "/fonts/ChineseFonts.ttf";
PdfFont font = PdfFontFactory.createFont(fontPath, PdfEncodings.IDENTITY_H);
// 第一行:基本信息
Paragraph header1 = new Paragraph()
.add("预见期(小时): " + (project.getForecastPeriod() != null ? project.getForecastPeriod() : "无") + " ")
.add("预热期(天): " + (project.getForecastWarm() != null ? project.getForecastWarm() : "无") + " ")
.add("预报时间: " + formatDate(project.getForecastTm()) + " ")
.add("开始时间: " + formatDate(project.getStartTm()) + " ")
.add("结束时间: " + formatDate(project.getEndTm()))
.setFont(font)
.setFontSize(10);
document.add(header1);
document.add(new Paragraph("\n"));
try (PdfWriter writer = new PdfWriter(baos);
PdfDocument pdfDoc = new PdfDocument(writer);
Document document = new Document(pdfDoc, PageSize.A4.rotate())) {
// 第二行:统计信息
Paragraph header2 = new Paragraph()
.add("预报最高调洪水位(m): " + (project.getYcMaxSwH() != null ? project.getYcMaxSwH().setScale(2, RoundingMode.HALF_UP) : "无") + " ")
.add("预报最大入库流量(m³/s): " + (project.getYcMaxRkQ() != null ? project.getYcMaxRkQ().setScale(2, RoundingMode.HALF_UP) : "无") + " ")
.add("预报最大下泄流量(m³/s): " + (project.getYcMaxCkQ() != null ? project.getYcMaxCkQ().setScale(2, RoundingMode.HALF_UP) : "无") + " ")
.add("预报洪水总量(万m³): " + (project.getYcSumFlood() != null ? project.getYcSumFlood().setScale(2, RoundingMode.HALF_UP) : "无"))
.setFont(font)
.setFontSize(10);
// 设置字体
String fontPath = "/fonts/ChineseFonts.ttf";
PdfFont font = PdfFontFactory.createFont(fontPath, PdfEncodings.IDENTITY_H);
document.add(header2);
document.add(new Paragraph("\n"));
// 添加内容(但不添加页码)
addContentToDocument(document, font, project, false);
// 第三行:数据表格
if (CollectionUtils.isNotEmpty(project.getVoList())) {
Table table = new Table(9); // 10列
table.setWidth(UnitValue.createPercentValue(100));
// 返回总页数
return pdfDoc.getNumberOfPages();
// 表头
table.addHeaderCell(createCell("序号", font, true));
table.addHeaderCell(createCell("时间", font, true));
table.addHeaderCell(createCell("降雨量(mm)", font, true));
table.addHeaderCell(createCell("实测水位(m)", font, true));
table.addHeaderCell(createCell("预报水位(m)", font, true));
table.addHeaderCell(createCell("入库流量(m³/s)", font, true));
table.addHeaderCell(createCell("预报入库流量(m³/s)", font, true));
table.addHeaderCell(createCell("实际出库流量(m³/s)", font, true));
table.addHeaderCell(createCell("预报出库流量(m³/s)", font, true));
} catch (Exception e) {
log.error("计算PDF页数失败", e);
throw new RuntimeException("PDF页数计算失败");
}
}
// 第二次生成正式生成带页码的PDF
private void generatePdfWithPageNumbers(ForecastProject project, ZipOutputStream outputStream, int totalPages) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (PdfWriter writer = new PdfWriter(baos);
PdfDocument pdfDoc = new PdfDocument(writer);
Document document = new Document(pdfDoc, PageSize.A4.rotate())) {
// 设置字体
String fontPath = "/fonts/ChineseFonts.ttf";
PdfFont font = PdfFontFactory.createFont(fontPath, PdfEncodings.IDENTITY_H);
// 添加页码处理器
PageNumberEventHandler eventHandler = new PageNumberEventHandler(font, totalPages);
pdfDoc.addEventHandler(PdfDocumentEvent.END_PAGE, eventHandler);
// 添加内容
addContentToDocument(document, font, project, true);
// 表格数据
int index = 1;
for (ForecastResultVo vo : project.getVoList()) {
table.addCell(createCell(String.valueOf(index++), font, false));
table.addCell(createCell(vo.getTm(), font, false));
table.addCell(createCell(formatBigDecimal(vo.getDrp()), font, false));
table.addCell(createCell(formatBigDecimal(vo.getRealSwHValue()), font, false));
table.addCell(createCell(formatBigDecimal(vo.getYcSwHValue()), font, false));
table.addCell(createCell(formatBigDecimal(vo.getRealRkQValue()), font, false));
table.addCell(createCell(formatBigDecimal(vo.getYcRkQValue()), font, false));
table.addCell(createCell(formatBigDecimal(vo.getRealCkQValue()), font, false));
table.addCell(createCell(formatBigDecimal(vo.getYcCkQValue()), font, false));
}
document.add(table);
} else {
document.add(new Paragraph("无预报结果数据").setFont(font));
}
// 5. 关闭文档关键先关闭Document再关闭PdfDocument
document.close();
pdfDoc.close();
} catch (Exception e) {
log.error("生成PDF失败", e);
throw new RuntimeException("PDF生成失败");
}
// 在try-with-resources块外获取字节数组并写入输出流
try {
byte[] pdfBytes = baos.toByteArray();
outputStream.write(pdfBytes);
}catch (IOException e){
} catch (IOException e) {
log.error("写入ZIP失败", e);
throw new RuntimeException("写入ZIP失败");
}
}
// 页码事件处理器 - 简化版本
private static class PageNumberEventHandler implements IEventHandler {
private final PdfFont font;
private final int totalPages;
public PageNumberEventHandler(PdfFont font, int totalPages) {
this.font = font;
this.totalPages = totalPages;
}
@Override
public void handleEvent(Event event) {
PdfDocumentEvent docEvent = (PdfDocumentEvent) event;
PdfDocument pdfDoc = docEvent.getDocument();
PdfPage page = docEvent.getPage();
int currentPage = pdfDoc.getPageNumber(page);
// 获取页面尺寸
Rectangle pageSize = page.getPageSize();
// 创建画布
PdfCanvas pdfCanvas = new PdfCanvas(page.newContentStreamAfter(), page.getResources(), pdfDoc);
// 设置页码文本
String pageText = "第 " + currentPage + " 页 / 共 " + totalPages + " 页";
// 直接使用PdfCanvas绘制文本更简单的方式
pdfCanvas.beginText()
.setFontAndSize(font, 10)
.moveText((pageSize.getLeft() + pageSize.getRight()) / 2 - 50, pageSize.getBottom() + 20)
.showText(pageText)
.endText();
pdfCanvas.release();
}
}
// 添加内容到文档的公共方法
private void addContentToDocument(Document document, PdfFont font, ForecastProject project, boolean addPageMargins) throws IOException {
if (addPageMargins) {
// 为页码预留底部边距
document.setMargins(36, 36, 36, 50);
}
// 第一行:基本信息
Paragraph header1 = new Paragraph()
.add("预见期(小时): " + (project.getForecastPeriod() != null ? project.getForecastPeriod() : "无") + " ")
.add("预热期(天): " + (project.getForecastWarm() != null ? project.getForecastWarm() : "无") + " ")
.add("预报时间: " + formatDate(project.getForecastTm()) + " ")
.add("开始时间: " + formatDate(project.getStartTm()) + " ")
.add("结束时间: " + formatDate(project.getEndTm()))
.setFont(font)
.setFontSize(10);
document.add(header1);
document.add(new Paragraph("\n"));
// 第二行:统计信息
Paragraph header2 = new Paragraph()
.add("预报最高调洪水位(m): " + (project.getYcMaxSwH() != null ? project.getYcMaxSwH().setScale(2, RoundingMode.HALF_UP) : "无") + " ")
.add("预报最大入库流量(m³/s): " + (project.getYcMaxRkQ() != null ? project.getYcMaxRkQ().setScale(2, RoundingMode.HALF_UP) : "无") + " ")
.add("预报最大下泄流量(m³/s): " + (project.getYcMaxCkQ() != null ? project.getYcMaxCkQ().setScale(2, RoundingMode.HALF_UP) : "无") + " ")
.add("预报洪水总量(万m³): " + (project.getYcSumFlood() != null ? project.getYcSumFlood().setScale(2, RoundingMode.HALF_UP) : "无"))
.setFont(font)
.setFontSize(10);
document.add(header2);
document.add(new Paragraph("\n"));
// 第三行:数据表格
if (CollectionUtils.isNotEmpty(project.getVoList())) {
Table table = new Table(9);
table.setWidth(UnitValue.createPercentValue(100));
// 表头
table.addHeaderCell(createCell("序号", font, true));
table.addHeaderCell(createCell("时间", font, true));
table.addHeaderCell(createCell("降雨量(mm)", font, true));
table.addHeaderCell(createCell("实测水位(m)", font, true));
table.addHeaderCell(createCell("预报水位(m)", font, true));
table.addHeaderCell(createCell("入库流量(m³/s)", font, true));
table.addHeaderCell(createCell("预报入库流量(m³/s)", font, true));
table.addHeaderCell(createCell("实际出库流量(m³/s)", font, true));
table.addHeaderCell(createCell("预报出库流量(m³/s)", font, true));
// 表格数据
int index = 1;
for (ForecastResultVo vo : project.getVoList()) {
table.addCell(createCell(String.valueOf(index++), font, false));
table.addCell(createCell(vo.getTm(), font, false));
table.addCell(createCell(formatBigDecimal(vo.getDrp()), font, false));
table.addCell(createCell(formatBigDecimal(vo.getRealSwHValue()), font, false));
table.addCell(createCell(formatBigDecimal(vo.getYcSwHValue()), font, false));
table.addCell(createCell(formatBigDecimal(vo.getRealRkQValue()), font, false));
table.addCell(createCell(formatBigDecimal(vo.getYcRkQValue()), font, false));
table.addCell(createCell(formatBigDecimal(vo.getRealCkQValue()), font, false));
table.addCell(createCell(formatBigDecimal(vo.getYcCkQValue()), font, false));
}
document.add(table);
} else {
document.add(new Paragraph("无预报结果数据").setFont(font));
}
}
// 辅助方法:创建表格单元格
private Cell createCell(String text, PdfFont font, boolean isHeader) {
Cell cell = new Cell().add(new Paragraph(text).setFont(font).setFontSize(8));