EasyExcel导出excel再转PDF转图片详解

封装EasyExcel导出工具类

相关的依赖自己网上搜索加上,这里不在阐述

bash 复制代码
@Slf4j
@Service
public class AgentExcelUtils {
    public String syncDynamicHeadWrite(String fileName,
                                       String sheetName,
                                       List<List<String>> headList,
                                       List<?> data) throws IOException {
        String downloadPath = "D:\\file\\" + fileName + DateUtils.dateTimeNow() + ".xlsx";
        String downloadBusinessFileName = "D:\\file\\" + fileName + DateUtils.dateTimeNow() + "1" + ".xlsx";
        EasyExcel.write(downloadPath)
                .inMemory(true)
                .head(headList)
                .sheet(sheetName)
                .registerWriteHandler(new AgentWaterMarkHandlerUtil("供应链"))
                .registerWriteHandler(registerWriteHandler())
                .doWrite(data);
        File exportedFile = new File(downloadPath);
        Workbook workbook = WorkbookFactory.create(exportedFile);
        Sheet sheet = workbook.getSheet(sheetName);
        // 根据列内容动态调整列宽
        for (int i = 0; i < headList.size(); i++) {
            int maxLength = headList.get(i).get(0).length();  // 获取标题列的长度
            for (Object row : data) {
                String cellValue = row.toString();
                if (cellValue.length() > maxLength) {
                    maxLength = cellValue.length();  // 更新列宽
                }
            }
            sheet.setColumnWidth(i, maxLength * 256);  // 乘以256以适配Excel的宽度单位
        }
        // 保存文件
        try (FileOutputStream fos = new FileOutputStream(downloadBusinessFileName)) {
            workbook.write(fos);
            workbook.close();
        } catch (Exception e) {
            log.error("仓储、配送导出文件失败=", e);
        }
        return downloadBusinessFileName;
    }

    private HorizontalCellStyleStrategy registerWriteHandler() {
        // 头的策略
        WriteCellStyle headWriteCellStyle = new WriteCellStyle();
        // 头背景设置
        headWriteCellStyle.setFillForegroundColor(IndexedColors.AQUA.getIndex());
        WriteFont headWriteFont = new WriteFont();
        headWriteFont.setFontHeightInPoints((short) 10);
        headWriteCellStyle.setWriteFont(headWriteFont);
        headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        // 内容的策略
        WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
        WriteFont contentWriteFont = new WriteFont();
        // 字体大小
        contentWriteFont.setFontHeightInPoints((short) 10);
        contentWriteCellStyle.setWriteFont(contentWriteFont);
        contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        return new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
    }
}

excel水印代码

bash 复制代码
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import lombok.RequiredArgsConstructor;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFRelation;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.core.io.ClassPathResource;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * @author sunpeiyang
 * @date 2025/5/9 14:33
 */
@RequiredArgsConstructor
public class AgentWaterMarkHandlerUtil implements SheetWriteHandler {
    private final String WATER_MARK;

