新增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,18 +95,115 @@ 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());
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);
// 添加内容(但不添加页码)
addContentToDocument(document, font, project, false);
// 返回总页数
return pdfDoc.getNumberOfPages();
} 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);
} 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) {
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() : "无") + " ")
@ -131,7 +231,7 @@ public class ForecastProjectService extends ServiceImpl<ForecastProjectMapper, F
// 第三行:数据表格
if (CollectionUtils.isNotEmpty(project.getVoList())) {
Table table = new Table(9); // 10列
Table table = new Table(9);
table.setWidth(UnitValue.createPercentValue(100));
// 表头
@ -162,21 +262,6 @@ public class ForecastProjectService extends ServiceImpl<ForecastProjectMapper, F
} 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 {
byte[] pdfBytes = baos.toByteArray();
outputStream.write(pdfBytes);
}catch (IOException e){
log.error("写入ZIP失败", e);
throw new RuntimeException("写入ZIP失败");
}
}
// 辅助方法:创建表格单元格