功能包括:

1. 根据HTML模板生成PDF文件

2. 对生成的PDF文件添加盖章(指定位置放置图片)

3. 对生成的PDF文件添加水印(指定位置放置文字)

1.引入PDF依赖&设置framework配置

<!--freemarker模板引擎-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!--PDF相关-->
<dependency>
    <groupId>org.xhtmlrenderer</groupId>
    <artifactId>flying-saucer-core</artifactId>
    <version>9.1.22</version>
</dependency>
<dependency>
    <groupId>org.xhtmlrenderer</groupId>
    <artifactId>flying-saucer-pdf</artifactId>
    <version>9.1.22</version>
</dependency>
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itextpdf</artifactId>
    <version>5.5.13.2</version>
</dependency>
<!--application.yml-->
freemarker:
  cache: false
  suffix: .html
  charset: UTF-8
  template-loader-path: classpath:/templates/

2.生成PDF文件|添加盖章|添加水印

public class PdfTest {

    /*生成的PDF文件*/
    private static final String TARGET = "target.pdf";
    /*生成的印章文件*/
    private static final String STAMPER = "stamper_result.pdf";
    /*生成的水印文件*/
    public static final String WATERMARK = "watermark_result.pdf";
    /*印章图片*/
    public static final String STAMPER_IMG = "stamper.png";