    public static ByteArrayOutputStream createWaterMark(String content) throws IOException, FontFormatException {
        int width = 200;
        int height = 150;
        // 添加中文字体 加载下载的中文字体,这样就不会出现中文乱码或中文丢失的情况
        ClassPathResource resource = new ClassPathResource("font/msyh.ttf");
        InputStream fi = resource.getInputStream();
        BufferedInputStream fb = new BufferedInputStream(fi);
        // String fontType = "微软雅黑";
        int fontStyle = Font.PLAIN;
        int fontSize = 20;
        // Font font = new Font(fontType, fontStyle, fontSize);
        Font font = Font.createFont(fontStyle, fb);
        font = font.deriveFont(fontStyle, fontSize);
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);// 获取bufferedImage对象
        // 获取Graphics2d对象
        Graphics2D g2d = image.createGraphics();
        image = g2d.getDeviceConfiguration().createCompatibleImage(width, height, Transparency.TRANSLUCENT);
        g2d.dispose();
        g2d = image.createGraphics();
        //设置字体颜色和透明度,最后一个参数为透明度
        g2d.setColor(new Color(0, 0, 0, 40));
        // 设置字体
        g2d.setStroke(new BasicStroke(1));
        // 设置字体类型  加粗 大小
        g2d.setFont(font);
        g2d.rotate(-0.5, (double) image.getWidth() / 2, (double) image.getHeight() / 2);//设置倾斜度
        FontRenderContext context = g2d.getFontRenderContext();
        Rectangle2D bounds = font.getStringBounds(content, context);
        double x = (width - bounds.getWidth()) / 2;
        double y = (height - bounds.getHeight()) / 2;
        double ascent = -bounds.getY();
        double baseY = y + ascent;
        // 写入水印文字原定高度过小,所以累计写水印,增加高度
        g2d.drawString(content, (int) x, (int) baseY);
        // 设置透明度
        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
        // 释放对象
        g2d.dispose();
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        ImageIO.write(image, "png", os);
        return os;
    }

    /**
     * 为Excel打上水印工具函数
     *
     * @param sheet excel sheet
     * @param bytes 水印图片字节数组
     */
    public static void putWaterRemarkToExcel(XSSFSheet sheet, byte[] bytes) {
        //add relation from sheet to the picture data
        XSSFWorkbook workbook = sheet.getWorkbook();

        int pictureIdx = workbook.addPicture(bytes, Workbook.PICTURE_TYPE_PNG);
        String rID = sheet.addRelation(null, XSSFRelation.IMAGES, workbook.getAllPictures().get(pictureIdx))
                .getRelationship().getId();
        //set background picture to sheet
        sheet.getCTWorksheet().addNewPicture().setId(rID);
    }

    @Override
    public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
    }

    @Override
    public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
        try (ByteArrayOutputStream waterMark = createWaterMark(WATER_MARK)) {
            XSSFSheet sheet = (XSSFSheet) writeSheetHolder.getSheet();
            putWaterRemarkToExcel(sheet, waterMark.toByteArray());
        } catch (IOException | FontFormatException e) {
            e.printStackTrace();
        }
    }
}

字体文件私聊问我要

excel执行导出

bash 复制代码
String excelPath;
        try {
            excelPath = agentExcelUtils.syncDynamicHeadWrite(fileName, sheetName, headList, dataLists);
        } catch (Exception e) {
            log.error("导出excel失败=", e);
           
        }

excel转pdf转图片

bash 复制代码
String imageName = fileName + DateUtils.dateTimeNow() + ".png";
        String pdfPath = "D:\\file\\" + fileName + DateUtils.dateTimeNow() + ".pdf";
        String imagePath = "D:\\file\\" + fileName + DateUtils.dateTimeNow() + ".png";
        try {
            exportToImage.convertExcelToImage(excelPath, pdfPath, imagePath);
        } catch (Exception e) {
            log.error("导出excel转图片失败=", e);
        }

AgentExcelToImage工具类

bash 复制代码
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Document;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfWriter;
import com.juepeiscm.common.utils.DateUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

/**
 * Excel 转图片
 */
@Slf4j
@Service
public class AgentExcelToImage {

    /**
     * Excel->pdf->图片
     * @param EXCEL_PATH excel文件路径
     * @param PDF_PATH pdf文件路径
     * @param IMAGE_PATH 图片文件路径
     */
    public void convertExcelToImage(String EXCEL_PATH, String PDF_PATH, String IMAGE_PATH) {
        try {
            // Step 1: Excel to PDF
            convertExcelToPdf(EXCEL_PATH, PDF_PATH);
            // Step 2: PDF to Image
            convertPdfToImage(PDF_PATH, IMAGE_PATH);
        } catch (Exception e) {
            log.error("convertExcelToImage异常=", e);
        }
    }

