JAVA操作pdf——创建表格

JAVA操作pdf——创建表格

一、前言

在实习的时候遇到了需要将查询到的数据构建成为PDF的情况,于是在网上查找到了相关的Java操作pdf的教程,看到大部分的文章都是使用ITextPdf操作的,于是边借此机会写个笔记记录一下。

准备工作:

此次使用的是maven工具构建环境依赖,依赖如下:

        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>kernel</artifactId>
            <version>7.0.3</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>io</artifactId>
            <version>7.0.3</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>layout</artifactId>
            <version>7.0.3</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>font-asian</artifactId>
            <version>7.0.3</version>
        </dependency>

由于我在使用的时候这个依赖的版本已经是7.0.3了,在网上我找到的教程大都是5.0+版本的,所以结合网上的教程和自己的摸索,来探究这个包对pdf的使用方式是怎么样的

二、ItextPdf基本使用

创建一个pdf文档

// 准备写入一个pdf文件
// file对象中的路径是待写入文件的路径
File file = new File("C:/Users/achao/Desktop/hellowpdf.pdf");
// 创建文件输出流
FileOutputStream outputStream = new FileOutputStream(file);
// 创建pdf写入对象
PdfWriter writer = new PdfWriter(outputStream);
// 创建pdf文档对象
PdfDocument pdfDocument = new PdfDocument(writer);
// 创建documen对象(直接与硬盘关联)
Document document = new Document(pdfDocument, PageSize.A4);
// 往document中添加段落
document.add(new Paragraph("hellow pdf"));
// 关闭
document.close();

通过上面的方法,便创建了一个pdf文件在"C:/Users/achao/Desktop/hellowpdf.pdf"路径下,pdf中的内容是一个“hello pdf” 段落。

设置段落格式

// 设置段落的格式
// 对齐方式
paragraph.setTextAlignment(TextAlignment.CENTER);	// 居中
paragraph.setTextAlignment(TextAlignment.LEFT);		// 靠左

插入表格

File file = new File("C:/Users/achao/Desktop/hellowpdf.pdf");
FileOutputStream outputStream = new FileOutputStream(file);
PdfWriter writer = new PdfWriter(outputStream);
PdfDocument pdfDocument = new PdfDocument(writer);
Document document = new Document(pdfDocument, PageSize.A4);
// 这个数组的大小为表格的列数,里面的数值为列的宽度
float[] columnWidths = new float[]{10.0f, 10.0f, 10.0f, 10.0f};
// 这个布尔值参数的意思是横向拉伸表格,true按比例分配列宽
Table table = new Table(columnWidths, true);
// table和document关联
table.setDocument(document);
// 创建一个单元格
Cell cell = new Cell();
// 单元格添加内容
cell.add("cell1");
// 放进table中
table.addCell(cell);
// 以下的方式可以默认创建cell,但是无法设置更多的样式
table.addCell("string");
table.addCell("string");
table.addCell("string");
table.addCell("string");
table.addCell("string");
table.addCell("string");
table.addCell("string");
// 表格制作完成
table.complete();
document.close();

好了,这就是itext的简单使用,接下来我就要根据工作的具体需求来实现操作表格了。等一下?itex项目中不能用?因为它不是开源的,不能用于商业用途。。。。。

听到上面的话时,心想,那就换一个呗。

三、PdfBox基本使用

反正已经学了itext基础知识,现在学习PdfBox也应该是得心应手的,两者的代码漏记应该是差不多的吧!我略带自信地在网上寻找pdfbox的相关信息。

准备工作

maven依赖:

		<dependency>
            <groupId>org.apache.pdfbox</groupId>
            <artifactId>pdfbox</artifactId>
            <version>2.0.21</version>
        </dependency>

创建一个pdf文档

            // 创建一个pdf对象
            PDDocument document = new PDDocument();
            // 向这个pdf对象中添加一个新的页面
            PDPage page = new PDPage();
            document.addPage(page);
            // 获取内容流
            PDPageContentStream contentStream = new PDPageContentStream(document, page);
			// 往pdf文件中写入字符串
            contentStream.showText("hellow pdfbox");
            // 内容输出流关闭
            contentStream.close();
            // 文档保存
            document.save("C:/Users/achao/Desktop/pdfbox.pdf");
            // 文档关闭
            document.close();

