因项目需求,需要在java代码中生成pdf文件,之前没做过类似的功能,所以网上查了一下后,采用了普遍使用的itextpdf的方式。
这篇文章包含了一些特殊的需求,比如采用正则表达式处理html代码,特殊字符写入pdf等。如果有需要的,希望能帮到各位。
1、本文采用的jar包版本:
<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>
2、具体生成pdf代码
PdfUtils.java工具类代码
package cn.trasen.data.hsemp.util;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Document;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.pdf.AcroFields;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfCopy;
import com.itextpdf.text.pdf.PdfImportedPage;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfStamper;
import com.itextpdf.text.pdf.PdfWriter;
/**
* PDF工具类
*
* @date: 2020-08-18 16:26:59
* @Copyright: Copyright (c) 2006 - 2020
* @Company: 湖南xxx有限公司
* @Version: V1.0
*/
public class PdfUtils {
static Logger LOGGER = Logger.getLogger(PdfUtils.class);
/** 字体 */
private static BaseFont baseFont = null;
/**
* 设置字体
*/
static {
try {
// 支持中文
baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
} catch (Exception e) {
LOGGER.error("设置字体错误:", e);
}
}
/**
* 文档超级 排版
*
* @param type 0.标题;1.标题一;2.标题二;3.标题三;5.正文;6.左对齐
*/
public static Paragraph getFont(int type, String text) {
Font font = new Font(baseFont);
if (0 == type) {
// 1-标题
font.setSize(16f);
font.setStyle(Font.BOLD);
} else if (1 == type) {
// 1-标题一
font.setSize(14f);
font.setStyle(Font.BOLD);
} else if (2 == type) {
// 2-标题二
font.setSize(12f);
font.setStyle(Font.BOLD);
} else if (3 == type) {
// 3-标题三
font.setSize(10f);
font.setStyle(Font.BOLD);
} else if (5 == type) {
// 5-正文
font.setSize(8f);
} else if (6 == type) {
// 6-左对齐
font.setSize(8f);
} else {
// 默认大小
font.setSize(8f);
}
// 注: 字体必须和文字一起new
Paragraph paragraph = new Paragraph(text, font);
if (0 == type) {
paragraph.setAlignment(Paragraph.ALIGN_CENTER);// 居中
paragraph.setSpacingBefore(10f);// 上间距
paragraph.setSpacingAfter(10f);// 下间距
} else if (1 == type) {// 1-标题一
paragraph.setAlignment(Element.ALIGN_JUSTIFIED); // 默认
paragraph.setSpacingBefore(5f);// 上间距
paragraph.setSpacingAfter(5f);// 下间距
} else if (2 == type) {// 2-标题二
paragraph.setSpacingBefore(5f);// 上间距
paragraph.setSpacingAfter(5f);// 下间距
} else if (3 == type) {// 3-标题三
paragraph.setSpacingBefore(5f);// 上间距
paragraph.setSpacingAfter(5f);// 下间距
} else if (5 == type) {
paragraph.setAlignment(Element.ALIGN_JUSTIFIED);
paragraph.setFirstLineIndent(24);// 首行缩进
paragraph.setSpacingBefore(1f);// 上间距
paragraph.setSpacingAfter(1f);// 下间距
} else if (6 == type) {// 左对齐
paragraph.setAlignment(Element.ALIGN_LEFT);
paragraph.setSpacingBefore(1f);// 上间距
paragraph.setSpacingAfter(1f);// 下间距
}
return paragraph;
}
/**
*
* @date: 2020-08-28 16:20:11
* @param fileFullUrl 文件绝对路径
* @param pdfMap pdf数据map
* @throws Exception
*/
@SuppressWarnings("unchecked")
public static void createPdf(String fileFullUrl, List<Map<String, Object>> pdfMap) throws Exception {
// 创建空白pdf文档
Document document = new Document();
PdfWriter.getInstance(document, new FileOutputStream(fileFullUrl));
document.open();
// 循环pdf所有元素
for (Map<String, Object> map : pdfMap) {
// 判断是否为标题
if ("title".equals(map.get("type").toString())) {
int titleNo = Integer.parseInt(map.get("key").toString());
String titleContent = map.get("value").toString();
document.add(PdfUtils.getFont(titleNo, titleContent));
} else if ("tableTitle".equals(map.get("type").toString())) {
// 获取列宽数组
float[] columnWidths = (float[])map.get("key");
// 获取列标题数组
String[] titles = (String[])map.get("value");
// 获取表格数据
List<List<String>> dataList = (List<List<String>>)map.get("tableContent");
// 创建表格
PdfPTable table = new PdfPTable(columnWidths);
// 设置表格大小为可用空白区域的100%。默认居中80%
table.setWidthPercentage(100);
// 设置表格标题列
for (String title : titles) {
PdfPCell cell = new PdfPCell(getFont(3, title));
cell.setBackgroundColor(new BaseColor(235,235,235));
cell.setUseAscender(true);
// 水平居中
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
// 垂直居中
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
table.addCell(cell);
}
// 循环表格行
for (List<String> rowList : dataList) {
// 循环表格列
for (String dataStr : rowList) {
PdfPCell cell = new PdfPCell(getFont(5, dataStr));
cell.setUseAscender(true);
// 垂直居中
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
// 设置表格内文本的行间距,1.5倍较为合理
cell.setLeading(0, 1.5f);
table.addCell(cell);
}
}
document.add(table);
}
}
document.close();
}
}
以上代码createPdf为生成pdf文件的核心方法,其中pdf数据map的pdfMap参数将在如下方法代码中说明:
@Override
public String downloadSelfReviewTemplate(String review_id) {
LOGGER.info("downloadSelfReviewTemplate start");
String fileFullUrl = null;
// 生成pdf需要的map说明:
// key="type"时,value:"title"标题,"tableTitle"表格标题,"tableContent"表格数据
// key="title"时,key="key":0.文件标题;1.一级标题;2.二级标题;3.三级标题;5.正文,key="value":标题或正文内容
// key="tableTitle"时,key="key":列宽百分比浮点数数组,key="value":列标题数组
// key="tableContent"时,key="value":表格数据
List<Map<String, Object>> pdfMap = new ArrayList<Map<String, Object>>();
TreeParamsDTO treeParamsDTO = new TreeParamsDTO();
treeParamsDTO.setParentCode("-1");
List<TreeModel> childs = indexCategoryService.treeIndexCategoryByParent(treeParamsDTO);
for (int j = 0; j < childs.size(); j++) {
TreeModel tm = childs.get(j);
String title3Content = tm.getCode() + " " + tm.getName();
List<List<String>> rowList = new ArrayList<List<String>>();
String categoryCode = tm.getId();
// 查询数据集评审数据
List<H_index_review> hIndexReviewList = indexReviewDao.listSelfReviewTemplate(categoryCode);
// 添加数据
for (H_index_review temp : hIndexReviewList) {
List<String> row = new ArrayList<String>();
row.add(temp.getReview_content());
row.add(temp.getCode());
String riOptions = temp.getReview_index_options();
row.add(formatOptionsHtml(riOptions));
row.add(temp.getScoring_instructions());
BigDecimal score = temp.getScore();
score = null == score ? new BigDecimal(0) : score;
row.add(score.toString());
rowList.add(row);
}
// 添加表格标题
Map<String, Object> title2Map = new HashMap<String, Object>();
title2Map.put("type", "title");
title2Map.put("key", 2);
title2Map.put("value", title3Content);
pdfMap.add(title2Map);
// 添加表格列标题
Map<String, Object> tableMap = new HashMap<String, Object>();
tableMap.put("type", "tableTitle");
// 设置列宽:百分比
float[] columnWidths = {0.2f, 0.1f, 0.4f, 0.2f, 0.1f};
tableMap.put("key", columnWidths);
// 表格标题
String[] titles = {"评审内容", "编号", "评审指标", "评分说明", "分数"};
tableMap.put("value", titles);
// 表格数据
tableMap.put("tableContent", rowList);
pdfMap.add(tableMap);
}
// 查询文档评审数据
// 组装填充pdf文档的数据
try {
String dir = uploadPath + "/review/template/";
// 判断文件路径是否存在
File dirPath = new File(dir);
if(!dirPath.exists()) {
dirPath.mkdirs();
}
// 获取当前系统时间
String times = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
fileFullUrl = dir + times + ".pdf";
PdfUtils.createPdf(fileFullUrl, pdfMap);
} catch (Exception e) {
LOGGER.error("生成PDF文件失败:", e);
}
LOGGER.info("downloadSelfReviewTemplate end");
return fileFullUrl;
}
以上代码中,大部分都是本人项目中的业务代码,无需关注。需要关注的为跟pdfMap相关的代码及说明,以及formatOptionsHtml方法。
formatOptionsHtml方法是利用了正则表达式处理html代码,将html显示在浏览器的效果,处理为写入pdf中的效果,具体代码如下:
private String formatOptionsHtml(String html) {
// 第一步:找出type="text"的input标签并处理start
String patternText = "<input type=\"text\" value=\"[^\"]+?\"[^>]*>";
// 创建 Pattern 对象
Pattern r = Pattern.compile(patternText);
// 现在创建 matcher 对象
Matcher m = r.matcher(html);
String textInputValue = "";
// 必须要.find()才能使用.group()找到
while (m.find()) {
String group = m.group();
// 找到type="text"的input标签的value值:包含了双引号
String patternValue = "value=\"[^\"]+?\"";
// 创建 Pattern 对象
Pattern rValue = Pattern.compile(patternValue);
// 现在创建 matcher 对象
Matcher mValue = rValue.matcher(group);
// 必须要.find()才能使用.group()找到
if (mValue.find()) {
String valueGroup = mValue.group();
// 去掉双引号后,得到value值
textInputValue +=
valueGroup.substring(valueGroup.indexOf("\"") + 1, valueGroup.lastIndexOf("\"")) + ",";
}
}
// 找出type="text"的input标签并处理end
// 第二步:通过正则表达式纯替换标签start
// 遇到</div>标签则替换为换行符
html = html.replaceAll("</div>", "\n");
// 遇到type="radio"的input标签,并且有checked="checked"属性,替换为●
html = html.replaceAll("<input type=\"radio\"[^>]+?(\\s)+checked=\"checked\">", "●");
// 遇到type="radio"的input标签,替换为○
html = html.replaceAll("<input type=\"radio\"[^>]*?>", "○");
// 遇到type="checkbox"的input标签,并且有checked="checked"属性,替换为■
html = html.replaceAll("<input type=\"checkbox\"[^>]+?(\\s)+checked=\"checked\">", "■");
// 遇到type="checkbox"的input标签,替换为○
html = html.replaceAll("<input type=\"checkbox\"[^>]*?>", "□");
// 处理第一步中找到的值相等的input标签,并替换成值
if (textInputValue.length() > 0) {
textInputValue = textInputValue.substring(0, textInputValue.length() - 1);
String[] tivs = textInputValue.split(",");
for (String string : tivs) {
html = html.replaceAll("<input type=\"text\" value=\"" + string + "\"[^>]*?>", "_" + string + "_");
}
}
// 遇到type="text"的input标签,替换为___
html = html.replaceAll("<input type=\"text\"[^>]*?>", "___");
// 将其他标签替换为空字符串
html = html.replaceAll("<[^>]*>", "");
// 第二步:通过正则表达式纯替换标签end
return html;
}
下面将展示替换前和替换后的效果:
(1)、纯单选按钮html代码替换前:
<div class="roDiv"><input type="radio" name="hzjbxxzj" class="roRadio dataSetRadio" value="1"><span class="roRadioText">无此数据</span></div><div class="roDiv"><input type="radio" name="hzjbxxzj" class="roRadio dataSetRadio" value="2"><span class="roRadioText">有且完全符合国家标准</span></div><div class="roDiv"><input type="radio" name="hzjbxxzj" class="roRadio dataSetRadio" value="3"><span class="roRadioText">有,部分符合国家标准</span></div>
效果图:
(2)、纯复选框按钮html代码替换前:
<div class="roDiv"><input type="checkbox" name="jsjg_31" class="roRadio xxzhfsCheckbox" value="1"><span class="roRadioText">数据层面整合:应用系统的数据库系统之间的数据交换和共享以及数据之间的映射转换</span></div><div class="roDiv"><input type="checkbox" name="jsjg_31" class="roRadio xxzhfsCheckbox" value="2"><span class="roRadioText">应用层面整合:应用程序之间实时或异步交换信息和相互调用</span></div>
效果图:
(3)、多组合html代码替换前:
<div class="roDiv"><input type="checkbox" name="fwqsb_4111" class="roRadio fwqsb" value="1"><span class="roRadioText">平台具备专用的集成服务器,数量<input type="text" value="" class="zdyInput" />,最低配置为:</div><div class="roDiv">品牌型号:<input type="text" value="" class="zdyInput" /></div><div class="roDiv">CPU:<input type="text" value="" class="zdyInput" />(注明型号和颗数)</div><div class="roDiv">内存:<input type="text" value="" class="zdyInput" />(GB)</div><div class="roDiv">硬盘:<input type="text" value="" class="zdyInput" />(TB)</div><div class="roDiv">冗余电源:<input type="text" value="" class="zdyInput" />(个)</div><div class="roDiv">网卡数量:<input type="text" value="" class="zdyInput" />(个)</div><div class="roDiv">宽带:<input type="text" value="" class="zdyInput" />(Bit/s)</span></div>
效果图:
3、特殊字符的问题
itextpdf包内自带的字体,对于特殊字符的支持很不友好,大部分特殊字符都不支持,导致写入到pdf后直接变成了空字符串(就是啥也看不到),只能看到中文、英文和数字。这个问题也困扰了我两天,最后在网上搜到了上面说用的几个支持的特殊字符。
●○■□
有几个跟我上面用到的很像,但其实编码不一样,所以写入pdf就看不到了,请慎用。
这篇文章介绍了itextpdf生成pdf的方法,正则表达式处理html代码以及特殊字符所需要注意的地方,希望能帮助到各位。