惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Tenable Blog
月光博客
月光博客
雷峰网
雷峰网
WordPress大学
WordPress大学
博客园 - 司徒正美
Last Week in AI
Last Week in AI
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
Visual Studio Blog
H
Help Net Security
Engineering at Meta
Engineering at Meta
Google DeepMind News
Google DeepMind News
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
S
Security @ Cisco Blogs
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
爱范儿
爱范儿
W
WeLiveSecurity
J
Java Code Geeks
Forbes - Security
Forbes - Security
H
Hacker News: Front Page
T
Threatpost
The Cloudflare Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
N
Netflix TechBlog - Medium
Latest news
Latest news
V2EX - 技术
V2EX - 技术
小众软件
小众软件
T
The Blog of Author Tim Ferriss
A
Arctic Wolf
B
Blog RSS Feed
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
I
InfoQ
C
Check Point Blog
N
News | PayPal Newsroom
Cyberwarzone
Cyberwarzone
V
V2EX
TaoSecurity Blog
TaoSecurity Blog
P
Privacy & Cybersecurity Law Blog
Microsoft Security Blog
Microsoft Security Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
D
DataBreaches.Net
F
Fortinet All Blogs
阮一峰的网络日志
阮一峰的网络日志
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
IT之家
IT之家
K
Kaspersky official blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Google DeepMind News
Google DeepMind News
C
CXSECURITY Database RSS Feed - CXSecurity.com
www.infosecurity-magazine.com
www.infosecurity-magazine.com

李锋镝的博客

