通过itextpdf操作PDF,向PDF文件最后一页添加图片(缩放图片并判断最后一页是否能放下图片)

  • 本人第一篇博客,哈哈!第一次接触itextpdf,想实现将图片向PDF尾部追加(判断原页面使用情况,图片缩放后是否可以放的下,放不下时新增页面插入图片),网上博客也看了很多,没有符合我的预期的(可能我没有发现吧,哈哈,百度也是个技术活),就研究了两天,写了这个,菜鸡一枚,希望多多交流哈!有代码优化及重构思路也希望可以指点一二!
    • pom.xml
    • ITextTest 测试类
    • PdfHandler类
      • 1.获取到最后一页(可以是任意页)document,拿到对应PDF页面宽度和高度;
      • 2.通过pdfRenderListener 处理最后一页所有文本内容,记录文本对应坐标,获取页面内容尾部高度;
      • 3.压缩图片:根据页面宽度和图片宽度进行图片缩放,过程中页边距按默认36进行计算,如需精确计算页边距可以通过PdfRenderListener记录的文本横坐标进行判定(不清楚是否itext有对应的方法可以直接调用获取);
      • 4.通过文本尾部高度-文本与插入图片保持的上下间距(自定义为10)-压缩后图片高度>0判断是否图片可以插入(没有考虑下页边距);
      • 5.当前页插入图片或新开一页插入图片
    • PdfRenderListener(实现RenderListener,重写renderText方法)
    • RectangeInfo

本人第一篇博客,哈哈!第一次接触itextpdf,想实现将图片向PDF尾部追加(判断原页面使用情况,图片缩放后是否可以放的下,放不下时新增页面插入图片),网上博客也看了很多,没有符合我的预期的(可能我没有发现吧,哈哈,百度也是个技术活),就研究了两天,写了这个,菜鸡一枚,希望多多交流哈!有代码优化及重构思路也希望可以指点一二!

pom.xml

   <!-- 核心依赖包 -->
    <dependency>
      <groupId>com.itextpdf</groupId>
      <artifactId>itextpdf</artifactId>
      <version>5.5.13.2</version>
    </dependency>
    <dependency>
      <groupId>com.itextpdf</groupId>
      <artifactId>itext-asian</artifactId>
      <version>5.2.0</version>
    </dependency>

 <!-- 辅助工具包 -->
    <dependency>
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
      <version>30.1.1-jre</version>
    </dependency>

    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.76</version>
    </dependency>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
    </dependency>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-collections4</artifactId>
      <version>4.4</version>
    </dependency>
    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.6</version>
    </dependency>
    <dependency>
      <groupId>cn.hutool</groupId>
      <artifactId>hutool-all</artifactId>
      <version>5.7.13</version>
    </dependency>
        <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.20</version>
      <scope>provided</scope>
    </dependency>

ITextTest 测试类

package com.dyx.springbootbase;

import com.dyx.springbootbase.pdf.itext.PdfHandler;
import java.io.IOException;
import org.junit.Test;

/**
 * @author duyunxi
 * @sign 世人莫羡长安景,风雨楼外多风雨
 * @date 2021-10-17 10:07
 */
public class ITextTest {
    private static String filePath = "C:\\Users\\TR\\Desktop\\java\\java电子书\\深入理解Java虚拟机:JVM高级特性与最佳实践(最新第三版).pdf";
    private static String filePath1 = "C:\\Users\\TR\\Desktop\\java\\java电子书\\阿里巴巴Java开发手册泰山版.pdf";
    private static String imageName = "C:\\Users\\TR\\Pictures\\1.jpg";

