itext 导出pdf,页码、目录

2019-03-15  本文已影响0人  o尐白

唉,搜个解决方式真难,网上各种版本,各种复制,关键用着还不爽,真有意思。
只好自己研究了,希望有个只需要关注业务代码的就好了。
底层类我放在了最下面。
不想说话,直接上代码吧、

maven版本

        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itextpdf</artifactId>
            <version>5.5.13</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itext-asian</artifactId>
            <version>5.2.0</version>
        </dependency>

仅导出页码code

这种只需要关注自己的业务代码就可以了,
底层类复制后,直接在writeBody中写自己的逻辑代码就好了

import com.alibaba.fastjson.JSONObject;
import com.itextpdf.text.Chapter;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.Section;
import com.itextpdf.text.pdf.PdfPTable;
import lombok.extern.slf4j.Slf4j;

import java.util.List;

/**
 * {
 *     "title":"班级表",
 *     "students":[
 *         {
 *             "id":1,
 *             "name":"小明",
 *             "age":12,
 *             "remark":"小明小明一把手雷弹"
 *         },
 *         {
 *             "id":2,
 *             "name":"小红",
 *             "age":12,
 *             "remark":"小红小红快来我加玩"
 *         }
 *     ],
 *     "teachers":[
 *         {
 *             "id":1,
 *             "name":"小铭",
 *             "age":22,
 *             "remark":"这老师是教...你猜"
 *         },
 *         {
 *             "id":2,
 *             "name":"小鸣",
 *             "age":23,
 *             "remark":"对不起,我只负责打孩纸"
 *         }
 *     ]
 * }
 */
@Slf4j
public class TestPdfView extends AbstractPdfView {

    private JSONObject json;

    private int chapterSeq = 1;             //书签序号

    public TestPdfView(JSONObject json) {
        this.json = json;
    }

    @Override
    protected void writeBody() throws Exception {
        buildTitle();
        //正文开始
        document.newPage();
        buildTeacher();
        buildStudent();
    }

    private void buildTitle() throws Exception {
        String title = json.getString("title");
        document.addTitle(title);
        document.newPage();
        PdfUtil.setLogo(document, title);
    }

    private void buildTeacher() throws Exception {
        Chapter chapter = getChapter("教师列表");

        List<JSONObject> teachers = json.getJSONArray("teachers").toJavaList(JSONObject.class);
        for (JSONObject j : teachers) {
            Section section = addSection(chapter, getKey(j,"name"));
            PdfPTable table = PdfUtil.getBorderTable(4, Rectangle.BOX);
            table.addCell(addFont10(getKey(j, "id")));
            table.addCell(addFont10(getKey(j, "name")));
            table.addCell(addFont10(getKey(j, "age")));
            table.addCell(addFont10(getKey(j, "remark")));
            document.add(table);
        }
    }

    private void buildStudent() throws Exception {
        Chapter chapter = getChapter("学生列表");

        List<JSONObject> students = json.getJSONArray("students").toJavaList(JSONObject.class);
        for (JSONObject j : students) {
            Section section = addSection(chapter, getKey(j,"name"));
            PdfPTable table = PdfUtil.getBorderTable(4, Rectangle.BOX);
            table.addCell(addFont10(getKey(j, "id")));
            table.addCell(addFont10(getKey(j, "name")));
            table.addCell(addFont10(getKey(j, "age")));
            table.addCell(addFont10(getKey(j, "remark")));
            document.add(table);
        }
    }

    private Chapter getChapter(String title) throws Exception {
        Chapter chapter = new Chapter(addFont12(title), chapterSeq++);
        chapter.setTriggerNewPage(false);
        chapter.setNumberStyle(Section.NUMBERSTYLE_DOTTED);
        document.add(chapter);

        return chapter;
    }

    private Section addSection(Chapter chapter, String title) throws Exception {
        Section section = chapter.addSection(addFont12(title));
        section.setIndentationLeft(8f);
        section.setNumberStyle(Section.NUMBERSTYLE_DOTTED);
        document.add(section);

        return section;
    }

