PdfBox品尝(II) TABLE组件的封装

为何使用pdfbox

        pdfbox 是一个java操作pdf的工具。相信有经验的大家会说,操作pdf最六的不是Itext吗?各种对于pdf常用的方法应有尽有;确实,Itext和pdfbox我都使用过,Itext确实好用的多的多,也封装了非常多实用的方法。而pdfbox却显得弱的多,都是一些基础的方法,并且对于英语一般的国内小伙伴来说,在网上pdfbox的资料还非常的少。
        但是无奈,在项目研发的技术选型中,咱们不能只优先考虑方便。
        Itext的License是GPL协议的,实际项目使用它的话,发布需要公开自己的所有源码,或者支付一些money;所以咱们有时还是需要pdfbox这个不起眼的小伙伴的。

pdfbox基础操作与封装

pdfbox的一些基础操作可以看我上一篇文章: PdfBox品尝(一) 常用方法的简单封装;
下面的代码中也有一些引用了上一篇文章我自己封装的简单工具类

pdfbox table

        如果做一些报表或者同级的话,我们时常需要在pdf中列一个表格。但是pdfbox并没有提供table方法,需要咱们自己封装;我这边封装了一个简单的table组件

table 的列实体
import lombok.AllArgsConstructor;
import lombok.Data;

/**
 * @author Panda
 */
@Data
@AllArgsConstructor
public class Column {
    private String name;
    private float width;
}

table 实体
import lombok.Data;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDFont;

import java.util.ArrayList;
import java.util.List;

/**
 * @author Panda
 */
@Data
public class Table {
    /**
     * Table 位置
     */
    private float margin;
    private float height;
    private PDRectangle pageSize;
    private float rowHeight;

    /**
     * table 字体
     */
    private PDFont textFont;
    private float fontSize;

    /**
     * table 内容
     */
    private Integer numberOfRows;
    private List<Column> header;
    private List<List<String>> records;
    private float cellMargin;


    public float getWidth() {
        float tableWidth = 0f;
        for (Column column : header) {
            tableWidth += column.getWidth();
        }
        return tableWidth;
    }

    public List<String> getColumnsNamesAsArray() {
        List<String> columnNames = new ArrayList<>(getNumberOfColumns());
        header.forEach(e -> columnNames.add(e.getName()));
        return columnNames;
    }

    public Integer getNumberOfColumns() {
        return this.getHeader().size();
    }

    public Integer getNumberOfRows() {
        return this.records.size();
    }

    /**
     * 获取page显示多少行数据
     * @return
     */
    public Integer getRowsPerPage() {
        return new Double(Math.floor(this.getHeight() / this.getRowHeight())).intValue() - 1;
    }
}
table 构造器
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDFont;

import java.util.List;

/**
 * @author Panda
 */
public class TableBuilder {
    private Table table = new Table();

    public TableBuilder setHeight(float height) {
        table.setHeight(height);
        return this;
    }

    public TableBuilder setNumberOfRows(Integer numberOfRows) {
        table.setNumberOfRows(numberOfRows);
        return this;
    }

    public TableBuilder setRowHeight(float rowHeight) {
        table.setRowHeight(rowHeight);
        return this;
    }

    public TableBuilder setContent(List<List<String>> content) {
        table.setRecords(content);
        return this;
    }



    public TableBuilder setColumns(List<Column> columns) {
        table.setHeader(columns);
        return this;
    }

    public TableBuilder setCellMargin(float cellMargin) {
        table.setCellMargin(cellMargin);
        return this;
    }

    public TableBuilder setMargin(float margin) {
        table.setMargin(margin);
        return this;
    }

    public TableBuilder setPageSize(PDRectangle pageSize) {
        table.setPageSize(pageSize);
        return this;
    }

    public TableBuilder setTextFont(PDFont textFont) {
        table.setTextFont(textFont);
        return this;
    }

    public TableBuilder setFontSize(float fontSize) {
        table.setFontSize(fontSize);
        return this;
    }

    public Table build() {
        return table;
    }
}
pdf 中放置table的第一页实体

因为封装的这个table实现了超过了pdf页高后会自动分页,而且默认是一页一个table开始。如果你要将自己的table放在某个有其它内容的页面中;需要将这个页的table实体,也就是第一页的table实体

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;

