【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);
        }
    }

}
相关推荐
杨荧几秒前
【JAVA毕业设计】基于Vue和SpringBoot的宠物咖啡馆平台
java·开发语言·jvm·vue.js·spring boot·spring cloud·开源
Ling_suu31 分钟前
Spring——单元测试
java·spring·单元测试
ModelBulider33 分钟前
十三、注解配置SpringMVC
java·开发语言·数据库·sql·mysql
苹果酱05671 小时前
C语言 char 字符串 - C语言零基础入门教程
java·开发语言·spring boot·mysql·中间件
csucoderlee1 小时前
eclipse mat leak suspects report和 component report的区别
java·ide·eclipse
代码小鑫1 小时前
A032-基于Spring Boot的健康医院门诊在线挂号系统
java·开发语言·spring boot·后端·spring·毕业设计
训山1 小时前
4000字浅谈Java网络编程
java·开发语言·网络
VertexGeek1 小时前
Rust学习(四):作用域、所有权和生命周期:
java·学习·rust
喔喔咿哈哈2 小时前
【手撕 Spring】 -- Bean 的创建以及获取
java·后端·spring·面试·开源·github
码农小丘2 小时前
了解springboot国际化用途以及使用
java·spring boot·spring