首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java POI 解決 PPT 轉 PDF 中文亂碼/方框問題:字體統一替換方案

     編輯:Java 相關知識

問題背景

使用 Apache POI 將 PPT/PPTX 轉換為圖片再生成 PDF 時,常遇到中文顯示為方框 (□) 或亂碼。根因:

  • PPT (.ppt, HSLF):運行環境缺少原始字體,POI 回退失敗。
  • PPTX (.pptx, XSLF):幻燈片包含多種中文字體 (如微軟雅黑 + 宋體),POI 只讀取第一種字體渲染,導致其他字體文本缺失字形。

核心解決思路

運行時統一替換所有文本字體為系統確保存在的中文字體 (如「宋體」/「SimSun」),確保渲染引擎能找到字形。

完整代碼實現 (支援 PPT 與 PPTX)


import org.apache.poi.hslf.usermodel.*;
import org.apache.poi.xslf.usermodel.*;
import org.apache.poi.sl.usermodel.*;
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.List;

public class PptToPdfConverter {

    private static final String TARGET_FONT = "宋體"; // 目標統一字體 (系統必須安裝)
    private static final double ZOOM = 2.0;           // 渲染縮放倍數 (提高清晰度)

    public static void convert(String sourcePath, String destPath) throws Exception {
        try (FileInputStream fis = new FileInputStream(sourcePath)) {
            String ext = sourcePath.toLowerCase().substring(sourcePath.lastIndexOf('.'));
            
            Document pdfDoc = new Document();
            PdfWriter writer = PdfWriter.getInstance(pdfDoc, new FileOutputStream(destPath));
            PdfPTable table = new PdfPTable(1);
            writer.setInitialLeading(0);
            
            AffineTransform at = new AffineTransform();
            at.setToScale(ZOOM, ZOOM);

            if (".ppt".equals(ext)) {
                // --- PPT (HSLF) 處理 ---
                SlideShow ppt = new SlideShow(fis);
                Dimension pgSize = ppt.getPageSize();
                pdfDoc.setPageSize(new Rectangle(pgSize.width, pgSize.height));
                pdfDoc.open();
                
                Slide[] slides = ppt.getSlides();
                for (int i = 0; i < slides.length; i++) {
                    // 關鍵: 統一替換所有文本運行的字體
                    TextRun[] runs = slides[i].getTextRuns();
                    for (TextRun run : runs) {
                        RichTextRun[] richRuns = run.getRichTextRuns();
                        for (RichTextRun rtr : richRuns) {
                            rtr.setFontIndex(1); // 強制使用字體索引 1 (通常映射為默認中文字體)
                            rtr.setFontName(TARGET_FONT);
                        }
                    }
                    
                    // 渲染幻燈片為圖片
                    BufferedImage img = renderSlide(slides[i], pgSize, at);
                    addImageToPdf(table, img);
                }
                ppt.close();
                
            } else if (".pptx".equals(ext)) {
                // --- PPTX (XSLF) 處理 ---
                XMLSlideShow pptx = new XMLSlideShow(fis);
                Dimension pgSize = pptx.getPageSize();
                pdfDoc.setPageSize(new Rectangle(pgSize.width, pgSize.height));
                pdfDoc.open();
                
                XSLFSlide[] slides = pptx.getSlides();
                for (int i = 0; i < slides.length; i++) {
                    // 關鍵: 遍歷所有形狀,針對文本形狀統一字體
                    for (XSLFShape shape : slides[i].getShapes()) {
                        if (shape instanceof XSLFTextShape) {
                            XSLFTextShape textShape = (XSLFTextShape) shape;
                            for (XSLFTextParagraph para : textShape.getTextParagraphs()) {
                                List runs = para.getTextRuns();
                                for (XSLFTextRun run : runs) {
                                    run.setFontFamily(TARGET_FONT); // 統一字體家族
                                }
                            }
                        }
                    }
                    
                    BufferedImage img = renderSlide(slides[i], pgSize, at);
                    addImageToPdf(table, img);
                }
                pptx.close();
            }
            
            pdfDoc.add(table);
            pdfDoc.close();
            writer.close();
            System.out.println("PPT 轉 PDF 成功: " + destPath);
        }
    }

