如何构建 PDF(使用 FPDF)以便 table 列跨页?

How to construct PDF (with FPDF) so that table columns span pages?

嘿,我在 python 中使用 fpdf 来显示列表, 下面是我的代码:


from fpdf import FPDF
pdf = FPDF("L", 'mm', 'Legal')


pdf.add_page()
pdf.set_font('helvetica', 'B', 16)

headers = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'H7']

body = [
    ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
    ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
    ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
    ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
    ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
    ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
    ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
    ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
    ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
    ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
    ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
    ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
    ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
    ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
    ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
    ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
    ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
    ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
    ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
    ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
    ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
    ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
    ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
    ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
    ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
    ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
    ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
    ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
    ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
    ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
    ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
    ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
    ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7'],
    ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7']
]
print(pdf.w)
print(pdf.get_x())
for r in headers:
    pdf.cell(70, 10, f'{r}')
pdf.set_font('helvetica', '', 16)

for d in range(len(body)):
    row = body[d]
    for i in range(len(row)):
        if i == len(row)-1:
            pdf.cell(70, 10, f'{row[i]}', 0, ln=1)
        else:
            pdf.cell(70, 10, f'{row[i]}', 0)
            # pdf.cell(5, 10, '')

pdf.output('/home/chandan/Documents/tournament.pdf')


注意:页眉和正文的长度始终相同

H6、H7 超出了页边距,所以我如何让一个 pdf 页面显示 5 列,其余列显示在下一页 现在 pdf 页面看起来像 this 现在我怎样才能做到这一点

我们需要一些简单的数学运算,最重要的是,我们需要一些关于如何打印 columns/rows 和单个单元格的清晰思路。

首先,我们需要决定每页要打印多少行和多少列。

我将其称为“打印分辨率”。我们的 table 输入数据可以是任意大小(按高度排列),但它必须显示在每页不超过 5 行 x 10 列的网格中,因此我们的 分辨率 是“每页 5 行 x 10 列”。

接下来,我们需要找到页面的 print-able 区域,以确定这些行和列的大小。

FPDF 用 pdf.wpdf.h 告诉我们页面的整体尺寸,它还告诉我们设置的页边距,用 pdf.t_margin(上边距)和左侧 (l_margin)、右侧 (r_margin) 和底部 (b_margin) 依此类推:

  • 总宽度减去左右页边距告诉我们页面上有多少 space 适合我们之前选择的 10 列。

  • 总高度减去顶部和底部边距告诉我们页面上有多少 space 适合 5 行。

选择了我们的打印分辨率并了解了这些原则table 尺寸后,我们现在可以计算出每个单元格的大小:

  • Printable-width / 10 等于各个单元格完全填满页面网格所需的宽度。

  • Printable-height / 5 给我们相同的单元格高度。

所以,让我们打印一个 5 行 10 列的网格,均匀地填满页面:

from fpdf import FPDF

from my_table import table

pdf = FPDF("L", "mm", "Letter")
pdf.set_font("Arial", "", 16)

# Set desired "resolution" of printed grid
rows_per_page = 5
cols_per_page = 10

# Calculate dimensions of print-able area
print_h = pdf.h - pdf.t_margin - pdf.b_margin
print_w = pdf.w - pdf.l_margin - pdf.r_margin

# Actual size of grid cells on the page is available space divided by desired resolution, in each dimension
c_h = print_h / rows_per_page
c_w = print_w / cols_per_page

# Add a page and draw a border to visualize the page
pdf.add_page()
pdf.set_draw_color(255, 0, 0)
pdf.set_line_width(1)
pdf.rect(0, 0, pdf.w, pdf.h, "D")

pdf.set_draw_color(0, 0, 0)
pdf.set_line_width(0.2)
pdf.rect(0.5, 0.5, pdf.w - 0.5, pdf.h - 0.5, "D")
for i in range(rows_per_page):
    for j in range(cols_per_page):
        # Create "A1 Notation" for our reference
        char = chr(65 + j)  # "A", then "B", ...
        s = f"{char}{i+1}"
        pdf.cell(c_w, c_h, s, border=1, ln=0, align="C")
    pdf.ln()

pdf.output("simple_5-by-10.pdf", dest="F")

我得到:

我决定在打印一行单元格的末尾使用 pdf.ln(),因为它不需要任何逻辑:因为我们在列循环之外,所以我们肯定知道我们已经完成该行并需要移至下一行的开头。

现在我们有了一个漂亮的网格和所需的分辨率,我们可以继续处理 table 大于打印分辨率的信息,以及如何处理溢出。

