【Java】制作pdf模板使用后端程序填充字段生成pdf或者图片

1.安装软件: Adobe Acrobat 9 Pro

自行下载安装;

2.制作模板pdf文件

打开pdf文件,表单-添加或编辑域

添加文本域,调整大小,可以编辑域的名字,默认fill_1这种名字。域鼠标右键-属性,可以调整字体大小等样式,编辑好还可以锁定;

编辑好保存,这个pdf文件就可以当模板使用了;

3.maven依赖

xml 复制代码
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itextpdf</artifactId>
    <version>5.5.13.3</version>
</dependency>
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itext-asian</artifactId>
    <version>5.2.0</version>
</dependency>
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.12</version>
</dependency>

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

4. 后端-字体引入

防止中文乱码,需要在网上下载字体ttf文件,这个是宋体的字体文件STSong-Light.ttf

字体文件放在配置文件的文件夹里,在程序里引用,这个工具类只加载字体用,别的方法没用到;

java 复制代码
/**
 * pdf工具类
 *
 * @author xwt
 * @date 2022/12/7 11:00
 */
@Slf4j
public class PdfUtils {
 
    public static final Base64.Decoder DECODER = Base64.getDecoder();
 
    private static Map<String, FontInfo> fontInfoMap = null;
 
    private static final String TTC_TYPE = "TTC";
 
    private static final String OTC_TYPE = "OTC";
 
   
    /***
     * pdfBase64转图片,返回图片对象
     *
     * @param pdfBase64  pdf类型base64
     * @return BufferedImage
     */
    public static List<BufferedImage> pdfToImage(String pdfBase64){
        List<BufferedImage> list = new ArrayList<>();
        if (CharSequenceUtil.isEmpty(pdfBase64)){
            return list;
        }
        byte[] decode = DECODER.decode(pdfBase64.getBytes(StandardCharsets.UTF_8));
        try(
                ByteArrayInputStream stream = new ByteArrayInputStream(decode);
                // 加载解析PDF文件
                PDDocument doc = PDDocument.load(stream);
        ) {
            // 业务处理
            PDFRenderer pdfRenderer = new PDFRenderer(doc);
            PDPageTree pages = doc.getPages();
            int pageCount = pages.getCount();
            for (int i = 0; i < pageCount; i++) {
                list.add(pdfRenderer.renderImageWithDPI(i, 200));
            }
        } catch (Exception e) {
            log.error("pdfBase64转图片异常", e);
        }
        return list;
    }
 
    /***
     * 设置字体
     * @param fontFormat 字体文件类型
     * @param fontName 字体名称
     * @param file 字体文件
     */
    public static void setFonts(FontFormat fontFormat, String fontName, File file){
        FontInfo fontInfo = getFontInfoMap().get(fontName);
        if(fontInfo != null){
            log.warn("pdfFont添加字体已经存在");
            return;
        }
        // 后缀
        log.info("pdfFont 添加字体{}", file.getName());
        String suffix = FileUtil.getSuffix(file);
        if(TTC_TYPE.equalsIgnoreCase(suffix) || OTC_TYPE.equalsIgnoreCase(suffix)){
            try(TrueTypeCollection trueTypeCollection = new TrueTypeCollection(file);) {
                trueTypeCollection.processAllFonts(trueTypeFont -> addTrueTypeFontImpl(trueTypeFont, file));
            } catch (IOException e) {
                log.warn("无法加载字体:{}", file.getAbsolutePath());
            }
            return;
        }
        getFontInfoMap().put(fontName, new MyFontInfo(file, fontFormat, fontName));
    }
 
    /***
     * 设置字体,扫描目录下所有字体
     * @param dir 目录
     */
    public static void setFonts(File dir){
        if (dir == null || !dir.exists() || !dir.isDirectory()) {
            log.warn("pdfFont添加字体为空");
            return;
        }
        for (File file : dir.listFiles()) {
            if (file.isDirectory()) {
                setFonts(file);
            } else {
                String fileName = file.getName();
                // 后缀
                String suffix = FileUtil.getSuffix(fileName);
                // 文件名称,不带后缀
                String prefix = FileUtil.getPrefix(fileName);
                if(FontFormat.OTF.name().equalsIgnoreCase(suffix)){
                    setFonts(FontFormat.OTF, prefix, file);
                }else if(FontFormat.TTF.name().equalsIgnoreCase(suffix) || TTC_TYPE.equalsIgnoreCase(suffix) || OTC_TYPE.equalsIgnoreCase(suffix)) {
                    setFonts(FontFormat.TTF, prefix, file);
                }else if(FontFormat.PFB.name().equalsIgnoreCase(suffix)) {
                    setFonts(FontFormat.PFB, prefix, file);
                }else{
                    log.warn("无法识别字体:{}", file.getAbsolutePath());
                }
            }
        }
    }
 
