JAVA PDF转图片,以及几种方案对比

背景

项目需要将PDF转为图片存储,在网上搜索,找到了三种方案(这里链接都是找的github上的地址):

  • pdfbox,开源软件,apache社区在维护,还比较活跃
  • icepdf, 商业软件,但是github上有开源版本,最近一年还有提交
  • jpedal, 商业软件,但是有LGPL版本的,很不活跃,上一个版本在2020年,但是没有上传到中央maven仓。

最终选择的方案是pdfbox。具体每种方案的实现分别说明。

PDFBOX

pdfbox是一个开源的转换实现,截止我使用时,在mvn库上看到的最新版本是3.0.0-alpha3

需要注意网上的很多示例还是针对2.x的版本,3.x版本接口有所变化,我这里是3.x版本。

pdfbox代码实现

pom引用

    <!-- https://mvnrepository.com/artifact/org.apache.pdfbox/pdfbox -->
    <dependency>
        <groupId>org.apache.pdfbox</groupId>
        <artifactId>pdfbox</artifactId>
        <version>3.0.0-alpha3</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.apache.pdfbox/fontbox -->
    <dependency>
        <groupId>org.apache.pdfbox</groupId>
        <artifactId>fontbox</artifactId>
        <version>3.0.0-alpha3</version>
    </dependency>

转换代码

实际使用时,碰到两个问题

  1. 发现压缩普通pdf没有问题,但是压缩超大pdf(300MB)时,出现内存溢出问题。
  2. 中文字体无法转换

这里先贴出初始代码,后面再贴出修改方案

//bis为一个InputStream,实际来源可能为文件、下载等
InputStream bis = .......
//这里使用了lombok的@Cleanup标记
@Cleanup PDDocument pdf = Loader.loadPDF(bis, MemoryUsageSetting.setupTempFileOnly());
//https://issues.apache.org/jira/browse/PDFBOX-3700, 这里参考这个问题进行内存优化
pdf.setResourceCache(null);
PDFRenderer pdfRenderer = new PDFRenderer(pdf);
pdfRenderer.setSubsamplingAllowed(true);
for (int i = 0; i < pdf.getNumberOfPages(); i++) {
    BufferedImage image = pdfRenderer.renderImageWithDPI(i, 120);
    String fileName = String.format("%d-%s.jpg", i, pdfFileId);
    //这里创建了一个fileItem用于上传,也可以用本地文件方式创建输出流
    FileItem fileItem = factory.createItem("file",
            MediaType.MULTIPART_FORM_DATA_VALUE,
            true,
            fileName);
    try (OutputStream os = fileItem.getOutputStream()) {
        ImageIO.write(image, "JPG", os);
    }

    MultipartFile multi = new CommonsMultipartFile(fileItem);
    //上传保存该MultipartFile,不在文本范围内
    
}

内存不足问题修改

简单分析了一下,也找了一些资料,初步分析代码并没有内存未释放问题,可能是pdfbox实现本身比较消耗内存,在解析pdf过程中会缓存一些信息导致内存利用率高。
我部署使用的机器为一台16GB内存的主机,如果主机内存足够大(如32G),个人猜想应该就不会出现该问题。

但是受资源限制,只能做一些内存优化,这里我尝试过单纯System.gc(),并不能完全释放内存,最终选择每处理10页左右,重新创建PDFDocument对象,问题没有再出现。

修改后代码如下:

  //bis为一个InputStream,实际来源可能为文件、下载等
  InputStream bis = .......
  int i = 0;
  PDDocument pdf = null;
  PDFRenderer pdfRenderer = null;
  try {
      while (true) {
          if (0 == (i % 8)) {
              if (null != pdf) {
                  //回收内存,重新处理
                  pdf.close();
                  pdf = null;
                  pdfRenderer = null;
              }
              log.info("GC.......");
              System.gc();
          }
          if (null == pdf) {
              bis.reset();
              pdf = Loader.loadPDF(bis, MemoryUsageSetting.setupTempFileOnly());
              pdf.setResourceCache(new DefaultResourceCacheExt());
              pdfRenderer = new PDFRenderer(pdf);
              pdfRenderer.setSubsamplingAllowed(true);
          }
          if (i >= pdf.getNumberOfPages()) {
              break;
          }

          BufferedImage image = pdfRenderer.renderImageWithDPI(i, 120);
          String fileName = String.format("%d-%s.jpg", i, pdfFileId);
          //图片转存
          FileItem fileItem = factory.createItem("file",
                  MediaType.MULTIPART_FORM_DATA_VALUE,
                  true,
                  fileName);
          try (OutputStream os = fileItem.getOutputStream()) {
              ImageIO.write(image, "JPG", os);
          }

          MultipartFile multi = new CommonsMultipartFile(fileItem);
          //对MutiparFile进行保存

          i++;
      }
  } finally {
      if (null != pdf) {
          pdf.close();
      }
  }

字体问题修改

这个问题在网上就能找到不少方案,这里简单总结一下

  1. 安装字体库, 如果已安装,则跳过此步
yum install fontconfig

2.安装更新字体命令, 如果已安装,则跳过此步

yum install mkfontscale
  1. 新建目录、上传中文字体
mkdir /usr/share/fonts/Chinese
# 切换到中文目录下 将Windows中文字体上传, 可以把Windows下的C:\Windows\Fonts中的文件拷贝到这里
cd /usr/share/fonts/Chinese
# 该目录及其下所有文件需要有执行权限
chmod -R 755 /usr/share/fonts/Chinese
  1. 重新建立字体索引、更新缓存
mkfontscale
mkfontdir
fc-cache

5、查看字体是否安装成功

fc-list :lang=zh