写了个hellow pdfbox之后,我开始沾沾自喜,切 ,这么简单,跟那个itext没什么两样嘛,真想不明白那个itext怎么要收费的,只有傻子才会花钱,买它。

后来,咦?啊这,这,这,这表格对象在哪里?我突然一阵懵逼,在网上疯狂搜索信息,就是没有找到它使用表格的类,只找到最最最原始的方法——画线!我一时间腿软了,问问项目组的人上面能不能花钱买个itext,那玩意好啊,真香。结果,贫穷剥削了我们的劳动力,别说了,撸起袖子加油干!

于是乎琢磨了一个下午,按照工作的需求我搞出了下面这些玩意~

绘制表格

public class PdfBox {
    public static void main(String[] args) throws Exception {
        List<Map<String, String>> mapList = new ArrayList<>();
        Map<String, String> map = new LinkedHashMap<>();
        map.put("sec_code", "NB");
        map.put("ap_code", "AP");
        map.put("ARN", "NBSF580002300");
        map.put("exp_ARN", "NBSF580002300");
        map.put("no_exp_ARN", "NBSF580002300");
        Map<String, String> map1 = new LinkedHashMap<>();
        map1.put("sec_code", "NB");
        map1.put("ap_code", "AP");
        map1.put("ARN", "NBSF580002300");
        map1.put("exp_ARN", "NBSF580002300");
        map1.put("no_exp_ARN", "NBSF580002300");
        Map<String, String> map2 = new LinkedHashMap<>();
        map2.put("sec_code2", "NB");
        map2.put("ap_code2", "AP");
        map2.put("ARN2", "NBSF580002300");
        map2.put("exp_ARN2", "NBSF580002300");
        map2.put("no_exp_ARN2", "NBSF580002300");
        Map<String, String> map3 = new LinkedHashMap<>();
        map3.put("sec_code3", "NB");
        map3.put("ap_code3", "AP");
        map3.put("ARN3", "NBSF580002300");
        map3.put("exp_ARN3", "NBSF580002300");
        map3.put("no_exp_ARN3", "NBSF580002300");
        mapList.add(map);
        mapList.add(map1);
        mapList.add(map2);
        mapList.add(map3);
        pringPdf(mapList);

    }

    public static void pringPdf(List<Map<String, String>> mapList) throws Exception {
        if (mapList == null || mapList.size() < 0) {
            throw new Exception("mapList is null or mapList`s size is not legal");
        } else {
            // 创建一个pdf对象
            PDDocument document = new PDDocument();
            // 向这个pdf对象中添加一个新的页面并获取内容流
            PDPageContentStream contentStream = insertPage(document);
            // 默认的表格宽度和高度
            float tableWidth = 500;
            float tableHeight = 700;
            // 计算map中的表头数量,确定表格的列数, 添加表头数据
            tableBodyText(document,contentStream, tableWidth, tableHeight, mapList);
            // 内容输出流关闭
            contentStream.close();
            // 文档保存
            document.save("C:/Users/achao/Desktop/pdfbox.pdf");
            // 文档关闭
            document.close();
        }
    }


