PPT在线预览 转换为图片实现方案 Apache POI 实现时踩坑:含嵌入文件ppt转换报错 ArrayStoreException

前言

背景:最近项目需要实现PPT预览功能,以及项目APP上浏览ppt,初步方案是ppt转为图片。

实现

1、ppt转为pdf,然后pdf转为图片

该种实现,先将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对资源进行处理 

 

2、利用apache poi直接将ppt转为图片

上述方案中,有两步实现,先转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。

 

后记

本文主要是开发过程中问题记录,比较繁杂,参考了官网及一些博客,如有侵权,立马删除。

 

你可能感兴趣的:(java,工具类)