Java通过itextpdf生成PDF

通过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();
    }
}

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值