1、问题描述
需要将发票导出成pdf,要求每页都必须包含发票信息和表头行。
2、解决方法
使用IText工具实现PDF导出
IText8文档:Examples (itextpdf.com)
3、我的代码
引入Itext依赖,我这里用的是8.0.1版本
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext7-core</artifactId>
<version>8.0.1</version>
<type>pom</type>
</dependency>
MyItextpdfUtils.java
package com.easyexcel.util;
import com.easyexcel.handler.PaginationEventHandler;
import com.itextpdf.html2pdf.HtmlConverter;
import com.itextpdf.io.font.PdfEncodings;
import com.itextpdf.io.image.ImageData;
import com.itextpdf.io.image.ImageDataFactory;
import com.itextpdf.kernel.events.PdfDocumentEvent;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.borders.Border;
import com.itextpdf.layout.element.*;
import com.itextpdf.layout.element.Image;
import com.itextpdf.layout.properties.AreaBreakType;
import com.itextpdf.layout.properties.TextAlignment;
import com.itextpdf.layout.properties.UnitValue;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
import java.awt.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @author Wulc
* @date 2023/8/10 17:08
* @description
*/
@Component
public class MyItextpdfUtils {
public void createPDF() throws java.io.IOException {
Resource resource = new ClassPathResource("/");
String path = resource.getFile().getPath();
//设置中文字体 C:\Windows\Fonts
//PdfFont chineseFont =getFont();
//PdfFont chineseFont = PdfFontFactory.createFont(this.getClass().getClassLoader().getResource("simsun.ttf").getPath());
PdfFont chineseFontForTemplate = PdfFontFactory.createFont("D:\\学习资料\\后端\\STSONG.TTF");
PdfFont chineseFontForContent = PdfFontFactory.createFont("D:\\学习资料\\后端\\STSONG.TTF");
//创建每页的共有模板
//*********************每页的共有模板*********************************
String templatePath = path + "\\template.pdf";
PdfDocument pdfDocumentTemplate = new PdfDocument(new PdfWriter(templatePath));
//Document documentTemplate = new Document(pdfDocumentTemplate, PageSize.A4).setFont(chineseFontForTemplate);
Document documentTemplate = new Document(pdfDocumentTemplate, PageSize.A4);
//插入logo图片
Table logoTemplateTable = new Table(UnitValue.createPercentArray(1)).useAllAvailableWidth().setBorder(Border.NO_BORDER);
ImageData imageData = ImageDataFactory.create(this.getClass().getClassLoader().getResource("logo.png"));
Image image = new Image(imageData);
image.setHeight(50);
image.setWidth(100);
logoTemplateTable.addCell(new Cell().setBorder(Border.NO_BORDER).add(image));
//插入logo图片下方的一些信息
Table logoInfoTable = new Table(UnitValue.createPercentArray(1)).useAllAvailableWidth().setBorder(Border.NO_BORDER);
logoInfoTable.addCell(new Cell().setBorder(Border.NO_BORDER).setPadding(1).setFontSize(10).add(new Paragraph("Description1")));
logoInfoTable.addCell(new Cell().setBorder(Border.NO_BORDER).setPadding(1).setFontSize(10).add(new Paragraph("Description2")));
logoInfoTable.addCell(new Cell().setBorder(Border.NO_BORDER).setPadding(1).setFontSize(10).add(new Paragraph("Description3")));
//插入标题
Table titleTable = new Table(UnitValue.createPercentArray(4)).useAllAvailableWidth().setBorder(Border.NO_BORDER);
titleTable.addCell(new Cell(1, 4).setBorder(Border.NO_BORDER).setPadding(1).setFontSize(15).add(new Paragraph("TITLE")).setTextAlignment(TextAlignment.CENTER));
//插入标题下的一些信息
Table titleInfoTable = new Table(UnitValue.createPercentArray(4)).useAllAvailableWidth();
titleInfoTable.addCell(new Cell().setPadding(1).setFontSize(10).add(new Paragraph("QuestionA")));
titleInfoTable.addCell(new Cell().setPadding(1).setFontSize(10).add(new Paragraph("AnswerA")));
titleInfoTable.addCell(new Cell().setPadding(1).setFontSize(10).add(new Paragraph("QuestionB")));
titleInfoTable.addCell(new Cell().setPadding(1).setFontSize(10).add(new Paragraph("AnswerB")));
titleInfoTable.addCell(new Cell().setPadding(1).setFontSize(10).add(new Paragraph("QuestionC")));
titleInfoTable.addCell(new Cell().setPadding(1).setFontSize(10).add(new Paragraph("AnswerC")));
titleInfoTable.addCell(new Cell().setPadding(1).setFontSize(10).add(new Paragraph("QuestionD")));
titleInfoTable.addCell(new Cell().setPadding(1).setFontSize(10).add(new Paragraph("AnswerD")));
titleInfoTable.addCell(new Cell().setPadding(1).setFontSize(10).add(new Paragraph("QuestionE")));
titleInfoTable.addCell(new Cell().setPadding(1).setFontSize(10).add(new Paragraph("AnswerE")));
titleInfoTable.addCell(new Cell().setPadding(1).setFontSize(10).add(new Paragraph("QuestionF")));
titleInfoTable.addCell(new Cell().setPadding(1).setFontSize(10).add(new Paragraph("AnswerF")));
documentTemplate.add(logoTemplateTable);
documentTemplate.add(logoInfoTable);
documentTemplate.add(titleTable);
documentTemplate.add(titleInfoTable);
//*********************每页的共有模板*********************************
//*********************每页的内容************************************
String contentPath = path + "\\content.pdf";
PdfDocument pdfDocumentContent = new PdfDocument(new PdfWriter(contentPath));
//把内容使用共有模板
pdfDocumentContent.addEventHandler(PdfDocumentEvent.END_PAGE, new PaginationEventHandler(pdfDocumentTemplate.getFirstPage().copyAsFormXObject(pdfDocumentContent)));
Document documentContent = new Document(pdfDocumentContent, PageSize.A4).setFont(chineseFontForContent);
//每页的content距离上面的template的距离
documentContent.setTopMargin(250);
Table contentTable = new Table(UnitValue.createPercentArray(6)).useAllAvailableWidth();
//插入清单表格标题
contentTable.addHeaderCell(new Cell().setFontSize(8).add(new Paragraph("No")));
contentTable.addHeaderCell(new Cell().setFontSize(8).add(new Paragraph("title1")));
contentTable.addHeaderCell(new Cell().setFontSize(8).add(new Paragraph("title2")));
contentTable.addHeaderCell(new Cell().setFontSize(8).add(new Paragraph("title3")));
contentTable.addHeaderCell(new Cell().setFontSize(8).add(new Paragraph("title4")));
contentTable.addHeaderCell(new Cell().setFontSize(8).add(new Paragraph("title5")));
for (int i = 0; i < 300; i++) {
contentTable.addCell(new Cell().setFontSize(8).add(new Paragraph(String.valueOf(i))));
contentTable.addCell(new Cell().setFontSize(8).add(new Paragraph("content1")));
contentTable.addCell(new Cell().setFontSize(8).add(new Paragraph("content2")));
contentTable.addCell(new Cell().setFontSize(8).add(new Paragraph("content3")));
contentTable.addCell(new Cell().setFontSize(8).add(new Paragraph("content4")));
contentTable.addCell(new Cell().setFontSize(8).add(new Paragraph("content5")));
}
//尾页
Table lastInfoTable = new Table(UnitValue.createPercentArray(3)).setWidth(300);
lastInfoTable.addCell(new Cell(1, 3).setPadding(1).setFontSize(8).add(new Paragraph("Total:")));
lastInfoTable.addCell(new Cell(1, 1).setPadding(1).setFontSize(8).add(new Paragraph("统计A:")));
lastInfoTable.addCell(new Cell(1, 2).setPadding(1).setFontSize(8).add(new Paragraph("1234567")));
lastInfoTable.addCell(new Cell(1, 1).setPadding(1).setFontSize(8).add(new Paragraph("统计B:")));
lastInfoTable.addCell(new Cell(1, 2).setPadding(1).setFontSize(8).add(new Paragraph("7654321")));
//*********************每页的内容************************************
documentContent.add(contentTable);
//尾页新开一页
documentContent.add(new AreaBreak(AreaBreakType.NEXT_PAGE));
documentContent.add(lastInfoTable);
documentTemplate.close();
documentContent.close();
}
}
PDFTest.java
package com.easyexcel;
import com.easyexcel.util.MyItextpdfUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.io.IOException;
/**
* @author Wulc
* @date 2023/8/10 17:52
* @description
*/
@SpringBootTest(classes = SpringbootApplication.class)
@RunWith(SpringRunner.class)
public class PDFTest {
@Autowired
private MyItextpdfUtils myItextpdfUtils;
@Test
public void test6() throws IOException {
myItextpdfUtils.createPDF();
}
}
测试一下:
4、总结
IText8不支持中文,需要引入外部字体文件,如果是以其中一个pdf作为每页的背景模板生成PDF这种方式(copyAsFormXObject),它只能支持其中一个pdf中文,另一个就不支持了。
Document documentTemplate = new Document(pdfDocumentTemplate, PageSize.A4).setFont(chineseFontForTemplate);
Document documentContent = new Document(pdfDocumentContent, PageSize.A4).setFont(chineseFontForContent);
如上代码,虽然我同时把背景版和内容同时都设置了中文字体,但是template和content合一块的时候,template的背景版pdf的中文字体就会失效了。
不过还好,因为是海外的发票都是英文的,因此不需要考虑支持中文的问题。
希望哪位大佬能帮忙解决一下IText8 copyAsFormXObject中文兼容性问题!!!
5、参考资料
https://www.cnblogs.com/sky-chen/p/13026203.html#autoid-1-4-5-0-0-0
https://kb.itextpdf.com/home/it7kb/examples/repeating-parts-of-a-form
https://zhuanlan.zhihu.com/p/537723847
https://blog.csdn.net/weixin_43409994/article/details/118157694
https://blog.csdn.net/u012397189/article/details/126345744
https://blog.csdn.net/Thinkingcao/article/details/84988392
6、补充
2023/8/27补充
关于两个pdf的document合并时其中一个pdf字体中文字体设置不生效的问题,我想了一个妥协的解决办法。就是把其中一个document转为图片插入另一个pdf中。
首先引入pdfbox用于将pdf转为图片。
pom.xml
<!-- https://mvnrepository.com/artifact/org.apache.pdfbox/pdfbox -->
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.29</version>
</dependency>
修改后的createPDF()方法
public void createPDF() throws java.io.IOException {
Resource resource = new ClassPathResource("/");
String path = resource.getFile().getPath();
//设置中文字体 C:\Windows\Fonts
//PdfFont chineseFont =getFont();
//PdfFont chineseFont = PdfFontFactory.createFont(this.getClass().getClassLoader().getResource("simsun.ttf").getPath());
PdfFont chineseFontForTemplate = PdfFontFactory.createFont("D:\\学习资料\\后端\\STSONG.TTF");
PdfFont chineseFontForContent = PdfFontFactory.createFont("D:\\学习资料\\后端\\STSONG.TTF");
//创建每页的共有模板
//*********************每页的共有模板*********************************
String templatePath = path + "\\template.pdf";
PdfDocument pdfDocumentTemplate = new PdfDocument(new PdfWriter(templatePath));
Document documentTemplate = new Document(pdfDocumentTemplate, new PageSize(595.0F, 250F)).setFont(chineseFontForTemplate);
//Document documentTemplate = new Document(pdfDocumentTemplate, PageSize.A4);
//插入logo图片
Table logoTemplateTable = new Table(UnitValue.createPercentArray(1)).useAllAvailableWidth().setBorder(Border.NO_BORDER);
ImageData imageData = ImageDataFactory.create(this.getClass().getClassLoader().getResource("logo.png"));
Image image = new Image(imageData);
image.setHeight(50);
image.setWidth(100);
logoTemplateTable.addCell(new Cell().setBorder(Border.NO_BORDER).add(image));
//插入logo图片下方的一些信息
Table logoInfoTable = new Table(UnitValue.createPercentArray(1)).useAllAvailableWidth().setBorder(Border.NO_BORDER);
logoInfoTable.addCell(new Cell().setBorder(Border.NO_BORDER).setPadding(1).setFontSize(10).add(new Paragraph("描述")));
logoInfoTable.addCell(new Cell().setBorder(Border.NO_BORDER).setPadding(1).setFontSize(10).add(new Paragraph("Description2")));
logoInfoTable.addCell(new Cell().setBorder(Border.NO_BORDER).setPadding(1).setFontSize(10).add(new Paragraph("Description3")));
//插入标题
Table titleTable = new Table(UnitValue.createPercentArray(4)).useAllAvailableWidth().setBorder(Border.NO_BORDER);
titleTable.addCell(new Cell(1, 4).setBorder(Border.NO_BORDER).setPadding(1).setFontSize(15).add(new Paragraph("标题")).setTextAlignment(TextAlignment.CENTER));
//插入标题下的一些信息
Table titleInfoTable = new Table(UnitValue.createPercentArray(4)).useAllAvailableWidth();
titleInfoTable.addCell(new Cell().setPadding(1).setFontSize(10).add(new Paragraph("问题A")));
titleInfoTable.addCell(new Cell().setPadding(1).setFontSize(10).add(new Paragraph("AnswerA")));
titleInfoTable.addCell(new Cell().setPadding(1).setFontSize(10).add(new Paragraph("QuestionB")));
titleInfoTable.addCell(new Cell().setPadding(1).setFontSize(10).add(new Paragraph("AnswerB")));
titleInfoTable.addCell(new Cell().setPadding(1).setFontSize(10).add(new Paragraph("QuestionC")));
titleInfoTable.addCell(new Cell().setPadding(1).setFontSize(10).add(new Paragraph("AnswerC")));
titleInfoTable.addCell(new Cell().setPadding(1).setFontSize(10).add(new Paragraph("QuestionD")));
titleInfoTable.addCell(new Cell().setPadding(1).setFontSize(10).add(new Paragraph("AnswerD")));
titleInfoTable.addCell(new Cell().setPadding(1).setFontSize(10).add(new Paragraph("QuestionE")));
titleInfoTable.addCell(new Cell().setPadding(1).setFontSize(10).add(new Paragraph("AnswerE")));
titleInfoTable.addCell(new Cell().setPadding(1).setFontSize(10).add(new Paragraph("QuestionF")));
titleInfoTable.addCell(new Cell().setPadding(1).setFontSize(10).add(new Paragraph("AnswerF")));
documentTemplate.add(logoTemplateTable);
documentTemplate.add(logoInfoTable);
documentTemplate.add(titleTable);
documentTemplate.add(titleInfoTable);
documentTemplate.close();
//*********************每页的共有模板*********************************
//把每页背景模板转为图片
String backgroundImgPath = path + "/backgroundImg.png";
pdfTopng(templatePath, backgroundImgPath, "png");
String backgroundPdfPath = path + "/backgroundPdf.pdf";
PdfDocument pdfDocBackground = new PdfDocument(new PdfWriter(backgroundPdfPath));
Document documentBackground = new Document(pdfDocBackground, PageSize.A4);
documentBackground.setTopMargin(3);
documentBackground.setLeftMargin(3);
Table background = new Table(UnitValue.createPercentArray(1)).useAllAvailableWidth().setBorder(Border.NO_BORDER);
ImageData imageBackgroundData = ImageDataFactory.create(backgroundImgPath);
Image imageBackground = new Image(imageBackgroundData);
imageBackground.setHeight(300);
imageBackground.setWidth(550);
background.addCell(new Cell().setBorder(Border.NO_BORDER).add(imageBackground));
documentBackground.add(background);
//*********************每页的内容************************************
String contentPath = path + "\\content.pdf";
PdfDocument pdfDocumentContent = new PdfDocument(new PdfWriter(contentPath));
//把内容使用共有模板
pdfDocumentContent.addEventHandler(PdfDocumentEvent.END_PAGE, new PaginationEventHandler(pdfDocBackground.getFirstPage().copyAsFormXObject(pdfDocumentContent)));
Document documentContent = new Document(pdfDocumentContent, PageSize.A4).setFont(chineseFontForContent);
//每页的content距离上面的template的距离
documentContent.setTopMargin(280);
Table contentTable = new Table(UnitValue.createPercentArray(6)).useAllAvailableWidth();
//插入清单表格标题
contentTable.addHeaderCell(new Cell().setFontSize(8).add(new Paragraph("No")));
contentTable.addHeaderCell(new Cell().setFontSize(8).add(new Paragraph("title1")));
contentTable.addHeaderCell(new Cell().setFontSize(8).add(new Paragraph("title2")));
contentTable.addHeaderCell(new Cell().setFontSize(8).add(new Paragraph("title3")));
contentTable.addHeaderCell(new Cell().setFontSize(8).add(new Paragraph("title4")));
contentTable.addHeaderCell(new Cell().setFontSize(8).add(new Paragraph("title5")));
for (int i = 0; i < 300; i++) {
contentTable.addCell(new Cell().setFontSize(8).add(new Paragraph(String.valueOf(i))));
contentTable.addCell(new Cell().setFontSize(8).add(new Paragraph("内容1")));
contentTable.addCell(new Cell().setFontSize(8).add(new Paragraph("内容2")));
contentTable.addCell(new Cell().setFontSize(8).add(new Paragraph("content3")));
contentTable.addCell(new Cell().setFontSize(8).add(new Paragraph("content4")));
contentTable.addCell(new Cell().setFontSize(8).add(new Paragraph("content5")));
}
//尾页
Table lastInfoTable = new Table(UnitValue.createPercentArray(3)).setWidth(300);
lastInfoTable.addCell(new Cell(1, 3).setPadding(1).setFontSize(8).add(new Paragraph("Total:")));
lastInfoTable.addCell(new Cell(1, 1).setPadding(1).setFontSize(8).add(new Paragraph("统计A:")));
lastInfoTable.addCell(new Cell(1, 2).setPadding(1).setFontSize(8).add(new Paragraph("1234567")));
lastInfoTable.addCell(new Cell(1, 1).setPadding(1).setFontSize(8).add(new Paragraph("统计B:")));
lastInfoTable.addCell(new Cell(1, 2).setPadding(1).setFontSize(8).add(new Paragraph("7654321")));
//*********************每页的内容************************************
documentContent.add(contentTable);
//尾页新开一页
documentContent.add(new AreaBreak(AreaBreakType.NEXT_PAGE));
documentContent.add(lastInfoTable);
documentBackground.close();
documentContent.close();
}
/**
* @author Wulc
* @date 2023/8/25 14:24
* @description 使用pdfbox把pdf转为图片
*/
public static void pdfTopng(String pdfFileAddress, String imgFileAddress, String type) {
// 将pdf装图片 并且自定义图片得格式大小
File file = new File(pdfFileAddress);
try {
PDDocument doc = PDDocument.load(file);
PDFRenderer renderer = new PDFRenderer(doc);
BufferedImage image = renderer.renderImageWithDPI(0, 144); // Windows native DPI
ImageIO.write(image, type, new File(imgFileAddress));
doc.close();
new File(pdfFileAddress).delete();
} catch (IOException e) {
e.printStackTrace();
}
}
测试一下:
2024/4/10补充
IText8生成的pdf,有时候会出现单元格中的文本内容过长,没有自动换行,导致pdf中的内容被截断。
解决方法:
引入pom.xml依赖
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.68</version>
<scope>compile</scope>
</dependency>
Document documentInvoice = new Document(pdfDocInvoice, PageSize.A4);
documentInvoice.setProperty(Property.SPLIT_CHARACTERS, new DefaultSplitCharacters() {
@Override
public boolean isSplitCharacter(GlyphLine text, int glyphPos) {
//解决word-break: break-all;不兼容的问题,解决纯英文或数字不自动换行的问题
return true;
}
});
参考资料:解决itextpdf Table 数字、英文长度过长导致不换行,解决itext生成pdf宽度超出_不换行生成pdf-CSDN博客
注意事项:
我这些只是本地测试,所以文件都是直接获取操作的resources目录。如果是发布打jar包的话,其实要改一下文件路径的。
可以参考这篇:
文章评论