具有水平分页符的 PDF 格式的表格

2022-09-01 23:51:43

有人知道Java的(最好是开源的)PDF布局引擎,能够呈现带有水平分页符的表格吗?“水平分页”至少是该功能在BIRT中的命名方式,但要澄清:如果一个表有太多的列来适应可用的页面宽度,我希望表在多个页面中水平拆分,例如,对于10列的表,列1-4在第一页上输出,列5-10在第二页上输出。当然,如果表格的太多而无法在一页上垂直放置,则也应该在以下页面上重复此操作。

到目前为止,搜索产品已经相当困难。我认为这样的功能在其他产品中可能会以不同的方式命名,这使得使用Google阿姨很难找到合适的解决方案。

到目前为止,我已经尝试过:

  • BIRT声称支持这一点,但实际的实现是如此错误,以至于无法使用。虽然对于这样的功能来说,这是不言而喻的,即行高在所有页面上保持一致,从而可以在将页面彼此相邻放置时对齐行。但是,BIRT 会为每个页面单独计算所需的行高。

  • Jasper没有支持。

  • 我也考虑过Apache FOP,但我在XSL-FO规范中找不到任何合适的语法。

  • 无论如何,iText对于此任务来说通常有点太“低级”(使得难以布局预期的PDF文档的其他部分),但似乎没有提供支持。

由于似乎有其他几十个报告或布局引擎,它们可能适合也可能不合适,我发现很难准确猜测要查找的内容,我希望有人可能已经有类似的要求,并且至少可以提供正确方向的建议。相对重要的是,该产品可以很容易地集成到Java服务器应用程序中,本机Java库将是理想的。

Expected Layout

现在,要使行在所有页面上保持对齐,必须按如下方式计算行高:

Row1.height = max(A1.height, B1.height, C1.height, D1.height)
Row2.height = max(A2.height, B2.height, C2.height, D2.height)

虽然BIRT目前似乎在做这样的事情:

Page1.Row1.height = max(A1.height, B1.height)
Page2.Row1.height = max(C1.height, D1.height)
Page1.Row2.height = max(A2.height, B2.height)
Page2.Row2.height = max(C2.height, D2.height)

Second Layout


答案 1

可以使用 按所需的方式显示表格。您需要使用自定义表定位和自定义行列写入。iText

我能够调整这个iText示例,以水平和垂直的方式在多个页面上书写。这个想法是记住在页面上垂直进入的开始和结束行。我已经放置了整个代码,因此您可以轻松运行它。

public class Main {
    public static final String RESULT = "results/part1/chapter04/zhang.pdf";

    public static final float PAGE_HEIGHT = PageSize.A4.getHeight() - 100f;

