java文档预览实现


近期因需要完成对word、excel、ppt、txt等文档的内容检索,在用户检索到相关内容时,需要给用户提供一个在线预览文档的功能。在网上找到部分参考后,实现了该功能。

目录
  • 主要步骤
  • 组件安装
    • Aspose
    • swftools
    • flexpaper
    • 功能实现
      • 文档转换为PDF
      • pdf.js预览
      • PDF转换为swf
      • flexpaper预览

主要步骤

要实现这些文档的预览,需要先将文档转换为PDF再进行预览。

转换步骤:

  • 使用OpenOffice/Aspose 将ppt、word、excel、txt类型的文件转换为pdf

预览步骤:

  • 高版本浏览器上,使用pdf.js直接预览PDF文件
  • 低版本浏览器上,使用swftools将PDF文件转换为swf文件,再使用flexpaper预览swf

组件安装

Aspose

由于OpenOffice的转换效果并不太佳,这里选择了Aspose

在Aspose官网下载Aspose的java版本,主要选择

  • Aspose.words
  • Aspose.cells(Excel)
  • Aspose.slides(PPT)
  • Aspose.pdf

下载完成后,在工程中引用jar包即可。

swftools

swftools主要用于将PDF文件转换为swf文件以便使用flexpaper进行播放。

在swftools下载页面 选择对应的版本下载即可。如windows下载exe后缀的文件,linux下载tar.gz后缀的文件。

flexpaper

flexpaper的作用是播放swf文件。

flexpaper官网为 https://flowpaper.com

flexpaper 2.3.6版本下载地址

功能实现

这里采用的所有组件版本为:

名称 版本
Aspose.words 16.8.0
Aspose.cells 9.0.0
Aspose.slides 116.7.0
Aspose.pdf 11.8.0
swftools swftools-2013-04-09-1007.exe
flexpaper 2.3.6

文档转换为PDF

使用Aspose进行文档转换很简单,直接引入相应的jar包,调用save方法,转换为PDF即可。

注意:

  1. 使用Aspose时,每一个模块(words,cells)都可能有相同的类,如License类,SaveOptions类,SaveFormat类。而在各自模块使用时,一定要用对应模块的类,这个坑我已爬过。
  1. 使用Aspose时,需要每次进行转换操作前调用设置License方法。

获取license示例代码:

package com.dm.docpreview.convert.util;


import org.apache.log4j.Logger;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;

/**
 * Aspose注册工具
 *
 * @author zxb
 * @version 1.0.0
 *          2016年10月17日 17:00
 * @since Jdk1.6
 */
public class AsposeLicenseUtil {

    private static InputStream inputStream = null;

    private static Logger logger = Logger.getLogger(AsposeLicenseUtil.class);

    /**
     * 获取License的输入流
     *
     * @return
     */
    private static InputStream getLicenseInput() {
        if (inputStream == null) {
            ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
            try {
                inputStream = new FileInputStream(contextClassLoader.getResource("license.xml").getPath());
            } catch (FileNotFoundException e) {
                logger.error("license not found!", e);
            }
        }
        return inputStream;
    }

    /**
     * 设置License
     *
     * @return true表示已成功设置License, false表示失败
     */
    public static boolean setWordsLicense() {
        InputStream licenseInput = getLicenseInput();
        if (licenseInput != null) {
            try {
                com.aspose.words.License aposeLic = new com.aspose.words.License();
                aposeLic.setLicense(licenseInput);
                return aposeLic.getIsLicensed();
            } catch (Exception e) {
                logger.error("set words license error!", e);
            }
        }
        return false;
    }

    /**
     * 设置License
     *
     * @return true表示已成功设置License, false表示失败
     */
    public static boolean setCellsLicense() {
        InputStream licenseInput = getLicenseInput();
        if (licenseInput != null) {
            try {
                com.aspose.cells.License aposeLic = new com.aspose.cells.License();
                aposeLic.setLicense(licenseInput);
                return true;
            } catch (Exception e) {
                logger.error("set cells license error!", e);
            }
        }
        return false;
    }

    /**
     * 设置License
     *
     * @return true表示已成功设置License, false表示失败
     */
    public static boolean setSlidesLicense() {
        InputStream licenseInput = getLicenseInput();
        if (licenseInput != null) {
            try {
                com.aspose.slides.License aposeLic = new com.aspose.slides.License();
                aposeLic.setLicense(licenseInput);
                return aposeLic.isLicensed();
            } catch (Exception e) {
                logger.error("set ppt license error!", e);
            }
        }
        return false;
    }