/**
 * @author Andy
 */
@Data
public class FirstTablePage {

    private PDPage firstPdPage;

    @ApiModelProperty("第一页显示的数据条数")
    private Integer dataNum;

    private Float margin;

    private PDPageContentStream contentStream;

}
table生成器
import com.zmg.panda.utils.pdfbox.PdfBoxUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * @author Andy
 */
public class PdfTableGenerator {

    /**
     * 生成table,无main业务首页
     * @param document
     * @param table
     * @throws IOException
     */
    public void generatePDF(PDDocument document, Table table) throws IOException{
        // 每页的行数
        Integer rowsPerPage = table.getRowsPerPage();
        // 计算需要多少页
        int numberOfPages = new Double(Math.ceil(table.getNumberOfRows().floatValue() / rowsPerPage)).intValue();

        // 生成每一页
        generateEachPage(document, table, rowsPerPage, numberOfPages);
    }

    /**
     * 生成table,含main业务首页
     * @param doc
     * @param firstTablePage main业务首页
     * @param table
     * @throws IOException
     */
    public void drawTableCustom(PDDocument doc, FirstTablePage firstTablePage, Table table) throws IOException {
        // 处理第一页是和业务相关,非独立的
        if (firstTablePage != null) {
            handleMainPage(firstTablePage, table);
        }
        // 每页的行数
        int rowsPerPage = table.getRowsPerPage();
        // 计算需要多少页
        int numberOfPages = new Double(Math.ceil(table.getNumberOfRows().floatValue() / rowsPerPage)).intValue();
        // 剩下的的页
        generateEachPage(doc, table, rowsPerPage, numberOfPages);
    }

    /**
     * 处理pdf拥有table的第一页
     * @param firstTablePage
     * @param table
     * @throws IOException
     */
    private void handleMainPage(FirstTablePage firstTablePage, Table table) throws IOException {
        Integer dataNum = firstTablePage.getDataNum();
        PDPageContentStream contentStream = firstTablePage.getContentStream();
        contentStream.setFont(table.getTextFont(), table.getFontSize());
        List<List<String>> content = table.getRecords();
        dataNum = dataNum > content.size() ? content.size() : dataNum;
        List<List<String>> firstPageContent = new ArrayList<>(dataNum);
        Iterator<List<String>> iterator = content.iterator();
        int index = 0;
        while (iterator.hasNext()) {
            List<String> next = iterator.next();
            firstPageContent.add(next);
            iterator.remove();
            index ++;
            if (index >= dataNum) {
                break;
            }
        }
        table.setRecords(content);
        drawFirstCurrentPage(table, firstPageContent, contentStream, firstTablePage.getMargin());
    }

    /**
     * 遍历自动生成page
     * @param doc
     * @param table
     * @param rowsPerPage
     * @param numberOfPages
     * @throws IOException
     */
    private void generateEachPage(PDDocument doc, Table table, Integer rowsPerPage, int numberOfPages) throws IOException {
        for (int pageCount = 0; pageCount < numberOfPages; pageCount++) {
            PDPage page = generatePage(doc, table);
            PDPageContentStream contentStream = generateContentStream(doc, page, table);
            List<List<String>> currentPageContent = getContentForCurrentPage(table, rowsPerPage, pageCount);
            drawCurrentPage(table, currentPageContent, contentStream);
        }
    }

    /**
     * 写页面
     * @param table
     * @param currentPageContent
     * @param contentStream
     * @throws IOException
     */
    private void drawCurrentPage(Table table, List<List<String>> currentPageContent, PDPageContentStream contentStream)
            throws IOException {
        float tableTopY = table.getPageSize().getHeight() - table.getMargin();
        drawPage(table, currentPageContent, contentStream, tableTopY);
    }