    /***
     * 获取系统字体缓存
     * @return 字体缓存
     */
    private static Map<String, FontInfo> getFontInfoMap(){
        if(fontInfoMap != null){
            return fontInfoMap;
        }
        FontMapper instance = FontMappers.instance();
        // 初始化加载系统字体
        instance.getCIDFont("STSong-Light", null, null);
        Class<? extends FontMapper> aClass = instance.getClass();
        try {
            Field field = aClass.getDeclaredField("fontInfoByName");
            if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || Modifier.isFinal(field.getModifiers())) && !field.isAccessible()) {
                field.setAccessible(true);
            }
            // 获取系统字体缓存
            fontInfoMap = (Map<String, FontInfo>) field.get(instance);
        }catch (Exception e){
            log.error("PdfUtils初始化异常", e);
            throw new RuntimeException("PdfUtils初始化异常");
        }
        return fontInfoMap;
    }
 
    /***
     * 将OTF或TTF字体添加到文件缓存
     * @param ttf 字体
     * @param file 字体文件
     * @throws IOException 异常
     */
    private static void addTrueTypeFontImpl(TrueTypeFont ttf, File file) throws IOException {
        if(ttf.getName() != null){
            if (ttf.getHeader() == null) {
                getFontInfoMap().put(ttf.getName(), new MyFontInfo(file, FontFormat.TTF, ttf.getName()));
                return;
            }
            int macStyle = ttf.getHeader().getMacStyle();
 
            int sFamilyClass = -1;
            int usWeightClass = -1;
            int ulCodePageRange1 = 0;
            int ulCodePageRange2 = 0;
            // Apple's AAT fonts don't have an OS/2 table
            if (ttf.getOS2Windows() != null)
            {
                sFamilyClass = ttf.getOS2Windows().getFamilyClass();
                usWeightClass = ttf.getOS2Windows().getWeightClass();
                ulCodePageRange1 = (int)ttf.getOS2Windows().getCodePageRange1();
                ulCodePageRange2 = (int)ttf.getOS2Windows().getCodePageRange2();
            }
 
            CIDSystemInfo ros = null;
            String registry = null;
            String ordering = null;
            int supplement = 0;
            FontFormat fontFormat;
            if (ttf instanceof OpenTypeFont && ((OpenTypeFont)ttf).isPostScript()) {
                CFFFont cff = ((OpenTypeFont)ttf).getCFF().getFont();
                fontFormat = FontFormat.OTF;
                if (cff instanceof CFFCIDFont) {
                    CFFCIDFont cidFont = (CFFCIDFont)cff;
                    registry = cidFont.getRegistry();
                    ordering = cidFont.getOrdering();
                    supplement = cidFont.getSupplement();
                }
            } else {
                fontFormat = FontFormat.TTF;
                if (ttf.getTableMap().containsKey("gcid")) {
                    // Apple's AAT fonts have a "gcid" table with CID info
                    byte[] bytes = ttf.getTableBytes(ttf.getTableMap().get("gcid"));
                    String reg = new String(bytes, 10, 64, Charsets.US_ASCII);
                    registry = reg.substring(0, reg.indexOf('\0'));
                    String ord = new String(bytes, 76, 64, Charsets.US_ASCII);
                    ordering = ord.substring(0, ord.indexOf('\0'));
                    supplement = bytes[140] << 8 & bytes[141];
                }
            }
            try {
                Constructor<CIDSystemInfo> constructor = CIDSystemInfo.class.getDeclaredConstructor(String.class, String.class, int.class);
                if ((!Modifier.isPublic(constructor.getModifiers()) || !Modifier.isPublic(constructor.getDeclaringClass().getModifiers()) || Modifier.isFinal(constructor.getModifiers())) && !constructor.isAccessible()) {
                    constructor.setAccessible(true);
                }
                ros = constructor.newInstance(registry, ordering, supplement);
            }catch (Exception e){
                e.printStackTrace();
            }
            getFontInfoMap().put(ttf.getName(), new MyFontInfo(file, fontFormat, ttf.getName(), ros, usWeightClass, sFamilyClass, ulCodePageRange1, ulCodePageRange2,
                    macStyle));
        }
    }
}

