使用Java将PPT、PDF和html转换图片并上传OSS


最近小雨遇到了一个需求,需要在前端小程序中嵌入展示Office文件的功能。然而,前端使用开源组件进行在线预览会导致性能消耗较大的问题(转半天圈圈)。产品理想的效果是用户上传Office文件后,浏览起来与页面一样流畅。

没错,作为服务端的老铁,可以提供更强大的计算资源和处理能力来支持前端小伙伴实现需求(We are a team🏠)!

这种情况下,可以在服务端使用开源插件对文件进行预览切片,将文件的预览效果保持为一张一张的图片,用户预览时直接夹在图片即可。此方法带来的另一个好处是可以做懒加载和缓存功能,预览过的文件图片可以缓存,再次预览的时候可以快速加载,无需消耗流量!

心急铁铁,可直接拉下项目使用:office-conver

PDF转图片

Apache PDFBox是一个功能丰富而强大的PDF处理库,提供了广泛的功能和工具来处理和操作PDF文档。它是一个开源项目,具有广泛的社区支持和活跃的开发。你可以在Apache PDFBox的官方网站上找到更多的文档、示例和API参考,以帮助你使用和了解该库的更多功能。

1. 万事第一步

 <dependency>
     <groupId>org.apache.pdfbox</groupId>
     <artifactId>pdfbox</artifactId>
     <version>${pdfbox.version}</version>
 </dependency>

2. 撸代码

/**
 * Description: pdf转换为图片转换器
 *
 * @author YanAn
 * @date 2023/9/20 14:11
 */
@Slf4j
public class PDFToPNGConverter extends BaseConverter {

    private static final float IMAGE_SCALE = 8;

    public PDFToPNGConverter(String inputPath) {
        super(inputPath);
    }

    @Override
    public List<String> convertToPNG() {
        InputStream is = null;
        try {
            is = getInputStream(inputFileUrl);
            PDDocument document = PDDocument.load(is);
            PDFRenderer renderer = new PDFRenderer(document);
            int pageSize = document.getNumberOfPages();
            for (int i = 0; i < pageSize; i++) {
                BufferedImage img = renderer.renderImage(i, IMAGE_SCALE);
                // save the output
                try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
                    javax.imageio.ImageIO.write(img, "png", bos);
                    String url = uploadFileToOss(bos);
                    outPathUrlList.add(url);
                }
            }
        } catch (Exception e) {
            log.error("pdf转换图片失败,{}", e.getMessage());
            throw new RuntimeException("pdf转换图片失败" + e.getMessage());
        } finally {
            try {
                if (is != null) {
                    is.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return outPathUrlList;
    }

}

PPT/PPTX转图片

Apache POI(Poor Obfuscation Implementation)是一个开源的Java库,用于处理和操作Microsoft Office格式的文件,包括Word文档(.doc和.docx)、Excel电子表格(.xls和.xlsx)、PowerPoint演示文稿(.ppt和.pptx)等。它提供了丰富的API和功能,使开发人员能够读取、创建和修改Office文件。小编的另外一篇基于poi实现ppt的骚操作博文如何使用POI读取模板PPT填充数据并拼接至目标文件

1. 万事第一步

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>4.1.2</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>4.1.2</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-scratchpad</artifactId>
    <version>4.1.2</version>
</dependency>

2. 撸代码

抽象ppt转换为图片转换器

/**
 * Description: 抽象ppt转换为图片转换器
 *
 * @author YanAn
 * @date 2023/9/20 14:11
 */
public abstract class AbstractPPTToPNGConverter extends BaseConverter {

    private final static double IMAGE_SCALE = 8;

    public AbstractPPTToPNGConverter(String inputPath) {
        super(inputPath);
    }

    /**
     * 幻灯片转换图片方法并且上传oss
     *
     * @param pgWidth  宽
     * @param pgHeight 高
     * @param slide    幻灯片
     * @return 图片于oss文件链接
     * @throws IOException
     */
    protected String toPNG(int pgWidth, int pgHeight, Slide slide) throws IOException {
        int imageWidth = (int) Math.floor(IMAGE_SCALE * pgWidth);
        int imageHeight = (int) Math.floor(IMAGE_SCALE * pgHeight);

        BufferedImage img = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_RGB);
        Graphics2D graphics = img.createGraphics();
        graphics.setPaint(Color.white);
        graphics.fill(new Rectangle2D.Float(0, 0, pgWidth, pgHeight));
        graphics.scale(IMAGE_SCALE, IMAGE_SCALE);
        slide.draw(graphics);
        // save the output
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try {
            bos = new ByteArrayOutputStream();
            javax.imageio.ImageIO.write(img, "png", bos);
            return uploadFileToOss(bos);
        } finally {
            bos.close();
        }
    }
}

ppt转换为图片转换器

/**
 * Description: ppt转换为图片转换器
 *
 * @author YanAn
 * @date 2023/9/21 13:35
 */
@Slf4j
public class PPTToPNGConverter extends AbstractPPTToPNGConverter{