LiteLLM 本地代理搭建 Claude-HUD 使用文档 Kratos+ —— Kratos 主题二次开发记录 译文:如何将单体应用拆解为微服务 codebase-memory-mcp 极简完整使用指南 Claude Haiku 4.5、Claude Sonnet 4.6、Claude Opus 4.7 区别以及各自的新特性 SchedulingConfigurer详解 踩坑60+次后,我终于搞懂 Claude Skill 怎么写才会真的触发 Everything Claude Code 详细使用文档 配置Jackson使用字段而不是getter/setter来序列化和反序列化 这个域名注册整整十年了,十年时间,真快啊 Claude Code全维度实战指南:从入门到精通,解锁AI编程新范式 Apollo配置中心中的protalDB的作用是什么 org.apache.ibatis.plugin.Interceptor类详细介绍及使用 岁末 Excel2016右键新建工作表,打开时提示“因为文件格式或文件扩展名无效。请确定文件未损坏,并且文件扩展名与文件的格式匹配。”的解决办法 wordpress增加说说功能 玩博客的人是不是越来越少了? Java 为什么有这么多 “O”? 别再背线程池的七大参数了,现在面试官都这么问 2024年11月1号 农历十月初一 我的第一个WordPress插件:Dylan Custom Plugin上线了 推荐一款比较养眼的Xshell配色方案 hnswlib installation failed 开工啦~ 阳了... MybatisCodeHelperPro激活 @Async注解的坑 新买的笔记本发货啦…… 这个中秋节感觉过的好累啊 IDEA下载源码报:Cannot connect to the Maven process. Try again later. RocketMQ的push消费方式实现详解 看病难~取药难~~ 笑死、腹肌……根本不可能有腹肌的~~ 居家办公了~ C# 11 的这个新特性,我愿称之最强! IntelliJ IDEA 2020.3.x永久白嫖(Windows/Mac) IDEA无限试用方法【2020.3最新亲测有效】 SpringBoot使用注解的方式构建Elasticsearch查询语句,实现多条件的复杂查询 UUID太长怎么办?快来试试NanoId 忽然发现,在校大学生可以免费领一年有道云笔记会员~ 醒醒~补个税了 来来来,用python画一个冰墩墩儿 SpringBoot整合GraphQL入门教程 办理居住证困难重重啊! 妈呀,昨天晚上睡觉做了一晚上的梦,可累死我了 感觉Typecho很简洁啊…… WordPress的自动更新好烦啊 BeanCopier工具类(性能优化工具类) 内存屏障浅析 居住证可算是申请通过了…… 试了下壁挂炉供暖 关于重阳节 居住证签注... Spring Boot 2.x使用PostgreSQL数据库 十一节后开工头一天,修了个耳机…… 玉楼春·尊前拟把归期说 nginx反向代理配置去除前缀 1931→2021! Navicat Premium数据库账号密码解密 百度的索引量数据现在有点儿太夸张了吧? 哇塞~这个小姐姐实在太惊艳了…… 关于8月29号下午博客故障的一些记录 如何形象的描述反应式编程中的背压(Backpressure)机制? 基于Java8的Either类 海琴烟~~~ JMX监控权限认证配置 IntelliJ IDEA 2019.3.3 永久激活 破解[Windows] SpringBoot整合Elasticsearch详细步骤以及代码示例(附源码) SpringBoot整合Elasticsearch游标查询(scroll) GitLab创建新项目,初次提交命令和流程 吐槽下Google浏览器~ 从零搭建Spring Cloud Gateway网关(一) 从零搭建Spring Cloud Gateway网关(二)—— 打印请求响应日志 博客有logo啦 你的答案 数据库事务的一点简单总结 Spring Boot发展史(Spring Boot介绍) 【漫画】戏说外行对程序员的误会有多深 【收藏】从面试官角度观察到的程序员技能瓶颈,同时给出突破瓶颈的建议 Git中.gitignore文件不起作用的解决办法 背影 - 朱自清 jmap命令(jdk1.8) 彻底搞懂mysql日志系统binlog,redolog,undolog 出院了~~~ linux中ftp查看不到文件列表的问题 PHP版本怎么更新啊…… 成语接龙 StarUML4.0破解文件 Java SPI详解 WordPress评论框增加自定义表情 因在公司上不正经网站,我没过试用期… 优化了MYSQL大量写入问题,老板奖励了1000块给我 不慌不忙的坚强(林徽因39段最美文字!) ConcurrentHashMap常用方法源码解析(jdk1.8) 我是如何失去团队掌控的? HBASE填坑日志 关闭apache httpclient4.5 DEBUG日志 使用OpenShift搭建k8s集群 SonarQube Scanner的配置与使用简介
使用itext和freemarker来根据Html模板生成PDF文件,加水印、印章
李锋镝 · 2022-03-14 · via 李锋镝的博客

功能:

实现根据freemarker模板生成对应的PDF文件;

可以指定文字、位置、页数生成指定的印章(图片),可以指定印章大小;

指定字体、字体大小、文字方向、颜色等生成文字水印

maven依赖:

        <dependency>
            <groupId>org.xhtmlrenderer</groupId>
            <artifactId>flying-saucer-pdf-itext5</artifactId>
            <version>9.1.18</version>
        </dependency>
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.27-incubating</version>
        </dependency>

将html模板和数据进行匹配,然后用流的方式生成PDF文件

生成PDF工具类源码:

package com.lifengdi.file.pdf;

import com.itextpdf.text.*;
import com.itextpdf.text.pdf.*;
import com.itextpdf.text.pdf.parser.PdfReaderContentParser;
import com.lifengdi.config.GlobalConfig;
import com.lifengdi.config.PDFFontConfig;
import com.lifengdi.config.SystemConfig;
import com.lifengdi.file.pdf.listener.MyTextLocationListener;
import com.lifengdi.model.pdf.Location;
import com.lifengdi.model.pdf.PDFStamperConfig;
import com.lifengdi.model.pdf.PDFTempFile;
import com.lifengdi.model.pdf.PDFWatermarkConfig;
import com.lifengdi.util.GeneratedKey;
import com.lifengdi.util.MyStringUtil;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateExceptionHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;

import javax.annotation.Resource;
import java.io.*;
import java.util.Objects;

