通过Java生成PDF我用的是itextpdf,需要引入的pop坐标如下
<!-- https://mvnrepository.com/artifact/com.itextpdf/itextpdf -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.itextpdf/itext-asian -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
我是通过接口的方式在浏览器显示pdf,类继承PdfPageEventHelper 这个类
@RequestMapping
@RestController
public class GetPdfByItextPdf extends PdfPageEventHelper {
}
重写自己需要的方法,我重谢了三个方法
1:onOpenDocument 我用这个方法初始化PdfTemplate,这个是文件打开的时候就会触发的方法,所以在这个里面初始化一些变量
2:onEndPage 这个是一页PDF完毕的时候会触发的方法,比如我需要再PDF里面加页眉,加当前是第几页的时候,在这个方法里面进行。
3:onCloseDocument 这个是pdf制作完成了的时候,关闭之前触发,这个可以帮我们加上一些内容,比如在每页都弄上这个pdf一共有多少页。目前我用到的是这三个方法,并且感觉比较好用。
先写个接口,再来看是怎么触发上面三个方法的
我是通过接口的HttpServletResponse 输出PDF 的,所以可以浏览器调接口看,也可以直接将pdf文件保存到本地
@GetMapping("createPdf")
public void createPdf(HttpServletResponse response) throws IOException, DocumentException {
// 定义一个pdf 的文件,这个文件包括PDF 的大小,内容所在的位置,这个跟网页的 margin 是一样的,自己设置大小
Document document = new Document(PageSize.A4, 80, 40, 90, 50);
// 对返回的response 进行设置一下 这样的话浏览器就会识别是PDF文件
response.setContentType("application/pdf");
// 得到一个PDF的输出流 这里可以换成是电脑上的具体路径
// PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream("E:\\myTestPdf.pdf"));
PdfWriter pdfWriter = PdfWriter.getInstance(document, response.getOutputStream());
document.open();
document.close();
}
在document.open() 和document.close() 中间添加内容就可以了,我这里没加东西,所以会报个错。
2020-08-17 16:35:37.210 ERROR 11316 --- [io-55555-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is ExceptionConverter: java.io.IOException: The document has no pages.] with root cause
java.io.IOException: The document has no pages.
at com.itextpdf.text.pdf.PdfPages.writePageTree(PdfPages.java:112) ~[itextpdf-5.5.13.jar:5.5.13]
但是这样的话就不会触发我上面提到的三个方法,要想使onOpenDocument 这个方法生效,需要pdfWriter 上面添加事件
@GetMapping("createPdf")
public void createPdf(HttpServletResponse response) throws IOException, DocumentException {
// 定义一个pdf 的文件,这个文件包括PDF 的大小,内容所在的位置,这个跟网页的 margin 是一样的,自己设置大小
Document document = new Document(PageSize.A4, 80, 40, 90, 50);
// 对返回的response 进行设置一下 这样的话浏览器就会识别是PDF文件
response.setContentType("application/pdf");
// 得到一个PDF的输出流 这里可以换成是电脑上的具体路径
// PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream("E:\\myTestPdf.pdf"));
PdfWriter pdfWriter = PdfWriter.getInstance(document, response.getOutputStream());
pdfWriter.setPageEvent(new GetPdfByItextPdf());
document.open();
document.close();
}
只有设置了 pdfWriter.setPageEvent(new GetPdfByItextPdf());这个才能使onOpenDocument onEndPage onCloseDocument 这三个方法生效。
在onOpenDocument 这个里面设置文件打开时要完成的动作,初始化方法
package mybatisplus.google.pdf;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.ExceptionConverter;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfPageEventHelper;
import com.itextpdf.text.pdf.PdfTemplate;
import com.itextpdf.text.pdf.PdfWriter;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 包: mybatisplus.google.pdf
* 作者: climber.luo
* 时间:2020.08.17 15:14
*/
@RequestMapping
@RestController
public class GetPdfByItextPdf extends PdfPageEventHelper {
public PdfTemplate tpl;
public BaseFont bf;
@GetMapping("createPdf")
public void createPdf(HttpServletResponse response) throws IOException, DocumentException {
// 定义一个pdf 的文件,这个文件包括PDF 的大小,内容所在的位置,这个跟网页的 margin 是一样的,自己设置大小
Document document = new Document(PageSize.A4, 80, 40, 90, 50);
// 对返回的response 进行设置一下 这样的话浏览器就会识别是PDF文件
response.setContentType("application/pdf");
// 得到一个PDF的输出流 这里可以换成是电脑上的具体路径
// PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream("E:\\myTestPdf.pdf"));
PdfWriter pdfWriter = PdfWriter.getInstance(document, response.getOutputStream());
pdfWriter.setPageEvent(new GetPdfByItextPdf());
document.open();
// pdf 页面加点东西 加入一行字
document.add(new Paragraph("这是一个测试",new com.itextpdf.text.Font(bfChinese, 18, com.itextpdf.text.Font.BOLD)));
document.close();
}
@Override
public void onOpenDocument(PdfWriter writer, Document document) {
super.onOpenDocument(writer, document);
try {
// 初始化模板,模板的宽和高自己设定, 初始化一个字体,这两个参数其实可以用代码块实现
tpl = writer.getDirectContent().createTemplate(100, 100);
bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
} catch (Exception e) {
throw new ExceptionConverter(e);
}
}
}
再来设置一下PDF的页眉和页脚,这个我是在onEndPage 这个方法中实现的,可以在这里划线和引入图片logo啥的,我的项目里是需要引入logo和公司的基本信息放在页眉,这里我简单的画两个线。
@Override
public void onEndPage(PdfWriter writer, Document document) {
super.onEndPage(writer, document);
float center = document.getPageSize().getRight() / 2;//页面的水平中点
float bottom = document.getPageSize().getBottom() + 20;
//在每页结束的时候把“第x页”信息写道模版指定位置
PdfContentByte cb = writer.getDirectContent();
// 线的宽度
cb.setLineWidth(1f);
// 线的起点,坐标这个是以左下角为原点的
cb.moveTo(70, 760);
// 线的终点
cb.lineTo(550, 760);
// stroke一下
cb.stroke();
cb.moveTo(70, 40);
cb.lineTo(550, 40);
cb.stroke();
// cb.saveState(); saveState只能一次,不然会报错
// 获得当前页
String text = writer.getPageNumber() + "/";
cb.beginText();
cb.setFontAndSize(bf, 8);
cb.setTextMatrix(center, bottom);//定位“第x页,共” 在具体的页面调试时候需要更改这xy的坐标
cb.showText(text);
cb.endText();
cb.addTemplate(tpl, center + 8, bottom);//定位“y页” 在具体的页面调试时候需要更改这xy的坐标
cb.stroke();
cb.saveState();
// 这个不能掉
cb.restoreState();
cb.closePath();
}
cb.saveState() 这个只能调用一次,cb.restoreState() 不能缺,不然的话会报错。
2020-08-17 16:14:43.497 ERROR 12700 --- [io-55555-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is com.itextpdf.text.exceptions.IllegalPdfSyntaxException: Unbalanced save/restore state operators.] with root cause
com.itextpdf.text.exceptions.IllegalPdfSyntaxException: Unbalanced save/restore state operators.
at com.itextpdf.text.pdf.PdfContentByte.sanityCheck(PdfContentByte.java:4193) ~[itextpdf-5.5.13.jar:5.5.13]
at com.itextpdf.text.pdf.PdfContentByte.reset(PdfContentByte.java:1813) ~[itextpdf-5.5.13.jar:5.5.13]
at com.itextpdf.text.pdf.PdfContentByte.reset(PdfContentByte.java:1801) ~[itextpdf-5.5.13.jar:5.5.13]
到了上面的步骤就可以得到一张A4的pdf ,但是不会有总页数,我是在onCloseDocument 这个方法中添加总页数的。
@Override
public void onCloseDocument(PdfWriter writer, Document document) {
//关闭document的时候获取总页数,并把总页数按模版写道之前预留的位置
tpl.beginText();
tpl.setFontAndSize(bf, 8);
tpl.showText(Integer.toString(writer.getPageNumber()));
tpl.endText();
tpl.closePath();//sanityCheck();
}
好了,最后将完整的demo 代码贴出来,这个类可以改为main() 启动,只是需要将HttpServletResponse 参数去掉。
完整代码
package mybatisplus.google.pdf;
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 包: mybatisplus.google.pdf
* 作者: climber.luo
* 时间:2020.08.17 15:14
*/
@RequestMapping
@RestController
public class GetPdfByItextPdf extends PdfPageEventHelper {
public PdfTemplate tpl;
public BaseFont bf;
// 这个是代码块 初始化一些参数
private static BaseFont bfChinese;
static {
try {
bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
} catch (DocumentException | IOException e) {
e.printStackTrace();
}
}
@GetMapping("createPdf")
public void createPdf(HttpServletResponse response) throws IOException, DocumentException {
// 定义一个pdf 的文件,这个文件包括PDF 的大小,内容所在的位置,这个跟网页的 margin 是一样的,自己设置大小
Document document = new Document(PageSize.A4, 80, 40, 90, 50);
// 对返回的response 进行设置一下 这样的话浏览器就会识别是PDF文件
response.setContentType("application/pdf");
// 得到一个PDF的输出流 这里可以换成是电脑上的具体路径
// PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream("E:\\myTestPdf.pdf"));
PdfWriter pdfWriter = PdfWriter.getInstance(document, response.getOutputStream());
pdfWriter.setPageEvent(new GetPdfByItextPdf());
document.open();
document.add(new Paragraph("这是一个测试",new com.itextpdf.text.Font(bfChinese, 18, com.itextpdf.text.Font.BOLD)));
document.close();
}
@Override
public void onOpenDocument(PdfWriter writer, Document document) {
try {
// 初始化模板,模板的宽和高自己设定, 初始化一个字体,这个其实可以用 代码块实现
tpl = writer.getDirectContent().createTemplate(100, 100);
bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
} catch (Exception e) {
throw new ExceptionConverter(e);
}
}
@Override
public void onEndPage(PdfWriter writer, Document document) {
float center = document.getPageSize().getRight() / 2;//页面的水平中点
float bottom = document.getPageSize().getBottom() + 20;
//在每页结束的时候把“第x页”信息写道模版指定位置
PdfContentByte cb = writer.getDirectContent();
// 线的宽度
cb.setLineWidth(1f);
// 线的起点,坐标这个是以左下角为原点的
cb.moveTo(70, 760);
// 线的终点
cb.lineTo(550, 760);
// stroke一下
cb.stroke();
cb.moveTo(70, 40);
cb.lineTo(550, 40);
cb.stroke();
// cb.saveState(); saveState只能一次,不然会报错
// 获得当前页
String text = writer.getPageNumber() + "/";
cb.beginText();
cb.setFontAndSize(bf, 8);
cb.setTextMatrix(center, bottom);//定位“第x页,共” 在具体的页面调试时候需要更改这xy的坐标
cb.showText(text);
cb.endText();
cb.addTemplate(tpl, center + 8, bottom);//定位“y页” 在具体的页面调试时候需要更改这xy的坐标
cb.stroke();
cb.saveState();
cb.restoreState();
cb.closePath();
}
@Override
public void onCloseDocument(PdfWriter writer, Document document) {
//关闭document的时候获取总页数,并把总页数按模版写道之前预留的位置
tpl.beginText();
tpl.setFontAndSize(bf, 8);
tpl.showText(Integer.toString(writer.getPageNumber()));
tpl.endText();
tpl.closePath();//sanityCheck();
}
}