# Excel转PDF合并单元格边框错乱?jxl+iText逐格解析样式,政务报表精准还原方案

Excel转PDF合并单元格边框错乱?jxl+iText逐格解析样式,政务报表精准还原方案

在政务信息化项目中,Excel模板转PDF是高频刚需,但合并单元格、边框样式、对齐方式经常在转换后丢失或错乱,导致无法满足盖章、归档、正式打印的合规要求。本文基于 jxl 读取 Excel 样式 + iText 生成 PDF,实现1:1 精准还原,包括边框、合并单元格、字体、行高、水印等,方案轻量无侵入,已在生产环境稳定运行多年。

一、业务背景与痛点

政务系统中大量使用固定版式Excel模板

  • 复杂表头、多层合并单元格
  • 严格边框样式、字体大小、对齐方式
  • 最终需生成PDF用于电子签章、打印、归档

传统工具常见问题:

  1. 合并单元格转换后边框断裂、错位、消失
  2. 只有左上单元格有样式,其余区域无边框
  3. 行高、字体、垂直对齐不一致
  4. 无法灵活添加水印、签章背景
  5. 格式不标准,无法通过政务验收

这些问题看似小,但直接影响项目验收、正式文书出具

二、整体设计思路

为了做到真正"所见即所得",整体思路如下:

  1. 使用 jxl 完整读取 Excel 每一格的值、字体、边框、对齐、行高、合并信息
  2. 合并单元格做特殊处理:只保留主格,其余格子跳过绘制
  3. 校正边框:合并区域的下边框取最底行、右边框取最右列
  4. 使用 iText 按照读取到的样式逐格还原 PdfPCell
  5. 最后统一添加水印/背景图,满足政务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();
}

四、关键技术亮点

  1. 逐单元格样式采集

    不依赖整体渲染,精确到每一条边框、每一种对齐。

  2. 合并单元格智能校正

    只渲染主格,其余格子跳过,避免重复绘制与边框错乱。

  3. 不侵入、不依赖第三方平台

    仅 jxl + iText 即可运行,适配老旧 Web 项目。

  4. 支持水印、背景、签章

    满足政务 PDF 标准化、合规化需求。

  5. 生产级稳定

    已在多个政务项目中长期使用,无样式类线上故障。


五、适用场景

  • 政务Excel模板转正式PDF
  • 需盖章、归档、打印的标准化报表
  • 老旧项目升级,不允许引入重型报表引擎
  • 对格式精度要求极高的表格类输出

六、总结

Excel 转 PDF 真正的难点不在于数据填充,而在于样式还原 ,尤其是合并单元格与边框。

本文通过jxl 全量采集样式 + iText 精准绘制,实现了一套轻量、稳定、可靠的解决方案,能够完美还原复杂 Excel 版式,满足政务、金融、企业等对格式严谨性要求极高的场景。

相比于 POI、Aspose、第三方报表工具,这套方案代码可控、无版权风险、部署简单、兼容性极强,是传统 Java 项目里非常实用的硬核工具类方案。


#Java #Excel #PDF #iText #jxl #Excel转PDF #合并单元格 #政务信息化 #自研框架 #报表开发


相关推荐
观无2 小时前
html+nginx实现看板
前端·nginx·html
Godson_beginner2 小时前
Aspose.PDF for Java(实现PDF转Word无水印无页数限制)
java·spring·pdf·文档转换
bcbobo21cn2 小时前
Web 3D 正方体贴图
前端·3d·贴图·mesh
灯光设计师2 小时前
JavaScript判断受访域名,调用不同的js文件
javascript
聊聊MES那点事2 小时前
报表控件Stimulsoft Reports.NET使用教程:发票报告设计
前端·javascript·html·报表工具
予你@。2 小时前
Vue2 使用 html2canvas 将 HTML 生成图片并上传到服务器
前端·html
星晨雪海2 小时前
优惠券秒杀的核心业务逻辑
java·前端·数据库
Bigger2 小时前
第五章:我是如何剖析 Claude Code 的 MCP 服务与插件生态系统的
前端·ai编程·claude
许彰午2 小时前
# 政务表单动态建表?运行时DDL引擎,前端拖完字段后端直接建
java·前端·后端·架构·政务