项目最近需要动态的生成pdf文件。一开始采用的是牛逼好用的Itext,但是后来由于Itext的license协议是GPL的。所以最终决定用pdfbox来替换掉它;
在研究pdfbox的过程中,发现pdfbox跟Itext差距不是一般的大,pdfbox提供的都是一些基础的“画pdf”的方法。对此,为了项目和日常使用的方便,简单封装了一个utils工具类。
public class PdfBoxUtils {
/**
* 插入一张图片
* @param document
* @param contentStream 输出流
* @param imgFile 图片文件
* @param xStart x主标
* @param yStart y主标
* @param width 图片宽
* @param hight 图片高
* @throws IOException
*/
public static void drawImage(PDDocument document, PDPageContentStream contentStream, File imgFile, float xStart, float yStart, float width, float hight) throws IOException {
PDImageXObject pdImage = PDImageXObject.createFromFileByContent(imgFile, document);
contentStream.drawImage(pdImage, xStart, yStart, width, hight );
}
/**
* 画一条线
* @param contentStream
* @param xStart
* @param yStart
* @param xEnd
* @param yEnd
* @throws IOException
*/
public static void drawLine(PDPageContentStream contentStream, float xStart, float yStart, float xEnd, float yEnd) throws IOException {
contentStream.moveTo(xStart, yStart);
contentStream.lineTo(xEnd, yEnd);
contentStream.stroke();
}
/**
* 定义文本输出流开始
* @param contentStream
* @param leading 行距
* @param offSetX 第一行的x坐标间距
* @param offSetY 第一行的y主表间距
* @throws IOException
*/
public static void beginTextSteam(PDPageContentStream contentStream, Float leading, Float offSetX, Float offSetY) throws IOException {
// 输出流开始
contentStream.beginText();
// 行间距
contentStream.setLeading(leading);
// 书写行定位
contentStream.newLineAtOffset(offSetX, offSetY);
}
/**
* 定义文本输出流结束
* @param contentStream
* @throws IOException
*/
public static void endTextSteam(PDPageContentStream contentStream) throws IOException {
contentStream.endText();
}
/**
* 创建一定数量的空行
* @param contentStream
* @param emptyNum
* @throws IOException
*/
public static void createEmptyParagraph(PDPageContentStream contentStream, Integer emptyNum) throws IOException {
for (int i = 0; i < emptyNum; i++) {
contentStream.newLine();
}
}
/**
* 写一个段落
* @param contentStream
* @param text
* @throws IOException
*/
public static void drawParagraph(PDPageContentStream contentStream, String text) throws IOException {
contentStream.showText(text);
contentStream.newLine();
}
/**
* 写一个段落并设定字体
* @param contentStream
* @param text
* @throws IOException
*/
public static void drawParagraph(PDPageContentStream contentStream, String text, PDFont font, Integer fontSize) throws IOException {
contentStream.setFont(font, fontSize);
contentStream.showText(text);
contentStream.newLine();
}
/**
* 将html转为pdf文件(中文)
* <p>采用的是openhtmltopdf插件</p>
* <p>注意html中每一个标签必须要有结尾标签</p>
* <p>如果要解决中文乱码问题,请在<head></head>标签中加上css样式
* <style>
* *{
* font-family: arialuni(这里填上对应的中文字体名)
* }
* </style></p>
* @param outputStream pdf文件输出流
* @param htmlFile html文件
* @param fontInputStream 字体文件输出流(自定义字体文件,解决中文乱码问题)如:simsun.tff(宋体)
* @param fontFamily 字体名,如:simsun(宋体)
* @throws IOException
*/
public static void convertHtmlToPdf(OutputStream outputStream, File htmlFile, InputStream fontInputStream, String fontFamily) throws IOException {
PdfRendererBuilder builder = new PdfRendererBuilder();
builder.useFastMode();
builder.useFont(setFSFont(fontInputStream), fontFamily);
builder.withFile(htmlFile);
builder.toStream(outputStream);
builder.run();
}
/**
* 将html转为pdf文件(中文)
* <p>采用的是openhtmltopdf插件</p>
* <p>注意html中每一个标签必须要有结尾标签</p>
* <p>如果要解决中文乱码问题,请在<head></head>标签中加上css样式
* <style>
* *{
* font-family: arialuni(这里填上对应的中文字体名)
* }
* </style></p>
* @param outputStream pdf文件输出流
* @param htmlFileStr Provides a string containing XHTML/XML to convert to PDF.
* @param fontInputStream 字体文件输出流(自定义字体文件,解决中文乱码问题)如:simsun.tff(宋体)
* @param fontFamily 字体名,如:simsun(宋体)
* @throws IOException
*/
public static void convertHtmlToPdf(OutputStream outputStream, String htmlFileStr, InputStream fontInputStream, String fontFamily) throws IOException {
PdfRendererBuilder builder = new PdfRendererBuilder();
builder.useFastMode();
builder.useFont(setFSFont(fontInputStream), fontFamily);
builder.withHtmlContent(htmlFileStr, null);
builder.toStream(outputStream);
builder.run();
}
/**
* 将html转为pdf文件
* <p>采用的是openhtmltopdf插件</p>
* <p>注意html中每一个标签必须要有结尾标签</p>
* @param outputStream pdf文件输出流
* @param htmlFile html文件
* @throws IOException
*/
public static void convertHtmlToPdf(OutputStream outputStream, File htmlFile) throws IOException {
PdfRendererBuilder builder = new PdfRendererBuilder();
builder.useFastMode();
builder.withFile(htmlFile);
builder.toStream(outputStream);
builder.run();
}
/**
* 返回自定义字体
* @param fontInputStream
* @return
*/
private static FSSupplier<InputStream> setFSFont(InputStream fontInputStream) {
return () -> fontInputStream;
}
上面这些代码引用的是pdfbox的jar和openHtmlToPdf的jar。其实只引用openHtmlToPdf就完全够了,因为openHtmlToPdf是一个基于pdfbox封装的处理html==>pdf的jar,里头提供了pdfbox。我实际是直接引用了openHtmlToPdf,如果项目中没有涉及到html转pdf,直接引用pdfbox的jar就行了。
需要html转pdf
<!-- https://mvnrepository.com/artifact/com.openhtmltopdf/openhtmltopdf-core -->
<dependency>
<groupId>com.openhtmltopdf</groupId>
<artifactId>openhtmltopdf-core</artifactId>
<version>1.0.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.openhtmltopdf/openhtmltopdf-pdfbox -->
<dependency>
<groupId>com.openhtmltopdf</groupId>
<artifactId>openhtmltopdf-pdfbox</artifactId>
<version>1.0.4</version>
</dependency>
只需要pdfbox
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>fontbox</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>xmpbox</artifactId>
<version>2.0.0</version>
</dependency>
pdfbox写pdf中文乱码的问题
pdfbox如果写入中文,会出现不显示/乱码的问题,解决的办法最直接的是引入自己下载好的字体;
PDDocument document = new PDDocument();
PDType0Font font = PDType0Font.load(document, new FileInputStream(new File("d:\\tmp\\arialuni.ttf")));
实例demo
private PDRectangle pageSize = PDRectangle.A4;
private Integer marginX = 50;
private Integer marginY = 50;
@Test
public void test1() throws IOException {
PDDocument document = new PDDocument();
PDType0Font font = PDType0Font.load(document, new FileInputStream(new File("d:\\tmp\\arialuni.ttf")));
drawFirstPage(document, font);
// drawSecondPage(document, font);
document.save(new FileOutputStream(new File("d:\\tmp\\test2.pdf")));
document.close();
}
private void drawFirstPage(PDDocument document, PDType0Font font) throws IOException {
PDPage pdPage = new PDPage(pageSize);
document.addPage(pdPage);
PDPageContentStream contentStream = new PDPageContentStream(document, pdPage);
PdfBoxUtils.beginTextSteam(contentStream, 20f, marginX.floatValue(), pageSize.getHeight()-(2*marginY));
// 书写信息
PdfBoxUtils.drawParagraph(contentStream, "物流单摘要", font, 18);
PdfBoxUtils.createEmptyParagraph(contentStream, 2);
contentStream.setFont(font, 13);
PdfBoxUtils.drawParagraph(contentStream, "物流单号:\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a02022099");
PdfBoxUtils.drawParagraph(contentStream, "结算时间段:\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0从20200909到20200807");
PdfBoxUtils.drawParagraph(contentStream, "商品总数量(件):\u00a0100000");
PdfBoxUtils.drawParagraph(contentStream, "商品总价格(元):\u00a0100000000000");
PdfBoxUtils.drawParagraph(contentStream, "买卖人名称:\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0李白");
PdfBoxUtils.createEmptyParagraph(contentStream, 4);
PdfBoxUtils.drawParagraph(contentStream, "公司(盖章):");
PdfBoxUtils.createEmptyParagraph(contentStream, 2);
contentStream.showText("日期:");
PdfBoxUtils.createEmptyParagraph(contentStream, 16);
contentStream.newLineAtOffset(195, 0);
PdfBoxUtils.drawParagraph(contentStream, "小熊猫超级防伪码", font, 12);
PdfBoxUtils.endTextSteam(contentStream);
// 划线
PdfBoxUtils.drawLine(contentStream, marginX, 545, PDRectangle.A4.getWidth() - marginX, 545);
PdfBoxUtils.drawLine(contentStream, marginX, 410, PDRectangle.A4.getWidth() - marginX, 410);
// 贴图
PdfBoxUtils.drawImage(document, contentStream, new File("d:\\tmp\\条形码测试.png"),
(pageSize.getWidth()/2)-80, 150, 160, 160);
contentStream.close();
}
pdf效果
image.png
如何用pdfbox画table表格
与Itext不同,pdfbox并没有直接提供画table的方法;要想用pdfbox生成table,需要我们自己用它的grid来画;这里头我也封装了一个可用的table,在这片文章篇幅问题我就不引入,我会接下来在我另一篇文章专门介绍。(ps:第一次在简书发表文章,如有不足欢迎指出~笑脸)