使用java将word转换为pdf

Published on 2020-12-24 23:39 in 分类: 博客 with 狂盗一枝梅
分类: 博客

本篇文章会讲解如何使用jacob以及jodconverter将word无格式损毁转换为pdf。

使用java将word转换为pdf大体上有三种方式:

  1. 使用poi,也就是apache开源框架poi,但是它的缺点很明显,使用它转换pdf格式损毁比较严重,效率还很低,它在windows和linux平台都可以使用
  2. 使用jodconverter,使用它将word转换为pdf效果上要比poi好很多,但是它依赖于服务器端安装的OpenOffice或者libreoffice,它在windows和linux平台都可以使用,前提是宿主机都安装了OpenOffice或者libreoffice软件
  3. 使用jacob,使用它能实现100%无格式损毁转换word到pdf,但是它不能跨平台,只能在windows平台使用。

一、jacob转换方法

使用jacob转换依赖于windows系统上安装的wps或者word,在运行程序前必须安装这两个软件中的其中一个。

jacob项目已经迁移到github,github地址为,https://github.com/freemansoft/jacob-project ,现在star数量感人。。。打开release下载最新版(最新版的版本号是 1.20 ),并解压得到如下三个文件:

jacob.jar
jacob-1.20-x64.dll
jacob-1.20-x86.dll

1. 将jacob.jar安装到本地maven仓库

适用如下命令将jacob安装到本地maven仓库以方便使用

mvn install:install-file -Dfile=./jacob.jar -DgroupId=com.kdyzm -DartifactId=jacob -Dversion=1.0 -Dpackaging=jar

2. 将jacob-1.20-x64.dll拷贝到jre/bin目录

由于jacob依赖于本地安装的wps或者office,这就需要jni调用,所以需要将dll文件放到jire/bin目录下

3. 安装wps或者office

4.编码转换

private int word2PDF(String inputFile, String pdfFile) {
        try {
            // 打开Word应用程序
            ActiveXComponent app = new ActiveXComponent("KWPS.Application");
            log.info("开始转化{}为{}...",inputFile,pdfFile);
            long date = new Date().getTime();
            // 设置Word不可见
            app.setProperty("Visible", new Variant(false));
            // 禁用宏
            app.setProperty("AutomationSecurity", new Variant(3));
            // 获得Word中所有打开的文档,返回documents对象
            Dispatch docs = app.getProperty("Documents").toDispatch();
            // 调用Documents对象中Open方法打开文档,并返回打开的文档对象Document
            Dispatch doc = Dispatch.call(docs, "Open", inputFile, false, true).toDispatch();
            /***
             *
             * 调用Document对象的SaveAs方法,将文档保存为pdf格式
             *
             * Dispatch.call(doc, "SaveAs", pdfFile, wdFormatPDF word保存为pdf格式宏,值为17 )
             *
             */
            Dispatch.call(doc, "ExportAsFixedFormat", pdfFile, wdFormatPDF);// word保存为pdf格式宏,值为17
            // 关闭文档
            long date2 = new Date().getTime();
            int time = (int) ((date2 - date) / 1000);

            Dispatch.call(doc, "Close", false);
            // 关闭Word应用程序
            app.invoke("Quit", 0);
            return time;
        } catch (Exception e) {
            // TODO: handle exception
            log.error("",e);
            return -1;
        }

    }

另外,完整代码如下所示

/**
 * @author kdyzm
 */

import com.jacob.activeX.ActiveXComponent;
import com.jacob.com.ComThread;
import com.jacob.com.Dispatch;
import com.jacob.com.Variant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Arrays;
import java.util.Date;
import java.util.Objects;
import java.util.Properties;

/***
 * office文件转换为PDF文件
 *
 * @author leo.li
 *
 */
public class Main2 {

    private String office_path = "D:\\ProgramFiles\\WPS Office\\11.1.0.10132\\office6";
    private String pdf_path = "./pdfs";

    private static final int wdFormatPDF = 17;
    private static final int xlTypePDF = 0;
    private static final int ppSaveAsPDF = 32;
    private static final Logger log= LoggerFactory.getLogger(Main2.class);

    // 8 代表word保存成html
    public static final int WORD_HTML = 8;

    // 1 代表html保存成word
    public static final int HTML_WORD = 1;

