php中调用Java实现word文档的预览

php预览word文档的实现 以及实现过程当中遇到的各类坑

在作软件工程的课程设计的时候,咱们小组选择作一个资料分享网站,网站最重要的功能固然就是上传文件和下载文件。可是这中间就须要一个比较重要的过程:预览。

预览最终结果是一张长图,很长很长的png图片。大体能够分为下面这几个步骤:php

  1. 将WORD文档转为PDF
  2. 将PDF拆分,毕竟只是预览,而不是查看所有,这里我设置的是预览10页
  3. 将PDF按页转换为PNG图片
  4. 将全部的PNG图片合并成一张长图

四个步骤,分别须要用到不一样的工具:前端

第一步:对于WORD转PDF,在度娘的帮助下,咱们决定使用Java语言实现这一功能。使用开源的openoffice+jodconverter来对WORD进行转换。

首先咱们要考虑php如何调用Java,很幸运,有一个叫作JavaBridge的东西为咱们解决了这个问题,JavaBridge的使用在百度中有大量的博客教程(ps:虽然我对国内这些博客互相抄袭,还错误百出很看不习惯,但确实也有好的博客,而且数量不少);java

而后就是要安装OpenOffice,这个很简单,不管是Windows仍是Linux都不难(直接百度搜索openoffice安装便可);
最后下载jodconverter的jar包,编写程序将WORD转为PDF。linux

源码以下web

import java.io.File;
import java.io.IOException;

import com.artofsolving.jodconverter.DocumentConverter;
import com.artofsolving.jodconverter.openoffice.connection.OpenOfficeConnection;
import com.artofsolving.jodconverter.openoffice.connection.SocketOpenOfficeConnection;
import com.artofsolving.jodconverter.openoffice.converter.OpenOfficeDocumentConverter;