    // 通用幻燈片渲染方法
    private static BufferedImage renderSlide(Slide slide, Dimension pgSize, AffineTransform at) {
        BufferedImage img = new BufferedImage(
            (int) Math.ceil(pgSize.width * ZOOM),
            (int) Math.ceil(pgSize.height * ZOOM),
            BufferedImage.TYPE_INT_RGB
        );
        Graphics2D g = img.createGraphics();
        g.setTransform(at);
        g.setPaint(Color.WHITE);
        g.fill(new java.awt.geom.Rectangle2D.Float(0, 0, pgSize.width, pgSize.height));
        slide.draw(g);
        g.dispose();
        return img;
    }

    private static BufferedImage renderSlide(XSLFSlide slide, Dimension pgSize, AffineTransform at) {
        BufferedImage img = new BufferedImage(
            (int) Math.ceil(pgSize.width * ZOOM),
            (int) Math.ceil(pgSize.height * ZOOM),
            BufferedImage.TYPE_INT_RGB
        );
        Graphics2D g = img.createGraphics();
        g.setTransform(at);
        g.setPaint(Color.WHITE);
        g.fill(new java.awt.geom.Rectangle2D.Float(0, 0, pgSize.width, pgSize.height));
        slide.draw(g);
        g.dispose();
        return img;
    }

    private static void addImageToPdf(PdfPTable table, BufferedImage img) throws Exception {
        Image pdfImg = Image.getInstance(img, null);
        PdfPCell cell = new PdfPCell(pdfImg, true);
        cell.setBorder(Rectangle.NO_BORDER);
        table.addCell(cell);
    }
}

Maven 依賴配置


<dependencies>
    <!-- POI 核心 -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>5.3.0</version> <!-- 建議用最新版 -->
    </dependency>
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>5.3.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-scratchpad</artifactId>
        <version>5.3.0</version>
    </dependency>
    
    <!-- iText 5 (PDF 生成) -->
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>itextpdf</artifactId>
        <version>5.5.13.3</version>
    </dependency>
</dependencies>

關鍵技術點解析

環節PPT (HSLF)PPTX (XSLF)
文本容器TextRun → RichTextRunXSLFTextShape → XSLFTextParagraph → XSLFTextRun
字體設置方法setFontName() + setFontIndex()setFontFamily()
多字體問題單字體較少見一頁多字體極常見,必須遍歷所有 Run 統一
渲染引擎Slide.draw(Graphics2D)XSLFSlide.draw(Graphics2D)

進階優化建議

  1. 字體回退鏈:不僅設置 `setFontFamily("宋體")`,還可嘗試 `setFontFamily("SimSun")`、`setFontFamily("Microsoft YaHei")` 多重嘗試,或使用 `FontConfig` 配置邏輯字體映射。
  2. Linux 服務器字體缺失:Docker/服務器需安裝中文字體:`apt-get install fonts-wqy-zenhei fonts-wqy-microhei` 或複製 Windows `simsun.ttc` 到 `/usr/share/fonts/` 並 `fc-cache -fv`。
  3. 高性能渲染:大量頁面轉換時,考慮使用 PDFRenderer 直接輸出 PDF (POI 5.0+ 實驗性支援) 或 LibreOffice headless 轉換 (`soffice --headless --convert-to pdf`),避免 Java2D 渲染瓶頸。
  4. 內存控制:大文件分頁處理,及時 `close()` SlideShow、刷新 PDF 文檔,避免 OOM。

總結

PPT/X 轉 PDF 中文方框問題本質是渲染環境字體缺失與多字體統一渲染缺陷。通過運行時遍歷所有文本 Run 統一替換為確保存在的中文字體,配合適當縮放渲染,可解決 95% 以上亂碼場景。生產環境務必驗證目標字體在服務器真實安裝,並考慮 LibreOffice 方案作為兜底。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved