背景:最近项目需要实现PPT预览功能,以及项目APP上浏览ppt,初步方案是ppt转为图片。
该种实现,先将ppt转为pdf文件,实现方式有很多,可参考之前文章 文档在线预览,将文档转为pdf
然后实现将pdf转为图片,实现方式有很多,这里介绍其中一种, apache pdfbox ,具体如下:
maven依赖:
org.apache.pdfbox
fontbox
2.0.12
org.apache.pdfbox
pdfbox
2.0.12
java代码:
try {
int dpi = 296;
PDDocument pdDocument = PDDocument.load(new File("pdf file path"));
PDFRenderer renderer = new PDFRenderer(pdDocument);
int pageCount = pdDocument.getNumberOfPages();
/* dpi越大转换后越清晰,相对转换速度越慢 */
for (int i = 0; i < pageCount; i++) {
File dstFile = new File("png file path");
BufferedImage image = renderer.renderImageWithDPI(i, dpi);
ImageIO.write(image, "png", dstFile);
}
}catch (Exception e){
e.printStackTrace();
}
转换之后,可自行添加finally对资源进行处理
上述方案中,有两步实现,先转pdf,再转图片。转pdf方案中,比较靠谱快速的方法可能需要依赖外部 liberoffice 或者 openoffice. 实现ppt预览,也可通过apache poi 将ppt直接转为图片
apache poi提供丰富的office操作api,可直接通过java代码读写操作excel sheet,表格,内容等。也可以直接操作ppt slider,插入,读取内容等。
talk is cheap, show my code
maven 依赖:
org.apache.poi
poi
${appache.poi.version}
org.apache.poi
poi-ooxml
${appache.poi.version}
org.apache.poi
poi-scratchpad
${appache.poi.version}
官方最新版本已经到了 4.0.0
由于 powrpoint 有ppt和pptx(office 2007之后) ,ppt文件本质是二进制文件,而pptx 是xml 。所以需要不同的处理。具体实现如下:
ppt,使用HSLFSlide 读取
FileInputStream is = new FileInputStream("ppt file path");
HSLFSlideShow ppt = new HSLFSlideShow(is);
Dimension pageSize = ppt.getPageSize();
int idx = 1;
try {
for (HSLFSlide slide : ppt.getSlides()) {
toPNG(pageSize.getWidth(), pageSize.getHeight(), slide);
idx++;
}
} catch (Exception e) {
throw e;
} finally {
if (is != null) {
is.close();
}
if (ppt != null){
ppt.close();
}
}
pptx 使用XSLF
FileInputStream is = new FileInputStream("pptx file path");
XMLSlideShow pptx = new XMLSlideShow(is);
Dimension pageSize = pptx.getPageSize();
int idx = 1;
try {
for (XSLFSlide xslfSlide : pptx.getSlides()) {
this.toPNG(pageSize.width, pageSize.height, xslfSlide );
idx++;
}
} catch (Exception e) {
throw e;
} finally {
if (is != null){
is.close();
}
if (pptx != null){
pptx.close();
}
}
topng 实现:
//image_rate 图片尺寸倍率,rate越大,图片越清晰,转换时间越长
int imageWidth = (int) Math.floor(image_rate * pageSize.width);
int imageHeight = (int) Math.floor(image_rate * pageSize.height);
BufferedImage img = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = img.createGraphics();
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
// clear the drawing area
graphics.setPaint(Color.white);
graphics.fill(new Rectangle2D.Float(0, 0, imageWidth, imageHeight));
graphics.scale(this.IMAGE_RATE, this.IMAGE_RATE);
FileOutputStream out = null;
try {
xslfSlide.draw(graphics);
out = new FileOutputStream(new File("png file path"));
javax.imageio.ImageIO.write(img, "png", out);
} catch (IOException ioe) {
ioe.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (out != null) {
out.flush();
out.close();
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
if (graphics != null){
graphics.dispose();
}
if (img != null){
img.flush();
}
}
这样能初步实现ppt转图片,实际过程中有如下几个问题:
1、中文 问题: 如果在本地测试觉得一切ok,那么部署到测试环境或者线上就可能出现中文乱码问题。
原因可能是ppt内容中字体不支持,服务器未安装中文字体。 如linux上,可 通过 fc-list :lang=zh 查看。
如果未安装,可通过 yum -y install fontconfig 安装,然后在/usr/share 目录下会发现 fonts目录,下载中文字体如:heiti.ttf
拷贝到fonts目录下,chmod 赋权限。再次执行 fc-list :lang=zh 命令,显示如下,则表示安装成功,也可以安装宋体等其他字体
如果是docker环境,则可将上述安装步骤写入到dockerfile中。
安装完成字体后,代码中设置字体如下:
ppt:
for (HSLFShape shape : slide.getShapes()) {
if (shape instanceof HSLFTextShape) {
HSLFTextShape sh = (HSLFTextShape) shape;
List textParagraphs = sh.getTextParagraphs();
for (HSLFTextParagraph hslfTextParagraph : textParagraphs) {
List textRuns = hslfTextParagraph.getTextRuns();
for (HSLFTextRun hslfTextRun : textRuns) {
hslfTextRun.setFontFamily("宋体");
hslfTextRun.setFontIndex(1);
}
}
}
}
pptx:
for (XSLFShape shape : slide.getShapes()){
if (shape instanceof XSLFTextShape){
XSLFTextShape sh = (XSLFTextShape) shape;
List textParagraphs = sh.getTextParagraphs();
for (XSLFTextParagraph xslfTextParagraph : textParagraphs) {
List textRuns = xslfTextParagraph.getTextRuns();
for (XSLFTextRun xslfTextRun : textRuns) {
xslfTextRun.setFontFamily("宋体");
}
}
}
}
经上述操作,中文问题基本能解决了
2、如果pptx文件中含有嵌入文件,即在ppt中插入外部文件,可能会报错:
java.lang.ArrayStoreException
at java.lang.System.arraycopy(Native Method)
at org.apache.xmlbeans.impl.values.XmlObjectBase._typedArray(XmlObjectBase.java:409)
at org.apache.xmlbeans.impl.values.XmlObjectBase.selectPath(XmlObjectBase.java:457)
at org.apache.xmlbeans.impl.values.XmlObjectBase.selectPath(XmlObjectBase.java:415)
at org.apache.poi.xslf.usermodel.XSLFDrawing.(XSLFDrawing.java:44)
at org.apache.poi.xslf.usermodel.XSLFSheet.initDrawingAndShapes(XSLFSheet.java:201)
at org.apache.poi.xslf.usermodel.XSLFSheet.getShapes(XSLFSheet.java:188)
at org.apache.poi.sl.draw.DrawSheet.draw(DrawSheet.java:55)
at org.apache.poi.sl.draw.DrawSlide.draw(DrawSlide.java:41)
at org.apache.poi.xslf.usermodel.XSLFSlide.draw(XSLFSlide.java:307)
ppt文件不会有该问题,主要是上文中提到,pptx 可解析为xml文件,解析为XmlObject 时,类型转换错误,
XmlObjectBase中:
System.arraycopy(input, 0, result, 0, input.length);
debug源码发现,input 数组,三个元素类型不同,如下:
至于完整的pptx文件解析后的xml文件,有兴趣的同学可以自己看看,这里就不贴出来了。
百思不得其姐,一度想 通过 poi remove掉这个文件,发现事情没那么简单,然后准备祈祷不要有嵌入文件的pptx时,发现有一老哥和我一样的问题,参考:可能需要
原来是其他pom引入中含有xmlbeans 2.3 版本 。
解决办法是确保xmlbeans 版本到2.6 。希望有老哥遇到该问题时能解决,找到依赖源, exclusion 一下。
3、转换慢,OOM等问题
由于转换是ppt的每一页进行单独转换,如果ppt页数多,可能会慢。 解决办法,一种是减小上文中的image_rate,如设置为1.
还有就是可以通过多线程并发转换,但是由于该转换操作是CPU密集型操作,所以需要根据机器性能决定。具体代码如下:
for (XSLFSlide slide : ppt.getSlides()) {
CompletableFuture future = CompletableFuture.supplyAsync(() -> toPNG(pgsize.width, pgsize.height, slide, targetType), workers)
.handle((targetFilePath, e) -> {
if (e != null) {
logger.error("covertPPTX error", e);
return null;
} else {
return targetFilePath;
}
});
resultMap.put(idx, future);
idx++;
}
方式有很多种
并发转换时,一定要注意资源的释放,否则会容易出现OOM。
本文主要是开发过程中问题记录,比较繁杂,参考了官网及一些博客,如有侵权,立马删除。