带有水平分页符的PDF表格

时间:2013-03-12 12:24:30

标签: java pdf-generation layout-engine

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

到目前为止,搜索产品一直很困难。我认为这样的功能在其他产品中可能会有不同的名称,因此很难使用谷歌阿姨找到合适的解决方案。

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

  • 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

5 个答案:

答案 0 :(得分:3)

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

我能够调整this iText example水平和垂直地在多个页面上书写。我们的想法是记住在页面上垂直进入的起始行和结束行。我把整个代码放在一起,这样你就可以轻松运行它了。

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倍更高,但代码不会分裂,但也可以适应这一点。

答案 1 :(得分:1)

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

HTML

<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

答案 2 :(得分:0)

您是否尝试过http://code.google.com/p/flying-saucer/。它应该将HTML转换为PDF。

答案 3 :(得分:0)

我的建议是使用FOP变压器。

Here您可以看到一些示例以及如何使用它。

Here你可以找到一些fop和table的例子。

答案 4 :(得分:0)

  

Jasper没有支持。

根据Jasper文档,它确实有支持,通过:

  • 列分隔元素(即具有type = column属性的break元素)。这可以放在报告中的任何位置。
  • group / headers
  • 上的isStartNewColumn属性

请参阅http://books.google.com.au/books?id=LWTbssKt6MUC&pg=PA165&lpg=PA165&dq=jasper+reports+%22column+break%22&source=bl&ots=aSKZfqgHR5&sig=KlH4_OiLP-cNsBPGJ7yzWPYgH_k&hl=en&sa=X&ei=h_1kUb6YO6uhiAeNk4GYCw&redir_esc=y#v=onepage&q=column%20break&f=false

如果您真的陷入困境,作为最后的手段,您可以使用Excel / OpenOffice Calc:手动将数据复制到单元格中,根据需要手动格式化,另存为xls格式。然后使用java中的apache POI动态填充/替换所需的数据&amp;打印到文件/ PDF。至少它对色谱柱和色谱柱进行了非常精细的控制。行格式/中断/边距等。