ICEPDF

搜索java pdf转图片能找到很多icepdf相关内容,但是想找(白嫖)一个商业版本,只在csdn上有,但是要的积分太多了。。
惊喜的是在github上竟然有开源版,而且看起来还有人在维护,虽然缺少商业版的一些组件,但是对于转图片功能已经够了。
虽然最终没有选择方案,但是使用下来也是能用的程度。

优点是转换速度不错,内存占用也比较好。
缺点是图片文字等,转换出来的还原度比pdfbox稍差一点,且生成相同质量、格式的图片,icepdf转换出来的比pdfbox大一些。

ICEPDF代码

ICEPDF pom引用

真正使用时,可以去maven上找一找是否有更新的版本

        <dependency>
            <groupId>com.github.pcorless.icepdf</groupId>
            <artifactId>icepdf-core</artifactId>
            <version>7.0.0</version>
        </dependency>
        <dependency>
            <groupId>com.github.pcorless.icepdf</groupId>
            <artifactId>icepdf-viewer</artifactId>
            <version>7.0.0</version>
        </dependency>

ICEPDF 代码逻辑

  //bis为一个InputStream,实际来源可能为文件、下载等
  InputStream bis = .......
  Document document = new Document();
  try {
      document.setInputStream(bis, null);
      float scale = 2.0f;//缩放比例,影响输出图片大小
      float rotation = 0f;//旋转角度

      for (int i = 0; i < document.getNumberOfPages(); i++) {
          BufferedImage image = (BufferedImage)
                  document.getPageImage(i, GraphicsRenderingHints.SCREEN, org.icepdf.core.pobjects.Page.BOUNDARY_CROPBOX, rotation, scale);

          try {
              String fileName = String.format("%d-%s.jpg", i, pdfFileId);
              //图片转存,也可以直接创建本地文件File进行保存
              FileItem fileItem = factory.createItem("file",
                      MediaType.MULTIPART_FORM_DATA_VALUE,
                      true,
                      fileName);
              try (OutputStream os = fileItem.getOutputStream()) {
                  ImageIO.write(image, "JPG", os);
              }

              MultipartFile multi = new CommonsMultipartFile(fileItem);
              //这里可以上传或保存该MultiparFile
          } catch (IOException e) {
              e.printStackTrace();
          }
      }
  } finally {
      if (null != document) {
          document.dispose();
      }
  }

JPEDAL

看起来是比较古老的代码了,maven上最后一个LGPL版本是2012年的,如果要用最新的版本,需要到github上去下载自己打包(地址

最终测试速度和内存占用还可以,但是中文转换始终有乱码,按网络教程尝试了安装字体,增加字体映射,都无效。。
所以最终没有采用该方案。

但是还是把代码在这里贴一下,如果有其他人有方案解决中文问题,也可采用看看。

JPEDAL代码

JPEDAL pom引用

这里我是下载下来自己编译的最新版,所以用的<scope>system</scope>
我也上传到了CSDN,链接地址

    <dependency>
        <groupId>org.jpedal</groupId>
        <artifactId>jpedal_lgpl</artifactId>
        <version>4.92-p13</version>
        <scope>system</scope>
        <systemPath>${project.basedir}/../lib/jpedal_lgpl-4.92-p13.jar</systemPath>
    </dependency>

JPEDAL 代码逻辑

  //bis为一个InputStream,实际来源可能为文件、下载等
  InputStream bis = .......
  //true as we are rendering page
  PdfDecoder decodePdf = new PdfDecoder(true);

//            /**optional JAI code for faster rendering*/
//            org.jpedal.external.ImageHandler myExampleImageHandler = new org.jpedal.examples.handlers.ExampleImageDrawOnScreenHandler();
//            decodePdf.addExternalHandler(myExampleImageHandler, Options.ImageHandler);
  //mappings for non-embedded fonts to use
  //这里是网上找到的中文转换方法,但是没有用
  FontMappings.setFontReplacements();
  //don't bother to extract text and images
  decodePdf.setExtractionMode(0, 1.5f);

  try {
      decodePdf.openPdfFileFromInputStream(bis, false);

      int start = 1, end = decodePdf.getPageCount();
      for (int i = start; i < end + 1; i++) {
          BufferedImage image = decodePdf.getPageAsImage(i);
          try {
              String fileName = String.format("%d-%s.jpg", i, pdfFileId);
              //图片转存,也可以创建本地文件File进行保存
              FileItem fileItem = factory.createItem("file",
                      MediaType.MULTIPART_FORM_DATA_VALUE,
                      true,
                      fileName);
              try (OutputStream os = fileItem.getOutputStream()) {
                  ImageIO.write(image, "JPG", os);
              }

              MultipartFile multi = new CommonsMultipartFile(fileItem);
              //对生成的MultipartFile进行保存
              ......
          } catch (IOException e) {
              e.printStackTrace();
          }
      }
  } catch (PdfException e) {
      e.printStackTrace();
  } finally {
      if (null != decodePdf) {
          decodePdf.closePdfFile();
      }
  }

总结

优缺点对比

  • PDFBOX
    • 优点: 支持中文,转换图片大小相对较小,转换效果好;开发者相对活跃
    • 缺点: 转换速度慢,内存占用大
  • ICEPDF
    • 优点: 支持中文,转换速度较快,内存占用较小;代码仍有在维护
    • 缺点: 转换相比PDFBOX略有失真,图片大小也稍大一些
  • JPEDAL
    • 优点: 转换速度较快,内存占用较小
    • 缺点: 不支持中文,几乎没有在维护

posted @ 2023-02-09 14:51  mosakashaka  阅读(6099)  评论(2编辑  收藏  举报