点击数:0

前言

最近项目需要用到有关PDF的操作,网上查了很多资料,觉得itext7比较符合我的需求,在此做一下简单地记录。

itext7相对于itext5做了很多改动,所以如果使用itext5的话本文并不适用。不过官网有着非常详细的API文档以及演示,具体可以去官网查看。

官网链接

配置

1.maven依赖

我是使用maven引入的,并且是全家桶,因为第一次使用,这样比较方便。

<!-- PDF操作,itext7全家桶 -->
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itext7-core</artifactId>
    <version>7.1.11</version>
    <type>pom</type>
 </dependency>

2.PDFUtils

封装的PDF工具类,导包直接选中itext相关的就好,注意IOException不要导itext的(第一次无脑导的后果..)

public class PDFUtils {

    /**
     * 
     * @Title: getDefaultFont 
     * @Description: 获取系统自带字体,如未安装可先安装或引入 
     * @return PdfFont
     * @author yanghainan
     * @date 2020年7月8日上午10:46:39
     */
    public static PdfFont getDefaultFont() {
        try {
//          return PdfFontFactory.createFont("C://windows//fonts//simsun.ttc,1", PdfEncodings.IDENTITY_H, false);// 引用系统字体
            return PdfFontFactory.createFont("STSong-Light", "UniGB-UCS2-H", false);// 使用itext字体
        } catch (IOException e) {
            // 记录日志
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 
     * @Title: getImportFont 
     * @Description: 获取引入的其他字体 (支持Linux系统)
     * @return PdfFont
     * @author yanghainan
     * @date 2020年7月8日上午10:13:01
     */
    public static PdfFont getImportFont(String fontName) {
        // 获取resource下文件夹路径
        String path = "/template/";
        String fontPath = path + fontName;
        try {
            // 处理中文乱码(支持Linux系统)
            FontProgram fontProgram = FontProgramFactory.createFont(fontPath, false);
            PdfFont font = PdfFontFactory.createFont(fontProgram, PdfEncodings.IDENTITY_H, false);
            return font;
        } catch (IOException e) {
            // 记录日志
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 替换PDF文本表单域变量
     * 
     * @param templatePdfPath
     *            要替换的pdf全路径
     * @param params
     *            替换参数
     * @param destPdfPath
     *            替换后保存的PDF全路径
     * 
     * @throws FileNotFoundException
     * @throws IOException
     */
    public static final void replaceTextFieldPdf(String templatePdfPath, String destPdfPath,
            Map<String, String> params) {
        PdfDocument pdf = null;
        try {
            // 判断文件是否存在
            File file = new File(destPdfPath);
            if (!file.getParentFile().exists()) {
                file.getParentFile().mkdirs();
            }
            // 有参数才替换
            if (params != null && !params.isEmpty()) {
                pdf = new PdfDocument(new PdfReader(templatePdfPath), new PdfWriter(destPdfPath));
                PdfAcroForm form = PdfAcroForm.getAcroForm(pdf, true);
                Map<String, PdfFormField> fields = form.getFormFields(); // 获取所有的表单域
                for (String param : params.keySet()) {
                    PdfFormField formField = fields.get(param); // 获取某个表单域
                    if (formField != null) {
                        formField.setFont(getImportFont("SourceHanSansCN-Regular.ttf")).setValue(params.get(param)); // 替换值
//                      formField.setFont(getDefaultFont()).setValue(params.get(param)); // 替换值
                    }
                }
                form.flattenFields();// 锁定表单,不让修改
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (pdf != null) {
                pdf.close();
            }
        }
    }

    /**
     * 替换PDF图片表单域(文本)变量,1、获取表单域的大小;2、根据表单域的位置,确定图片的位置;3、如果图片的宽或者高大于表单域,则等比压缩图片。
     * 
     * @param templatePdfPath
     *            要替换的pdf全路径
     * @param params
     *            替换参数
     * @param destPdfPath
     *            替换后保存的PDF全路径
     * 
     * @throws FileNotFoundException
     * @throws IOException
     */
    public static final void replaceImageFieldPdf(String templatePdfPath, String destPdfPath,
            Map<String, String> params) throws FileNotFoundException, IOException {
        PdfDocument pdf = new PdfDocument(new PdfReader(templatePdfPath), new PdfWriter(destPdfPath));

        if (params != null && !params.isEmpty()) {// 有参数才替换
            PdfAcroForm form = PdfAcroForm.getAcroForm(pdf, true);
            Map<String, PdfFormField> fields = form.getFormFields(); // 获取所有的表单域
            for (String param : params.keySet()) {
                PdfFormField formField = fields.get(param);
                if (formField != null) {
                    replaceFieldImage(params, pdf, param, formField); // 替换图片
                }
            }
            form.flattenFields();// 锁定表单,不让修改
        }
        pdf.close();
    }

    /**
     * 替换域中的图片
     * 
     * @param params
     * @param pdf
     * @param param
     * @param formField
     * @throws MalformedURLException
     */
    private static final void replaceFieldImage(Map<String, String> params, PdfDocument pdf, String param,
            PdfFormField formField) throws MalformedURLException {
        String value = params.get(param);
        String[] values = value.split("\\|");
        Rectangle rectangle = formField.getWidgets().get(0).getRectangle().toRectangle(); // 获取表单域的xy坐标
        PdfCanvas canvas = new PdfCanvas(pdf.getPage(Integer.parseInt(values[0])));
        ImageData image = ImageDataFactory.create(values[1]);
        float imageWidth = image.getWidth();
        float imageHeight = image.getHeight();
        float rectangleWidth = rectangle.getWidth();
        float rectangleHeight = rectangle.getHeight();

        float tempWidth = 0;
        float tempHeight = 0;

        int result = 1; // 压缩宽度
        if (imageWidth > rectangleWidth) {
            tempHeight = imageHeight * rectangleWidth / imageWidth;
            if (tempHeight > rectangleHeight) {
                tempHeight = rectangleHeight;
                result = 2; // 压缩高度
            } else {
                tempWidth = rectangleWidth;
                tempHeight = imageHeight * rectangleWidth / imageWidth;
            }
        } else {
            if (imageHeight > rectangleHeight) {
                tempHeight = rectangleHeight;
                result = 2;
            } else {
                result = 3;
            }
        }

        float y = 0;

        if (result == 1) { // 压缩宽度
            y = rectangleHeight - tempHeight;
        } else if (result == 3) { // 不压缩
            y = rectangleHeight - imageHeight;
        }

        // y/=2; // 如果想要图片在表单域的上下对齐,这个值除以2就行。同理可以计算x的偏移

        if (result == 1) {
            canvas.addImage(image, rectangle.getX(), rectangle.getY() + y, tempWidth, false);
        } else if (result == 2) {
            canvas.addImage(image, rectangle.getX(), rectangle.getY(), tempHeight, false, false);
        } else if (result == 3) {
            canvas.addImage(image, rectangle.getX(), rectangle.getY() + y, false);
        }
    }

    /**
     * 
     * @Title: addWatermark 
     * @Description: 添加文字水印 
     * @param srcPdfPath 原文件路径
     * @param destPdfPath 替换后保存的PDF全路径
     * @param watermarkText 水印
     * @throws FileNotFoundException
     * @throws IOException void
     * @author yanghainan
     * @date 2020年7月8日上午8:48:15
     */
    @SuppressWarnings("resource")
    public static final void addWatermark(String srcPdfPath, String destPdfPath, String watermarkText)
            throws FileNotFoundException, IOException {
        // 判断文件是否存在
        File f1 = new File(srcPdfPath);
        if (!f1.exists()) {
            return;
        }
        // 判断文件是否存在
        File f2 = new File(destPdfPath);
        if (!f2.getParentFile().exists()) {
            f2.getParentFile().mkdirs();
        }

        PdfDocument pdfDoc = new PdfDocument(new PdfReader(srcPdfPath), new PdfWriter(destPdfPath));

        pdfDoc.addEventHandler(PdfDocumentEvent.END_PAGE, new IEventHandler() {
            @Override
            public void handleEvent(Event event) {
                PdfDocumentEvent docEvent = (PdfDocumentEvent) event;
                PdfDocument pdfDoc = docEvent.getDocument();
                PdfPage page = docEvent.getPage();
                PdfFont font = null;
                font = getImportFont("STSong.ttf"); // 要显示中文水印的话,需要设置中文字体
                PdfCanvas canvas = new PdfCanvas(page);
                PdfExtGState gs1 = new PdfExtGState();
                gs1.setFillOpacity(0.7f); // 水印透明度
                canvas.setExtGState(gs1);
                new Canvas(canvas, pdfDoc, page.getPageSize()).setFontColor(ColorConstants.LIGHT_GRAY) // 颜色
                        .setFontSize(60) // 字体大小
                        .setFont(font) // 字体的格式 即导入的字体包
                        // 水印的内容(中英文都支持) 坐标(例如:298f, 421f) 当前页数 最后的值为倾斜度(170)
                        .showTextAligned(new Paragraph(watermarkText), 298, 421, pdfDoc.getPageNumber(page),
                                TextAlignment.CENTER, VerticalAlignment.MIDDLE, 45);
            }
        });
        pdfDoc.close();
    }

    /**
     * 
     * @Title: pdfMerger 
     * @Description: PDF合并 
     * @param paths 需要合并的所有文件路径(路径+名称)
     * @param outputPath 合并后的文件路径(路径+名称)
     *  void
     * @author yanghainan
     * @date 2020年7月3日下午3:21:00
     */
    public static boolean pdfMerger(List<String> paths, String outputPath) {
        if (paths == null || paths.isEmpty() || StringUtils.isBlank(outputPath)) {
            return false;
        }
        // 首页与其他页创建方式不同,所以需要创建两个
        PdfDocument firstSourcePdf = null;
        PdfDocument secondSourcePdf = null;
        // 合并需要的工具类
        PdfMerger merger = null;
        try {
            for (int i = 0; i < paths.size(); i++) {
                if (i == 0) {
                    PdfWriter pdfWriter = new PdfWriter(outputPath);
                    // 启用完全压缩
//                  pdfWriter.isFullCompression();
                    firstSourcePdf = new PdfDocument(new PdfReader(paths.get(i)), pdfWriter);
                    merger = new PdfMerger(firstSourcePdf);
                    continue;
                }
                secondSourcePdf = new PdfDocument(new PdfReader(paths.get(i)));
                merger.merge(secondSourcePdf, 1, secondSourcePdf.getNumberOfPages());
                secondSourcePdf.close();
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭流
            if (firstSourcePdf != null) {
                firstSourcePdf.close();
            }
            if (secondSourcePdf != null) {
                secondSourcePdf.close();
            }
            if (merger != null) {
                merger.close();
            }
        }
        return false;
    }

    /**
     * 
     * @Title: downPdf 
     * @Description: 文件下载 
     * @param response
     * @param reviewName
     * @param outputFilePath
     * @return String
     * @author yanghainan
     * @date 2020年7月6日下午4:52:48
     */
    public static String downPdf(HttpServletResponse response, String reviewName, String outputFilePath) {
        File file = new File(outputFilePath);
        if (!file.exists()) {
            return "下载文件不存在";
        }
        // 设置格式
        try {
            response.reset();
            response.setContentType("application/octet-stream");
            response.setCharacterEncoding("utf-8");
            response.setContentLength((int) file.length());
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(reviewName, "UTF-8"));
        } catch (UnsupportedEncodingException e1) {
            e1.printStackTrace();
        }
        // 文件写出
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));) {
            byte[] buff = new byte[1024];
            OutputStream os = response.getOutputStream();
            int i = 0;
            while ((i = bis.read(buff)) != -1) {
                os.write(buff, 0, i);
                os.flush();
            }
        } catch (Exception e) {
            return "下载失败";
        } finally {
            // 删除服务器文件
            forceDelete(file);
        }
        return "下载成功";
    }

    /**
     * 
     * @Title: forceDelete 
     * @Description: 强制删除文件 
     * @param f
     * @return boolean
     * @author yanghainan
     * @date 2020年7月7日下午3:05:14
     */
    public static boolean forceDelete(File f) {
        boolean result = false;
        int tryCount = 0;
        while (!result && tryCount++ < 10) {
//          System.gc();
            result = f.delete();
        }
        return result;
    }

    /**
     * 
     * @Title: forceBatchDelete 
     * @Description: 强制删除文件(批量) 
     * @param paths
     * @return int
     * @author yanghainan
     * @date 2020年7月7日下午3:09:37
     */
    public static int forceBatchDelete(List<String> paths) {
        if (paths == null || paths.isEmpty()) {
            return 0;
        }
        int result = 0;
        for (int i = 0; i < paths.size(); i++) {
            boolean delete = forceDelete(new File(paths.get(i)));
            if (delete) {
                result++;
            }
        }
        return result;
    }

}

使用

1.Adobe Acrobat Pro DC

使用Adobe Acrobat Pro DC 可以很方便的设置PDF模板,这样我们只需要将数据填充进去就可以了。如果没有安装可以去我的云盘下载:

下载链接

访问码:ce2p
解压密码:@vposy
如下是项目中用到的一个模板:

1

光有模板是不够的,还需要对模板进行表单设置:

2

默认选择就好,成功后会变成这样:

3

双击字段可以修改属性:

4

注意这里的名称就是javaBean的属性,需要按照相对应的设置。本文工具类里使用的是Map集合,设置对应的key就好。

2. 测试

public static void main(String[] args) throws FileNotFoundException, IOException {
    String templatePdfPath = "F:\\PDFtemplate\\模板.pdf";
    String destPdfPath = "F:\\PDFtemplate\\测试.pdf";
    Map<String, String> params = new HashedMap();
    params.put("fill_5", "111");
    try {
        PDFUtils.replaceTextFieldPdf(templatePdfPath, destPdfPath, params);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

运行后:

5

3. 合并

刚搞好了数据填充生成PDF,又加了个合并PDF的需求,不过好在很简单.

  • 官网Demo
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.utils.PdfMerger;

import java.io.IOException;

public class PdfMerge {
    private static final String FILE1 = "/uploads/first.pdf";
    private static final String FILE2 = "/uploads/second.pdf";
    private static final String OUTPUT_FOLDER = "/myfiles/";

    public static void main(String args[]) throws IOException {
        PdfDocument pdfDocument = new PdfDocument(new PdfReader(FILE1), new PdfWriter(OUTPUT_FOLDER + "merged.pdf"));
        PdfDocument pdfDocument2 = new PdfDocument(new PdfReader(FILE2));

        PdfMerger merger = new PdfMerger(pdfDocument);
        merger.merge(pdfDocument2, 1, pdfDocument2.getNumberOfPages());

        pdfDocument2.close();
        pdfDocument.close();
    }
}
  • 我自己写的小Demo
    private static final String FILE1 = "F:\\PDFtemplate\\测试1.pdf";
    private static final String FILE2 = "F:\\PDFtemplate\\测试2.pdf";
    private static final String FILE3 = "F:\\PDFtemplate\\测试3.pdf";
    private static final String OUTPUT_FOLDER = "F:\\PDFtemplate\\";

    public static void main(String[] args) throws FileNotFoundException, IOException {
        List<String> paths = new ArrayList<String>();
        paths.add(FILE1);
        paths.add(FILE2);
        paths.add(FILE3);
        PDFUtils.pdfMerger(paths, OUTPUT_FOLDER + "测试.pdf");
    }

具体效果就是合并为一个文件,就不再演示了。
本人也是初学,如有错漏欢迎批评指正。


心之所向,素履前往 ;生之逆旅,一苇以航 .