    public void createPdf(String filename)
            throws IOException, DocumentException {

        // step 1
        Document document = new Document();
        // step 2
        PdfWriter writer
                = PdfWriter.getInstance(document, new FileOutputStream(filename));
        // step 3
        document.open();

        //setup of the table: first row is a really tall one
        PdfPTable table = new PdfPTable(new float[] {1, 5, 5, 1});

        StringBuilder sb = new StringBuilder();

        for(int i = 0; i < 50; i++) {
            sb.append("tall text").append(i + 1).append("\n");
        }

        for(int i = 0; i < 4; i++) {
            table.addCell(sb.toString());
        }

        for (int i = 0; i < 50; i++) {
            sb = new StringBuilder("some text");
            table.addCell(sb.append(i + 1).append(" col1").toString());

            sb = new StringBuilder("some text");
            table.addCell(sb.append(i + 1).append(" col2").toString());

            sb = new StringBuilder("some text");
            table.addCell(sb.append(i + 1).append(" col3").toString());

            sb = new StringBuilder("some text");
            table.addCell(sb.append(i + 1).append(" col4").toString());
        }

        // set the total width of the table
        table.setTotalWidth(600);
        PdfContentByte canvas = writer.getDirectContent();

        ArrayList<PdfPRow> rows = table.getRows();

        //check every row height and split it if is taller than the page height
        //can be enhanced to split if the row is 2,3, ... n times higher than the page  
        for (int i = 0; i < rows.size(); i++) {
            PdfPRow currentRow = rows.get(i);

            float rowHeight = currentRow.getMaxHeights();

            if(rowHeight > PAGE_HEIGHT) {
                PdfPRow newRow = currentRow.splitRow(table,i, PAGE_HEIGHT);
                if(newRow != null) {
                    rows.add(++i, newRow);
                }
            }
        }

        List<Integer[]> chunks = new ArrayList<Integer[]>();

        int startRow = 0;
        int endRow = 0;
        float chunkHeight = 0;

        //determine how many rows gets in one page vertically
        //and remember the first and last row that gets in one page
        for (int i = 0; i < rows.size(); i++) {
            PdfPRow currentRow = rows.get(i);

            chunkHeight += currentRow.getMaxHeights();

            endRow = i;   

            //verify against some desired height
            if (chunkHeight > PAGE_HEIGHT) {
                //remember start and end row
                chunks.add(new Integer[]{startRow, endRow});
                startRow = endRow;
                chunkHeight = 0;
                i--;
            }
        }

        //last pair
        chunks.add(new Integer[]{startRow, endRow + 1});

        //render each pair of startRow - endRow on 2 pages horizontally, get to the next page for the next pair
        for(Integer[] chunk : chunks) {
            table.writeSelectedRows(0, 2, chunk[0], chunk[1], 236, 806, canvas);
            document.newPage();
            table.writeSelectedRows(2, -1, chunk[0], chunk[1], 36, 806, canvas);

            document.newPage();
        }


        document.close();
    }

    public static void main(String[] args) throws IOException, DocumentException {
        new Main().createPdf(RESULT);
    }
}

我知道也许iText对于报告来说级别太低了,但它可以与标准报告工具一起使用,以满足这样的特殊需求。

更新:现在,高于页面高度的行将首先被拆分。如果行高2,3,..., n倍,则代码不会进行拆分,但也可以适应这种情况。


答案 2

这里的想法与Dev Blanked相同,但使用wkhtmltopdf(https://code.google.com/p/wkhtmltopdf/)和一些javascript,你可以实现你需要的东西。当针对这个小提琴运行wkhtmltopdf时,你会得到如下所示的结果(pdf页面的屏幕截图)。您可以将“中断后”类放在标题行的任意位置。我们在Java EE Web应用程序中使用wkhtmltopdf服务器端来生成动态报告,性能实际上非常好。

断续器

<body>
        <table id="table">
            <thead>
                <tr><th >Header 1</th><th class="break-after">Header 2</th><th>Header 3</th><th>Header 4</th></tr>
            </thead>
            <tbody>
                <tr valign="top">
                    <td>A1<br/>text<br/>text</td>
                    <td>B1<br/>text</td>
                    <td>C1</td>
                    <td>D1</td>
                </tr>
                <tr valign="top">
                    <td>A2</td>
                    <td>B2<br/>text<br/>text<br/>text</td>
                    <td>C2</td>
                    <td>D2<br/>text</td>
                </tr>
            </tbody>
        </table>
    </body>

脚本

$(document).ready(function() {
    var thisTable = $('#table'),
        otherTable= thisTable.clone(false, true),
        breakAfterIndex = $('tr th', thisTable).index($('tr th.break-after', thisTable)),
        wrapper = $('<div/>');

    wrapper.css({'page-break-before': 'always'});
    wrapper.append(otherTable);
    thisTable.after(wrapper);
    $('tr', thisTable).find('th:gt(' + breakAfterIndex + ')').remove(); 
    $('tr', thisTable).find('td:gt(' + breakAfterIndex + ')').remove(); 
    $('tr', otherTable).find('th:lt(' + (breakAfterIndex + 1) + ')').remove(); 
    $('tr', otherTable).find('td:lt(' + (breakAfterIndex + 1) + ')').remove();

    $('tr', table).each(function(index) {
        var $this =$(this),
            $otherTr = $($('tr', otherTable).get(index)),
            maxHeight = Math.max($this.height(), $otherTr.height());
        $this.height(maxHeight);
        $otherTr.height(maxHeight);      
    });
});

Screenshot of the resulting PDF


推荐