Excel 转pdf

news/2026/1/22 15:07:39/文章来源:https://www.cnblogs.com/gushiye/p/19517293
package com.example.demo.controller;

import com.itextpdf.text.*;
import com.itextpdf.text.Font;
import com.itextpdf.text.pdf.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFCell;

import java.io.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

@Slf4j
public class ExcelToPdfUtil {
private static final Map<String, int[]> mergedRegionCache = new HashMap<>();
private static final Font DEFAULT_FONT = new Font();
public static final int NO_BORDER = PdfPCell.NO_BORDER;
public static final float DEFAULT_MIN_HEIGHT = 20f;

/**
* Excel转PDF
*/
public static byte[] excelToPdf(byte[] excelByteArray) throws IOException, DocumentException {
ByteArrayOutputStream pdfStream = new ByteArrayOutputStream();

Workbook workbook = WorkbookFactory.create(new ByteArrayInputStream(excelByteArray));
Sheet sheet = workbook.getSheetAt(0);

preloadMergedRegions(sheet);

float[] widths = getColWidths(sheet);
PdfPTable table = new PdfPTable(widths);
table.setWidthPercentage(100);
int colCount = widths.length;

// 设置基本字体
BaseFont baseFont = loadEmbeddedFont();
Font font = new Font(baseFont, 10); // 减小字体以适应单元格

for (int rowIndex = sheet.getFirstRowNum(); rowIndex <= sheet.getLastRowNum(); rowIndex++) {
Row row = sheet.getRow(rowIndex);
PdfPCell[] rowCells = new PdfPCell[colCount];
int cellIndex = 0;

if (row == null) {
// 插入空对象
for (int i = 0; i < colCount; i++) {
rowCells[i] = createEmptyCell(font);
rowCells[i].setMinimumHeight(DEFAULT_MIN_HEIGHT);
}
} else {
for (int columnIndex = 0; columnIndex < colCount; columnIndex++) {
Cell cell = row.getCell(columnIndex, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);

// 检查是否是合并区域的起始单元格
String key = rowIndex + "," + columnIndex;
if (mergedRegionCache.containsKey(key)) {
int[] span = mergedRegionCache.get(key);
// 只处理起始单元格,跳过其他合并区域内的单元格
if (span[0] == 0 && span[1] == 0) {
continue; // 这个单元格是合并区域的一部分,不需要重复添加
}

PdfPCell pdfPCell = excelCellToPdfCell(sheet, cell, baseFont);
pdfPCell.setRowspan(span[0]);
pdfPCell.setColspan(span[1]);

// 设置行高
float rowHeight = row.getHeightInPoints();
if (rowHeight > 0) {
pdfPCell.setMinimumHeight(rowHeight);
}

rowCells[cellIndex++] = pdfPCell;
} else {
// 普通单元格
PdfPCell pdfPCell = excelCellToPdfCell(sheet, cell, baseFont);

// 设置行高
float rowHeight = row.getHeightInPoints();
if (rowHeight > 0) {
pdfPCell.setMinimumHeight(rowHeight);
}

rowCells[cellIndex++] = pdfPCell;
}
}
}

// 添加这一行的所有单元格
for (int i = 0; i < cellIndex; i++) {
if (rowCells[i] != null) {
table.addCell(rowCells[i]);
}
}
}

Document document = new Document();
PdfWriter.getInstance(document, pdfStream);
document.open();
document.add(table);
document.close();

return pdfStream.toByteArray();
}

/**
* 预加载合并单元格信息
*/
private static void preloadMergedRegions(Sheet sheet) {
mergedRegionCache.clear();
for (int i = 0; i < sheet.getNumMergedRegions(); i++) {
CellRangeAddress range = sheet.getMergedRegions().get(i);
int firstRow = range.getFirstRow();
int firstCol = range.getFirstColumn();
int lastRow = range.getLastRow();
int lastCol = range.getLastColumn();

int rowSpan = lastRow - firstRow + 1;
int colSpan = lastCol - firstCol + 1;

// 标记合并区域的起始单元格
String startKey = firstRow + "," + firstCol;
mergedRegionCache.put(startKey, new int[]{rowSpan, colSpan});

// 标记非起始单元格(这些单元格在PDF表格中会被跳过)
for (int r = firstRow; r <= lastRow; r++) {
for (int c = firstCol; c <= lastCol; c++) {
if (r != firstRow || c != firstCol) {
String key = r + "," + c;
mergedRegionCache.put(key, new int[]{0, 0}); // 标记为非起始单元格
}
}
}
}
}

/**
* 加载嵌入字体
*/
private static BaseFont loadEmbeddedFont() throws IOException {
InputStream fontStream = ExcelToPdfUtil.class.getResourceAsStream("/fonts/simsun.ttc");
if (fontStream == null) {
// 尝试加载系统字体
try {
return BaseFont.createFont(BaseFont.HELVETICA, BaseFont.WINANSI, BaseFont.NOT_EMBEDDED);
} catch (Exception e) {
// log.warn("Could not load embedded font, using default font", e);
throw new IOException("Font file not found: /fonts/simsun.ttc", e);
}
}
try {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
byte[] data = new byte[4096];
int nRead;
while ((nRead = fontStream.read(data)) != -1) {
buffer.write(data, 0, nRead);
}
byte[] fontData = buffer.toByteArray();

return BaseFont.createFont(
"simsun.ttc,0", // 指定字体索引
BaseFont.IDENTITY_H,
BaseFont.EMBEDDED,
true,
fontData,
null
);
} catch (Exception e) {
// log.error("Failed to load font: /fonts/simsun.ttc", e);
throw new IOException(e);
} finally {
if (fontStream != null) {
fontStream.close();
}
}
}

/**
* Excel单元格转PDF单元格
*/
private static PdfPCell excelCellToPdfCell(Sheet sheet, Cell excelCell, BaseFont baseFont) {
if (Objects.isNull(excelCell)) {
return createPdfPcell(null, NO_BORDER, DEFAULT_MIN_HEIGHT, null);
}

String excelCellValue = getExcelCellValue(excelCell);

// 获取Excel单元格的字体信息
org.apache.poi.ss.usermodel.Font excelFont = getExcelFont(sheet, excelCell);
Font pdfFont = new Font(baseFont,
Math.min(excelFont.getFontHeightInPoints(), 12), // 限制最大字体大小
excelFont.getBold() ? Font.BOLD : Font.NORMAL,
BaseColor.BLACK);

// 检查边框
int border = hasBorder(excelCell) ? Rectangle.BOX : NO_BORDER;

// 获取行高
float rowHeight = excelCell.getRow().getHeightInPoints();
if (rowHeight <= 0) {
rowHeight = DEFAULT_MIN_HEIGHT;
}

PdfPCell pCell = createPdfPcell(excelCellValue, border, rowHeight, pdfFont);

// 设置对齐方式
CellStyle cellStyle = excelCell.getCellStyle();
pCell.setHorizontalAlignment(getHorAlign(cellStyle.getAlignment().getCode()));
pCell.setVerticalAlignment(getVerAlign(cellStyle.getVerticalAlignment().getCode()));

// 设置背景色
if (cellStyle.getFillForegroundColor() != IndexedColors.AUTOMATIC.getIndex()) {
short fgColor = cellStyle.getFillForegroundColor();
pCell.setBackgroundColor(getColorFromIndex(fgColor));
}

return pCell;
}

/**
* 根据索引获取颜色
*/
private static BaseColor getColorFromIndex(short colorIndex) {
// 简化的颜色映射,实际应用中可以扩展
switch (colorIndex) {
case 10: return BaseColor.LIGHT_GRAY;
case 11: return BaseColor.YELLOW;
case 12: return BaseColor.PINK;
case 13: return BaseColor.GREEN;
default: return BaseColor.WHITE;
}
}

/**
* 检查是否有边框
*/
private static boolean hasBorder(Cell excelCell) {
CellStyle style = excelCell.getCellStyle();
return style.getBorderTop() != BorderStyle.NONE ||
style.getBorderBottom() != BorderStyle.NONE ||
style.getBorderLeft() != BorderStyle.NONE ||
style.getBorderRight() != BorderStyle.NONE;
}

/**
* 获取Excel字体
*/
private static org.apache.poi.ss.usermodel.Font getExcelFont(Sheet sheet, Cell excelCell) {
if (sheet instanceof HSSFSheet) {
Workbook workbook = sheet.getWorkbook();
return workbook.getFontAt(excelCell.getCellStyle().getFontIndex());
} else {
return excelCell.getSheet().getWorkbook().getFontAt(excelCell.getCellStyle().getFontIndex());
}
}

/**
* 创建PDF单元格(简化版)
*/
private static PdfPCell createPdfPcell(String content) {
return createPdfPcell(content, Rectangle.BOX, DEFAULT_MIN_HEIGHT, DEFAULT_FONT);
}

/**
* 创建PDF单元格
*/
private static PdfPCell createPdfPcell(String content, int border, float minimumHeight, Font pdfFont) {
if (minimumHeight < 0) {
minimumHeight = DEFAULT_MIN_HEIGHT;
}

String contentValue = content == null ? "" : content;
Font font = pdfFont == null ? DEFAULT_FONT : pdfFont;

PdfPCell cell = new PdfPCell(new Phrase(contentValue, font));
cell.setBorder(border);
cell.setMinimumHeight(minimumHeight);
cell.setPadding(3); // 添加内边距,使内容更美观
return cell;
}

/**
* 创建空单元格
*/
private static PdfPCell createEmptyCell(Font font) {
PdfPCell cell = new PdfPCell(new Phrase("", font));
cell.setBorder(NO_BORDER);
cell.setMinimumHeight(DEFAULT_MIN_HEIGHT);
cell.setPadding(3);
return cell;
}

/**
* 获取垂直对齐方式
*/
private static int getVerAlign(int align) {
switch (align) {
case 2: // BOTTOM
return Element.ALIGN_BOTTOM;
case 3: // TOP
return Element.ALIGN_TOP;
default:
return Element.ALIGN_MIDDLE;
}
}

/**
* 获取水平对齐方式
*/
private static int getHorAlign(int align) {
switch (align) {
case 1: // LEFT
return Element.ALIGN_LEFT;
case 3: // RIGHT
return Element.ALIGN_RIGHT;
default:
return Element.ALIGN_CENTER;
}
}

/**
* 获取Excel单元格值
*/
private static String getExcelCellValue(Cell excelCell) {
if (excelCell == null) {
return "";
}

CellType cellType = excelCell.getCellType();
if (cellType == CellType.STRING) {
return excelCell.getStringCellValue();
}

if (cellType == CellType.BOOLEAN) {
return String.valueOf(excelCell.getBooleanCellValue());
}

DataFormatter formatter = new DataFormatter();
if (cellType == CellType.FORMULA) {
FormulaEvaluator evaluator = excelCell.getSheet().getWorkbook()
.getCreationHelper().createFormulaEvaluator();
return formatter.formatCellValue(excelCell, evaluator);
}

if (cellType == CellType.NUMERIC) {
if (DateUtil.isCellDateFormatted(excelCell)) {
return formatter.formatCellValue(excelCell);
} else {
return formatter.formatCellValue(excelCell);
}
}

if (cellType == CellType.ERROR) {
return "Error";
}

return "";
}

/**
* 获取列宽度
*/
private static float[] getColWidths(Sheet sheet) {
int maxCol = 0;
// 遍历所有行找出最大列数
for (int i = sheet.getFirstRowNum(); i <= sheet.getLastRowNum(); i++) {
Row row = sheet.getRow(i);
if (row != null) {
maxCol = Math.max(maxCol, row.getLastCellNum());
}
}

if (maxCol <= 0) maxCol = 1;

float[] widths = new float[maxCol];
for (int i = 0; i < maxCol; i++) {
// 获取Excel列宽并转换为PDF单位
int width = sheet.getColumnWidth(i);
if (width <= 0) {
width = 256 * 10; // 默认宽度
}
// POI列宽单位是1/256字符宽度,转换为PDF点单位
widths[i] = width / 256f * 72f / 96f; // 假设Excel字体大小为12pt
}

return widths;
}

/**
* 合并PDF(如果需要合并多个PDF)
*/
private static void mergerPdf(byte[] pdfOne, PdfCopy copy) throws IOException, BadPdfFormatException {
if (pdfOne.length > 0) {
PdfReader readerOne = new PdfReader(pdfOne);
int numberOfPages = readerOne.getNumberOfPages();
for (int i = 1; i <= numberOfPages; i++) {
PdfImportedPage page = copy.getImportedPage(readerOne, i);
copy.addPage(page);
}
readerOne.close();
}
}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1200573.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

收藏这份大模型架构指南!从GPT到DeepSeek的技术演进与核心创新

文章对比分析了DeepSeek、OLMo、Gemma、Llama、Qwen等主流大语言模型架构&#xff0c;揭示了它们在注意力机制、归一化策略和专家混合等方面的创新与演进。尽管大模型能力不断提升&#xff0c;但其整体架构在七年中保持了高度一致性&#xff0c;更多是在原有框架上的精雕细琢而…

免费文献检索网站推荐:高效获取学术资源的实用平台指南

做科研的第一道坎&#xff0c;往往不是做实验&#xff0c;也不是写论文&#xff0c;而是——找文献。 很多新手科研小白会陷入一个怪圈&#xff1a;在知网、Google Scholar 上不断换关键词&#xff0c;结果要么信息过载&#xff0c;要么完全抓不到重点。今天分享几个长期使用的…

为什么35+开发者更吃香?2026年经验红利解析

在2026年的科技行业&#xff0c;35岁以上开发者&#xff08;包括软件测试从业者&#xff09;正迎来前所未有的职业机遇。传统“35岁危机”的叙事被颠覆&#xff0c;经验不再是负担&#xff0c;而是核心竞争优势。本文从软件测试视角出发&#xff0c;解析经验红利的本质、驱动因…

2026广东最新婚纱MV工作室top5推荐榜!广州优质婚纱MV机构榜单发布,专业影像团队助力定格爱情美好瞬间

引言 随着新消费时代的到来,新人对婚纱影像的需求已从传统记录转向情感叙事与个性表达,婚纱MV作为承载爱情故事的重要载体,其艺术性与专业性愈发受到重视。据中国摄影行业协会2025年度报告显示,国内婚纱MV市场规模…

解决VMware Ubuntu端口映射SSH连接失败问题:无需重启服务器的快速修复方案

解决VMware Ubuntu端口映射SSH连接失败问题&#xff1a;无需重启服务器的快速修复方案 问题背景 在Windows服务器上运行VMware虚拟机&#xff0c;将Ubuntu的SSH端口(22)映射到Windows主机的5099端口&#xff0c;突然无法通过SSH连接。重启整个Windows服务器会影响其他服务&am…

别再埋头写代码!2026年开发者的跨界晋升秘籍

测试工程师的跨界时代已至 2026年&#xff0c;软件行业正经历颠覆性变革&#xff1a;AI驱动的自动化测试覆盖率超过80%&#xff0c;DevOps流水线加速迭代&#xff0c;传统“找bug”角色正被重新定义。作为软件测试从业者&#xff0c;若只埋头写脚本或执行用例&#xff0c;职业天…

【Python NLP】拒绝 AI “乱发挥”:揭秘如何利用“约束解码”实现 100% 统一的品牌术语翻译

Python NLP 约束解码 术语管理 Constrained Decoding 跨境电商摘要在跨境电商的品牌化&#xff08;Branding&#xff09;进程中&#xff0c;术语一致性&#xff08;Terminology Consistency&#xff09; 是衡量品牌专业度的重要指标。然而&#xff0c;生成式 AI&#xff08;如 …

2026国内最新新加坡留学服务top5推荐!广东等地优质留学项目权威榜单发布,资质服务双优助力海外升学

随着全球化教育趋势深化,我国出国留学人数持续攀升,其中新加坡、英国、澳大利亚等英语国家因教育质量高、学历认可度强成为热门选择。据教育部留学服务中心最新数据显示,2025年我国出国留学人员中,选择本科阶段出境…

2026年健身教练培训机构排名TOP榜及院校筛选技巧

健身行业的蓬勃发展,让优质培训机构成为从业者进阶、新人入行的重要依托。市场上机构良莠不齐,排名仅为参考,筛选核心应聚焦资质、课程、师资三大维度。零基础新人或技能拓展者选对机构,能高效搭建职业能力框架,规…

基于最小二乘支持向量机LSSVM、粒子群算法支持向量机和改进粒子群算法支持向量机三种方法的电力短期负荷预测Matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;擅长数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。 &#x1f34e; 往期回顾关注个人主页&#xff1a;Matlab科研工作室 &#x1f447; 关注我领取海量matlab电子书和数学建模资料 &#…

2026年靠谱的平板显示器支架厂家推荐及选购参考榜

行业背景与市场趋势 随着远程办公和混合办公模式的普及,全球平板显示器支架市场规模持续增长。据Frost & Sullivan数据显示,2025年全球显示器支架市场规模已达45亿美元,预计2026年将突破50亿美元,年复合增长率…

普洱市镇沅江城孟连澜沧西盟英语雅思培训辅导机构推荐,2026权威出国雅思课程中心学校口碑排行榜

在全球化留学趋势持续升温的2026年,雅思成绩已成为普洱市镇沅、江城、孟连、澜沧、西盟等区县学子开启海外求学之门的核心凭证。然而,本地雅思考生普遍面临诸多备考困境:优质雅思培训教育机构资源稀缺,难以筛选到靠…

2025电滑环领域佼佼者:国内十大实力厂家推荐,气滑环/气电滑环/电环/定制滑环/导电滑环/光纤滑环,电滑环销售厂家排行

随着工业4.0与智能制造的深度推进,电滑环作为关键旋转连接部件,在新能源、机器人、航空航天等领域的应用需求持续攀升。据行业数据统计,2025年国内电滑环市场规模预计突破50亿元,但市场集中度较低,头部企业与中小…

typora picgo-core上传图片失败

typora picgo-core上传图片失败 参考文章:解决 tunneling socket could not be established, cause=getaddrinfo ENOTFOUND 8000 问题-CSDN博客 (2 封私信 / 40 条消息) 解决:stack Error: tunneling socket could n…

用自然语言玩转 AI 原生数据库 —— seekdb MCP Server

用自然语言玩转 AI 原生数据库 —— seekdb MCP Server本文将带你上手 seekdb MCP Server,并通过自然语言构建 AI 应用,来让大家体验 AI 原生数据库 seekdb 的魅力。引言 想象一下:你只需要用自然语言描述你的需求,…

2026年市场有名的纸盒批发厂家排行,纸箱/工业纸盒/纸盒/彩印包装/农产品纸箱/工业纸箱,纸盒批发厂家排行榜

当前,纸盒包装行业正经历技术迭代与需求升级的双重驱动。随着电商物流、食品加工、电子电器等领域的快速发展,市场对纸盒产品的功能性、环保性及定制化能力提出更高要求。兼具规模化生产能力、技术实力与绿色合规资质…

DolphinScheduler 3.1.9 + Minio 开发环境【IDEA】搭建访问及相关问题处理

这里按照官方提供的文档进行操作。目录DolphinScheduler 3.1.9 开发环境【IDEA】搭建访问前提1、软件要求 2、克隆代码库 3、编译源码DolphinScheduler 普通开发模式1、编译问题: 2、启动zookeeper官方内容 存储配置 …

2026.1.22 模拟赛 T2 补题记录

我怎么这么菜。原题 考场做法是对 \(a\) 猫树分治,然后把 \(a_l, a_{l + 1}, \dots, a_r\) 在 \(b\) 中出现的数在 \(b\) 中的位置拎出来,做扫描线,用线段树求答案。 这个做法是 \(2\log\) 的,考虑如何优化。 事实…

2026年南通铜轴瓦厂推荐,雪龙铜制品如何解决用户适配性差痛点

在工业制造的精密世界里,铜轴瓦作为设备传动系统的关节软骨,其品质直接决定了机械运行的稳定性与使用寿命。面对市场上良莠不齐的铜轴瓦厂商,如何挑选到既能适配复杂工况、又能保障长期稳定供应的优质合作伙伴?以下…

2026年健身教练培训机构排名权威榜单及择校指南

随着健身行业规范化发展,专业教练需求稳步上升,选择优质培训机构是入行或进阶的关键。靠谱的机构能提供系统课程与权威认证,助力从业者快速立足。本文结合行业口碑与教学实力,精选5家机构,从核心维度拆解,为不同…