    /**
     * pdf转图片
     * @param pdfFilePath pdf文件路径
     * @param imageFilePath 图片路径
     * @throws Exception 异常
     */
    private void convertPdfToImage(String pdfFilePath, String imageFilePath) throws Exception {
        PDDocument document = PDDocument.load(new File(pdfFilePath));
        PDFRenderer renderer = new PDFRenderer(document);

        float scale = 2f; // DPI 比例(可适当提高如 3f)
        List<BufferedImage> croppedImages = new ArrayList<>();

        int pageWidth = 0;
        int totalHeight = 0;

        // Step 1: 渲染并裁剪每一页
        for (int i = 0; i < document.getNumberOfPages(); i++) {
            BufferedImage image = renderer.renderImage(i, scale);

            if (i == document.getNumberOfPages() - 1) {
                image = cropBottomWhiteSpace(image); // 只裁剪最后一页
            }
            croppedImages.add(image);
            pageWidth = Math.max(pageWidth, image.getWidth());
            totalHeight += image.getHeight();
        }

        // Step 2: 创建最终合并图像
        BufferedImage combinedImage = new BufferedImage(pageWidth, totalHeight, BufferedImage.TYPE_INT_RGB);
        Graphics2D g2d = combinedImage.createGraphics();

        int currentY = 0;
        for (BufferedImage img : croppedImages) {
            g2d.drawImage(img, 0, currentY, null);
            currentY += img.getHeight();
        }

        g2d.dispose();

        // Step 3: 保存图像
        ImageIO.write(combinedImage, "PNG", new File(imageFilePath));

        document.close();
    }

    /**
     * 裁剪图像底部的空白区域(假设背景为白色)
     */
    private BufferedImage cropBottomWhiteSpace(BufferedImage img) {
        int width = img.getWidth();
        int height = img.getHeight();

        // 默认从下往上找第一个非白色像素
        int bottom = height - 1;

        outer:
        for (int y = height - 1; y >= 0; y--) {
            for (int x = 0; x < width; x++) {
                if ((img.getRGB(x, y) & 0xFFFFFF) != 0xFFFFFF) { // 非白色像素
                    bottom = y;
                    break outer;
                }
            }
        }

        // 设置新的高度(裁剪到底部有效行)
        int newHeight = bottom + 1;

        return img.getSubimage(0, 0, width, newHeight);
    }

    /**
     * excel转pdf
     * @param excelFilePath excel路径
     * @param pdfFilePath pdf路径
     * @throws Exception 异常
     */
    private void convertExcelToPdf(String excelFilePath, String pdfFilePath) throws Exception {
        Workbook workbook = new XSSFWorkbook(Files.newInputStream(Paths.get(excelFilePath)));
        Sheet sheet2 = workbook.getSheetAt(0);

        float rowHeightPt = 60f;
        int headerRows = 1;
        int dataRows = sheet2.getLastRowNum();
        float calculatedHeight = (headerRows + dataRows) * rowHeightPt - (dataRows * 30);

        // 设置页面高度为自适应高度
        Rectangle pageSize = new Rectangle(PageSize.A4.getWidth(), calculatedHeight);
        Document document = new Document(pageSize, 20, 20, 20, 0); //设置上下左右边距

        PdfWriter writer = PdfWriter.getInstance(document, Files.newOutputStream(Paths.get(pdfFilePath)));
        writer.setPageEvent(new WaterMarkEvent());
        document.open();

        Font font = null;
        BaseFont baseFont = null;
        ClassPathResource resource = new ClassPathResource("font/msyh.ttf");
        try (InputStream inputStream = resource.getInputStream()) {
            byte[] fontBytes = IOUtils.toByteArray(inputStream);
            baseFont = BaseFont.createFont("font/msyh.ttf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED, true, fontBytes, null);
            font = new Font(baseFont, 12, Font.NORMAL);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("convertExcelToPdf异常=", e);
        }

        //----开始测试
//        String fontPath = "D:\\src\\main\\resources\\font\\msyh.ttf";
//        File fontFile = new File(fontPath);
//        if (!fontFile.exists()) {
//        }
//        BaseFont baseFont = BaseFont.createFont(fontPath, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
//        Font font = new Font(baseFont, 12, Font.NORMAL);
        //----结束测试

        // Iterate over sheets, but only add header once
        boolean headerAdded = false;

        for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
            Sheet sheet = workbook.getSheetAt(i);
            if (sheet == null || sheet.getLastRowNum() < 0) continue;

            int columnCount = sheet.getRow(0).getLastCellNum();
            PdfPTable table = new PdfPTable(columnCount);
            table.setWidthPercentage(100);
            table.setSplitLate(false);  // 允许拆分到下一页
            table.setExtendLastRow(false);

            // Add header row only once
            if (!headerAdded) {
                Font whiteFont = new Font(baseFont, 12, Font.NORMAL);
                whiteFont.setColor(BaseColor.WHITE);
                addTableRows(sheet, table, whiteFont, true); //使用 whiteFont
                headerAdded = true;
            }
            addTableRows(sheet, table, font, false);


            document.add(table);

            if (i < workbook.getNumberOfSheets() - 1) {
                document.newPage();
            }
        }

        document.close();
        writer.close();
        workbook.close();
    }

