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即可。
注意:
- 使用Aspose时,每一个模块(words,cells)都可能有相同的类,如License类,SaveOptions类,SaveFormat类。
而在各自模块使用时,一定要用对应模块的类
,这个坑我已爬过。
- 使用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>
Preparing document for printing...
0%
其中需要注意的是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包