public class PDFConverter {
    /**
     * 将WORD文档转换为PDF
     * @param srcPath WORD文档路径
     * @param desPath 目标PDF保存路径
     * @param pages 转换页数
     * @throws IOException
     */
    public void Word2Pdf(String srcPath, String desPath) throws IOException {
        // 源文件目录
        File inputFile = new File(srcPath);
        if (!inputFile.exists()) {
            System.out.println("源文件不存在!");
            System.out.println(srcPath + ", " + desPath);
            return;
        }
        
        // 输出文件目录
        File outputFile = new File(desPath);
        if (!outputFile.getParentFile().exists()) {
            outputFile.getParentFile().mkdirs();
        }
        // 调用openoffice服务线程
//        String command = "C:\\Program Files (x86)\\OpenOffice 4\\program\\soffice.exe -headless -accept=\"socket,host=127.0.0.1,port=8100;urp;\"";
        String command = "/opt/openoffice4/program/soffice -headless -accept=\"socket,host=127.0.0.1,port=8100;urp;\" -nofirststartwizard";
        Process p = Runtime.getRuntime().exec(command);
        // 链接openoffice服务
        OpenOfficeConnection connection = new SocketOpenOfficeConnection(
                "127.0.0.1", 8100);
        connection.connect();
        // 转换word到pdf
        DocumentConverter converter = new OpenOfficeDocumentConverter(
                connection);
        converter.convert(inputFile, outputFile);
        // 关闭链接
        connection.disconnect();
        // 关闭进程
        p.destroy();
        System.out.println("转换完成!");
    }
}
在程序中咱们建立一个线程来打开OpenOffice的服务 Process p = Runtime.getRuntime().exec(command);这条代码的做用至关于在命令行(终端)输入command字符串,而command字符串就是咱们启动openoffice服务的命令。后面链接openoffice而后利用它提供的接口将WORD转为PDF便可。(注意: 注释起来的command是Windows下的启动命令,由于Windows系统和linux系统中openoffice安装路径不一样,因此须要使用不一样的路径启动服务,因此在安装OpenOffice时必定要注意安装路径
第二步:将一个大的PDF拆分为小的PDF,多是PHP这方面的支持不够,也多是我对Java颇有好感,这一步我选择的是使用Apache的pdfbox结合Java实现的。

在apache官网中找到pdfbox,下载fontbox-2.0.15.jar、pdfbox-2.0.15.jar、commons-logging-1.2.jar这三个jar包,可是我在apahce官网上面并无找到最后一个jar包,是在别人分享的百度云盘里面下载的,因此最后我会把我用到的全部jar包上传到百度云并提供永久下载连接;得到这三个jar包后编写程序;数据库

源码以下apache

/**
 * 将一个大pdf拆分为小pdf
 * @param src 大PDF路径
 * @param dest 小PDF保存路径
 * @param pages 拆分页数
 */
public void split(String src, String dest, int pages) {
    File srcFile = new File(src);
    if(!srcFile.exists()) {
        System.out.println("原文件不存在");
        return;
    }
    
    // 若是目标路径的父目录不存在,则建立
    File destFile = new File(dest);
    if(!destFile.getParentFile().exists()) {
        destFile.getParentFile().mkdirs();
    }
    
    try {
        System.out.println("开始拆分");
        // 加载原PDF文件
        PDDocument pdf = PDDocument.load(srcFile, MemoryUsageSetting.setupTempFileOnly());
        // 获取原PDF文件总页数
        int pageCount = pdf.getPages().getCount();
        
        // 当所须要的页数大于总页数时,按最大总页数进行拆分
        if(pages > pageCount) {
            pages = pageCount;
        }
        
        PDDocument newPdf = new PDDocument();
        for(int i=0;i<pages;i++) {
            newPdf.addPage(pdf.getPage(i));
        }
        newPdf.save(destFile);
        
        pdf.close();
        newPdf.close();
        System.out.println("拆分结束");
    } catch (IOException e) {
        e.printStackTrace();
    }
}
第三步:将PDF转为PNG图片,很幸运在pdfbox中提供了这样的功能,因此这一步不须要任何工具包
直接就能够编写代码
/**
 * 将pdf转为png图片
 * @param src 原pdf路径
 * @param dest 图片的父目录
 */
public void Pdf2Png(String src, String dest) {
    File srcFile = new File(src);
    if(!srcFile.exists()) {
        System.out.println("源文件不存在");
        return;
    }
    
    File destFile = new File(dest);
    if(!destFile.exists()) {
        destFile.mkdirs();
    }
    
    try {
        PDDocument doc = PDDocument.load(srcFile);
        PDFRenderer renderer = new PDFRenderer(doc);
        int pageCount = doc.getNumberOfPages();
        for(int i=0;i<pageCount;i++){
            BufferedImage image = renderer.renderImageWithDPI(i, 300);
            File file = new File(dest + "\" + i + ".png");
            ImageIO.write(image, "PNG", file);
        }
        doc.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

因为第二步和第三步用到了一样的外部jar包,因此我把它们放在同一个项目,下面是完整的项目编程

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

import org.apache.pdfbox.io.MemoryUsageSetting;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer;

public class PDFProcess {
    /**
     * 将pdf转为png图片
     * @param src 原pdf路径
     * @param dest 图片的父目录
     */
    public void Pdf2Png(String src, String dest) {
        File srcFile = new File(src);
        if(!srcFile.exists()) {
            System.out.println("源文件不存在");
            return;
        }
        
        File destFile = new File(dest);
        if(!destFile.exists()) {
            destFile.mkdirs();
        }
        
        try {
            PDDocument doc = PDDocument.load(srcFile);
            PDFRenderer renderer = new PDFRenderer(doc);
            int pageCount = doc.getNumberOfPages();
            for(int i=0;i<pageCount;i++){
                BufferedImage image = renderer.renderImageWithDPI(i, 300);
                File file = new File(dest + "\" + i + ".png");
                ImageIO.write(image, "PNG", file);
            }
            doc.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 将一个大pdf拆分为小pdf
     * @param src 大PDF路径
     * @param dest 小PDF保存路径
     * @param pages 拆分页数
     */
    public void split(String src, String dest, int pages) {
        File srcFile = new File(src);
        if(!srcFile.exists()) {
            System.out.println("原文件不存在");
            return;
        }
        
        // 若是目标路径的父目录不存在,则建立
        File destFile = new File(dest);
        if(!destFile.getParentFile().exists()) {
            destFile.getParentFile().mkdirs();
        }
        
        try {
            System.out.println("开始拆分");
            // 加载原PDF文件
            PDDocument pdf = PDDocument.load(srcFile, MemoryUsageSetting.setupTempFileOnly());
            // 获取原PDF文件总页数
            int pageCount = pdf.getPages().getCount();
            
            // 当所须要的页数大于总页数时,按最大总页数进行拆分
            if(pages > pageCount) {
                pages = pageCount;
            }
            
            PDDocument newPdf = new PDDocument();
            for(int i=0;i<pages;i++) {
                newPdf.addPage(pdf.getPage(i));
            }
            newPdf.save(destFile);
            
            pdf.close();
            newPdf.close();
            System.out.println("拆分结束");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
最后一个合并PNG,这个要求直接使用php便可实现,不过须要GD2扩展库,若是是Windows下这个扩展是自带的,Linux下须要本身安装扩展
<?php
/**
     * 竖直方向上合并图片(合并为png格式),目标图片的宽度取全部图片中宽度最大的那张,高度取全部图片高度之和
     * @param array 一组图片的路径
     * @param String $dest 合并以后的图片地址
     */
    public static function merge($source, String $dest) {
        $width = 0;        // 合并后图片的宽
        $height = 0;    // 合并后图片的高

        $arr = array();
        for($i=0;$i<count($source);$i++) {
            $info = getimagesize($source[$i]);        // 获取图片详细信息(宽、高、类型等)
            // 获取图片类型
            $type = image_type_to_extension($info[2], false);
            $fun = "Imagecreatefrom{$type}";        // 根据图片类型构造一个符合该类型图片的读取函数
            $arr[$i]['source'] = $fun($source[$i]);
            $arr[$i]['size'] = $info;

            if($arr[$i]['size'][0] > $width) {
                $width = $arr[$i]['size'][0];
            }
            $height += $arr[$i]['size'][1];
        }

        $merge = imagecreate($width, $height+10*count($source));
        $space = imagecreate($width, 10);

        $dst_x = 0;
        $dst_y = 0;
        for($i=0;$i<count($source);$i++) {
            imagecopy($merge, $arr[$i]['source'], $dst_x, $dst_y, 0, 0, $arr[$i]['size'][0], $arr[$i]['size'][0]);
            imagecopy($merge, $space, $dst_x, $dst_y, 0, 0, $width, 10);
            $dst_y += $arr[$i]['size'][1];
        }

        imagepng($merge, $dest);
        imagedestroy($merge);
    }
?>
具体的思想就是先建立一张空的长图,而后将要合并的图片一张一张放进去(用的是imagecopy()函数),这里我作了一个间隔处理,每两张图片之间间隔了10像素。

到目前为止貌似全部的问题都获得了解决,咱们只须要将Java项目打包成jar包,放入JavaBridge所要求的jre环境的ext目录中,就能完成全部的功能了。windows

可是,当我将全部东西放入项目中运行时,会发生各类“意外”(特别是在Windows开发,而后源码移植到linux上时问题最为严重)。具体有哪些问题,下面我一一列举,而且将之与个人项目结合在一块儿说明服务器

  • 因为系统不一样,因此文件路径的分隔符也不一样,在windows下分隔符为"",而Linux下分隔符为"/",因而在我将PDF转为PNG时,这个问题出现了,如今回去看PDF转PNG的源码,会有这样一条代码File file = new File(dest + "\" + i + ".png");若是你看懂了个人代码,就会知道问题所在。在Windows下一切运行正常,可是到Linux下PNG文件的文件名和生成路径就会发生变化,这里的""不会被看成路径分隔符了,而是看成文件名的一部分,其实修改起来也很简单:File file = new File(dest + File.separator + i + ".png");
  • 建立另外一个线程启动openoffice服务,总是会出现没法链接服务的异常。根据个人各类调试,发现有时候会有这样的状况发生:在程序运行到链接服务的时候,服务并无开启。按理说开启服务的代码写在链接服务以前,不该该出现这样的问题。我猜想是因为建立了另外一个线程来启动服务,而主线程并无中止,而会继续执行,若是主线程先执行到链接服务的代码,那么就会出现这个错误。虽然是猜想,我以为八九不离十了,有两种方式解决这个问题:
第一种就是线程等待,让主线程等待,直到另外一个线程执行完毕后唤醒主线程;
第二种就是让openoffice服务长期开启,而不须要在程序中启动服务;
因为此时处于开发初期,小组选择先长期开启服务,等后期再改成线程等待策略。那么代码也得作相应的修改,将启动openoffice服务的代码删除便可,可是要记住须要手动启动服务。
  • 若是web网站放在远程服务器上,并且只能用终端进行远程链接没有图形界面时,JavaBridge和openoffice服务就必须放在后台执行了,还好Linux有直接提供将进程转为后台执行的命令,这都不是大问题。
  • 第一个问题和第二个问题须要不断的尝试,才能发现问题,浪费了大部分时间。最后一个问题和Java编程经验有很大关系了。前面提到咱们须要将Java项目打包成jar包,并且须要将外部jar包也放入其中。而eclipse在打包的时候不能包含外部jar包(这里打包须要选择JAR File而不能选择Runnable Jar File),咱们得想办法将外部jar包塞进去。这里我提供三种方法,可是我只测试过其中一种方法。
方法一:将打包好的jar包用rar打开,将外部jar包直接复制到里面,至于复制到哪里,根据程序中import外部jar包张的类所使用的路径来判断,实在判断不出来就一个目录一个目录尝试。

方法二:将外部jar包解压缩成许多class文件,将class文件复制到目标jar包中,这种方式我经过了测试。例如:将jodconverter的jar包解压缩后,有四个文件夹(com、drafts、org、META-INT),将这四个文件夹复制到目标jar包的顶层目录中。

方法三:若是能找到外部jar包的源代码,能够将源代码直接复制到项目中,跟项目一块儿打包成jar。这里的源代码指的是.java文件而不是.class文件,这种方式应该百分之百能成,可是通常想要找到源代码很难。

总结一下:
  • jar文件至关于一个压缩包,可使用winrar这样的压缩软件打开,而且往里面加入其余文件;
  • 编写程序中若是使用到路径,不要直接用""或"/",使用编程语言中提供的常量来表示分隔符,例如Java中的File.separator,php中的DIRECTORY_SEPARATOR常量;
  • 排查错误的时候,要结合前端和数据库一块儿进行排查,同时能够经过输入日志判断错误位置,php中提供了error_log()函数输入日志。

jar包的永久下载连接:
jodconverter:https://pan.baidu.com/s/1XwhiVhmlXxVvkPiiIkYi_Q
提取码:1dgj
pdfbox:https://pan.baidu.com/s/19bPBsoJhEv-m0l5ZLlMZbg
提取码:ymnt
JavaBridge:https://pan.baidu.com/s/1XKdC8vSLlmOGIGYRAmSQTA
提取码:k3lb

联系方式(qq):1518542802如果以为这篇博客有地方不明白能够加个人qq问我,在下必然知无不言言无不尽。

相关文章
相关标签/搜索