我认为我们要做的是选择适合页面的一批行,然后从 left-to-right 打印适合页面的一批列移动这些行:

  1. 将 row-counter 设置为 0
  2. 从 table 的 row-counter 开始,然后 select 接下来的 number-of-rows-per-page 行。
  3. 将 column-counter 设置为 0
  4. 从 table 的 column-counter 开始,然后 select 接下来的 number-of-columns-per-page 列。
  5. 打印该行和列范围内的所有单元格。
  6. 推进列计数器,并重复步骤 4 和 5,直到打印完所有列。
  7. 增加行计数器,重复步骤 2 - 6 直到打印完所有行。
# Same as above...
c_h = print_h / rows_per_page
c_w = print_w / cols_per_page

# Get table's dimensions
num_rows = len(table)
num_cols = len(table[0])

# Track how far "down" the table we've moved
row_offset = 0
while row_offset < num_rows:
    # Limit this batch of rows to either the maximum (rows_per_page), unless we're at the end of the table, limit to the remaining of rows
    row_max = row_offset + rows_per_page
    if row_max > num_rows:
        row_max = num_rows

    # Track how far "across" this batch of rows we've moved
    col_offset = 0
    while col_offset < num_cols:
        # Limit this batch of columns to the either the maximum (columns_per_page), unless we're at the end of the batch of rows, limit to the remaining columns
        col_max = col_offset + cols_per_page
        if col_max > num_cols:
            col_max = num_cols

        # Ready to actually start printing, so create page
        pdf.add_page()

        # Print a grid of cells, no bigger than rows_per_page-high by cols_per_page-wide
        for i in range(row_offset, row_max):
            for j in range(col_offset, col_max):
                cell_value = table[i][j]
                pdf.cell(c_w, c_h, cell_value, border=1, ln=0, align="C")
            pdf.ln()

        # Advance to the right, "across" the batch of rows, for the next batch of columns
        col_offset += cols_per_page

    # Advance "down" the table, for the next batch of rows
    row_offset += rows_per_page


pdf.output("span_cols.pdf", dest="F")

如果我给它这个table:

table = [
 ['A1' , 'B1' , 'C1' , 'D1' , 'E1' , 'F1' , 'G1' , 'H1' , 'I1' , 'J1' , 'K1' , 'L1'],
 ['A2' , 'B2' , 'C2' , 'D2' , 'E2' , 'F2' , 'G2' , 'H2' , 'I2' , 'J2' , 'K2' , 'L2'],
 ['A3' , 'B3' , 'C3' , 'D3' , 'E3' , 'F3' , 'G3' , 'H3' , 'I3' , 'J3' , 'K3' , 'L3'],
 ['A4' , 'B4' , 'C4' , 'D4' , 'E4' , 'F4' , 'G4' , 'H4' , 'I4' , 'J4' , 'K4' , 'L4'],
 ['A5' , 'B5' , 'C5' , 'D5' , 'E5' , 'F5' , 'G5' , 'H5' , 'I5' , 'J5' , 'K5' , 'L5'],
 ['A6' , 'B6' , 'C6' , 'D6' , 'E6' , 'F6' , 'G6' , 'H6' , 'I6' , 'J6' , 'K6' , 'L6'],
 ['A7' , 'B7' , 'C7' , 'D7' , 'E7' , 'F7' , 'G7' , 'H7' , 'I7' , 'J7' , 'K7' , 'L7'],
 ['A8' , 'B8' , 'C8' , 'D8' , 'E8' , 'F8' , 'G8' , 'H8' , 'I8' , 'J8' , 'K8' , 'L8'],
 ['A9' , 'B9' , 'C9' , 'D9' , 'E9' , 'F9' , 'G9' , 'H9' , 'I9' , 'J9' , 'K9' , 'L9'],
 ['A10', 'B10', 'C10', 'D10', 'E10', 'F10', 'G10', 'H10', 'I10', 'J10', 'K10', 'L10'],
 ['A11', 'B11', 'C11', 'D11', 'E11', 'F11', 'G11', 'H11', 'I11', 'J11', 'K11', 'L11'],
 ['A12', 'B12', 'C12', 'D12', 'E12', 'F12', 'G12', 'H12', 'I12', 'J12', 'K12', 'L12']
]

并选择 5 行 10 列的相同分辨率,我得到以下结果:

我没有时间或精力来说明如何处理 header,但我认为可以简单地 if i == 0 then header-style else normal-style 检查一下您是否处理了第一行。并且,将 header 包含在数据中,不要尝试将它们 handle/print 作为单独的数据结构(那样会弄乱数学)。

我也在想,如果你想要每个页面上的 header,请查看 FPDF 教程中关于 Headers, Footers 的部分。我不确定添加 header 会如何影响页边距,所以请四处看看。