    private String getKey(JSONObject j, String key) {
        return String.valueOf(j.get(key));
    }
}

效果图


image.png
image.png

导出目录

这个只关注业务代码其实也可以搞,麻烦些也可以实现,但我这人有个优点:懒,蟹蟹。

导出目录比较麻烦:试过几种方法,最终思路:
1.先导出正文,得到目录结构与其对应页码的缓存
2.导出目录到新的document中,得到目录占用的页数
3.合并俩个document,此时目录缓存的页码=原页码+目录总页码
注:该方法不支持点击目录跳转页码,所以可以使用标签来进行定位。
所以,标签中的值也需要重新计算。
不想说话,不想上代码,啊哈哈哈
效果图


image.png
image.png
image.png

好吧,代码不复杂,须重写抽象类的finish方法:
1.获取目录+标题应占用的页数
2.创建目录页(注意目录最后的页数应该是多少)
3.调用父类finish方法(注意reader的顺序)
因目录不支持点击跳转,所以替代方案使用标签
效果图

image.png

所以,如果要搞一个只需要关注业务逻辑的话,思路应该是:
1.有个缓存用于存放目录缓存
2.有个抽象标题方法,用户自定义标题,仅支持一页
3.添加一个根据目录缓存构造目录的方法
4.改造finish方法:合并标题、目录与正文,此处应重写目录对应的页码
5.欢迎大家试着写一下,最好留言给个链接,嘿嘿嘿嘿

工具类PdfUtil:

我喜欢所有排版都使用table,主要是简单方便还好用


import com.itextpdf.text.*;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfPTable;
import org.springframework.core.io.ClassPathResource;

public class PdfUtil {

    private static final int A4_WIDTH = 595;
    private static final int A4_HEIGHT = 842; 
    private static final int MARGIN_TOP = 36; 
    private static final int MARGIN_BOTTOM = 36;
    private static final int MARGIN_LEFT = 57; 
    private static final int MARGIN_RIGHT = 57; 

    public static BaseFont getBaseFont() {
        try {
            return BaseFont.createFont("/TTF/NotoSansCJKsc-Regular.otf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
        } catch (Exception e) {
            throw new MsgException("");
        }
    }

    public static Font getFont(float fontSize) {
        return new Font(getBaseFont(), fontSize, Font.NORMAL);
    }

    public static PdfPTable getNoBorderTable() {
        return getBorderTable(1, Rectangle.NO_BORDER);
    }

    public static PdfPTable getBorderTable(int numColumns, int border) {
        PdfPTable table = new PdfPTable(numColumns);
        table.setSpacingBefore(4f);
        table.setSpacingAfter(4f);
        table.setTotalWidth(getTableWidth());
        table.setLockedWidth(true);
        table.getDefaultCell().setBorder(border);
        table.getDefaultCell().setMinimumHeight(24f);
        table.getDefaultCell().setHorizontalAlignment(Element.ALIGN_LEFT);
        table.getDefaultCell().setVerticalAlignment(Element.ALIGN_MIDDLE);
        return table;
    }

    /**
     * 上面是图标,下面是pdf标题名称
     */
    public static void setLogo(Document document, String name) throws Exception {
        Image logo = Image.getInstance(new ClassPathResource("/pdf/logo.png").getURL());

        PdfPTable t = getNoBorderTable();
        t.getDefaultCell().setVerticalAlignment(Element.ALIGN_BOTTOM);
        t.getDefaultCell().setHorizontalAlignment(Element.ALIGN_CENTER);
        t.getDefaultCell().setFixedHeight(150f);//表格固定行高
        t.addCell(logo);
        t.addCell(new Paragraph(name, getFont(16f)));
        document.add(t);
    }

    public static float getTableWidth() {
        return A4_WIDTH - MARGIN_LEFT - MARGIN_RIGHT;
    }

}

公用抽象类

import com.itextpdf.text.*;
import com.itextpdf.text.pdf.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;

public abstract class AbstractPdfView {
    private final Logger log = LoggerFactory.getLogger(AbstractPdfView.class);