    /**
     * 在页面中写入table
     * @param table
     * @param currentPageContent
     * @param contentStream
     * @param tableTopY
     * @throws IOException
     */
    private void drawPage(Table table, List<List<String>> currentPageContent, PDPageContentStream contentStream, float tableTopY) throws IOException {
        // 给table画网格
        drawTableGrid(table, currentPageContent, contentStream, tableTopY);

        // 游标开始点
        float nextTextX = table.getMargin() + table.getCellMargin();
        // 考虑字体高度计算单元格中文本的中心对齐方式
        float nextTextY = tableTopY - (table.getRowHeight() / 2)
                - ((table.getTextFont().getFontDescriptor().getFontBoundingBox().getHeight() / 1000 * table.getFontSize()) / 4);

        // 写入table的表头
        writeContentLine(table.getColumnsNamesAsArray(), contentStream, nextTextX, nextTextY, table);
        nextTextY -= table.getRowHeight();
        nextTextX = table.getMargin() + table.getCellMargin();

        // 写入表数据
        for (int i = 0; i < currentPageContent.size(); i++) {
            writeContentLine(currentPageContent.get(i), contentStream, nextTextX, nextTextY, table);
            nextTextY -= table.getRowHeight();
            nextTextX = table.getMargin() + table.getCellMargin();
        }

        contentStream.close();
    }

    /**
     * 写入含有业务的第一页数据
     * @param table
     * @param currentPageContent
     * @param contentStream
     * @throws IOException
     */
    private void drawFirstCurrentPage(Table table, List<List<String>> currentPageContent, PDPageContentStream contentStream, Float margin)
            throws IOException {
        float tableTopY = table.getPageSize().getHeight() - table.getMargin() - margin;

        // 在页面中写入table
        drawPage(table, currentPageContent, contentStream, tableTopY);
    }

    /**
     * 为table每一行写入数据
     * @param lineContent
     * @param contentStream
     * @param nextTextX
     * @param nextTextY
     * @param table
     * @throws IOException
     */
    private void writeContentLine(List<String> lineContent, PDPageContentStream contentStream, float nextTextX, float nextTextY,
                                  Table table) throws IOException {
        for (int i = 0; i < table.getNumberOfColumns(); i++) {
            String text = lineContent.get(i);
            contentStream.beginText();
            contentStream.newLineAtOffset(nextTextX, nextTextY);
            contentStream.showText(text != null ? text : "");
            contentStream.endText();
            nextTextX += table.getHeader().get(i).getWidth();
        }
    }

    /**
     * 画页面中的table网格
     * @param table
     * @param currentPageContent
     * @param contentStream
     * @param tableTopY
     * @throws IOException
     */
    private void drawTableGrid(Table table, List<List<String>> currentPageContent, PDPageContentStream contentStream, float tableTopY)
            throws IOException {
        // 画行线
        float nextY = tableTopY;
        for (int i = 0; i <= currentPageContent.size() + 1; i++) {
            PdfBoxUtils.drawLine(contentStream, table.getMargin(), nextY, table.getMargin() + table.getWidth(), nextY);
            nextY -= table.getRowHeight();
        }

        // 画列线
        final float tableYLength = table.getRowHeight() + (table.getRowHeight() * currentPageContent.size());
        final float tableBottomY = tableTopY - tableYLength;
        float nextX = table.getMargin();
        for (int i = 0; i < table.getNumberOfColumns(); i++) {
            PdfBoxUtils.drawLine(contentStream, nextX, tableTopY, nextX, tableBottomY);
            nextX += table.getHeader().get(i).getWidth();
        }
        PdfBoxUtils.drawLine(contentStream, nextX, tableTopY, nextX, tableBottomY);
    }

    /**
     * 获取page中需要展示的数据行
     * @param table
     * @param rowsPerPage
     * @param pageCount
     * @return
     */
    private List<List<String>> getContentForCurrentPage(Table table, Integer rowsPerPage, int pageCount) {
        int startRange = pageCount * rowsPerPage;
        int endRange = (pageCount * rowsPerPage) + rowsPerPage;
        if (endRange > table.getNumberOfRows()) {
            endRange = table.getNumberOfRows();
        }
        List<List<String>> content = table.getRecords();
        List<List<String>> result = new ArrayList<>(endRange - startRange);
        for (int i = startRange; i < endRange; i ++){
            result.add(content.get(i));
        }
        return result;
    }

    /**
     * 生成page
     * @param doc
     * @param table
     * @return
     */
    private PDPage generatePage(PDDocument doc, Table table) {
        PDPage page = new PDPage(table.getPageSize());
        doc.addPage(page);
        return page;
    }

