1

前言

突然有一天在业务系统中发现OpenOffice转换word为pdf时,出现个别中文字丢失以及格式发生变化。这在业务系统中预览合同等重要附件是致命的。Google了半天也没找到问题所在。于是采用LibreOffice进行转换,看看转换效果。office文件在线预览原理一样,先转换成pdf,然后采用pdf. js预览。

问题复现

原word文件


OpenOffice转换后pdf文件


链接

快速开始

下载

地址

下载最新版本,目前是7.0.1

下载三个包:

安装

  • 主包
cd LibreOffice_7.0.1.2_Linux_x86-64_rpm/RPMS
yum localinstall -y *.rpm
  • SDK
cd LibreOffice_7.0.1.2_Linux_x86-64_rpm_sdk/RPMS
yum localinstall -y *.rpm
  • 语言包
cd LibreOffice_7.0.1.2_Linux_x86-64_rpm_langpack_zh-CN
yum localinstall -y *.rpm

启动

/opt/libreoffice7.0/program/soffice --headless --accept="socket,host=127.0.0.1,port=8101;urp;" --nofirststartwizard > /opt/server/libre.log 2>&1 &
# 自启动
将以上命令添加到/etc/rc.local中

测试

libreoffice7.0 --headless --invisible --convert-to pdf 原office文件 --outdir 输出目录

Java集成

安装jar

<dependency>
    <groupId>org.jodconverter</groupId>
    <artifactId>jodconverter-local</artifactId>
    <version>4.3.0</version>
</dependency>

配置文件libre.properties

在resources目录下新建libre.properties

# LibreOffice主目录
libreOfficeHome=/opt/libreoffice7.0
# 开启多个LibreOffice进程,每个端口对应一个进程
# portNumbers=2002,2003
portNumbers=8101
# 任务执行超时为5分钟
taskExecutionTimeoutMinutes=5
# 任务队列超时为1小时
taskQueueTimeoutHours=1

读取配置,并连接LibreOffice服务

新建类:OfficeManagerInstance.java

package cn.sccl.common.config;

import org.jodconverter.core.office.OfficeManager;
import org.jodconverter.local.office.LocalOfficeManager;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.util.Properties;

@Component
public class OfficeManagerInstance {
    private static OfficeManager INSTANCE = null;

    public static synchronized void start() {
        officeManagerStart();
    }

    @PostConstruct
    private void init() {
        try {
            Properties properties = PropertiesLoaderUtils.loadAllProperties("libre.properties");
            String[] portNumbers = properties.getProperty("portNumbers", "").split(",");
            int[] ports = new int[portNumbers.length];

            for (int i = 0; i < portNumbers.length; i++) {
                ports[i] = Integer.parseInt(portNumbers[i]);
            }

            LocalOfficeManager.Builder builder = LocalOfficeManager.builder().install();
            builder.officeHome(properties.getProperty("libreOfficeHome", ""));
            builder.portNumbers(ports);
            builder.taskExecutionTimeout(Long.parseLong(properties.getProperty("taskExecutionTimeoutMinutes", "")) * 1000 * 60); // minute
            builder.taskQueueTimeout(Long.parseLong(properties.getProperty("taskQueueTimeoutHours", "")) * 1000 * 60 * 60); // hour

            INSTANCE = builder.build();
            officeManagerStart();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void officeManagerStart() {
        if (INSTANCE.isRunning()) {
            return;
        }

        try {
            INSTANCE.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

工具类LibreOfficeUtil

新建工具类:LibreOfficeUtil.java

package cn.sccl.common.util;

import cn.sccl.common.config.OfficeManagerInstance;
import org.jodconverter.local.JodConverter;

import java.io.File;

public class LibreOfficeUtil {
    /**
     * 利用 JodConverter 将 Offfice 文档转换为 PDF(要依赖 LibreOffice),该转换为同步转换,返回时就已经转换完成
     */
    public static boolean convertOffice2PDFSyncIsSuccess(File sourceFile, File targetFile) {
        try {
            OfficeManagerInstance.start();
            JodConverter.convert(sourceFile).to(targetFile).execute();
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

        return true;
    }

    /**
     * 利用 LibreOffice 将 Office 文档转换成 PDF,该转换是异步的,返回时,转换可能还在进行中,转换是否有异常也未可知
     * @param filePath       目标文件地址
     * @param targetFilePath 输出文件夹
     * @return 子线程执行完毕的返回值
     */
    public static int convertOffice2PDFAsync(String filePath, String fileName, String targetFilePath) throws Exception {
        String command;
        int exitStatus;
        String osName = System.getProperty("os.name");
        String outDir = targetFilePath.length() > 0 ? " --outdir " + targetFilePath : "";

        if (osName.contains("Windows")) {
            command = "cmd /c cd /d " + filePath + " && start soffice --headless --invisible --convert-to pdf ./" + fileName + outDir;
        } else {
            command = "libreoffice6.3 --headless --invisible --convert-to pdf:writer_pdf_Export " + filePath + fileName + outDir;
        }

        exitStatus = executeOSCommand(command);
        return exitStatus;
    }

    /**
     * 调用操作系统的控制台,执行 command 指令
     * 执行该方法时,并没有等到指令执行完毕才返回,而是执行之后立即返回,返回结果为 0,只能说明正确的调用了操作系统的控制台指令,但执行结果如何,是否有异常,在这里是不能体现的,所以,更好的姿势是用同步转换功能。
     */
    private static int executeOSCommand(String command) throws Exception {
        Process process;
        process = Runtime.getRuntime().exec(command); // 转换需要时间,比如一个 3M 左右的文档大概需要 8 秒左右,但实际测试时,并不会等转换结束才执行下一行代码,而是把执行指令发送出去后就立即执行下一行代码了。

        int exitStatus = process.waitFor();

        if (exitStatus == 0) {
            exitStatus = process.exitValue();
        }

        // 销毁子进程
        process.destroy();
        return exitStatus;
    }
}

使用方法

boolean ret = LibreOfficeUtil.convertOffice2PDFSyncIsSuccess(sourceFile, pdfFile);

效果对比

效果明显比OpenOffice效果好,就转换速率来说,差不多时间,这个就没有详细计算了。

中文乱码问题解决

不管是用LibreOffice还是OpenOffice,都会遇到中文乱码问题。统一解决办法如下:

环境:CentOS7

  • 查看字体目录vim /etc/fonts/fonts.conf,默认在/usr/share/fonts/
  • 安装中文字体
mkdir /usr/share/fonts/chinese
cd /usr/share/fonts/chinese
# 将windows字体拷贝到该目录,如下:搜索“简体”,拷贝所有

chmod 755 *.TTF
chmod 755 *.TTC
# (如果提示 mkfontscale: command not found,需自行安装 # yum install mkfontscale)
mkfontscale

mkfontdir

# (如果提示 fc-cache: command not found,则需要安装# yum install fontconfig )
fc-cache -fv

# 安装完成,重启LibreOffice或OpenOffice服务

赞助作者,互相交流

image
image

转载请注明:我的技术分享 » 跨平台文件在线预览解决方案(三)- LibreOffice vs OpenOffice


silianpan
160 声望9 粉丝

专注于web前端,spring boot,微服务架构。坚持原创技术分享,为开源贡献力量。