    // 绘制表头并填充信息
    private static void tableBodyText(PDDocument document,PDPageContentStream contentStream, float tableWidth,
                                      float tableHeight,
                                      List<Map<String, String>> mapList) throws Exception {
        if (mapList == null || mapList.size() <= 0){
            throw new Exception("mapList is null or mapList`s size is not legal");
        }
        // 绘制表格外部方框
        drawTable(contentStream, 50, 50, tableWidth, tableHeight);
        // 表格横数
//        int rows = (int) tableHeight / 50;
        int rows = 3;     // test
        // 计算map中的表头数量,确定表格的列数, 添加表头数据
        Map<String, String> map = mapList.get(0);
        Set<String> keys = map.keySet();
        String[] strings = keys.toArray(new String[keys.size()]);
        // 按字符串的长度比例分配列宽度
        float[] wids = distributeWids(map, tableWidth);
        // 相邻两列的和一半
        float[] halfWids = new float[wids.length];
        for (int i = 0; i < wids.length; i++) {
            if (i==0) {
                halfWids[i] = wids[i] / 2;
            }else {
                halfWids[i] = (wids[i] + wids[i-1]) / 2;
            }
        }
//        System.out.println(halfWids.length == strings.length);
        int size = mapList.size();
        int residue = 0;
        if (rows < size) {
            residue = size - rows;
            size = rows;
        }
        for (int i = 0; i < size; i ++){
            // 列的绘制坐标增量
            float a = 0;
            // 插入文字的横坐标增量
            float b = 0;
            if (i == 0){
                // 这个循环添加表头
                for (int j = 0; j < strings.length; j++) {
                    a += wids[j];
                    b += halfWids[j];
                    // 这里的50是表格边缘的宽度
                    drawLine(contentStream, 50 + a, 50, 50 + a, 50 + tableHeight);
                    // 获取字符串的长度,能使它居中
                    int len = strings[i].length();
                    // 注意这里字体大小12的时候,是len*6/2
                    insertText(contentStream, 50 + b - len * 6 / 2, 50 + tableHeight - 30, strings[j]);
                }
                drawLine(contentStream, 50, tableHeight, 50 + tableWidth, tableHeight);
            }
            String[] cells =  mapList.get(i).values().toArray(new String[mapList.get(i).values().size()]);
            b = 0;
            // 插入每行的数据
            for (int k = 0; k < cells.length; k++) {
                b += halfWids[k];
                int len = cells[k].length();
                insertText(contentStream, 50 + b- len * 6 / 2, tableHeight-i*50-30, cells[k]);
            }
            drawLine(contentStream, 50, tableHeight-i*50, 50 + tableWidth, tableHeight-i*50);
        }
        if (residue > 0){
            contentStream.close();
            residue = mapList.size() - residue;
            // 对于剩下的数据,新创建page递归填充
            List<Map<String, String>> newMapList =  new ArrayList<>();
            while (residue < mapList.size()){
                newMapList.add(mapList.get(residue));
                residue ++;
            }
            PDPageContentStream newcontentStream = insertPage(document);
            tableBodyText(document, newcontentStream, tableWidth, tableHeight, newMapList);
        }
        contentStream.close();
    }