/**
 * 生成PDF工具类
 *
 * @author 李锋镝
 * @date Create at 10:42 2019/5/9
 */
@Component
@Slf4j
public class HtmlToPDF {

    @Resource
    private SystemConfig systemConfig;

    @Resource
    private GeneratedKey generatedKey;

    /**
     * 模板和数据匹配
     *
     * @param data        数据
     * @param pdfTempFile pdfTempFile
     * @return html
     */
    public String matchDataToHtml(Object data, PDFTempFile pdfTempFile) {
        StringWriter writer = new StringWriter();
        String html;
        try {
            // FreeMarker配置
            Configuration config = new Configuration(Configuration.VERSION_2_3_25);
            config.setDefaultEncoding(GlobalConfig.DEFAULT_ENCODING);
            // 注意这里是模板所在文件夹,不是模版文件
            String parentPath, tempFileName = pdfTempFile.getTemplateFileName();
            if (MyStringUtil.isHttpUrl(pdfTempFile.getTemplateFileParentPath())) {
                parentPath = systemConfig.getLocalTempPath();
            } else {
                parentPath = pdfTempFile.getTemplateFileParentPath();
                // 将项目中的文件copy到服务器本地
                if (!parentPath.endsWith(File.separator))
                    parentPath = parentPath + File.separator;
                String localTempPath = systemConfig.getLocalTempPath();
                String target = (localTempPath.endsWith(File.separator) ? localTempPath : localTempPath + File.separator) + tempFileName;
                InputStream tempFileInputStream = new ClassPathResource(parentPath + tempFileName).getInputStream();
                FileUtils.copyInputStreamToFile(tempFileInputStream, new File(target));
            }
            log.info("模板和数据匹配,模板文件parentPath:{}", parentPath);

            config.setDirectoryForTemplateLoading(new File(systemConfig.getLocalTempPath()));
            config.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
            config.setLogTemplateExceptions(false);
            // 根据模板名称 获取对应模板
            Template template = config.getTemplate(tempFileName);
            // 模板和数据的匹配
            template.process(data, writer);
            writer.flush();
            html = writer.toString();
            return html;
        } catch (Exception e) {
            log.error("PDF模板和数据匹配异常", e);
        } finally {
            try {
                writer.close();
            } catch (IOException e) {
                log.error("StringWriter close exception.", e);
            }
        }
        return null;
    }