    /**
     * 设置Aspose PDF的license
     * @return true表示设置成功,false表示设置失败
     */
    public static boolean setPdfLicense() {
        InputStream licenseInput = getLicenseInput();
        if (licenseInput != null) {
            try {
                com.aspose.pdf.License aposeLic = new com.aspose.pdf.License();
                aposeLic.setLicense(licenseInput);
                return true;
            } catch (Exception e) {
                logger.error("set pdf license error!", e);
            }
        }
        return false;
    }
}

doc转pdf示例代码,其中加水印功能可用:

package com.dm.docpreview.convert.service.impl;

import com.aspose.words.*;
import com.aspose.words.Shape;
import com.dm.docpreview.convert.domain.ConvertStatus;
import com.dm.docpreview.convert.service.File2PdfService;
import com.dm.docpreview.convert.util.AsposeLicenseUtil;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Service;

import java.awt.*;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * 将doc文档转换为pdf文件
 *
 * @author zxb
 * @version 1.0.0
 *          2016年10月17日 16:12
 * @since Jdk1.6
 */
@Service
public class Doc2PdfServiceImpl implements File2PdfService {

    private Logger logger = Logger.getLogger(getClass());

    @Override
    public ConvertStatus convert2Pdf(InputStream inputStream, OutputStream outputStream) {
        try {
            if (AsposeLicenseUtil.setWordsLicense()) {
                long start = System.currentTimeMillis();

                Document doc = new Document(inputStream);

                // insertWatermarkText(doc, "测试水印"); // 添加水印

                PdfSaveOptions pdfSaveOptions = new PdfSaveOptions();
                pdfSaveOptions.setSaveFormat(SaveFormat.PDF);
                pdfSaveOptions.getOutlineOptions().setHeadingsOutlineLevels(3); // 设置3级doc书签需要保存到pdf的heading中
                pdfSaveOptions.getOutlineOptions().setExpandedOutlineLevels(1); // 设置pdf中默认展开1级

                doc.save(outputStream, pdfSaveOptions);
                long end = System.currentTimeMillis();
                logger.debug("convert doc2pdf completed, elapsed " + (end - start) / 1000.0 + " seconds!");
                return ConvertStatus.SUCCESS;
            } else {
                return ConvertStatus.LICENSE_ERROR;
            }
        } catch (Exception e) {
            logger.error("convert doc2pdf error!", e);
            return ConvertStatus.CONVERT_DOC2PDF_ERROR;
        }
    }


    /**
     * Inserts a watermark into a document.
     *
     * @param doc           The input document.
     * @param watermarkText Text of the watermark.
     */
    private void insertWatermarkText(Document doc, String watermarkText) throws Exception {
        // Create a watermark shape. This will be a WordArt shape.
        // You are free to try other shape types as watermarks.
        Shape watermark = new Shape(doc, ShapeType.TEXT_PLAIN_TEXT);

        // Set up the text of the watermark.
        // watermark.getTextPath().setSize(16.0);
        // watermark.getTextPath().setFontFamily("Arial"); // 使用Arial时最后那个字会丢
        watermark.getTextPath().setFontFamily("宋体");
        watermark.getTextPath().setItalic(true);
        watermark.getTextPath().setText(watermarkText);

        // Font size does not have effect if you specify height of the shape.
        // So you can just specify height instead of specifying font size.
        double fontSize = 100.0;
        watermark.setWidth(watermarkText.length() * fontSize);
        watermark.setHeight(fontSize);

        // Text will be directed from the bottom-left to the top-right corner.
        watermark.setRotation(-30);
        // Remove the following two lines if you need a solid black text.
        watermark.getFill().setColor(Color.lightGray); // Try LightGray to get more Word-style watermark
        watermark.setStrokeColor(Color.lightGray); // Try LightGray to get more Word-style watermark

        // Place the watermark in the page center.
        watermark.setRelativeHorizontalPosition(RelativeHorizontalPosition.PAGE);
        watermark.setRelativeVerticalPosition(RelativeVerticalPosition.PAGE);
        watermark.setWrapType(WrapType.NONE);
        watermark.setVerticalAlignment(VerticalAlignment.CENTER);
        watermark.setHorizontalAlignment(HorizontalAlignment.CENTER);
        // watermark.setHorizontalAlignment(HorizontalAlignment.LEFT);

        // Create a new paragraph and append the watermark to this paragraph.
        Paragraph watermarkPara = new Paragraph(doc);
        watermarkPara.appendChild(watermark);

        // Insert the watermark into all headers of each document section.
        for (Section sect : doc.getSections()) {
            // There could be up to three different headers in each section, since we want
            // the watermark to appear on all pages, insert into all headers.
            insertWatermarkIntoHeader(watermarkPara, sect, HeaderFooterType.HEADER_PRIMARY);
            insertWatermarkIntoHeader(watermarkPara, sect, HeaderFooterType.HEADER_FIRST);
            insertWatermarkIntoHeader(watermarkPara, sect, HeaderFooterType.HEADER_EVEN);
        }
    }

