23

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

到目前为止,搜索产品非常困难。我估计这样的功能在其他产品中可能会有不同的命名方式,让阿姨谷歌很难找到合适的解决方案。

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

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

  • 贾斯珀没有支持。

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

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

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

预期布局

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

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)

第二种布局

4

5 回答 5

3

可以使用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 倍,则代码不会进行拆分,但也可以对此进行调整。

于 2013-04-11T20:51:05.980 回答
1

这里的想法与 Dev Blanked 相同,但使用 wkhtmltopdf ( https://code.google.com/p/wkhtmltopdf/ ) 和一些 javascript,您可以实现您所需要的。当针对这个小提琴运行 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);      
    });
});

生成的 PDF 的屏幕截图

于 2013-04-10T17:28:32.670 回答
0

贾斯珀没有支持。

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

  • 分栏元素(即具有 type=column 属性的分栏元素)。这可以放置在报告中的任何位置。
  • 组/标题上的 isStartNewColumn 属性

See 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 动态填充/替换所需数据并打印到文件/PDF。至少它对列和行格式/中断/边距等提供了非常细粒度的控制。

于 2013-04-10T06:24:04.483 回答
0

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

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

在这里您可以找到一些带有 fop 和表格的示例。

于 2013-03-18T10:45:54.487 回答
0

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

于 2013-03-17T14:44:25.530 回答