    public static void createPdf(String content) {
        try {
            ITextRenderer render = new ITextRenderer();
            ITextFontResolver fontResolver = render.getFontResolver();
            // 使用资源字体-宋体
            fontResolver.addFont("C:/Windows/Fonts/simsun.ttc", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
            // HTML内容创建PDF
            render.setDocumentFromString(content);
            render.layout();
            render.createPDF(new FileOutputStream(TARGET));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void generateStamper() {
        try {
            PdfReader pdfReader = new PdfReader(new FileInputStream(TARGET));
            PdfStamper pdfStamper = new PdfStamper(pdfReader, new FileOutputStream(STAMPER));
            Image image = Image.getInstance(STAMPER_IMG);
            PdfReaderContentParser parser = new PdfReaderContentParser(pdfReader);
            int pageSize = pdfReader.getNumberOfPages();
            KeywordListener keywordListener = new KeywordListener();
            keywordListener.setKeyWord("重复打印");
            ArrayList<KeywordListener.Coordinate> coordinateList = new ArrayList();
            for (int pageNumber = 1; pageNumber <= pageSize; pageNumber++) {
                // 获取某页的关键字位置
                keywordListener.init(pageNumber);
                parser.processContent(pageNumber, keywordListener);
                coordinateList.addAll(keywordListener.getCoordinates());
            }

            for (KeywordListener.Coordinate coordinate : coordinateList) {
                // 获取操作的页面
                PdfContentByte pdfContentByte = pdfStamper.getOverContent(coordinate.getPage());
                // 根据域的大小缩放图片
                image.scaleAbsolute(80f, 80f);
                // 添加图片
                image.setAbsolutePosition(coordinate.getX(), coordinate.getY());
                pdfContentByte.addImage(image);
            }

            pdfStamper.close();
            pdfReader.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void generateWaterMark() {
        try {
            PdfReader pdfReader = new PdfReader(new FileInputStream(TARGET));
            PdfStamper pdfStamper = new PdfStamper(pdfReader, new FileOutputStream(WATERMARK));
            // 原pdf文件的总页数
            int pageSize = pdfReader.getNumberOfPages();
            // 设置字体
            BaseFont font = BaseFont.createFont("c:/WINDOWS/Fonts/simsun.ttc,0", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
            // 设置填充字体不透明度为0.2f
            PdfGState gs = new PdfGState();
            gs.setFillOpacity(0.2f);
            Document document = new Document();
            float documentWidth = document.getPageSize().getWidth(), documentHeight = document.getPageSize().getHeight();
            final float xStart = 0, yStart = 0, xInterval = 40, yInterval = 40, rotation = 45, fontSize = 18;
            String watermarkWord = "水印";
            int red = 128, green = 128, blue = 128;

            for (int i = 1; i <= pageSize; i++) {
                // 水印在之前文本下
                PdfContentByte pdfContentByte = pdfStamper.getUnderContent(i);
                pdfContentByte.beginText();
                // 文字水印 颜色
                pdfContentByte.setColorFill(new BaseColor(red, green, blue));
                // 文字水印 字体及字号
                pdfContentByte.setFontAndSize(font, fontSize);
                pdfContentByte.setGState(gs);
                // 文字水印 起始位置
                pdfContentByte.setTextMatrix(xStart, yStart);

                for (float x = xStart; x <= documentWidth + xInterval; x += xInterval) {
                    for (float y = yStart; y <= documentHeight + yInterval; y += yInterval) {
                        pdfContentByte.showTextAligned(Element.ALIGN_CENTER, watermarkWord, x, y, rotation);
                    }
                }
                pdfContentByte.endText();
            }
            // 关闭
            pdfStamper.close();
            pdfReader.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

3.KeywordListener (在keyword处签章)

public class KeywordListener implements RenderListener {

    private ArrayList<Coordinate> coordinates = new ArrayList<Coordinate>();
    private String keyWord;
    private int page;

    public void init(int pageNumber) {
        this.page = pageNumber;
        this.coordinates.clear();
    }

    public ArrayList<Coordinate> getCoordinates() {
        return coordinates;
    }

    public String getKeyWord() {
        return keyWord;
    }

    public void setKeyWord(String keyWord) {
        this.keyWord = keyWord;
    }

    public int getPage() {
        return page;
    }

    public void setPage(int page) {
        this.page = page;
    }

    @Override
    public void beginTextBlock() {
    }

    @Override
    public void endTextBlock() {
    }

    @Override
    public void renderImage(ImageRenderInfo arg0) {
    }

    @Override
    public void renderText(TextRenderInfo textRenderInfo) {
        String text = textRenderInfo.getText();
        if (null != text && text.contains(this.keyWord)) {
            Rectangle2D.Float boundingRectange = textRenderInfo.getBaseline().getBoundingRectange();
            Coordinate coordinate = new Coordinate();
            coordinate.setX(boundingRectange.x + 320);
            coordinate.setY(boundingRectange.y);
            coordinate.setPage(this.page);
            coordinates.add(coordinate);
        }
    }

    class Coordinate {
        public float x;
        public float y;
        public int page;

        public float getX() {
            return x;
        }

        public void setX(float x) {
            this.x = x;
        }

        public float getY() {
            return y;
        }

        public void setY(float y) {
            this.y = y;
        }

        public int getPage() {
            return page;
        }

        public void setPage(int page) {
            this.page = page;
        }
    }
}

4.pdfTemplate.html模板

<div>
    <div>
        <div style="text-align: center;"><span>重庆公司业务专用凭证</span></div>
        <div style="text-align: center;margin-top: 10px;"><span>交易日期:${date?string("yyyy年MM月dd日")}</span></div>
    </div>
    <div>
        <table align="center" border="1px" cellspacing="0">

            <tbody>

            <tr>
                <td rowspan="3" style="width: 12%;text-align: center;">付款人</td>
                <td style="width:12%;text-align: center;">全称</td>
                <td colspan="5" width="76%">${payName}</td>
            </tr>
            <tr>
                <td style="text-align: center;">账号</td>
                <td colspan="2">283012834413275198</td>
                <td style="text-align: center;">开户行号</td>
                <td colspan="2">312975138452134</td>
            </tr>
            <tr>
                <td style="text-align: center;">开户行名称</td>
                <td colspan="5">重庆公司直属支行</td>
            </tr>

            <tr>
                <td rowspan="3" style="text-align: center;">收款人</td>
                <td style="text-align: center;">全称</td>
                <td colspan="5">重庆深妹科技有限公司</td>
            </tr>
            <tr>
                <td style="text-align: center;">账号</td>
                <td colspan="2">283012834413275198</td>
                <td style="text-align: center;">开户行号</td>
                <td colspan="2">312975138452134</td>
            </tr>
            <tr>
                <td style="text-align: center;">开户行名称</td>
                <td colspan="5">重庆公司江北支行</td>
            </tr>

            <tr>
                <td colspan="7">
                    <span>币种及金额(大写):贰佰肆拾元整</span>
                    <br/>
                    <span style="margin-left: 70px;">(小写):¥240.00</span>
                </td>
            </tr>

            <tr>
                <td style="text-align: center;">用途</td>
                <td colspan="6">货款</td>
            </tr>

            <tr>
                <td style="text-align: center;">交易流水号</td>
                <td colspan="3">0992827112211</td>
                <td style="text-align: center;">手续费</td>
                <td colspan="2"></td>
            </tr>

            <tr>
                <td style="text-align: center;">验证码</td>
                <td colspan="6">231234912375827348583435431</td>
            </tr>

            <tr>
                <td style="height: 180px;text-align: center;">风险提示</td>
                <td colspan="6">

                    <ol>
                        <li>本回单不作为收款发货依据,请勿重复记账;</li>
                        <li>本回单被伪造、变造、篡改的,不具有法律效力;</li>
                        <li>由于系统原因或通讯故障而导致的回单或对账单与客户实际交易不符的,
                            <br/>以客户实际交易为准;
                        </li>
                        <li>每笔汇款流水号唯一,请仔细核对,避免重复打印。</li>
                    </ol>

                    <div style="text-align: center;position: absolute;left: 50%;"><span>打印人员:01081111</span></div>

                </td>
            </tr>
            </tbody>
        </table>
    </div>
</div>

5.UnitTest

@Autowired
private FreeMarkerConfigurer freeMarkerConfigurer;

@Test
public void test() {
    Map<String, Object> data = new HashMap();
    data.put("date", new Date());
    data.put("payName", "稀男大学");
    PdfTest pdfTest = new PdfTest();

    StringBuffer sb = new StringBuffer();
    sb.append("<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\"/><title>Title</title><style>body {font-family: SimSun;font-size: 14px;line-height: 150%;}td {height: 25px;}</style></head><body>\n");
    // 获取模板,并设置编码方式
    String templateHtml = null;
    try {
        Template template = freeMarkerConfigurer.getConfiguration().getTemplate("pdfTemplate.html");
        template.setOutputEncoding("UTF-8");
        templateHtml = FreeMarkerTemplateUtils.processTemplateIntoString(template, data);
    } catch (Exception e) {
        e.printStackTrace();
    }
    sb.append(templateHtml);
    sb.append(templateHtml);
    sb.append(templateHtml);
    sb.append("</body></html>");

    /*1.生成模板PDF文件*/
    pdfTest.createPdf(sb.toString());
    /*2.对生成的文件进行印章*/
    pdfTest.generateStamper();
    /*3.对生成的文件添加水印*/
    pdfTest.generateWaterMark();
}

6.说明

每个值用逗号分开,如果字体名称包含空格,它必须加上引号,如果浏览器不支持第一个字体,则会尝试下一个