    @Test
    public void insertToPdf() {
        try {
            PdfHandler pdfHandler = new PdfHandler(filePath, imageName);
            pdfHandler.addImageToPdf();
            // 关流,删除临时文件
            pdfHandler.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

PdfHandler类

1.获取到最后一页(可以是任意页)document,拿到对应PDF页面宽度和高度;

2.通过pdfRenderListener 处理最后一页所有文本内容,记录文本对应坐标,获取页面内容尾部高度;

3.压缩图片:根据页面宽度和图片宽度进行图片缩放,过程中页边距按默认36进行计算,如需精确计算页边距可以通过PdfRenderListener记录的文本横坐标进行判定(不清楚是否itext有对应的方法可以直接调用获取);

4.通过文本尾部高度-文本与插入图片保持的上下间距(自定义为10)-压缩后图片高度>0判断是否图片可以插入(没有考虑下页边距);

5.当前页插入图片或新开一页插入图片

package com.dyx.springbootbase.pdf.itext;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Date;
import java.util.Objects;

import com.itextpdf.text.BadElementException;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Image;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfStamper;
import com.itextpdf.text.pdf.parser.PdfReaderContentParser;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.NumberUtil;

/**
 * @author duyunxi
 * @sign 世人莫羡长安景,风雨楼外多风雨
 * @date 2021-10-16 22:21
 */
public class PdfHandler {

    private static final int WORD_IMAGE_SPACE = 10;
    private String targetPath = "D:\\target";
    private String targetImagePath = "D:\\target";
    private String filePath;
    private String imagePath;
    private int pages;

    private PdfReader reader;
    private Document document;
    private PdfStamper stamper;
    private FileOutputStream fos;

    public PdfHandler(String filePath, String imagePath) throws IOException {
        this.filePath = filePath;
        this.imagePath = imagePath;
        this.targetPath = targetPath + File.separator + DateUtil.format(new Date(), "yyyyMMddHHmmss") + ".pdf";
        this.targetImagePath =
            targetImagePath + File.separator + DateUtil.format(new Date(), "yyyyMMddHHmmss") + ".jpg";
        this.reader = new PdfReader(filePath);
        this.pages = reader.getNumberOfPages();
        this.fos = new FileOutputStream(targetPath);
    }

    /**
     * 在pdf最后一页添加图片
     */
    public void addImageToPdf() {
        try {
            // 获取最后一页文档对象进行判断
            document = new Document(reader.getPageSize(pages));

            // Create a stamper that will copy the document to a new file
            PdfReaderContentParser contentParser = new PdfReaderContentParser(reader);
            PdfRenderListener pdfRenderListener = new PdfRenderListener();
            // 处理最后一页(pages) 
            pdfRenderListener = contentParser.processContent(pages, pdfRenderListener);
            RectangeInfo rectangeInfo = pdfRenderListener.getRectangeInfo();
            // 最后一页实际文字内容到达的纵坐标位置
            float bottomY = rectangeInfo.getRectangeY();

            // 如果图片大小超过PDF宽度,对图片按宽度进行等比压缩(一般图片宽度大于高度)
            Image oldImage = Image.getInstance(imagePath);
            // 图片宽度
            float imgWidth = oldImage.getWidth();
            // pdf宽度
            float width = document.getPageSize().getWidth();

            float scale = scaleImageByWidth(imagePath, targetImagePath, width, imgWidth);
            // 压缩后图片高度
            float scaleHeight = scale * oldImage.getHeight();
            // Can't fit the picture on the current page
            if (bottomY - WORD_IMAGE_SPACE < scaleHeight) {
                float height = document.getPageSize().getHeight();
                // add image to the new page
                addImgToNewPage(filePath, targetPath, targetImagePath, width, height);
            } else {
                // add image on the current page
                // 文字到达的纵坐标位置-文字与图片间距10-压缩后图片高度
                float imageY = bottomY - WORD_IMAGE_SPACE - scaleHeight;
                addImageToBottom(filePath, targetImagePath, width, imageY);
            }
        } catch (BadElementException | IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * when the imgWidth more than the width,scale image, return the scale
     *
     * @param imagePath
     *            image path
     * @param targetImage
     *            target image
     * @param width
     *            pdf width
     * @param imgWidth
     *            img width
     * @return scale
     */
    public float scaleImageByWidth(String imagePath, String targetImage, float width, float imgWidth) {
        // 缩放比例
        float div = 1;
        // 图片宽度过长时进行压缩处理
        if (width < imgWidth) {
            // 一般PDF页边距为36,减去两边的页边距72
            div = (float)NumberUtil.div(width - 72, imgWidth, 4);
        }
        ImgUtil.scale(FileUtil.file(imagePath), FileUtil.file(targetImage), div);
        return div;
    }

    /**
     * 添加图片到新页面
     *
     * @param filePath
     *            文件路径
     * @param targetPath
     *            修改后pdf路径
     * @param imagePath
     *            压缩后图片路径
     * @param width
     *            pdf宽度
     * @param height
     *            pdf高度
     */
    public void addImgToNewPage(String filePath, String targetPath, String imagePath, float width, float height) {
        try {
            stamper = new PdfStamper(reader, fos);
            // insert new page
            stamper.insertPage(reader.getNumberOfPages() + 1, reader.getPageSizeWithRotation(1));
            // get new page
            PdfContentByte under = stamper.getOverContent(reader.getNumberOfPages());

            Image image = Image.getInstance(imagePath);

            float imageX = (width - image.getWidth()) / 2;
            // PDF高度-图片高度-页面上边距
            float imageY = height - image.getHeight() - 36;
            // 设置图片坐标位置
            image.setAbsolutePosition(imageX, imageY);
            // 图片居中
            image.setAlignment(Image.ALIGN_MIDDLE);
            under.addImage(image);
        } catch (DocumentException | IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 添加图片到当前页面尾部
     *
     * @param filePath
     *            文件路径
     * @param imagePath
     *            图片路径
     * @param width
     *            pdf宽度
     * @param imageY
     *            图片放置位置纵坐标
     */
    private void addImageToBottom(String filePath, String imagePath, float width, float imageY) {
        try {
            stamper = new PdfStamper(reader, fos);
            Image img = Image.getInstance(imagePath);
            float imageX = (width - img.getWidth()) / 2;
            // 图片放置位置
            img.setAbsolutePosition(imageX, imageY);
            // set image in center
            img.setAlignment(Image.ALIGN_MIDDLE);
            // Text over the existing page
            PdfContentByte over = stamper.getOverContent(pages);
            over.addImage(img);
        } catch (DocumentException | IOException e) {
            e.printStackTrace();
        }
    }

    public void close() {
        try {
            if (!Objects.isNull(stamper)) {
                stamper.close();
            }
            if (!Objects.isNull(document)) {
                document.close();
            }
            if (!Objects.isNull(fos)) {
                fos.close();
            }
            if (!Objects.isNull(reader)) {
                reader.close();
            }
            // 删除压缩产生的临时图片
            FileUtil.del(targetImagePath);
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

PdfRenderListener(实现RenderListener,重写renderText方法)

package com.dyx.springbootbase.pdf.itext;

import com.itextpdf.awt.geom.Rectangle2D;
import com.itextpdf.text.pdf.parser.ImageRenderInfo;
import com.itextpdf.text.pdf.parser.RenderListener;
import com.itextpdf.text.pdf.parser.TextRenderInfo;
import java.util.ArrayList;
import java.util.List;

/**
 * @author duyunxi
 * @sign 世人莫羡长安景,风雨楼外多风雨
 * @date 2021-10-16 21:10
 */
public class PdfRenderListener implements RenderListener {
    // 用于记录页面文字内容对应的文字横纵坐标及文字宽高
    private List<RectangeInfo> rectangeInfos = new ArrayList<>();
    // 
    private RectangeInfo rectangeInfo;

    public List<RectangeInfo> getRectangeInfos() {
        return rectangeInfos;
    }

    public void setRectangeInfos(List<RectangeInfo> rectangeInfos) {
        this.rectangeInfos = rectangeInfos;
    }

    public RectangeInfo getRectangeInfo() {
        return rectangeInfo;
    }

    @Override
    // 开始读取文字内容时
    public void beginTextBlock() {}

    @Override
    // 读取文字内容,经测试,读取文字顺序是按页眉、页脚、正文顺序读取
    //即使页面包含图片,读取结束的text对应的也是整个页面所有内容的终止坐标点
    // 本文只关注纵坐标Y的位置 ,如需精确页面页边距可通过集合记录横坐标X确定左右页边距大小
    public void renderText(TextRenderInfo renderInfo) {
    
        String text = renderInfo.getText();
        Rectangle2D.Float boundingRectange =renderInfo.getBaseline().getBoundingRectange();
        RectangeInfo rectangeInfo =        RectangeInfo.builder().rectangeX(boundingRectange.x).rectangeY(boundingRectange.y).build();
        rectangeInfo.setContent(text);
        rectangeInfos.add(rectangeInfo);
    }

    @Override
    public void endTextBlock() {
        rectangeInfo = rectangeInfos.get(rectangeInfos.size() - 1);
    }

    @Override
    // 对页面中图片进行操作
    public void renderImage(ImageRenderInfo renderInfo) {}
}

RectangeInfo

package com.dyx.springbootbase.pdf.itext;

import lombok.Builder;
import lombok.Data;
import lombok.ToString;

/**
 * 记录文本内容坐标信息
 * 
 * @author duyunxi
 * @sign 世人莫羡长安景,风雨楼外多风雨
 * @date 2021-10-16 21:14
 */
@Data
@ToString
@Builder
public class RectangeInfo {
    private String content;
    private float rectangeX;
    private float rectangeY;
}

更多推荐

通过itextpdf操作PDF,动态向PDF文件最后一页添加图片