    public PPTToPNGConverter(String inputPath) {
        super(inputPath);
    }

    @Override
    public List<String> convertToPNG() {
        InputStream is = null;
        HSLFSlideShow ppt = null;
        try {
            is = getInputStream(inputFileUrl);
            ppt =new HSLFSlideShow(is);
            Dimension pgSize = ppt.getPageSize();
            for (HSLFSlide slide : ppt.getSlides()) {
                String url = toPNG(pgSize.width, pgSize.height, slide);
                outPathUrlList.add(url);
            }
        } catch (IOException e) {
            log.error("ppt转换图片失败,{}", e.getMessage());
            throw new RuntimeException("ppt转换图片失败" + e.getMessage());
        } finally {
            try {
                if (is != null) {
                    is.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (ppt != null) {
                    ppt.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return outPathUrlList;
    }
}

pptx转换为图片转换器

/**
 * Description: pptx转换为图片转换器
 *
 * @author YanAn
 * @date 2023/9/21 13:35
 */
@Slf4j
public class PPTXToPNGConverter extends AbstractPPTToPNGConverter {

    public PPTXToPNGConverter(String inputPath) {
        super(inputPath);
    }

    @Override
    public List<String> convertToPNG() {
        InputStream is = null;
        XMLSlideShow ppt = null;
        try {
            is = getInputStream(inputFileUrl);
            ppt = new XMLSlideShow(is);
            Dimension pgSize = ppt.getPageSize();
            for (XSLFSlide slide : ppt.getSlides()) {
                String url = toPNG(pgSize.width, pgSize.height, slide);
                outPathUrlList.add(url);
            }
        } catch (IOException e) {
            log.error("pptx转换图片失败,{}", e.getMessage());
            throw new RuntimeException("pptx转换图片失败" + e.getMessage());
        } finally {
            try {
                if (is != null) {
                    is.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (ppt != null) {
                    ppt.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return outPathUrlList;
    }
}

验收一下

在这里插入图片描述

HTML转图片

1. 万事第一步

<dependency>
   <groupId>org.xhtmlrenderer</groupId>
   <artifactId>core-renderer</artifactId>
   <version>R8</version>
</dependency>
<dependency>
  <groupId>net.sourceforge.nekohtml</groupId>
  <artifactId>nekohtml</artifactId>
  <version>1.9.14</version>
</dependency>

<dependency>
  <groupId>gui.ava</groupId>
  <artifactId>html2image</artifactId>
  <version>2.0.1</version>
</dependency>

这里 gui.ava 的jar包拉不下。本src/main/resources/file提供了相关jar包,同学们可将jar包直接copy至企业私仓或本地仓库即可。
在这里插入图片描述

2. 撸代码

HTML转换器

package com.hgw.officeconver.converter;

import gui.ava.html.parser.HtmlParserImpl;
import gui.ava.html.renderer.ImageRenderer;
import gui.ava.html.renderer.ImageRendererImpl;
import lombok.extern.slf4j.Slf4j;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Objects;

/**
 * Description: HTML转换器
 *
 * @author LinHuiBa-YanAn
 * @date 2023/10/19 15:51
 */
@Slf4j
public class HtmlToPNGConverter extends BaseConverter{

    public HtmlToPNGConverter(String inputSource) {
        super(inputSource);
    }

    @Override
    public List<String> convertToPNG() {
        if (Objects.isNull(inputSource)) {
            throw new RuntimeException( "html内容不能为空");
        }
        HtmlParserImpl htmlParser = new HtmlParserImpl();
        htmlParser.loadHtml(inputSource);
        ImageRenderer imageRenderer = new ImageRendererImpl(htmlParser);
        BufferedImage img = imageRenderer.getBufferedImage();
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try {
            ImageIO.write(img, "png", bos);
        } catch (IOException e) {
            log.error("html转换图片失败,{}", e);
            throw new RuntimeException("html转换图片失败" + e.getMessage());
        }
        outPathUrlList.add(uploadFileToOss(bos));
        return outPathUrlList;
    }

}

验收一下

@Test
public void htmlToPNGConverterTest() {
    String html = "<p style=\"line-height: 1;\">测试内容编辑</p><p style=\"line-height: 3;\">阿萨德和i</p><p style=\"line-height: 3;\">地址:杭州市余杭区溪西八方城11幢</p><p style=\"line-height: 3;\">来个图片</p><hr><p style=\"line-height: 3;\"><img style=\"width:100%;height:auto\" src=\"https://images.location.pub/FuvjKyKF5GRxf_PfrwEux4bP232T-pms_original\" class=\"editor--\" data-custom=\"id=wux-upload--1697682266313-1\">图片右侧文案</p><p style=\"line-height: 3;\">分割线</p><p style=\"line-height: 3;\"><br></p><hr><ul data-checked=\"false\"><li style=\"line-height: 3;\">哈哈1</li><li style=\"line-height: 3;\">哈哈2</li></ul><ul data-checked=\"true\"><li style=\"line-height: 3;\">哈哈选中(能看到吗)</li></ul><hr><p style=\"line-height: 3;\" class=\"ql-indent-1\">内容缩紧速度能达到收到</p><ol><li style=\"line-height: 3;\" class=\"ql-indent-1\">阿斯顿</li><li style=\"line-height: 3;\" class=\"ql-indent-1\">d s d</li><li style=\"line-height: 3;\" class=\"ql-indent-1\">说到底</li><li style=\"line-height: 3;\">说到底</li><li style=\"line-height: 3;\">3434 </li><li style=\"line-height: 3;\">当时的</li></ol><hr><p style=\"text-align: center; line-height: 3;\"><strong style=\"background-color: yellow; color: rgb(253, 49, 54); font-size: 18px;\"><del>背景色</del></strong></p><p style=\"line-height: 3;\"><span style=\"font-size: 14px;\"><img style=\"width:100%;height:auto\" src=\"https://images.location.pub/FrV26hFjDyKMgugwbH5uuSeyu94L-pms_original\" data-custom=\"id=wux-upload--1697634247667-1\" class=\"   \"></span></p><p style=\"line-height: 3;\"><span style=\"font-size: 14px;\">\uFEFF</span></p><p style=\"line-height: 3; text-align: center;\"><strong style=\"color: rgb(246, 140, 65); font-size: 18px;\"><em><u>无语佛啊啊</u></em></strong><strong style=\"color: rgb(246, 140, 65); font-size: 14px;\"><em><u>\uFEFF</u></em></strong></p><p style=\"text-align: left;\"><br></p><p style=\"text-align: left;\">sad</p><p style=\"text-align: left;\"><br></p><p><img style=\"width:100%;height:auto\" src=\"https://images.location.pub/FsXbCDwqZHVVl1fLXBZzkWxN4L-I-pms_original\" data-custom=\"id=wux-upload--1697633627669-1\" class=\" \"></p>";
    HtmlToPNGConverter htmlToPNGConverter = new HtmlToPNGConverter(html);
    List<String> urlList = htmlToPNGConverter.convertToPNG();
    System.out.println(JSONObject.toJSONString(urlList));
}

在这里插入图片描述
效果如下:

["https://message-center-gc.oss-cn-hangzhou.aliyuncs.com/20231020000000000c76f731b-a6bc-44e3-9e23-82eae3608b13.png"]

在这里插入图片描述

踩坑经历

自此office转换图片的功能基本实现,我们部署至服务器!

1、PPT/PPTX转换时中文乱码问题

当我们在本地测试一切ok,提测后部署到服务器之后突然收到了一个BUG(中文乱码,成方框)!

在这里插入图片描述

那是因为ppt内容中字体不支持,服务器未安装中文字体,一般我们的服务器部署方案不支持做这件事(将所有的字体下载至镜像),业务代码的服务器都在 k8s 集群上,相对都是无状态的,没有什么其它额外的东西,如果将所有字体放进去整个镜像会特别大不太适合走 k8s这套了。若非要这样的话,推荐重新部署一台服务器,专门用于文件转换。当然,有条件的铁铁就不用考虑啦,直接下载所有字体!

如果未安装,可通过 yum -y install fontconfig 安装,然后在/usr/share 目录下会发现 fonts目录,下载中文字体如:heiti.ttf

拷贝到fonts目录下,chmod 赋权限。再次执行 fc-list :" class=“has” data-src=“/image/https://img-blog.csdnimg.cn/20181106180741352.png” height=“54” src=“/assets/images/photo.gif” width=“738”/>

如果是docker环境,则可将上述安装步骤写入到dockerfile中。

这里演示没有条件的解决方案,下载 "苹方"字体,并在PPT/PPT转换文件时统一字体为 “苹方”(当然可以是其他字体,如“宋体”……)

第一步、下载字体

在这里插入图片描述

第二步、编写dockerfile安装字体

#PDF 转图片中文乱码#
RUN set -xe \
&& apk --no-cache add fontconfig
#&& apk --no-cache add ttf-dejavu fontconfig
COPY pingfang.ttf /usr/share/fonts/ttf-dejavu/pingfang.ttf
#PDF 转图片中文乱码#

第三步、转换器中指定字体

在这里插入图片描述

2、OOM问题

由于转换是ppt的每一页进行单独转换,如果ppt页数多,可能会慢。 解决办法,一种是减小上文中的image_rate,如设置为1。还有就是可以通过多线程并发转换,但是由于该转换操作是CPU密集型操作,所以需要根据机器性能决定。具体代码如下:

文件转换器专用线程工具类

根据机器性能线程池配置如下(大家根据自己的服务器自行调前三项):

  • 核心线程池大小:5
  • 最大线程池大小:5
  • 阻塞工作队列:2
  • 拒绝策略:调用方执行(此处核心,请勿改动!当然有条件的除外)

因为本文主要是讲解office文件转换至图片,关于线程池的相关知识这里不做解释,给大家推荐一本书《Java并发编程的艺术》

package com.hgw.officeconver.thread;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 * Description: 文件转换器专用线程工具
 *
 * @author LinHuiBa-YanAn
 * @date 2023/10/10 16:23
 */
@Slf4j
public class BizThreadPool {

    private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 5, 2, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1), new ThreadPoolExecutor.CallerRunsPolicy());


    /**
     * 线程执行(有返回值)
     *
     * @param supplier
     * @param <T>
     * @return
     */
    public static <T> CompletableFuture<T> supplyAsync(Supplier<T> supplier) {
        return CompletableFuture.supplyAsync(() -> {
            try {
                return supplier.get();
            } catch (Exception e) {
                log.warn("异步执行错误", e);
                throw e;
            }
        }, threadPoolExecutor);
    }

}

以PDF转换代码为例:

在这里插入图片描述

3、Cannot read JPEG2000 image: Java Advanced Imaging (JAI) Image I/O Tools are not installed 问题解决

我们发现pdf中包含JPEG2000格式的图片时,图片将渲染不出来。
pdf效果
在这里插入图片描述
转换后:
在这里插入图片描述
通过查看日志发现以下报错。
在这里插入图片描述
上传是不能读取JPEG2000格式的图片,需要引入以下工具。

  <dependency>
      <groupId>com.github.jai-imageio</groupId>
      <artifactId>jai-imageio-core</artifactId>
      <version>1.4.0</version>
  </dependency>
  <dependency>
      <groupId>com.github.jai-imageio</groupId>
      <artifactId>jai-imageio-jpeg2000</artifactId>
      <version>1.3.0</version>
  </dependency>
  <dependency>
      <groupId>org.apache.pdfbox</groupId>
      <artifactId>jbig2-imageio</artifactId>
      <version>3.0.3</version>
  </dependency>

4、Could not read embedded TTF for font AAAAAI+MicrosoftYaHeiLight 问题处理

我们发现pdf中包含一些特殊字体时,解析之后将会是一串编码。

pdf效果:
在这里插入图片描述
转换后:
在这里插入图片描述
通过查看日志发现以下报错。
在这里插入图片描述
引入以下包:

<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itextpdf</artifactId>
    <version>5.5.10</version>
</dependency>

5、html转换时中文乱码问题

老样子,我们在本地并没有出现,部署到linux服务器上之后便出现了中文乱码,如下图。
在这里插入图片描述
没毛病又是字体的问题,参考以下两位大佬:

解决方案,下载 sumSun.ttf 字体,配置至环境

RUN tar -xf /tmp/server-jre-8u202-linux-x64.tar.gz -C /opt && \
    mkdir /opt/jdk1.8.0_202/jre/lib/fonts/fallback && \
    cp /tmp/simsun.ttc /opt/jdk1.8.0_202/jre/lib/fonts/fallback/simsun.ttc && \
    cp /tmp/simsun.ttf /opt/jdk1.8.0_202/jre/lib/fonts/fallback/simsun.ttf && \
    rm -rf /tmp/* /var/cache/apk/*

ENV JAVA_HOME=/opt/jdk1.8.0_202 \
    CLASSPATH=/opt/jdk1.8.0_202/lib \
    PATH=${PATH}:/opt/jdk1.8.0_202/bin
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值