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();
}
}
}