在程序启动类里面调用上面的代码(也可以在使用的地方调用,就是每次都要调用一下感觉不太好),就引入字体了

java 复制代码
    public static void main(String[] args) {
        SpringApplication.run(ReceiptApplication.class, args);
        PdfUtil.setFonts(FontFormat.TTF, "STSong-Light", new File("/app/conf/STSong-Light.ttf"));
    }

5. 后端-PdfUtil代码

项目使用的pdf只有一页,我的代码是可行的;实现了功能:

1.pdf模板填充生成pdf文件(step1.pdf);

outputPdf(Map<String, Object> o, String templatePath, String outPutPath)

2.step1.pdf上加一张图片,例如加签名,生成step2.pdf;

addImgOnPdf(String imgPath, String oldPath, String newPath)

3.将step2.pdf转成jpg图片文件;

pdf2png(String fileAddress, String filename, int indexOfStart, int indexOfEnd, String type)

工具类使用Map传参,接口参数放到map里,key对应域的名字

java 复制代码
        map.put("fill_1", params.getAccountTitle());//户名
        map.put("fill_2", d_cardNo);//账号

工具类

java 复制代码
@Slf4j
public class PdfUtil {
    /**
     * @param o            写入的数据
     * @param templatePath pdf模板路径
     */
    // 利用模板生成pdf
    public static String outputPdf(Map<String, Object> o, String templatePath, String outPutPath) throws IOException {
        PdfReader reader;
        ByteArrayOutputStream bos = null;
        PdfStamper stamper;
        FileOutputStream out = null;

        try {
            File file = new File(outPutPath);
            if (!file.getParentFile().exists()) {
                file.getParentFile().mkdirs();
            }
            out = new FileOutputStream(file);
            reader = new PdfReader(templatePath);// 读取pdf模板
            bos = new ByteArrayOutputStream();
            stamper = new PdfStamper(reader, bos);

            AcroFields form = stamper.getAcroFields();
            // 设置字体,否则可能会不显示中文
            //BaseFont bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED);
            BaseFont bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED);
            form.addSubstitutionFont(bf);
            // 设置文本
            form.setField("processingTime", "20");
            java.util.Iterator<String> it = form.getFields().keySet().iterator();
            while (it.hasNext()) {
                String name = it.next().toString();
                //log.info(name);
                String value = o.get(name) != null ? o.get(name).toString() : null;
                if (value != null) {
                    // 设置支持中文
                    form.setFieldProperty(name, "textfont", bf, null);
                    form.setField(name, value);
                }
            }
            stamper.setFormFlattening(true);// 如果为false那么生成的PDF文件还能编辑,一定要设为true
            stamper.close();
            Document doc = new Document();
            PdfCopy copy = new PdfCopy(doc, out);
            doc.open();
            byte[] bytes = bos.toByteArray();
            //log.info("[pdf bytes size] " + bytes.length);
            PdfImportedPage importPage = copy.getImportedPage(new PdfReader(bytes), 1);
            copy.addPage(importPage);
            doc.close();
        } catch (IOException e) {
            log.error("生成pdf IOException", e);
            throw e;
        } catch (DocumentException e) {
            log.error("生成pdf DocumentException", e);
        } finally {
            if (out != null) {
                out.close();
            }
            if (bos != null) {
                bos.close();
            }
        }
        return outPutPath;
    }


    public static void addImgOnPdf(String imgPath, String oldPath, String newPath) {
        try {
            InputStream inputStream = Files.newInputStream(Paths.get(oldPath));
//            System.out.println(inputStream == null? "in null":inputStream.available());
            log.info(inputStream + ":" + inputStream.available());
            FileOutputStream out = new FileOutputStream(newPath);
            PdfReader reader = new PdfReader(inputStream);
            //pdf页数
            int pdfPages = reader.getNumberOfPages();
            PdfStamper stamper = new PdfStamper(reader, out);
            //图片
            BufferedImage bufferedImage = ImageIO.read(Files.newInputStream(Paths.get(imgPath)));
//            System.out.println(bufferedImage == null? "img null":bufferedImage.getMinX());
            log.info(bufferedImage + ":" + bufferedImage.getMinX());
            //x轴坐标
            int x = 450;
            //y轴坐标
            int y = 440;
            //图片放置的页码
            for (int i = pdfPages; i <= pdfPages; i++) {
                //图片处理
                Image img = Image.getInstance(ImageUtil.imageToBytes(bufferedImage, "png"));
                //设置图片大小
                img.scaleAbsolute(40, 22);
                img.setTransparency(new int[0]);
                //设置图片位置
                img.setAbsolutePosition(x, y);
                stamper.getOverContent(i).addImage(img);
            }
            //关闭资源
            stamper.close();
            out.close();
            reader.close();
        } catch (DocumentException e) {
            log.error("DocumentException :{0}", e);
        } catch (IOException e) {
            log.error("IOException :{0}", e);
        }

    }

    /**
     * 转换全部的pdf
     *
     * @param fileAddress 文件地址
     */
    public static void pdf2png(String fileAddress, String imgAddress) {
        String type = "png";
        // 将pdf装图片 并且自定义图片得格式大小
        File file = new File(fileAddress);
        try {
            PDDocument doc = PDDocument.load(file);
            PDFRenderer renderer = new PDFRenderer(doc);
            int pageCount = doc.getNumberOfPages();
            for (int i = 0; i < pageCount; i++) {
                BufferedImage image = renderer.renderImageWithDPI(i, 144); // Windows native DPI
                // BufferedImage srcImage = resize(image, 240, 240);//产生缩略图
                ImageIO.write(image, type, new File(imgAddress));
            }
        } catch (IOException e) {
            log.error("IOException : {0}", e);
        }
    }


    /**
     * 自由确定起始页和终止页
     *
     * @param fileAddress  文件地址
     * @param filename     pdf文件名
     * @param indexOfStart 开始页  开始转换的页码,从0开始
     * @param indexOfEnd   结束页  停止转换的页码,-1为全部
     * @param type         图片类型
     */
    public static void pdf2png(String fileAddress, String filename, int indexOfStart, int indexOfEnd, String type) {
        // 将pdf装图片 并且自定义图片得格式大小
        File file = new File(fileAddress + "\\" + filename + ".pdf");
        try {
            PDDocument doc = PDDocument.load(file);
            PDFRenderer renderer = new PDFRenderer(doc);
            int pageCount = doc.getNumberOfPages();
            for (int i = indexOfStart; i < indexOfEnd; i++) {
                BufferedImage image = renderer.renderImageWithDPI(i, 144); // Windows native DPI
                // BufferedImage srcImage = resize(image, 240, 240);//产生缩略图
                ImageIO.write(image, type, new File(fileAddress + "\\" + filename + "_" + (i + 1) + "." + type));
            }
        } catch (IOException e) {
            log.error("IOException : {0}", e);
        }
    }

}
相关推荐
HanhahnaH3 分钟前
Spring集合注入Bean
java·spring
未定义.2219 分钟前
电子削铅笔刀顺序图详解:从UML设计到PlantUML实现
java·软件工程·uml
小墨宝17 分钟前
js 生成pdf 并上传文件
前端·javascript·pdf
雾月5526 分钟前
LeetCode 1292 元素和小于等于阈值的正方形的最大边长
java·数据结构·算法·leetcode·职场和发展
24k小善1 小时前
Flink TaskManager详解
java·大数据·flink·云计算
想不明白的过度思考者1 小时前
Java从入门到“放弃”(精通)之旅——JavaSE终篇(异常)
java·开发语言
.生产的驴2 小时前
SpringBoot 封装统一API返回格式对象 标准化开发 请求封装 统一格式处理
java·数据库·spring boot·后端·spring·eclipse·maven
猿周LV2 小时前
JMeter 安装及使用 [软件测试工具]
java·测试工具·jmeter·单元测试·压力测试
晨集2 小时前
Uni-App 多端电子合同开源项目介绍
java·spring boot·uni-app·电子合同
时间之城2 小时前
笔记:记一次使用EasyExcel重写convertToExcelData方法无法读取@ExcelDictFormat注解的问题(已解决)
java·spring boot·笔记·spring·excel