    protected Document document;
    protected PdfWriter writer;

    protected final int size = 2048;
    protected Font font8 = PdfUtil.getFont(8f);     //页码用
    protected Font font10 = PdfUtil.getFont(10f);   //正文用
    protected Font font12 = PdfUtil.getFont(12f);   //标题用

    public void start(HttpServletResponse response) {
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream(size);
            document = new Document(PageSize.A4);
            writer = PdfWriter.getInstance(document, baos);
            prepare();
            document.open();
            writeBody();
            document.close();

            List<PdfReader> readers = new ArrayList<>();
            readers.add(new PdfReader(baos.toByteArray()));
            baos = finish(readers);

            // flush to http response.
            flushStream(baos, response);

        } catch (Exception e) {
            log.error("导出pdf文档失败", e);
            throw new MsgException("导出pdf文档失败");
        }
    }

    /**
     * 添加监听事件请重写该方法
     * 如:writer.setPageEvent(PageHelper...),这种无须finish方法
     */
    protected void prepare() throws Exception {
        writer.setViewerPreferences(PdfWriter.ALLOW_PRINTING | PdfWriter.PageLayoutSinglePage);
    }

    protected abstract void writeBody() throws Exception;

    /**
     * 完成pdf,写入页码
     */
    protected ByteArrayOutputStream finish(List<PdfReader> readers) throws Exception {
        ByteArrayOutputStream out = new ByteArrayOutputStream(size);
        Document doc = new Document(PageSize.A4);
        PdfCopy copy = new PdfCopy(doc, out);
        doc.open();

        int currentPage = 1, totalPage = readers.stream().mapToInt(PdfReader::getNumberOfPages).sum();
        for (PdfReader reader : readers) {
            PdfImportedPage page;
            PdfCopy.PageStamp stamp;
            int pages = reader.getNumberOfPages();
            for (int index = 1; index <= pages; index++) {
                doc.newPage();
                page = copy.getImportedPage(reader, index);
                stamp = copy.createPageStamp(page);
                ColumnText.showTextAligned(stamp.getUnderContent(), Element.ALIGN_CENTER,
                        new Phrase(addFont8(String.format("第%d页,共%d页", currentPage++, totalPage))), 300f, 16f, 0f);
                stamp.alterContents();

                copy.addPage(page);
            }

            reader.close();
        }

        doc.close();
        copy.close();

        return out;
    }

    private void flushStream(ByteArrayOutputStream baos, HttpServletResponse response) throws Exception {
        response.reset();
        response.setHeader("Cache-Control", "no-cache");
        response.setHeader("Pragma", "no-cache");
        response.setDateHeader("Expires", 0);
        response.setHeader("Content-disposition", "inline; filename=" + RandomUtil.uuid() + ".pdf");
        response.setCharacterEncoding(Charset.forName("UTF-8").name());
        response.setContentType(MediaType.APPLICATION_PDF_VALUE);
        response.setContentLength(baos.size());
        ServletOutputStream out = response.getOutputStream();
        baos.writeTo(out);
        out.flush();
    }

    protected Paragraph addFont8(String content) {
        return addText(content, font8);
    }

    protected Paragraph addFont10(String content) {
        return addText(content, font10);
    }

    protected Paragraph addFont12(String content) {
        return addText(content, font12);
    }

    private Paragraph addText(String content, Font font) {
        Paragraph paragraph = new Paragraph(content, font);
        paragraph.setAlignment(Element.ALIGN_LEFT);
        return paragraph;
    }
}

对了,logo.png是放在resources里面的, 我直接截的百度图
字体呢,我用的是思源宋体,大家自己搜索,随便下好了。
有些地方没有给,一些大家直接就能猜到,一些就是我故意的,就是留那么一手防复制党,就是辣么jian,呵。
拷过去只需要关注pdf的正文代码和样式代码就好了

上一篇 下一篇

猜你喜欢

热点阅读