    private void insertWatermarkIntoHeader(Paragraph watermarkPara, Section sect, int headerType) throws Exception {
        HeaderFooter header = sect.getHeadersFooters().getByHeaderFooterType(headerType);

        if (header == null) {
            // There is no header of the specified type in the current section, create it.
            header = new HeaderFooter(sect.getDocument(), headerType);
            sect.getHeadersFooters().add(header);
        }

        // Insert a clone of the watermark into the header.
        header.appendChild(watermarkPara.deepClone(true));
    }
}

其余示例代码,在文章最末尾处将给出下载链接。

pdf.js预览

当文档被转换为PDF文件后,就可以使用pdf.js进行预览了。
在官网下载pre-built版的pdf.js,解压出来。

由于我们要集成到自己的工程中来,所以此处直接copy了viewer.html页面,修改为了我们工程需要的view_pdfjs.vm

view_pdfjs.vm





    
    
    
    
    PDF.js viewer
    
    <script src="#springUrl('')/res/js/pdfjs-dist/web/compatibility.js"></script>
    
    
    <script src="#springUrl('')/res/js/pdfjs-dist/web/l10n.js"></script>
    <script src="#springUrl('')/res/js/pdfjs-dist/build/pdf.js"></script>
    <script src="#springUrl('')/res/js/pdfjs-dist/web/debugger.js"></script>
    <script src="#springUrl('')/res/js/pdfjs-dist/web/viewer.js"></script>
    <script type="application/javascript">
        PDFJS.workerSrc = "#springUrl('')/res/js/pdfjs-dist/build/pdf.worker.js";

        /*
        PDFJS.onerror = function(message, moreInfo){
            var queryString = document.location.search.substring(1);
            var params = parseQueryStringRegexImpl(queryString);
            var file = 'file' in params ? params.file : null;

            // redirect to file
            if(file){
                location.href = file;
            }
        }

        function parseQueryStringRegexImpl(query){
            var reg = /([^\?\=\&]+)\=([^\&]*)/g;
            var obj = {};
            while (reg.exec (query)) {
                obj[RegExp.$1] = RegExp.$2;
            }
            return obj;
        }
        */
    </script>



Current View

其中需要注意的是pdf.worker.js默认是在viewer.js中指定路径的,使用相对路径来指定的。

function configure(PDFJS) {
  PDFJS.imageResourcesPath = './images/';
  //PDFJS.workerSrc = '../build/pdf.worker.js';
  PDFJS.cMapUrl = '../web/cmaps/';
  PDFJS.cMapPacked = true;
}

此处注释了 PDFJS.workerSrc = '../build/pdf.worker.js';

然后在我们的view_pdfjs.vm中手动指定了该js路径为绝对路径:

PDFJS.workerSrc = "#springUrl('')/res/js/pdfjs-dist/build/pdf.worker.js";

预览
pdf.js使用参数file来指定需要预览的文件。例如我们的view_pdfjs.vm对应的controller地址为:http://localhost:8080/docpreview/docpreview/Preview_preview.do?file=abc.pdf

跨域
如果pdf.js预览页面同pdf文件资源在同一个域下,此处可以忽略。

pdf.js使用的异步请求来请求的pdf文件资源,这也就意味着会存在跨域问题。pdf.js中主动检测了file参数对应的地址是否是同一个域中。

我目前采用的方式是封装一个同域下的.do请求,该.do请求会拿到请求资源然后再回写。实现后的地址如下:
http://localhost:8080/docpreview/docpreview/Preview_preview.do?file=http://localhost:8080/docpreview/docpreview/Preview_crossDomainSource.do?filePath=ftp://localhost/test.pdf

crossDomainSource.do代码示例:

    /**
     * 将跨域的资源转换为本地的资源
     *
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    public ModelAndView crossDomainSource(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 如果为本域资源
        String filePath = request.getParameter("filePath");
        if (StringUtil.isNotEmpty(filePath)) {
            try {
                writeResponse(response, filePath);
            } catch (FileNotFoundException e) {
                logger.error("file not found!", e);
                write404(request, response);
            } catch (IOException e) {
                logger.error("get file error!", e);
                write500(request, response);
            }
        } else {
            write404(request, response);
        }
        return null;
    }


    /**
     * 取出filePath对应的数据并写入response中
     *
     * @param response 响应
     * @param filePath 文件路径
     * @throws IOException
     */
    private void writeResponse(HttpServletResponse response, String filePath) throws IOException {
        InputStream inputStream = null;
        URLConnection conn = null;
        try {
            URL url = new URL(filePath);
            conn = url.openConnection();
            conn.setConnectTimeout(30000); // 30s
            conn.setReadTimeout(30000);
            conn.connect();

            inputStream = new DataInputStream(conn.getInputStream());
            copyStream(inputStream, response.getOutputStream());
        } finally {
            if (conn != null) {
                if (conn instanceof HttpURLConnection) {
                    ((HttpURLConnection) conn).disconnect();
                }
            }
        }
    }

