Java Itextpdf 导出pdf
需求
- 公司新需求:导出前端页面上的某个部分为pdf,由于涉及领导签名的图片以及只是页面的一部分而非整个页面 这个需求交给后端完成
- 接下去我将以Java为基础讲述在服务端如何导出pdf
思路
- 使用itextpdf+原生Document编写
- 过于麻烦
- 需要掌握大量元素,在后端写前端页面
- 使用itextpdf + pdf模板
- 对基本字段替换支持较好
- 样式不好看
- 需要自己绘制模板,工具有限
- 使用html模板+html转为pdf
- 可以与前端同学配合完成
- css样式支持不完善 部分不支持,现在探测表格样式较为完善
- 自带的分页若表格跨页支持不完善,一行被分为两段分布在两页,需特殊处理
方案
-
最终选定使用html模板+html转为pdf
-
使用mustache模板引擎+itextpdf进行html渲染以及转为pdf,使用pdfbox将pdf转为图片
-
依赖
-
<dependency> <groupId>com.samskivert</groupId> <artifactId>jmustache</artifactId> <version>1.15</version> </dependency> <dependency> <groupId>com.itextpdf</groupId> <artifactId>itextpdf</artifactId> <version>5.5.6</version> <optional>true</optional> </dependency> <dependency> <groupId>com.itextpdf</groupId> <artifactId>itext-asian</artifactId> <version>5.2.0</version> <optional>true</optional> </dependency> <dependency> <groupId>com.itextpdf.tool</groupId> <artifactId>xmlworker</artifactId> <version>5.5.13.1</version> </dependency> <dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>fontbox</artifactId> <version>2.0.15</version> </dependency> <dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>pdfbox</artifactId> <version>2.0.15</version> </dependency>
主要代码
// html 模板
String template = "template/mustache_template.html";
// 随便构造数据
Map<String, Object> dataMap = new HashMap<>();
// 组件替换
String htmlContent = Mustache.compiler().compile(templateString).execute(dataMap);
String newPDFPath = "D:\\Learning\\export\\testM12.pdf";
FileOutputStream fileOutputStream = new FileOutputStream(newPDFPath);
// 自定义转换 修复base64图片转换 table分页出错
html2pdf1(htmlContent, fileOutputStream);
/**
* 自定义内容转换
* @param html html模板替换后的文本
* @param fileOutputStream 输出流
* @throws RuntimeException
* @throws DocumentException
* @throws IOException
*/
public static void html2pdf1(String html, FileOutputStream fileOutputStream) throws RuntimeException, DocumentException, IOException {
Document doc = new Document();
PdfWriter writer = PdfWriter.getInstance(doc, fileOutputStream);
// 添加页眉/页脚
// PDFPageHeadFootHelper headFooter = new PDFPageHeadFootHelper(doc);
// writer.setPageEvent(headFooter);
doc.open();
// 主要自定义img标签处理机制,html中的img标签图片存放分两种:uri/base64的
final TagProcessorFactory tagProcessorFactory = Tags.getHtmlTagProcessorFactory();
tagProcessorFactory.removeProcessor(HTML.Tag.IMG);
tagProcessorFactory.removeProcessor(HTML.Tag.TABLE);
tagProcessorFactory.addProcessor(new ImageTagProcessor(), HTML.Tag.IMG);
tagProcessorFactory.addProcessor(new TableTagProcessor(), HTML.Tag.TABLE);
// 自定义
InputStream cssInputStream = XMLWorkerHelper.class.getResourceAsStream("/default.css");
// 可通过ClassPathResource 加载资源路径下的文件
// ClassPathResource classPathResource = new ClassPathResource("template/my.css");
CssFile css = getCSS(cssInputStream);
XMLWorkerFontProvider xmlWorkerFontProvider = new XMLWorkerFontProvider();
CssFilesImpl cssFiles = new CssFilesImpl();
cssFiles.add(css);
StyleAttrCSSResolver cssResolver = new StyleAttrCSSResolver(cssFiles);
HtmlPipelineContext hpc = new HtmlPipelineContext(new CssAppliersImpl(xmlWorkerFontProvider));
// 特殊tag处理工厂
hpc.setAcceptUnknown(true).autoBookmark(true).setTagFactory(tagProcessorFactory).setResourcesRootPath(null);
HtmlPipeline htmlPipeline = new HtmlPipeline(hpc, new PdfWriterPipeline(doc, writer));
Pipeline<?> pipeline = new CssResolverPipeline(cssResolver, htmlPipeline);
XMLWorker worker = new XMLWorker(pipeline, true);
XMLParser p = new XMLParser(true, worker, StandardCharsets.UTF_8);
ByteArrayInputStream in = new ByteArrayInputStream(html.getBytes(StandardCharsets.UTF_8));
p.parse(in, StandardCharsets.UTF_8);
doc.close();
in.close();
}
/**
* pdf转成一张图片
*
* @param is pdf输入流
* @param outputStream 图片输出流
*/
private static void pdf2multiImage(InputStream is, OutputStream outputStream) {
try {
// 读取文件
PDDocument pdf = PDDocument.load(is);
int actSize = pdf.getNumberOfPages();
// 转为image对象
List<BufferedImage> piclist = new ArrayList<BufferedImage>();
for (int i = 0; i < actSize; i++) {
BufferedImage image = new PDFRenderer(pdf).renderImageWithDPI(i, 130, ImageType.RGB);
piclist.add(image);
}
// 图片输出
yPic(piclist, outputStream);
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
踩坑
Java BIO input/outputStream转换
- 可通过ByteArrayOutputStream/ByteArrayOutputStream进行流的方向转换
- ByteArray底层用字节数组承载全量二进制数据,因此这个方案要注意不能载入太大文件到内存
- Java自带的一些stream使用缓冲区解决把内存撑爆的问题,例如FileInputStream每次读取缓冲区大小的数据
Base64图片处理不支持
- 自定义ImageTagProcessor 处理base64格式
public class ImageTagProcessor extends com.itextpdf.tool.xml.html.Image {
@Override
public List<Element> end(final WorkerContext ctx, final Tag tag, final List<Element> currentContent) {
final Map<String, String> attributes = tag.getAttributes();
String src = attributes.get(HTML.Attribute.SRC);
List<Element> elements = new ArrayList<Element>(1);
if (null != src && src.length() > 0) {
Image img = null;
if (src.startsWith("data:image/")) {
final String base64Data = src.substring(src.indexOf(",") + 1);
try {
img = Image.getInstance(Base64.decode(base64Data));
} catch (Exception e) {
throw new RuntimeException(e);
}
if (img != null) {
try {
final HtmlPipelineContext htmlPipelineContext = getHtmlPipelineContext(ctx);
elements.add(getCssAppliers().apply(new Chunk((Image) getCssAppliers().apply(img, tag, htmlPipelineContext), 0, 0, true), tag,
htmlPipelineContext));
} catch (NoCustomContextException e) {
throw new RuntimeWorkerException(e);
}
}
}
if (img == null) {
elements = super.end(ctx, tag, currentContent);
}
}
return elements;
}
}
table自动分页错误
- 自定义TableTagProcessor 处理分页问题
public class TableTagProcessor extends com.itextpdf.tool.xml.html.table.Table {
@Override
protected PdfPTable intPdfPTable(int numberOfColumn) {
PdfPTable table = new PdfPTable(numberOfColumn);
table.setHorizontalAlignment(Element.ALIGN_LEFT);
table.setSplitLate(true);
// table.setSplitLate(false);
// table.setSplitRows(false);
return table;
}
}
服务器导出中文字体不支持
yum -y install fontconfig
fc-list :lang=zh
mkdir -p /usr/share/fonts/chinese
chmod -R 755 /usr/share/fonts/chinese
cd /usr/share/fonts/chinese/
# 上传字体
yum -y install mkfontscale
mkfontscale
fc-list :lang=zh
详细代码
https://github.com/luoziling/Design_Pattern/tree/master/src/priv/wzb/itext