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