    // 绘制表格,返回每条边的长度
    private static void drawTable(PDPageContentStream contentStream, float x, float y, float tableWidth,
                                  float tableHeight) {
        try {
            contentStream.setStrokingColor(Color.GRAY);
            // 左边
            drawLine(contentStream, x, y, x, y + tableHeight);
            // 上边
            drawLine(contentStream, x, y + tableHeight, x + tableWidth, y + tableHeight);
            // 右边
            drawLine(contentStream, x + tableWidth, y + tableHeight, x + tableWidth, y);
            // 下边
            drawLine(contentStream, x + tableWidth, y, x, y);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 绘制线段
    private static void drawLine(PDPageContentStream contentStream, float startX, float startY, float endX, float endY) {
        try {
            // 这里是确定了画线的两个端点
            contentStream.moveTo(startX, startY);
            contentStream.lineTo(endX, endY);
            contentStream.stroke();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 插入文字
    private static void insertText(PDPageContentStream contentStream, float x, float y, String text) throws IOException {
        PDFont font = PDType1Font.HELVETICA_BOLD;
        contentStream.setFont(font, 12);
        contentStream.beginText();
        contentStream.newLineAtOffset(x, y);
        contentStream.showText(text);
        contentStream.endText();
    }

    // 插入一个新的page
    private static PDPageContentStream insertPage(PDDocument document) throws IOException {
        PDPage page = new PDPage();
        document.addPage(page);
        return new PDPageContentStream(document, page);
    }

    // 按照比例返回每列的宽度的占比
    private static float[] distributeWids(Map<String, String> map, float tableWidth){
        Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
        List<Float> wids = new ArrayList<>();
        // 总的字符串长度
        float sumLen = 0;
        while (iterator.hasNext()){
            Map.Entry<String, String> next = iterator.next();
            if (next.getValue().length() > next.getKey().length()){
                sumLen += next.getValue().length();
                wids.add((float) next.getValue().length());
            }else {
                sumLen += next.getKey().length();
                wids.add((float) next.getKey().length());
            }
        }
        float[] widscol = new float[map.size()];
        for (int i = 0; i < wids.size(); i++) {
            widscol[i] = tableWidth * wids.get(i) / sumLen;
        }
        return widscol;
    }
}

虽然只有一百行的代码,但是字字透露着我的无奈,我恨不得我直接手绘表格,因为这东西真的是太慢了。效果如下:

在这里插入图片描述

我想摊牌了,不想搞下去了,这玩意之后要合并表格岂不是要把我累死。我在想有没有开源的包可以操作doc文档的,然后在转换成pdf。一起负责的同事告诉我说,之前他用过一个模板引擎,感觉还不错。啊这,我果断抛弃pdfbox,追寻这个模板引擎去了。

四、PoiTl模板引擎基本使用

去查看了它的官方文档,好家伙,全中文很是友善,http://deepoove.com/poi-tl/。好像这玩意是个人开发的,这样的话就不用考虑版权的问题了。

粗略开了一下入门的教程,他写的还是很清楚的,我就照搬过来了,等到后面看不懂的时候再复制过来。

大概地操作就是:首先你得有一个本地docx文档,然后这文档内容里面有按照它规范化的标签,在代码中开始编译这个docx文档,等到了相应的对象后,使用一些方法将一些字符串或者行为与文档中的标签关联起来。最后开始渲染输出docx文件。

我一开始心想:这玩意要自己先有模板才能操作?那岂不是我要先自己画好表格然后找它填充数据?感觉有点草率,看到后面才发现它的高级用法——自定义插件(策略)。

表格操作

// 新建一个策略继承指定的接口
public class TablePolicy extends DynamicTableRenderPolicy {

    @SneakyThrows
    // 记住这个data参数,之后弄清楚怎么传递的就好了。第一个参数是自动获取的
    public void render(XWPFTable table, Object data) {
        // 判空
        if (data == null || table == null){
            throw new Exception("data or table is null");
        }
        // 拿到传递过来的每行的数据
        List<Map<String, String>> rows = (List<Map<String, String>>) data;
        table.removeRow(0);
        // 获取表头
        Set<String> keySet = rows.get(0).keySet();
        String[] heads = keySet.toArray(new String[keySet.size()]);
        // 先删除标签行
        table.removeRow(0);
        // 添加表头行
        XWPFTableRow headRow = table.insertNewTableRow(0);
        // 往表头行中添加表头数据
        for (int i = 0; i < heads.length; i++) {
            XWPFTableCell cell = headRow.createCell();
            cell.setText(heads[i]);
        }
        List<String> pre = new ArrayList(2);
        List<Integer> a = new LinkedList<>();
        // 遍历每一行的数据
        for (int i = 0; i < rows.size(); i++) {
            List<String> bef = new ArrayList<>(2);
            XWPFTableRow row = table.insertNewTableRow(table.getRows().size());
            RowRenderData rowRenderData = new RowRenderData();
            // 拿出map
            Map<String, String> rowMap = rows.get(i);
            // 拿出map中的values
            for (int j = 0; j < heads.length; j++) {
                // 在该行中添加每个单元格的信息
                XWPFTableCell cell = row.createCell();
                cell.setText(rowMap.get(heads[j]));
                // 记录前三个单元格的信息
                if (j<2){
                    bef.add(rowMap.get(heads[j]));
                }
            }
            // 判断前面一行与当前行的条件是否一致
            boolean tag = true;
            if (bef.size() > 0 && pre.size() > 0){
                for (int z = 0; z < bef.size(); z++) {
                    if (bef.get(z) != pre.get(z)) {
                        tag = false;
                        break;
                    }
                }
            }
            // 将该行数据添加
            if (tag && bef.size() > 0 && pre.size() > 0) a.add(table.getRows().size());
            pre = bef;
        }
        // 合并单元格
        for (int i = 0; i < a.size(); i++) {
            TableTools.mergeCellsVertically(table, 0, a.get(i)-2-i, a.get(i)-1);
            TableTools.mergeCellsVertically(table, 1, a.get(i)-2-i, a.get(i)-1);
        }
//        // 遍历data中每行的行数据
//        Iterator<Map<String, String>> iterator = rows.iterator();
//        while (iterator.hasNext()){
//            //  创建行
//            XWPFTableRow row = table.insertNewTableRow(table.getRows().size());
//            // 往行中添加数据
//            for (String value : iterator.next().values()){
//                XWPFTableCell cell = row.createCell();
//                cell.setText(value);
//                cell.setWidthType(TableWidthType.AUTO);
//            }
//        }
//        // 合并列
//        TableTools.mergeCellsVertically(table,1, 1, 2);
        table.setTableAlignment(TableRowAlign.CENTER);
    }
}

// 数据封装类
public class ReportJob {
    public static void tablePolicy() throws IOException {
        // 构造好要传递的数据结构
        final List<Map<String, String>> data = new ArrayList<Map<String, String>>();
        data.add(new LinkedHashMap<String, String>(){{put("name", "achao");put("age", "18");put("score", "11");put(
                "school", "xinning");}});
        data.add(new LinkedHashMap<String, String>(){{put("name", "achao");put("age", "18");put("score", "22");put(
                "school",
                "xinning");}});
        data.add(new LinkedHashMap<String, String>(){{put("name", "achao");put("age", "18");put("score", "33");put(
                "school",
                "xinning");}});
        data.add(new LinkedHashMap<String, String>(){{put("name", "dt");put("age", "18");put("score", "44");put("school",
                "xinning");}});
        Map map = new HashMap(){{put("tablepolicy", data);}};
        // 策略和标签进行绑定
        Configure configure = Configure.createDefault();
        configure.customPolicy("tablepolicy", new TablePolicy());
        // 编译
        XWPFTemplate template = XWPFTemplate.compile("C:\\Users\\achao\\Desktop\\payment.docx", configure);
        template.render(map);
        FileOutputStream stream = new FileOutputStream("C:\\Users\\achao\\Desktop\\payment_out.docx");
        // 渲染
        template.write(stream);
        stream.flush();
        // 关闭
        stream.close();
        template.close();
    }
}

// 调用上述数据封装类的方法....

先看看效果吧:
在这里插入图片描述

在上面的代码中,那个参数的传递是在一个map中的,而且map中的key值要对应这绑定的标签值。

获取到data参数之后,执行了一下操作:

  • 根据参数中的字段数量,确定表格的列数
  • 根据表格中的数据量,确定行数
  • 根据表格中头几个字段属性的情况,合并单元格

至此,对于公司的数据报表的基本功能实现已经完成了,但是后面还是要转换成为pdf呀,这本来觉得很简单的事情再一次搞昏了头脑。。。。

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要读取 PDF 文件的表格,可以使用 Java 库 Apache PDFBox。下面是一个简单的示例代码,可以读取 PDF 文件的第一个表格: ```java // 导入 PDFBox 库 import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.text.PDFTextStripper; import org.apache.pdfbox.text.PDFTextStripperByArea; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.interactive.form.PDField; import org.apache.pdfbox.pdmodel.interactive.form.PDTextField; import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm; import org.apache.pdfbox.pdmodel.interactive.form.PDCheckBox; import org.apache.pdfbox.pdmodel.interactive.form.PDComboBox; import org.apache.pdfbox.pdmodel.interactive.form.PDListBox; import org.apache.pdfbox.pdmodel.interactive.form.PDNonTerminalField; import org.apache.pdfbox.pdmodel.interactive.form.PDRadioButton; import org.apache.pdfbox.pdmodel.interactive.form.PDVariableText; import java.io.File; import java.io.IOException; public class ReadPDFTable { public static void main(String[] args) { try { // 读取 PDF 文件 PDDocument document = PDDocument.load(new File("example.pdf")); // 获取第一页 PDPage page = document.getPage(0); // 创建 PDFTableStripper 对象 PDFTableStripper stripper = new PDFTableStripper(); // 设置表格边界 stripper.setSortByPosition(true); stripper.setStartPage(1); stripper.setEndPage(1); // 获取表格内容 String tableText = stripper.getText(document); // 输出表格内容 System.out.println(tableText); // 关闭文档 document.close(); } catch (IOException e) { e.printStackTrace(); } } } ``` 其PDFTableStripper 是一个自定义的 PDF 文本提取器,用于提取表格内容。你可以根据自己的需求修改它的实现。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值