一、实现效果
二、遇到的问题
- 实现导出PDF主体代码参考:Java纯代码实现导出PDF功能,下图是原作者实现的效果
- 导出报错
Font 'STSong-Light' with 'UniGB-UCS2-H' is not recognized.
。参考:itext 生成 PDF(五) 使用外部字体
网上都是说jar包的版本不对,导致的字体兼容性问题。换了jar包版本发现没效果,后来索性直接把字体下载到本地直接引入。
- jar包发布到服务器上导出PDF的时候发生报错
BOOT-INF/classes!/fonts/SimSun.ttf not exists
。
可以看到字体文件在jar目录下面是有的,但是发现classes后面多了个叹号。这是引入外部字体方式不对,后改用问题2参考文章的第三种写法就没问题了。
- 添加水印参考:itextpdf5.5.13给pdf添加图片水印、添加文字水印(平铺)、添加文字水印(单个)、添加页眉、页脚、页眉事件、添加图片
三、测试数据展示
list:子节点数据
0 = {BasBudgetDetailVo@16046} "BasBudgetDetailVo(budgetId=2064535550, functionId=231231232, budgetQuantity=3, totalPrice=2664.00, functionName=功能1, functionDescription=功能1描述, functionUnit=套, functionPrice=888.00, parentId=231234512, functionSort=1)"
1 = {BasBudgetDetailVo@16047} "BasBudgetDetailVo(budgetId=2039369726, functionId=231236478, budgetQuantity=1, totalPrice=888.00, functionName=功能1, functionDescription=功能1描述, functionUnit=套, functionPrice=888.00, parentId=231234879, functionSort=1)"
2 = {BasBudgetDetailVo@16048} "BasBudgetDetailVo(budgetId=2039369725, functionId=231236473, budgetQuantity=1, totalPrice=888.00, functionName=功能2, functionDescription=功能2描述, functionUnit=套, functionPrice=888.00, parentId=231234879, functionSort=2)"
3 = {BasBudgetDetailVo@16049} "BasBudgetDetailVo(budgetId=2056146943, functionId=231231241, budgetQuantity=1, totalPrice=888.00, functionName=功能2, functionDescription=功能2描述, functionUnit=套, functionPrice=888.00, parentId=231234512, functionSort=2)"
4 = {BasBudgetDetailVo@16050} "BasBudgetDetailVo(budgetId=2047758334, functionId=231236487, budgetQuantity=1, totalPrice=888.00, functionName=功能3, functionDescription=功能3描述, functionUnit=套, functionPrice=888.00, parentId=231234512, functionSort=3)"
5 = {BasBudgetDetailVo@16051} "BasBudgetDetailVo(budgetId=2039369724, functionId=231231245, budgetQuantity=1, totalPrice=888.00, functionName=功能3, functionDescription=功能3描述, functionUnit=套, functionPrice=888.00, parentId=231234879, functionSort=3)"
6 = {BasBudgetDetailVo@16052} "BasBudgetDetailVo(budgetId=2047758333, functionId=231231597, budgetQuantity=1, totalPrice=888.00, functionName=功能4, functionDescription=功能4描述, functionUnit=套, functionPrice=888.00, parentId=231234512, functionSort=4)"
7 = {BasBudgetDetailVo@16053} "BasBudgetDetailVo(budgetId=2030981118, functionId=231233154, budgetQuantity=1, totalPrice=888.00, functionName=功能4, functionDescription=功能4描述, functionUnit=套, functionPrice=888.00, parentId=231234879, functionSort=4)"
8 = {BasBudgetDetailVo@16054} "BasBudgetDetailVo(budgetId=2030981117, functionId=231234596, budgetQuantity=1, totalPrice=888.00, functionName=功能5, functionDescription=功能5描述, functionUnit=套, functionPrice=888.00, parentId=231234879, functionSort=5)"
9 = {BasBudgetDetailVo@16055} "BasBudgetDetailVo(budgetId=2030981116, functionId=231235487, budgetQuantity=1, totalPrice=888.00, functionName=功能6, functionDescription=功能6描述, functionUnit=套, functionPrice=888.00, parentId=231234879, functionSort=6)"
functionInfoList:根节点数据
0 = {BasFunctionInfo@16090} "BasFunctionInfo(functionId=231234512, functionName=模块1, functionDescription=, functionUnit=0, functionPrice=0.00, createName=管理员, createBy=admin, createTime=Wed Jan 24 16:56:35 CST 2024, updateName=管理员, updateBy=admin, updateTime=Wed Jan 24 16:56:38 CST 2024, functionQuantity=null, functionSort=1, parentId=null)"
1 = {BasFunctionInfo@16091} "BasFunctionInfo(functionId=231234879, functionName=模块2, functionDescription=, functionUnit=0, functionPrice=0.00, createName=管理员, createBy=admin, createTime=Wed Jan 24 16:56:35 CST 2024, updateName=管理员, updateBy=admin, updateTime=Wed Jan 24 16:56:38 CST 2024, functionQuantity=null, functionSort=2, parentId=null)"
matchList:当前节点的子节点数据
四、jar包引入
<!--导出pdf所需包-->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.10</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
</dependencies>
五、外部字体引入
字体文件资源自己百度,直接搜SimSun.ttf字体下载
不难找
六、代码实现
private final ResourceLoader resourceLoader;
public BasBudgetDetailServiceImpl(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
/**
* 导出pdf
*
* @param response
* @throws Exception
*/
@Override
public void downloadPdf(HttpServletResponse response) throws Exception {
// 业务数据,根据需求查询获取
// 子节点数据
List<BasBudgetDetailVo> list;
// 根子节点数据
List<BasFunctionInfo> functionInfoList;
// 定义全局的字体静态变量
Font content = null;
Resource resource = resourceLoader.getResource("classpath:/fonts/SimSun.ttf");
InputStream inputStream = resource.getInputStream();
BaseFont bfChinese = null;
try {
// 字体
bfChinese = BaseFont.createFont("SimSun.ttf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED, true, IOUtils.toByteArray(inputStream), null);
content = new Font(bfChinese, 10, Font.NORMAL);
} catch (Exception e) {
e.printStackTrace();
}
BaseFont bf = null;
Font font = null;
try {
//创建字体
bf = BaseFont.createFont("SimSun.ttf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED, true, IOUtils.toByteArray(inputStream), null);
//使用字体并给出颜色
font = new Font(bf, 20, Font.BOLD, BaseColor.BLACK);
} catch (Exception e) {
e.printStackTrace();
}
Document document = new Document(new RectangleReadOnly(842F, 595F));
try {
PdfWriter pdfWriter = PdfWriter.getInstance(document, response.getOutputStream());
//打开生成的pdf文件
document.open();
//设置标题
Paragraph paragraph = new Paragraph("这是标题文档标题", font);
paragraph.setAlignment(1);
//引用字体
document.add(paragraph);
// 总额
BigDecimal detailTotal = BigDecimal.valueOf(0);
for (BasFunctionInfo functionInfo : functionInfoList) {
// 匹配明细
List<BasBudgetDetailVo> matchList = list.stream().filter(item ->
String.valueOf(item.getParentId()).equals(String.valueOf(functionInfo.getFunctionId())))
.collect(Collectors.toList());
// 设置表格的列宽和列数
float[] widths = {10f, 35f, 70f, 10f, 10f, 20f, 20f};
PdfPTable table = new PdfPTable(widths);
table.setSpacingBefore(20f);
// 设置表格宽度为100%
table.setWidthPercentage(100.0F);
table.setHeaderRows(1);
table.getDefaultCell().setHorizontalAlignment(1);
//列表-表头
String[] titleList = new String[]{"序号", "功能名称", "功能描述", "数量", "单位", "单价(元)", "总价(元)"};
addTableTitle(table, content, titleList);
// 模块总额
BigDecimal modelTotal = BigDecimal.valueOf(0);
//列表数据
if (matchList.size() > 0) {
Integer index = 1;
for (BasBudgetDetailVo item : matchList) {
PdfPCell cell1 = new PdfPCell(new Paragraph(String.valueOf(index), content));
PdfPCell cell2 = new PdfPCell(new Paragraph(item.getFunctionName(), content));
PdfPCell cell3 = new PdfPCell(new Paragraph(item.getFunctionDescription(), content));
PdfPCell cell4 = new PdfPCell(new Paragraph(String.valueOf(item.getBudgetQuantity()), content));
PdfPCell cell5 = new PdfPCell(new Paragraph(item.getFunctionUnit(), content));
PdfPCell cell6 = new PdfPCell(new Paragraph(String.valueOf(item.getFunctionPrice()), content));
BigDecimal totalPrice = item.getFunctionPrice().multiply(BigDecimal.valueOf(item.getBudgetQuantity()));
PdfPCell cell7 = new PdfPCell(new Paragraph(String.valueOf(totalPrice), content));
//单元格对齐方式
cell1.setFixedHeight(20);
cell1.setHorizontalAlignment(Element.ALIGN_CENTER);
cell1.setVerticalAlignment(Element.ALIGN_MIDDLE);
// 文字长度大于15的时候,设置表格行间距,底边距离
if (item.getFunctionName().length() > 15) {
cell2.setLeading(0f, 1.5f);
cell2.setPaddingBottom(10);
}
cell2.setHorizontalAlignment(Element.ALIGN_CENTER);
cell2.setVerticalAlignment(Element.ALIGN_MIDDLE);
// 文字长度大于30的时候,设置表格行间距,底边距离
if (item.getFunctionDescription().length() > 30) {
cell3.setLeading(0f, 1.5f);
cell3.setPaddingBottom(10);
}
cell3.setHorizontalAlignment(Element.ALIGN_CENTER);
cell3.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell4.setHorizontalAlignment(Element.ALIGN_CENTER);
cell4.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell5.setHorizontalAlignment(Element.ALIGN_CENTER);
cell5.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell6.setHorizontalAlignment(Element.ALIGN_CENTER);
cell6.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell7.setHorizontalAlignment(Element.ALIGN_CENTER);
cell7.setVerticalAlignment(Element.ALIGN_MIDDLE);
table.addCell(cell1);
table.addCell(cell2);
table.addCell(cell3);
table.addCell(cell4);
table.addCell(cell5);
table.addCell(cell6);
table.addCell(cell7);
// 序号
index++;
modelTotal = modelTotal.add(totalPrice);
}
// 合计行
PdfPCell cell1 = new PdfPCell(new Paragraph("合计", content));
cell1.setFixedHeight(20);
cell1.setHorizontalAlignment(Element.ALIGN_CENTER);
cell1.setVerticalAlignment(Element.ALIGN_MIDDLE);
// 空格
PdfPCell cell2 = new PdfPCell(new Paragraph("", content));
cell2.setFixedHeight(20);
cell2.setHorizontalAlignment(Element.ALIGN_CENTER);
cell2.setVerticalAlignment(Element.ALIGN_MIDDLE);
// 数额
PdfPCell cell3 = new PdfPCell(new Paragraph(String.valueOf(modelTotal), content));
cell3.setFixedHeight(20);
cell3.setHorizontalAlignment(Element.ALIGN_CENTER);
cell3.setVerticalAlignment(Element.ALIGN_MIDDLE);
table.addCell(cell1);
table.addCell(cell2);
table.addCell(cell2);
table.addCell(cell2);
table.addCell(cell2);
table.addCell(cell2);
table.addCell(cell3);
detailTotal = detailTotal.add(modelTotal);
}
document.add(new Paragraph("\n"));
document.add(new Paragraph("▋ " + functionInfo.getFunctionName(), content));
document.add(table);
document.add(new Paragraph("\n"));
if (matchList.size() == 0) {
document.add(new Paragraph("暂无数据", content));
}
}
document.add(new Paragraph("\n"));
document.add(new Paragraph("总计:" + detailTotal + "元", content));
// 加水印
PdfContentByte waterMar = pdfWriter.getDirectContentUnder();
String text = "天天想辞职月月拿全勤";
addTextFullWaterMark(waterMar, text, bfChinese);
document.close();
} catch (DocumentException e) {
e.printStackTrace();
log.error("导出pdf失败:{}", e);
}
}
/**
* 给表格添加表头
*
* @param table
* @param content
* @param titleList
*/
public void addTableTitle(PdfPTable table, Font content, String[] titleList) {
PdfPCell cell = null;
for (String title : titleList) {
cell = new PdfPCell(new Paragraph(title, content));
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
cell.setFixedHeight(20);
cell.setNoWrap(false);
table.addCell(cell);
}
}
/**
* 给pdf添加文字水印(平铺)
*
* @param waterMar
* @param text 水印文本
* @throws Exception
*/
public static void addTextFullWaterMark(PdfContentByte waterMar, String text, BaseFont bf) {
waterMar.beginText();
PdfGState gs = new PdfGState();
// 设置填充字体不透明度为0.2f
gs.setFillOpacity(0.2f);
waterMar.setFontAndSize(bf, 20);
// 设置透明度
waterMar.setGState(gs);
// 设置水印对齐方式 水印内容 X坐标 Y坐标 旋转角度
for (int x = 0; x <= 900; x += 200) {
for (int y = -50; y <= 800; y += 200) {
waterMar.showTextAligned(Element.ALIGN_RIGHT, text, x, y, 35);
}
}
// 设置水印颜色
waterMar.setColorFill(BaseColor.GRAY);
//结束设置
waterMar.endText();
waterMar.stroke();
}