    private void addTableRows(Sheet sheet, PdfPTable table, Font font, boolean isHeader) {
        // Add header row (only once)
        if (isHeader) {
            Row row = sheet.getRow(0);
            if (row != null) {
                int columnCount = row.getLastCellNum();
                for (int k = 0; k < columnCount; k++) {
                    Cell cell = row.getCell(k);
                    String cellValue = cell != null ? cell.toString() : "";
                    PdfPCell pdfCell = new PdfPCell(new Phrase(cellValue, font));
                    applyCellStyle(pdfCell, true);
                    pdfCell.setMinimumHeight(30f);
                    table.addCell(pdfCell);
                }
            }
        }
        if (!isHeader) {
            for (int j = 1; j <= sheet.getLastRowNum(); j++) {
                Row dataRow = sheet.getRow(j);
                if (dataRow == null) continue;

                int columnCount = dataRow.getLastCellNum();
                for (int k = 0; k < columnCount; k++) {
                    Cell cell = dataRow.getCell(k);
                    String cellValue = cell != null ? cell.toString() : "";
                    // 动态计算字体大小
                    float baseFontSize = 12f;
                    float minFontSize = 6f; // 最小字体
                    int maxChars = 10;      // 每个单元格最多容纳字符数
                    int charCount = cellValue.length();
                    float adjustedFontSize = baseFontSize;
                    if (charCount > maxChars) {
                        adjustedFontSize = Math.max(minFontSize, baseFontSize * ((float) maxChars / charCount));
                    }
                    Font dynamicFont = new Font(font.getBaseFont(), adjustedFontSize, font.getStyle());
                    PdfPCell pdfCell = new PdfPCell(new Phrase(cellValue, dynamicFont));
                    applyCellStyle(pdfCell, false);
                    pdfCell.setMinimumHeight(30f); // 固定行高
                    pdfCell.setNoWrap(false);      // 允许换行(可选)
                    table.addCell(pdfCell);
                }
            }
        }
    }

    private void applyCellStyle(PdfPCell cell, boolean isHeader) {
        cell.setPadding(5);
        cell.setBorder(Rectangle.BOX);
        cell.setBorderWidth(1f);
        cell.setBorderColor(BaseColor.BLACK);

        if (isHeader) {
            // 设置表头背景色为 AQUA(浅蓝色)
            cell.setBackgroundColor(new BaseColor(0, 85, 145));
            cell.setHorizontalAlignment(Element.ALIGN_CENTER);
        } else {
            cell.setBackgroundColor(BaseColor.WHITE);
            cell.setHorizontalAlignment(Element.ALIGN_CENTER);
        }
    }

    public static void main(String[] args) {
        AgentExcelToImage agentExcelToImage = new AgentExcelToImage();
        String EXCEL_PATH = "D:\\file\\出库单信息确认202505131514021.xlsx";
        String PDF_PATH = "D:\\file\\output" + DateUtils.dateTimeNow() + ".pdf";
        String IMAGE_PATH = "D:\\file\\output_image" + DateUtils.dateTimeNow() + ".png";
        agentExcelToImage.convertExcelToImage(EXCEL_PATH, PDF_PATH, IMAGE_PATH);
    }
}

