Excel转PDF合并单元格边框错乱?jxl+iText逐格解析样式,政务报表精准还原方案
在政务信息化项目中,Excel模板转PDF是高频刚需,但合并单元格、边框样式、对齐方式经常在转换后丢失或错乱,导致无法满足盖章、归档、正式打印的合规要求。本文基于 jxl 读取 Excel 样式 + iText 生成 PDF,实现1:1 精准还原,包括边框、合并单元格、字体、行高、水印等,方案轻量无侵入,已在生产环境稳定运行多年。
一、业务背景与痛点
政务系统中大量使用固定版式Excel模板:
- 复杂表头、多层合并单元格
- 严格边框样式、字体大小、对齐方式
- 最终需生成PDF用于电子签章、打印、归档
传统工具常见问题:
- 合并单元格转换后边框断裂、错位、消失
- 只有左上单元格有样式,其余区域无边框
- 行高、字体、垂直对齐不一致
- 无法灵活添加水印、签章背景
- 格式不标准,无法通过政务验收
这些问题看似小,但直接影响项目验收、正式文书出具。
二、整体设计思路
为了做到真正"所见即所得",整体思路如下:
- 使用 jxl 完整读取 Excel 每一格的值、字体、边框、对齐、行高、合并信息
- 对合并单元格做特殊处理:只保留主格,其余格子跳过绘制
- 校正边框:合并区域的下边框取最底行、右边框取最右列
- 使用 iText 按照读取到的样式逐格还原 PdfPCell
- 最后统一添加水印/背景图,满足政务PDF合规要求
这套方案不依赖复杂报表引擎,代码少、稳定性极高、兼容性极强。
三、完整实现代码
3.1 Excel 读取与样式采集
java
public void xlsToPdfRender(Workbook workbook, String targetPdf) throws Exception {
Sheet sheet = workbook.getSheet(0);
// 获取所有合并区域
Range[] mergedRegions = sheet.getMergedCells();
List<List<Map<String, Object>>> tableData = new ArrayList<>();
int totalRows = sheet.getRows();
int totalCols = sheet.getColumns();
for (int i = 0; i < totalRows; i++) {
List<Map<String, Object>> rowList = new ArrayList<>();
double rowHeight = sheet.getRowView(i).getSize() / 18.0;
for (int j = 0; j < totalCols; j++) {
Cell cell = sheet.getCell(j, i);
CellFormat fmt = cell.getCellFormat();
Map<String, Object> cellInfo = new HashMap<>();
cellInfo.put("value", cell.getContents());
cellInfo.put("rowHeight", rowHeight);
// 对齐方式
String hAlign = "left";
String vAlign = "top";
if (fmt != null) {
if (fmt.getAlignment() == Alignment.CENTRE) hAlign = "center";
else if (fmt.getAlignment() == Alignment.RIGHT) hAlign = "right";
if (fmt.getVerticalAlignment() == VerticalAlignment.CENTRE) vAlign = "middle";
else if (fmt.getVerticalAlignment() == VerticalAlignment.BOTTOM) vAlign = "bottom";
}
cellInfo.put("hAlign", hAlign);
cellInfo.put("vAlign", vAlign);
// 边框信息
String top = "0", bottom = "0", left = "0", right = "0";
if (fmt != null) {
top = String.valueOf(fmt.getBorder(Border.TOP).getValue());
bottom = String.valueOf(fmt.getBorder(Border.BOTTOM).getValue());
left = String.valueOf(fmt.getBorder(Border.LEFT).getValue());
right = String.valueOf(fmt.getBorder(Border.RIGHT).getValue());
}
cellInfo.put("borderTop", top);
cellInfo.put("borderBottom", bottom);
cellInfo.put("borderLeft", left);
cellInfo.put("borderRight", right);
// 字体、加粗
int fontSize = 12;
boolean bold = false;
if (fmt != null && fmt.getFont() != null) {
fontSize = fmt.getFont().getPointSize();
bold = fmt.getFont().getBoldWeight() == 700;
}
cellInfo.put("fontSize", fontSize);
cellInfo.put("bold", bold);
rowList.add(cellInfo);
}
tableData.add(rowList);
}
// 后续传入 iText 生成 PDF
generatePdfWithTable(tableData, mergedRegions, targetPdf);
}
3.2 合并单元格校正逻辑
java
private boolean isMergedCell(int row, int col, Range[] mergedRegions) {
for (Range range : mergedRegions) {
int rs = range.getTopLeft().getRow();
int re = range.getBottomRight().getRow();
int cs = range.getTopLeft().getColumn();
int ce = range.getBottomRight().getColumn();
if (row >= rs && row <= re && col >= cs && col <= ce) {
return !(row == rs && col == cs);
}
}
return false;
}
3.3 iText 生成 PDF 并还原样式
java
public void generatePdfWithTable(List<List<Map<String, Object>>> data,
Range[] mergedRegions,
String outFile) throws Exception {
Document doc = new Document(PageSize.A4, 36, 36, 36, 36);
PdfWriter.getInstance(doc, new FileOutputStream(outFile));
doc.open();
PdfPTable table = new PdfPTable(data.get(0).size());
table.setWidthPercentage(100);
for (int i = 0; i < data.size(); i++) {
List<Map<String, Object>> row = data.get(i);
for (int j = 0; j < row.size(); j++) {
if (isMergedCell(i, j, mergedRegions)) {
table.addCell(new PdfPCell() {{ setBorder(NO_BORDER); }});
continue;
}
Map<String, Object> cell = row.get(j);
PdfPCell pdfCell = new PdfPCell();
// 设置内容
pdfCell.setPhrase(new Phrase(String.valueOf(cell.get("value"))));
// 对齐
if ("center".equals(cell.get("hAlign"))) pdfCell.setHorizontalAlignment(Element.ALIGN_CENTER);
else if ("right".equals(cell.get("hAlign"))) pdfCell.setHorizontalAlignment(Element.ALIGN_RIGHT);
// 边框
pdfCell.setBorder(0);
if ("1".equals(cell.get("borderTop"))) pdfCell.enableBorderSide(PdfCell.TOP_BORDER);
if ("1".equals(cell.get("borderBottom"))) pdfCell.enableBorderSide(PdfCell.BOTTOM_BORDER);
if ("1".equals(cell.get("borderLeft"))) pdfCell.enableBorderSide(PdfCell.LEFT_BORDER);
if ("1".equals(cell.get("borderRight"))) pdfCell.enableBorderSide(PdfCell.RIGHT_BORDER);
table.addCell(pdfCell);
}
}
doc.add(table);
doc.close();
}
3.4 PDF 水印添加(政务必备)
java
public void addWaterMark(String srcPdf, String destPdf, String imagePath) throws Exception {
PdfReader reader = new PdfReader(srcPdf);
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(destPdf));
Image img = Image.getInstance(imagePath);
img.setAbsolutePosition(300, 300);
img.scalePercent(40);
int total = reader.getNumberOfPages();
for (int i = 1; i <= total; i++) {
PdfContentByte under = stamper.getUnderContent(i);
under.addImage(img);
}
stamper.close();
reader.close();
}
四、关键技术亮点
-
逐单元格样式采集
不依赖整体渲染,精确到每一条边框、每一种对齐。
-
合并单元格智能校正
只渲染主格,其余格子跳过,避免重复绘制与边框错乱。
-
不侵入、不依赖第三方平台
仅 jxl + iText 即可运行,适配老旧 Web 项目。
-
支持水印、背景、签章
满足政务 PDF 标准化、合规化需求。
-
生产级稳定
已在多个政务项目中长期使用,无样式类线上故障。
五、适用场景
- 政务Excel模板转正式PDF
- 需盖章、归档、打印的标准化报表
- 老旧项目升级,不允许引入重型报表引擎
- 对格式精度要求极高的表格类输出
六、总结
Excel 转 PDF 真正的难点不在于数据填充,而在于样式还原 ,尤其是合并单元格与边框。
本文通过jxl 全量采集样式 + iText 精准绘制,实现了一套轻量、稳定、可靠的解决方案,能够完美还原复杂 Excel 版式,满足政务、金融、企业等对格式严谨性要求极高的场景。
相比于 POI、Aspose、第三方报表工具,这套方案代码可控、无版权风险、部署简单、兼容性极强,是传统 Java 项目里非常实用的硬核工具类方案。
#Java #Excel #PDF #iText #jxl #Excel转PDF #合并单元格 #政务信息化 #自研框架 #报表开发