Skip to content
Advertisement

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

Hey i am using fpdf in python for showing a list the, Below is my code:

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')


Note: the length of header and body is always same

the column H6,H7 are out of margin, so how an i make one pdf page show 5 columns and the rest of the columns show in next page Now the pdf page looks like this Now how can i achieve that

Advertisement

Answer

We’re going to need some simple math and, most importantly, some clear ideas about how we want to print the columns/rows and the individual cells.

First, we need to decide how many rows and columns we want to print per page.

I’ll call this the “print resolution”. Our table of input data can be any size (row by height), but it must be displayed, say, in a grid no bigger than 5 rows by 10 columns per page, so our resolution is “5 rows by 10 columns per page”.

Next, we need to find the print-able area of a page to figure out how big to make those rows and columns.

FPDF tell us the overall dimensions of the page with pdf.w and pdf.h, and it also tells us the margins that are set, with pdf.t_margin (for top margin) and so on for the left (l_margin), right (r_margin), and bottom (b_margin):

  • The overall width minus the left and right margins tells us how much space we have on the page to fit the 10 columns we picked earlier.

  • The overall height minus the top and bottom margins tells us how much space we have on the page to fit the 5 rows.

Having picked our print resolution and knowing those printable dimensions we can now figure out how big to make each cell:

  • Printable-width / 10 equals the width the individual cells need to be to completely fill the grid on the page.

  • Printable-height / 5 gives us the same for the cell’s height.

So, let’s just print a grid 5 rows by 10 columns that evenly fills the page:

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")

and I get:

enter image description here

I’ve decided to use pdf.ln() at the end of printing a line of cells as it doesn’t take any logic: since we’re outside the loop of columns we definitely know that we’ve finished that row and need to move to the beginning of the next line.

Now that we have a nice looking grid with a desired resolution, we can move on to dealing with a table of information that is bigger than our print resolution, and how to deal with the overflow.

I think of what we’re about to do as picking a batch of rows that fit on a page, then moving across those rows from left-to-right printing the batch of columns that fit on a page:

  1. Set the row-counter to 0
  2. Start at the row-counter of the table, and select the next number-of-rows-per-page rows.
  3. Set the column-counter to 0
  4. Start at the column-counter of the table, and select the next number-of-columns-per-page columns.
  5. Print all the cells in that range of rows and columns.
  6. Advance the column counter, and repeat steps 4 & 5 till all columns have been printed.
  7. Advance the row counter, and repeat steps 2 – 6 till all rows have been printed.
# 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")

If I give it this 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']
]

and pick the same resolution of 5 rows by 10 columns, I get the following:

enter image description here

I don’t have the time or energy to show how to handle the header, but I think a simple if i == 0 then header-style else normal-style check to see if your dealing with the first row. And, include the header with the data, don’t try to handle/print them as separate data structures (that’ll mess up the math).

I was also thinking that if you want the header on each page, check out the section on Headers, Footers in the FPDF tutorial. I’m not sure how adding a header will affect the margins, so play around and see.

User contributions licensed under: CC BY-SA
2 People found this is helpful
Advertisement