pdf水印工具类

bash 复制代码
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfGState;
import com.itextpdf.text.pdf.PdfPageEventHelper;
import com.itextpdf.text.pdf.PdfWriter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;

import java.io.File;
import java.io.InputStream;

import static com.google.common.io.ByteStreams.toByteArray;

/**
 * @author sunpeiyang
 * @date 2025/5/11 19:52
 */
@Slf4j
public class WaterMarkEvent extends PdfPageEventHelper {
    private String waterMarkText = "供应链";

    @Override
    public void onEndPage(PdfWriter writer, com.itextpdf.text.Document document) {
        try {
            PdfContentByte cb = writer.getDirectContent();
            Rectangle rect = document.getPageSize();

            // 加载中文字体(确保路径正确)
            BaseFont baseFont = null;
            ClassPathResource resource = new ClassPathResource("font/msyh.ttf");
            try (InputStream inputStream = resource.getInputStream()) {
                byte[] fontBytes = toByteArray(inputStream);
                baseFont = BaseFont.createFont("font/msyh.ttf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED, true, fontBytes, null);
            } catch (Exception e) {
                e.printStackTrace();
                log.error("水印字体转换异常=", e);
            }

            //----开始测试
//            String fontPath = "D:\\jp_project\\jp-customer-service-agent-admin2\\customer-service-agent-main\\src\\main\\resources\\font\\msyh.ttf";
//            File fontFile = new File(fontPath);
//            if (!fontFile.exists()) {
//            }
//            BaseFont baseFont = BaseFont.createFont(fontPath, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);

            // 设置透明度
            PdfGState gstate = new PdfGState();
            gstate.setFillOpacity(0.5f);
            cb.saveState();
            cb.setGState(gstate);

            float fontSize = 12;
            float spacingX = 80; // 减小横向间距
            float spacingY = 50; // 减小纵向间距

            float pageWidth = rect.getWidth();
            float pageHeight = rect.getHeight();

            // 遍历页面绘制多个水印
            for (float x = 0; x < pageWidth; x += spacingX) {
                for (float y = 0; y < pageHeight; y += spacingY) {
                    cb.beginText();
                    cb.setFontAndSize(baseFont, fontSize);
                    cb.setColorFill(BaseColor.LIGHT_GRAY); // 或者 BaseColor.GRAY 更深一些
                    cb.showTextAligned(
                            Element.ALIGN_CENTER,
                            waterMarkText, // 只传字符串
                            x + spacingX / 2,
                            y + spacingY / 2,
                            45 // 旋转 45°
                    );
                    cb.endText();
                }
            }

            cb.restoreState();
        } catch (Exception e) {
            System.err.println("绘制水印失败: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

效果图如下

相关推荐
敲代码的小吉米1 小时前
前端上传el-upload、原生input本地文件pdf格式(纯前端预览本地文件不走后端接口)
前端·javascript·pdf·状态模式
lisw052 小时前
Python高级进阶:Vim与Vi使用指南
python·vim·excel
Winter_world7 小时前
Excel中批量对多个结构相同的工作表执行操作,可以使用VBA宏来实现
excel·excel批量sheet操作
慧一居士7 小时前
EasyExcel集成使用总结与完整示例
java·excel
零凌林7 小时前
使用exceljs将excel文件转化为html预览最佳实践(完整源码)
前端·html·excel·vue3·最佳实践·文件预览·exceljs
aklry9 小时前
uniapp实现在线pdf预览以及下载
前端·pdf·uni-app
繁依Fanyi9 小时前
我的 PDF 工具箱:CodeBuddy 打造 PDFMagician 的全过程记录
java·pdf·uni-app·生活·harmonyos·codebuddy首席试玩官
晨曦backend9 小时前
EXCEL下拉菜单与交替上色设置
excel
DevOpenClub14 小时前
PPT 转高精度 PDF API 接口
pdf·powerpoint