    /**
     * 生成PDF
     *
     * @param html           html字符串
     * @param targetFileName 目标文件名
     * @return 生成文件的名称
     * @throws Exception e
     */
    public String createPDF(String html, String targetFileName) throws Exception {
        if (StringUtils.isBlank(html)) {
            return null;
        }
        targetFileName = getFileName(targetFileName);
        log.info("生成PDF,targetFileName:{}", targetFileName);
        String targetFilePath = getTargetFileTempPath(targetFileName);
        FileOutputStream outFile = new FileOutputStream(targetFilePath);
        ITextRenderer renderer = new ITextRenderer();
        renderer.setDocumentFromString(html);
        // 解决中文支持问题
        log.info("加载字体");
        ITextFontResolver fontResolver = renderer.getFontResolver();
        fontResolver.addFont(SystemConfig.SourceHanSansCN_Regular_TTF, "SourceHanSansCN", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED, null);
        fontResolver.addFont(SystemConfig.FONT_PATH_SONG, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
        renderer.layout();
        renderer.createPDF(outFile);
        log.info("生成PDF,targetFileName:{},生成成功", targetFileName);
        return targetFileName;
    }

    /**
     * 渲染文件
     *
     * @param filePath           PDF文件路径
     * @param outFilePath        PDF文件路径
     * @param pdfStamperConfig   pdfStamperConfig
     * @param pdfWatermarkConfig pdfWatermarkConfig
     */
    public void renderLayer(String filePath, String outFilePath, PDFStamperConfig pdfStamperConfig, PDFWatermarkConfig pdfWatermarkConfig) {

        log.info("渲染文件,filePath:{},outFilePath:{}", filePath, outFilePath);
        InputStream inputStream = null;
        try {
            filePath = getTargetFileTempPath(filePath);
            inputStream = new FileInputStream(filePath);
            PdfReader reader = new PdfReader(inputStream);
            PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(getTargetFileTempPath(outFilePath)));
            if (Objects.nonNull(pdfStamperConfig) && pdfStamperConfig.getGenerate()) {
                // 印章
                log.info("添加印章,pdfStamperConfig:{}", pdfStamperConfig);
                stamper(pdfStamperConfig, reader, stamper);
            }
            if (Objects.nonNull(pdfWatermarkConfig) && pdfWatermarkConfig.getGenerate()) {
                // 水印
                log.info("添加水印,pdfWatermarkConfig:{}", pdfWatermarkConfig);
                watermark(reader, stamper, pdfWatermarkConfig);
            }
            stamper.close();
            reader.close();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != inputStream) {
                    inputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 印章
     *
     * @param pdfStamperConfig pdfStamperConfig
     * @param reader           reader
     * @param stamper          stamper
     * @throws IOException       IOException
     * @throws DocumentException DocumentException
     */
    private void stamper(PDFStamperConfig pdfStamperConfig, PdfReader reader, PdfStamper stamper) throws IOException, DocumentException {
        Image image;
        if (!MyStringUtil.isHttpUrl(pdfStamperConfig.getStamperUrl())) {
            // 将项目中的文件copy到服务器本地
            String imagePath = pdfStamperConfig.getStamperUrl();
            String imageName = imagePath.substring(imagePath.indexOf("/") + 1, imagePath.length());
            String localTempPath = systemConfig.getLocalTempPath();
            String target = (localTempPath.endsWith(File.separator) ? localTempPath : localTempPath + File.separator) + imageName;
            InputStream tempFileInputStream = new ClassPathResource(pdfStamperConfig.getStamperUrl()).getInputStream();
            FileUtils.copyInputStreamToFile(tempFileInputStream, new File(target));
            image = Image.getInstance(target);
        } else {
            image = Image.getInstance(pdfStamperConfig.getStamperUrl());
        }

        PdfReaderContentParser parser = new PdfReaderContentParser(reader);
        Document document = new Document();
        log.info("A4纸width:{},height:{}", document.getPageSize().getWidth(), document.getPageSize().getHeight());
//            // 取所在页和坐标,左下角为起点
//            float x = document.getPageSize().getWidth() - 240;
//            float y = document.getPageSize().getHeight() - 680;
        int pageSize = reader.getNumberOfPages();
        Location location = pdfStamperConfig.getLocation();
        if (Objects.nonNull(location)) {
            Integer page = location.getPage();// -1:全部,0:最后一页,1:首页
            if (Objects.nonNull(page)) {
                switch (page) {
                    case -1:
                        for (int pageNumber = 1; pageNumber <= pageSize; pageNumber++) {
                            insertImage(pdfStamperConfig, stamper, image, parser, pageSize);
                        }
                        break;
                    case 0:
                        insertImage(pdfStamperConfig, stamper, image, parser, pageSize);
                        break;
                    default:
                        if (page > 0) {
                            insertImage(pdfStamperConfig, stamper, image, parser, page);
                        }
                        break;
                }
            }

        }
    }

    /**
     * 向指定位置插入图片
     *
     * @param pdfStamperConfig pdfStamperConfig
     * @param stamper          stamper
     * @param image            image
     * @param parser           parser
     * @param pageNumber       pageNumber
     * @throws IOException       IOException
     * @throws DocumentException DocumentException
     */
    private void insertImage(PDFStamperConfig pdfStamperConfig, PdfStamper stamper, Image image, PdfReaderContentParser parser, int pageNumber)
            throws IOException, DocumentException {
        float x, y;
        Location xy = new Location();
        if (StringUtils.isNotBlank(pdfStamperConfig.getLocation().getWord())) {
            parser.processContent(pageNumber, new MyTextLocationListener(pdfStamperConfig, xy));
        } else {
            xy = pdfStamperConfig.getLocation();
        }
        if (Objects.isNull(xy)) {
            return;
        }

        if (Objects.isNull(xy.getX()) || Objects.isNull(xy.getY())) {
            return;
        }
        x = xy.getX();
        y = xy.getY();
        if (x < 0 || y < 0) {
            return;
        }
        // 读图片
        // 获取操作的页面
        PdfContentByte under = stamper.getOverContent(pageNumber);
//         根据域的大小缩放图片
//        image.scaleToFit(Objects.isNull(pdfStamperConfig.getFitWidth()) ? GlobalConfig.PDF_STAMPER_DEFAULT_FIT_WIDTH : pdfStamperConfig.getFitWidth(),
//                Objects.isNull(pdfStamperConfig.getFitHeight()) ? GlobalConfig.PDF_STAMPER_DEFAULT_FIT_HEIGHT : pdfStamperConfig.getFitHeight());
        image.scaleAbsolute(Objects.isNull(pdfStamperConfig.getFitWidth()) ? GlobalConfig.PDF_STAMPER_DEFAULT_FIT_WIDTH : pdfStamperConfig.getFitWidth(),
                Objects.isNull(pdfStamperConfig.getFitHeight()) ? GlobalConfig.PDF_STAMPER_DEFAULT_FIT_HEIGHT : pdfStamperConfig.getFitHeight());
        // 添加图片
        image.setAbsolutePosition(x, y);
        under.addImage(image);
    }

    /**
     * 水印
     *
     * @param reader             reader
     * @param stamper            stamper
     * @param pdfWatermarkConfig pdfWatermarkConfig
     */
    private void watermark(PdfReader reader, PdfStamper stamper, PDFWatermarkConfig pdfWatermarkConfig) {
        if (Objects.isNull(pdfWatermarkConfig) || !pdfWatermarkConfig.getGenerate()) {
            return;
        }
        PdfContentByte under;
        // 字体
        BaseFont font = PDFFontConfig.FONT_MAP.get(PDFFontConfig.SIM_SUN);
        String fontFamily = pdfWatermarkConfig.getFontFamily();
        if (StringUtils.isNotBlank(fontFamily) && PDFFontConfig.FONT_MAP.containsKey(fontFamily)) {
            font = PDFFontConfig.FONT_MAP.get(fontFamily);
        }
        // 原pdf文件的总页数
        int pageSize = reader.getNumberOfPages();
        PdfGState gs = new PdfGState();
        // 设置填充字体不透明度为0.1f
        gs.setFillOpacity(0.1f);
        Document document = new Document();
        float documentWidth = document.getPageSize().getWidth(), documentHeight = document.getPageSize().getHeight();
        Location location = pdfWatermarkConfig.getLocation();
        if (Objects.isNull(location)) {
            location = new Location();
        }
        final float xStart = 0, yStart = 0,
                xInterval = Objects.nonNull(location.getXInterval()) ? location.getXInterval() : GlobalConfig.DEFAULT_X_INTERVAL,
                yInterval = Objects.nonNull(location.getYInterval()) ? location.getYInterval() : GlobalConfig.DEFAULT_Y_INTERVAL,
                rotation = 45,
                fontSize = Objects.isNull(pdfWatermarkConfig.getFontSize()) ? GlobalConfig.DEFAULT_FONT_SIZE : pdfWatermarkConfig.getFontSize();

        String watermarkWord = pdfWatermarkConfig.getWatermarkWord();
        int red = -1, green = -1, blue = -1;
        String[] colorArray = pdfWatermarkConfig.getWatermarkColor().split(",");
        if (colorArray.length >= 3) {
            red = Integer.parseInt(colorArray[0]);
            green = Integer.parseInt(colorArray[1]);
            blue = Integer.parseInt(colorArray[2]);
        }
        for (int i = 1; i <= pageSize; i++) {
            // 水印在之前文本下
            if (Objects.nonNull(location.getOverContent()) && location.getOverContent()) {
                under = stamper.getOverContent(i);
            } else {
                under = stamper.getUnderContent(i);
            }
            under.beginText();
            // 文字水印 颜色
            if (red >= 0) {
                under.setColorFill(new BaseColor(red, green, blue));
            } else {
                under.setColorFill(BaseColor.GRAY);
            }
            // 文字水印 字体及字号
            under.setFontAndSize(font, fontSize);
            under.setGState(gs);
            // 文字水印 起始位置
            under.setTextMatrix(xStart, yStart);

            if (StringUtils.isNotBlank(watermarkWord)) {
                for (float x = xStart; x <= documentWidth + xInterval; x += xInterval) {
                    for (float y = yStart; y <= documentHeight + yInterval; y += yInterval) {
                        under.showTextAligned(Element.ALIGN_CENTER, watermarkWord, x, y, rotation);
                    }
                }
            }
            under.endText();
        }
    }

    /**
     * 获取生成的PDF文件本地临时路径
     *
     * @param targetFileName 目标文件名
     * @return 本地临时路径
     */
    public String getTargetFileTempPath(String targetFileName) {

        String localTempPath = systemConfig.getLocalTempPath();
        if (!localTempPath.endsWith(File.separator)) {
            localTempPath = localTempPath + File.separator;
        }
        return localTempPath + targetFileName;
    }

    private String getFileName(String targetFileName) {
        if (StringUtils.isBlank(targetFileName)) {
            targetFileName = generatedKey.generatorKey();
        }
        if (!StringUtils.endsWithIgnoreCase(targetFileName, GlobalConfig.PDF_SUFFIX)) {
            targetFileName = targetFileName + GlobalConfig.PDF_SUFFIX;
        }
        return targetFileName;
    }
}

获取指定文字坐标,用来在指定文字上生成印章,主要实现了RenderListener来获取指定文字的坐标。

源码:

package com.lifengdi.file.pdf.listener;

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 com.lifengdi.config.GlobalConfig;
import com.lifengdi.model.pdf.Location;
import com.lifengdi.model.pdf.PDFStamperConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.util.Objects;

/**
 * @author 李锋镝
 * @date Create at 15:32 2019/5/9
 */
@Slf4j
public class MyTextLocationListener implements RenderListener {

    private String text;

    private PDFStamperConfig pdfStamperConfig;

    private Location location;

    public MyTextLocationListener(PDFStamperConfig pdfStamperConfig, Location location) {
        if (StringUtils.isNotBlank(pdfStamperConfig.getLocation().getWord())) {
            this.text = pdfStamperConfig.getLocation().getWord();
        }

        this.pdfStamperConfig = pdfStamperConfig;
        this.location = location;
    }

    @Override
    public void beginTextBlock() {
    }

    @Override
    public void renderText(TextRenderInfo renderInfo) {
        String renderInfoText = renderInfo.getText();
        if (!StringUtils.isEmpty(renderInfoText) && renderInfoText.contains(text)) {
            Rectangle2D.Float base = renderInfo.getBaseline().getBoundingRectange();
            float leftX = (float) base.getMinX();
            float leftY = (float) base.getMinY() - 1;
            float rightX = (float) base.getMaxX();
            float rightY = (float) base.getMaxY() + 1;
            Rectangle2D.Float rect = new Rectangle2D.Float(leftX, leftY, rightX - leftX, rightY - leftY);

            // 当前行长度
            int length = renderInfoText.length();
            // 单个字符的长度
            float wordWidth = rect.width / length;
            // 指定字符串首次出现的索引
            int i = renderInfoText.indexOf(text);

            Float fitWidth = Objects.isNull(pdfStamperConfig.getFitWidth()) ? GlobalConfig.PDF_STAMPER_DEFAULT_FIT_WIDTH : pdfStamperConfig.getFitWidth();
            Float fitHeight = Objects.isNull(pdfStamperConfig.getFitHeight()) ? GlobalConfig.PDF_STAMPER_DEFAULT_FIT_HEIGHT : pdfStamperConfig.getFitHeight();
            float fitWidthRadius = 0f, fitHeightRadius = 0f;
            if (fitWidth > 0) {
                fitWidthRadius = fitWidth / 2;
            }
            if (fitHeight > 0) {
                fitHeightRadius = fitHeight / 2;
            }
            // 偏移量
            Float xOffset = Objects.isNull(pdfStamperConfig.getXOffset()) ? 0F : pdfStamperConfig.getXOffset();
            Float yOffset = Objects.isNull(pdfStamperConfig.getYOffset()) ? 0F : pdfStamperConfig.getYOffset();
            // 设置印章的XY坐标
            float x, y;
            if (rect.x < 60) {
                x = wordWidth * i + fitHeightRadius + xOffset;
            } else {
                x = rect.x + xOffset;
            }
            y = rect.y - fitWidthRadius + yOffset;
            location.setY(y > 0 ? y : 0);
            location.setX(x > 0 ? x : 0);
            log.info("text:{}, location:{}", text, location);
        }
    }

    @Override
    public void endTextBlock() {
    }

    @Override
    public void renderImage(ImageRenderInfo renderInfo) {
    }
}

其他配置类:

package com.lifengdi.model.pdf;

import lombok.Data;

/**
 * PDF模板配置
 * @author 李锋镝
 * @date Create at 11:10 2019/5/9
 */
@Data
public class PDFTempFile {
    /**
     * PDF模板文件类型
     */
    private String pdfType;

    /**
     * 模板文件名称
     */
    private String templateFileName;

    /**
     * 模板文件所在父级路径
     */
    private String templateFileParentPath;

    /**
     * 模板文件本地地址
     */
    private String templateFileLocalPath;
}
package com.lifengdi.config;

import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

/**
 * @author 李锋镝
 * @date Create at 10:51 2019/5/9
 */
@Getter
@Configuration
public class SystemConfig {
    /**
     * 文件本地缓存目录
     */
    @Value("${file.localTempPath}")
    private String localTempPath;

    /**
     * 项目中自带字体的目录
     */
    public static final String FONT_PATH_SONG = "font/simsun.ttf";

    public static final String SourceHanSansCN_Regular_TTF = "font/SourceHanSansCN-Regular.ttf";

    /**
     * 项目中的缓存目录
     */
    @Value("${file.appTempFolder:file/}")
    private String appTemplateFolder;
}
package com.lifengdi.model.pdf;

import lombok.Data;

/**
 * PDF印章配置
 * @author 李锋镝
 * @date Create at 11:16 2019/5/9
 */
@Data
public class PDFStamperConfig {

    /**
     * 是否生成印章
     */
    private Boolean generate = false;

    /**
     * 印章文件路径(PNG格式)
     */
    private String stamperUrl;

    /**
     * 生成印章的位置
     */
    private Location location;

    /**
     * 印章宽度
     */
    private Float fitWidth;

    /**
     * 印章高度
     */
    private Float fitHeight;

    /**
     * X偏移量
     */
    private Float xOffset;
    /**
     * Y偏移量
     */
    private Float yOffset;
}
package com.lifengdi.model.pdf;

import lombok.Data;

/**
 * PDF文件水印配置
 * @author 李锋镝
 * @date Create at 11:17 2019/5/9
 */
@Data
public class PDFWatermarkConfig {

    /**
     * 是否生成水印
     */
    private Boolean generate;

    /**
     * 水印文字
     */
    private String watermarkWord;

    /**
     * 水印文字颜色 red,green,blue
     */
    private String watermarkColor = "128,128,128";

    /**
     * 水印透明度
     */
    private Float fillOpacity = 0.1F;

    /**
     * 水印文字大小
     */
    private Integer fontSize = 38;

    /**
     * 水印文字字体
     */
    private String fontFamily;

    /**
     * 生成水印的位置,不指定则默认整页
     */
    private Location location;

}
package com.lifengdi.model.pdf;

import lombok.Data;

/**
 * 页面位置坐标
 * @author 李锋镝
 * @date Create at 11:35 2019/5/9
 */
@Data
public class Location {

    /**
     * X坐标
     */
    private Float x;

    /**
     * Y坐标
     */
    private Float y;

    /**
     * 横向间隔
     */
    private Float xInterval;

    /**
     * 纵向间隔
     */
    private Float yInterval;

    /**
     * 指定页 -1:全部,0:最后一页,1:首页
     */
    private Integer page;

    /**
     * 在指定文字上盖章
     */
    private String word;

    /**
     * 水印是否在内容上边
     */
    private Boolean overContent;
}
package com.lifengdi.model.pdf;

import com.lifengdi.model.IType;
import lombok.Data;

/**
 * @author 李锋镝
 * @date Create at 14:07 2019/5/9
 */
@Data
public class PDFType implements IType {

    private PDFTempFile pdfTempFile;

    private PDFStamperConfig pdfStamperConfig;

    private PDFWatermarkConfig pdfWatermarkConfig;
}
package com.lifengdi.config;

import com.lifengdi.model.pdf.PDFType;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

/**
 * PDF配置
 * @author 李锋镝
 * @date Create at 11:10 2019/5/9
 */
@Data
@Component
@ConfigurationProperties("pdf")
public class PDFConfig {

    private Map<String, PDFType> pdfType = new HashMap<>();
}
package com.lifengdi.config;

/**
 * @author 李锋镝
 * @date Create at 10:54 2019/5/9
 */
public class GlobalConfig {
    /**
     * 默认字符集
     */
    public static final String DEFAULT_ENCODING = "UTF-8";

    /**
     * PDF文件后缀
     */
    public static final String PDF_SUFFIX = ".pdf";

    /**
     * PDF印章默认宽度
     */
    public static final float PDF_STAMPER_DEFAULT_FIT_WIDTH = 100f;

    /**
     * PDF印章默认高度
     */
    public static final float PDF_STAMPER_DEFAULT_FIT_HEIGHT = 100f;

    /**
     * 默认字体大小
     */
    public static final float DEFAULT_FONT_SIZE = 38;
    /**
     * 默认水印X方向间隔
     */
    public static final float DEFAULT_X_INTERVAL = 38;
    /**
     * 默认水印Y方向间隔
     */
    public static final float DEFAULT_Y_INTERVAL = 38;
}

在application.yml配置如下:

# 生成PDF文件配置
pdf:
  pdfType:
    test:
      pdfTempFile:
        pdfType: test
        templateFileName: test.ftl
        templateFileParentPath: file/
      pdfStamperConfig:
        generate: true
# 印章所在路径
        stamperUrl: image/666.png
        xOffset: 0
        yOffset: 45
        fitWidth: 120
        fitHeight: 120
        location:
          x: -1
          y: -1
          page: 1
          word: 检测日期
      pdfWatermarkConfig:
        generate: true
        watermarkWord: 水印
        watermarkColor:
        fillOpacity:
        fontSize:
        fontFamily: SourceHanSansCN
        location:
          xInterval: 100
          yInterval: 120
          page:
          overContent: true

生成PDF效果截图:

除非注明,否则均为李锋镝的博客原创文章,转载必须以链接形式标明本文链接

本文链接:https://www.lifengdi.com/article/861