1.安装软件: Adobe Acrobat 9 Pro

自行下载安装;

2.制作模板pdf文件

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

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

3.maven依赖

<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><dependency><groupId>org.apache.pdfbox</groupId><artifactId>fontbox</artifactId><version>2.0.11</version></dependency><dependency><groupId>org.apache.pdfbox</groupId><artifactId>pdfbox</artifactId><version>2.0.9</version></dependency>

4. 后端-字体引入

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

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

/** * pdf工具类 * * @author xwt * @date 2022/12/7 11:00 */@Slf4jpublic 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 pdfBase64pdf类型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 tableif (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 infobyte[] 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));}}}

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

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 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对应域的名字

map.put("fill_1", params.getAccountTitle());//户名map.put("fill_2", d_cardNo);//账号

工具类

@Slf4jpublic class PdfUtil {/** * @param o写入的数据 * @param templatePath pdf模板路径 */// 利用模板生成pdfpublic 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文件还能编辑,一定要设为truestamper.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);}}}