    public static void main(String[] args) throws IOException {

        Properties properties = new Properties();
        properties.load(new FileReader(new File("config.properties")));

        String sourceDirStr = properties.get("source.dir").toString();
        String outputDirStr = properties.get("output.dir").toString();

        File sourceDir = new File(sourceDirStr);
        Arrays.stream(Objects.requireNonNull(sourceDir.listFiles())).forEach(file->{
            String absolutePath = file.getAbsolutePath();
            String name = file.getName().substring(0,file.getName().indexOf("."));
            log.info(absolutePath);
            String targetName=outputDirStr+"/"+name+".pdf";
            log.info(targetName);
            new Main2().word2PDF(absolutePath,targetName);
        });
    }

    public static void wordToHtml(String docfile, String htmlfile)
    {
        // 启动word应用程序(Microsoft Office Word 2003)
        ActiveXComponent app = new ActiveXComponent("Word.Application");
        System.out.println("*****正在转换...*****");
        try
        {
            // 设置word应用程序不可见
            app.setProperty("Visible", new Variant(false));
            // documents表示word程序的所有文档窗口,(word是多文档应用程序)
            Dispatch docs = app.getProperty("Documents").toDispatch();
            // 打开要转换的word文件
            Dispatch doc = Dispatch.invoke(docs, "Open", Dispatch.Method, new Object[] { docfile, new Variant(false), new Variant(true) }, new int[1]).toDispatch();
            // 作为html格式保存到临时文件
            Dispatch.invoke(doc, "SaveAs", Dispatch.Method, new Object[] { htmlfile, new Variant(WORD_HTML) }, new int[1]);
            // 关闭word文件
            Dispatch.call(doc, "Close", new Variant(false));
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        finally
        {
            //关闭word应用程序
            app.invoke("Quit", new Variant[] {});
        }
        System.out.println("*****转换完毕********");
    }


    /**
     * 转换office文件为PDF
     *
     * @param inputFileName
     * @return
     */
    public boolean office2pdf(String inputFileName, String username) {
        String officeUserPath = String.format(office_path + "/%s/%s", username, inputFileName);
        inputFileName = inputFileName.substring(0, inputFileName.lastIndexOf("."));
        String pdfUserPath = String.format(pdf_path + "/%s/%s", username, inputFileName + ".pdf");

        int time = convert2PDF(officeUserPath, pdfUserPath);
        boolean result = false;
        if (time == -4) {
            log.info("转化失败,未知错误...");
        } else if (time == -3) {
            result = true;
            log.info("原文件就是PDF文件,无需转化...");
        } else if (time == -2) {
            log.info("转化失败,文件不存在...");
        } else if (time == -1) {
            log.info("转化失败,请重新尝试...");
        } else if (time < -4) {
            log.info("转化失败,请重新尝试...");
        } else {
            result = true;
            log.info("转化成功,用时:  " + time + "s...");
        }
        return result;
    }

    /***
     * 判断需要转化文件的类型(Excel、Word、ppt)
     *
     * @param inputFile
     * @param pdfFile
     */
    private int convert2PDF(String inputFile, String pdfFile) {
        String kind = getFileSufix(inputFile);
        File file = new File(inputFile);
        if (!file.exists()) {
            log.info("文件不存在");
            return -2;
        }
        if (kind.equals("pdf")) {
            log.info("原文件就是PDF文件");
            return -3;
        }
        if (kind.equals("doc") || kind.equals("docx") || kind.equals("txt")) {
            return word2PDF(inputFile, pdfFile);
        } else if (kind.equals("ppt") || kind.equals("pptx")) {
            return ppt2PDF(inputFile, pdfFile);
        } else if (kind.equals("xls") || kind.equals("xlsx")) {
            return Ex2PDF(inputFile, pdfFile);
        } else {
            return -4;
        }
    }

    /***
     * 判断文件类型
     *
     * @param fileName
     * @return
     */
    public static String getFileSufix(String fileName) {
        int splitIndex = fileName.lastIndexOf(".");
        return fileName.substring(splitIndex + 1);
    }

    /***
     *
     * Word转PDF
     *
     * @param inputFile
     * @param pdfFile
     * @return
     */

    private int word2PDF(String inputFile, String pdfFile) {
        try {
            // 打开Word应用程序
            ActiveXComponent app = new ActiveXComponent("KWPS.Application");
            log.info("开始转化{}为{}...",inputFile,pdfFile);
            long date = new Date().getTime();
            // 设置Word不可见
            app.setProperty("Visible", new Variant(false));
            // 禁用宏
            app.setProperty("AutomationSecurity", new Variant(3));
            // 获得Word中所有打开的文档,返回documents对象
            Dispatch docs = app.getProperty("Documents").toDispatch();
            // 调用Documents对象中Open方法打开文档,并返回打开的文档对象Document
            Dispatch doc = Dispatch.call(docs, "Open", inputFile, false, true).toDispatch();
            /***
             *
             * 调用Document对象的SaveAs方法,将文档保存为pdf格式
             *
             * Dispatch.call(doc, "SaveAs", pdfFile, wdFormatPDF word保存为pdf格式宏,值为17 )
             *
             */
            Dispatch.call(doc, "ExportAsFixedFormat", pdfFile, wdFormatPDF);// word保存为pdf格式宏,值为17
            // 关闭文档
            long date2 = new Date().getTime();
            int time = (int) ((date2 - date) / 1000);

            Dispatch.call(doc, "Close", false);
            // 关闭Word应用程序
            app.invoke("Quit", 0);
            return time;
        } catch (Exception e) {
            // TODO: handle exception
            log.error("",e);
            return -1;
        }

    }

    /***
     *
     * Excel转化成PDF
     *
     * @param inputFile
     * @param pdfFile
     * @return
     */
    private int Ex2PDF(String inputFile, String pdfFile) {
        try {
            ComThread.InitSTA(true);
            ActiveXComponent ax = new ActiveXComponent("KET.Application");
            log.info("开始转化Excel为PDF...");
            long date = new Date().getTime();
            ax.setProperty("Visible", false);
            ax.setProperty("AutomationSecurity", new Variant(3)); // 禁用宏
            Dispatch excels = ax.getProperty("Workbooks").toDispatch();

            Dispatch excel = Dispatch
                    .invoke(excels, "Open", Dispatch.Method,
                            new Object[] { inputFile, new Variant(false), new Variant(false) }, new int[9])
                    .toDispatch();
            // 转换格式
            Dispatch.invoke(excel, "ExportAsFixedFormat", Dispatch.Method, new Object[] { new Variant(0), // PDF格式=0
                    pdfFile, new Variant(xlTypePDF) // 0=标准 (生成的PDF图片不会变模糊) 1=最小文件
                    // (生成的PDF图片糊的一塌糊涂)
            }, new int[1]);

            // 这里放弃使用SaveAs
            /*
             * Dispatch.invoke(excel,"SaveAs",Dispatch.Method,new Object[]{ outFile, new
             * Variant(57), new Variant(false), new Variant(57), new Variant(57), new
             * Variant(false), new Variant(true), new Variant(57), new Variant(true), new
             * Variant(true), new Variant(true) },new int[1]);
             */
            long date2 = new Date().getTime();
            int time = (int) ((date2 - date) / 1000);
            Dispatch.call(excel, "Close", new Variant(false));

            if (ax != null) {
                ax.invoke("Quit", new Variant[] {});
                ax = null;
            }
            ComThread.Release();
            return time;
        } catch (Exception e) {
            // TODO: handle exception
            return -1;
        }
    }

    /***
     * ppt转化成PDF
     *
     * @param inputFile
     * @param pdfFile
     * @return
     */
    private int ppt2PDF(String inputFile, String pdfFile) {
        log.info("开始转化PPT为PDF...");
        try {
            ComThread.InitSTA(true);
            ActiveXComponent app = new ActiveXComponent("KWPP.Application");
//            app.setProperty("Visible", false);
            long date = new Date().getTime();
            Dispatch ppts = app.getProperty("Presentations").toDispatch();
            Dispatch ppt = Dispatch.call(ppts, "Open", inputFile, true, // ReadOnly
                    // false, // Untitled指定文件是否有标题
                    false// WithWindow指定文件是否可见
            ).toDispatch();
            Dispatch.invoke(ppt, "SaveAs", Dispatch.Method, new Object[] { pdfFile, new Variant(ppSaveAsPDF) },
                    new int[1]);
            log.info("PPT");
            Dispatch.call(ppt, "Close");
            long date2 = new Date().getTime();
            int time = (int) ((date2 - date) / 1000);
            app.invoke("Quit");
            return time;
        } catch (Exception e) {
            // TODO: handle exception
            return -1;
        }
    }



    // 删除多余的页,并转换为PDF
    public static void interceptPPT(String inputFile, String pdfFile) {
        ActiveXComponent app = null;
        try {
            ComThread.InitSTA(true);
            app = new ActiveXComponent("KWPP.Application");
            ActiveXComponent presentations = app.getPropertyAsComponent("Presentations");
            ActiveXComponent presentation = presentations.invokeGetComponent("Open", new Variant(inputFile),
                    new Variant(false));
            int count = Dispatch.get(presentations, "Count").getInt();
            System.out.println("打开文档数:" + count);
            ActiveXComponent slides = presentation.getPropertyAsComponent("Slides");
            int slidePages = Dispatch.get(slides, "Count").getInt();
            System.out.println("ppt幻灯片总页数:" + slidePages);

            // 总页数的20%取整+1 最多不超过5页
            int target = (int) (slidePages * 0.5) + 1 > 5 ? 5 : (int) (slidePages * 0.5) + 1;
            // 删除指定页数
            while (slidePages > target) {
                // 选中指定页幻灯片
                Dispatch slide = Dispatch.call(presentation, "Slides", slidePages).toDispatch();
                Dispatch.call(slide, "Select");
                Dispatch.call(slide, "Delete");
                slidePages--;
                System.out.println("当前ppt总页数:" + slidePages);
            }
            Dispatch.invoke(presentation, "SaveAs", Dispatch.Method, new Object[] { pdfFile, new Variant(32) },
                    new int[1]);
            Dispatch.call(presentation, "Save");
            Dispatch.call(presentation, "Close");
            presentation = null;
            app.invoke("Quit");
            app = null;
            ComThread.Release();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
        }
    }
}

5.修改文档属性

pdf的文档属性,也就是pdf的metadata数据,包含了标题、作者等信息,这里的标题和文件名不一样,谷歌浏览器能识别并打开pdf文件,会使用pdf的标题文档属性作为tab页的title,如果不修改,会出现诡异的问题:tab页的title和文件内容标题不一致。我转换了word到pdf,保留了metadata信息,结果tab页的title竟然是“大头”两个字。。

修改元数据方法:不会使用jacob修改,这里使用apache pdfbox 进行修改。

首先引入pom依赖

<dependency>
    <groupId>org.apache.pdfbox</groupId>
    <artifactId>pdfbox</artifactId>
    <version>2.0.22</version>
</dependency>

然后几行代码就可以完成转换:

    private static void changeMetadata(File destFile,String title) throws IOException {
        PDDocument doc = PDDocument.load(destFile);
        PDDocumentInformation documentInformation = doc.getDocumentInformation();
        documentInformation.setTitle(title);
        documentInformation.setAuthor("kdyzm");
        doc.save(destFile);
    }

参考文档:https://pdfbox.apache.org/1.8/cookbook/workingwithmetadata.htmlhttps://stackoverflow.com/questions/3491026/how-to-edit-pdf-properties-in-java

二、jodconverter转换方法

joconverter转换word到pdf是最简单的一种方法,只需要一行代码就可以搞定。

1.引入jodconverter依赖

上maven中央仓库搜索jodconverter关键字,将jodconverter-localjodconverter-core引入到项目中,我使用的是4.3.0版本

<!-- https://mvnrepository.com/artifact/org.jodconverter/jodconverter-local -->
<dependency>
    <groupId>org.jodconverter</groupId>
    <artifactId>jodconverter-local</artifactId>
    <version>4.3.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.jodconverter/jodconverter-core -->
<dependency>
    <groupId>org.jodconverter</groupId>
    <artifactId>jodconverter-core</artifactId>
    <version>4.3.0</version>
</dependency>

2. 安装openoffice或者libreoffice

jodconverter依赖于服务端的这两个软件之一,但是和jacob不同的是,这两个软件都可以安装到linux中,有了容器技术之后,即使实现这种方式的转换也不再变得麻烦,总之jodconverter是poi和jacob之间的折中方案了。

另外,wps也能安装到linux中了,但是jacob还不支持,jacob还只能在windows环境中使用。

3.编码转换

jodconverter是最简单的转换方式,代码就三行,简单来看,真正去做转换的代码只有一行,可谓大道至简。

import org.jodconverter.core.office.OfficeException;
import org.jodconverter.local.JodConverter;
import org.jodconverter.local.office.LocalOfficeManager;

import java.io.File;

/**
 * @author kdyzm
 */
public class Main1 {

    public static void main(String[] args) throws OfficeException {
        LocalOfficeManager manager = LocalOfficeManager.builder().officeHome("D:\\Program Files (x86)\\OpenOffice 4").install().build();
        manager.start();
        JodConverter.convert(new File("./mmmp.docx")).to(new File("./mmmp.pdf")).execute();
    }
}

#java
目录