    /**
     * 生成页面画笔输出流
     * @param doc
     * @param page
     * @param table
     * @return
     * @throws IOException
     */
    private PDPageContentStream generateContentStream(PDDocument doc, PDPage page, Table table) throws IOException {
        PDPageContentStream contentStream = new PDPageContentStream(doc, page);
        contentStream.setFont(table.getTextFont(), table.getFontSize());
        return contentStream;
    }
}

测试类

    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\\simsun.ttf")));
        drawFirstPage(document, font);
        drawSecondPage(document, font);
        document.save(new FileOutputStream(new File("d:\\tmp\\test2.pdf")));
        document.close();
    }

    private void drawSecondPage(PDDocument document, PDType0Font font) throws IOException {
        PDPage mainTablePage = new PDPage(pageSize);
        document.addPage(mainTablePage);
        PDPageContentStream contentStream = new PDPageContentStream(document, mainTablePage);

        PdfBoxUtils.beginTextSteam(contentStream, 20f, marginX.floatValue(), pageSize.getHeight() - 2*marginY);
        // 书写信息
        PdfBoxUtils.drawParagraph(contentStream, "买卖人商品提交明细", font, 18);
        PdfBoxUtils.endTextSteam(contentStream);

        // 开始绘制table
        List<Column> header = initTableHeader();

        List<List<String>> records = new ArrayList<>();
        for (int i = 0; i < 90; i++) {
            records.add(Arrays.asList( "李太白" + i, "广州市分机构","20202020", "10000000"));
        }

        float tableHight = pageSize.getHeight() - (2 * marginY);

        Table table = new TableBuilder()
                .setCellMargin(4)
                .setRowHeight(20)
                .setColumns(header)
                .setContent(records)
                .setHeight(tableHight)
                .setMargin(marginX)
                .setPageSize(pageSize)
                .setTextFont(font)
                .setFontSize(13)
                .build();

        // 每页最多显示的条数
        Integer rowsPerPage = table.getRowsPerPage();
        // 首页
        Integer dataNum = 30;
        FirstTablePage firstTablePage = new FirstTablePage();
        firstTablePage.setDataNum(dataNum);
        firstTablePage.setMargin(100f);
        firstTablePage.setContentStream(contentStream);

        int firstBatch = rowsPerPage + dataNum;
        List<List<String>> firstRecords = new ArrayList<>(firstBatch);
        Iterator<List<String>> iterator = records.iterator();
        int index = 0;
        while (iterator.hasNext()) {
            List<String> record = iterator.next();
            firstRecords.add(record);
            iterator.remove();
            index ++;
            if (index >= firstBatch) {
                break;
            }
        }
        table.setRecords(firstRecords);
        new PdfTableGenerator().drawTableCustom(document, firstTablePage, table);
        // 剩下的
        int batchNum = rowsPerPage * 2;
        List<List<String>> batchRecords = new ArrayList<>(batchNum);
        iterator = records.iterator();
        index = 0;
        while (iterator.hasNext()) {
            List<String> record = iterator.next();
            batchRecords.add(record);
            iterator.remove();
            index ++;
            if (index % batchNum == 0) {
                table.setRecords(batchRecords);
                new PdfTableGenerator().drawTableCustom(document, null, table);
                batchRecords = new ArrayList<>(batchNum);
            }
        }
        table.setRecords(batchRecords);
        new PdfTableGenerator().drawTableCustom(document, null, table);
    }

    private List<Column> initTableHeader() {
        List<Column> header = new ArrayList<Column>();
        header.add(new Column("买卖人人名称", 150));
        header.add(new Column("店铺名称", 150));
        header.add(new Column("商品号", 100));
        header.add(new Column("商品价格(元)", 100));
        return header;
    }

效果

image.png

image.png

image.png
计划写三篇关于pdfbox的文章,这边是table组件的。后续会有一篇关于用pdfbox做签章的。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,188评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,464评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,562评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,893评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,917评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,708评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,430评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,342评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,801评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,976评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,115评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,804评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,458评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,008评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,135评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,365评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,055评论 2 355