实现效果

PDF转换为swf

pdf转swf则需要通过swftools工具来完成。由于swftools为外部命令,在java中需要通过Runtime.getRuntime().exec(commandStr);这种方式来调用。

swftools转换pdf为swf文件命令示例:
pdf2swf.exe test.pdf -o "test.swf" -z -s flashversion=9 -s languagedir="xpdf-chinese-simplified" -s storeallcharacters -f -j 100

其中languagedir需要下载xpdf的简体中文包即可。其它参数请自行查看官方文档。

swftools中的java代码示例:

package com.dm.docpreview.convert.service.impl;

import com.dm.docpreview.convert.domain.ConvertStatus;
import com.dm.docpreview.convert.domain.SwfConfig;
import org.apache.commons.io.IOUtils;
import org.junit.Test;

import java.io.*;

import static junit.framework.Assert.assertTrue;

/**
 * @author zxb
 * @version 1.0.0
 *          2016年10月18日 17:15
 * @since Jdk1.6
 */
public class Pdf2SwfServiceImplTest {

    @Test
    public void pdf2swf() throws Exception {
        // 配置输入pdf及输出的swf
        String inputFilePath = getClass().getClassLoader().getResource("convertFile/test.pdf").getPath();
        File inputFile = new File(inputFilePath);
        File outputFile = new File(inputFile.getParentFile().getParent() + File.separator + "outputFile" + File.separator + "test.swf");

        InputStream inputStream = new FileInputStream(inputFile);
        OutputStream outputStream = new FileOutputStream(outputFile);

        // 配置SwfTools参数
        SwfConfig swfConfig = new SwfConfig();
        swfConfig.setFilePath("C:\\zxbProgramFiles\\workdir\\java\\swftools\\pdf2swf.exe");
        swfConfig.setLanguageDir("C:\\zxbProgramFiles\\workdir\\java\\swftools\\xpdf-chinese-simplified");
        swfConfig.setQuality(100);

        // 转换pdf为swf
        Pdf2SwfServiceImpl pdf2SwfService = new Pdf2SwfServiceImpl();
        pdf2SwfService.setSwfConfig(swfConfig);

        ConvertStatus convertStatus = pdf2SwfService.pdf2swf(inputStream, outputStream);

        // 关闭流
        IOUtils.closeQuietly(outputStream);
        IOUtils.closeQuietly(inputStream);

        assertTrue(convertStatus == ConvertStatus.SUCCESS);
    }
}

flexpaper预览

将下载好的flexpaper安装包解压。

把其中的js、FlexPaperViewer.swf、css、index.html等拷贝到工程目录中。

其中index.html已经有一个flexpaper的使用示例了,这里直接将index.html的内容copy到我们的 view_flexpaper.vm文件中。

view_flexpaper.vm文件内容:




    FlexPaper
    
    
    

    
    <script type="text/javascript" src="#springUrl('')/res/js/flexpaper/jquery.min.js"></script>
    <script type="text/javascript" src="#springUrl('')/res/js/flexpaper/flexpaper.js"></script>
    <script type="text/javascript" src="#springUrl('')/res/js/flexpaper/flexpaper_handlers.js"></script>


<script type="text/javascript"> $('#documentViewer').FlexPaperViewer( { config : { src : '#springUrl('')/res/js/flexpaper/FlexPaperViewer.swf', SWFFile : '$!{swf_address}', Scale : 0.6, ZoomTransition : 'easeOut', ZoomTime : 0.5, ZoomInterval : 0.2, FitPageOnLoad : true, FitWidthOnLoad : false, FullScreenAsMaxWindow : false, ProgressiveLoading : false, MinZoomSize : 0.2, MaxZoomSize : 5, SearchMatchAll : false, InitViewMode : 'Portrait', RenderingOrder : 'flash', StartAtPage : '', ViewModeToolsVisible : true, ZoomToolsVisible : true, NavToolsVisible : true, CursorToolsVisible : true, SearchToolsVisible : true, WMode : 'window', localeChain: 'zh_CN' }} ); </script>

其中需要注意的是flexpaper.js中使用的为相对路径来指定 FlexPaperViewer.swf 文件的路径。这里我把它改成绝对路径了。

而config下的参数SWFFile则指定你的swf文件路径即可完成播放。

实现效果

参考链接:
Java+FlexPaper+swfTools仿百度文库文档在线预览系统设计与实现

CSDN博客链接

附参考示例:
功能代码
Aspose包
FlexPaper包