py-pdf / fpdf2

Simple PDF generation for Python
https://py-pdf.github.io/fpdf2/
GNU Lesser General Public License v3.0
1.07k stars 246 forks source link

multipage table using fpdf #1196

Closed tekkadan95 closed 2 weeks ago

tekkadan95 commented 3 months ago

I am struggling to handle pagebreaks with multicell tables. example pdf

As you can see the rest of the row gets printed on the y position of the next page, not the first one.

I am using user input from a pyqt application. This is my code snippet from the application:

the second part is a minimal reproducible example to highlight the problem. I don't know if I have a logic error, or fpdf is not viable for this approach. This is something for an invoice generator.

def add_table_row(pdf, product, widths, alignments):
        pdf.set_font('Helvetica', '', 9)
        x_start = 25
        y_before = pdf.get_y()
        max_y = y_before

        for width, field, align in zip(widths, product, alignments):
            # Set x position to 25 for the first column
            pdf.set_xy(x_start, y_before)
            pdf.multi_cell(width, 5, field, border=1, align=align)
            x_start += width  # Move x position for the next column
            max_y = max(max_y, pdf.get_y())

        pdf.set_y(max_y)

   # Add table rows
    for idx, product in enumerate(product_lines, start=1):
        current_y = pdf.get_y()
        if current_y + 10 > pdf.h - 25:
            pdf.add_page()
            current_y = 25  # Set Y position to top margin for new page

        pdf.set_xy(25, current_y)  # Reset X position for the new row
        formatted_menge = f"{locale.format_string('%.2f', product['menge'], grouping=True)} {product['einheit']}"
        formatted_preis_netto = f"{locale.format_string('%.2f', product['preis_netto'], grouping=True)} EUR"
        formatted_betrag_netto = f"{product['betrag_netto']} EUR"
        product_data = [str(idx) + '.', product['produktname'], formatted_menge,
                        formatted_preis_netto, product['ust'] + ' %', formatted_betrag_netto]

        add_table_row(pdf, product_data, widths, alignments)

from fpdf import FPDF

class PDF(FPDF):
    def header(self):
        self.set_font('Arial', 'B', 12)
        self.cell(0, 10, 'Multi-page Table Example', 0, 1, 'C')

    def footer(self):
        self.set_y(-15)
        self.set_font('Arial', 'I', 8)
        self.cell(0, 10, f'Page {self.page_no()}', 0, 0, 'C')

def add_table_row(pdf, product, widths, alignments):
    x_start = 25
    y_before = pdf.get_y()
    max_y = y_before

    for width, field, align in zip(widths, product, alignments):
        pdf.set_xy(x_start, y_before)
        pdf.multi_cell(width, 10, field, border=1, align=align)
        x_start += width
        max_y = max(max_y, pdf.get_y())

    pdf.set_y(max_y)

# Create instance of FPDF class & add a page
pdf = PDF()
pdf.add_page()

# Set initial position for the first row
pdf.set_xy(25, 50)

# Sample data
products = [
    ["1", "Product A", "100", "5.00", "20 %", "600.00"],
    ["2", "Product Bahsdhasdhkjashd ahsdhjk ashdjk ahjkdhjka hjkdlahjskld hjklasdjhkl ahjklads hjkadhjkasdhjk ahjkhajkl",
        "150", "7.50", "20 %", "900.00"],
    ["2", "Product Bahsdhasdhkjashd ahsdhjk ashdjk ahjkdhjka hjkdlahjskld hjklasdjhkl ahjklads hjkadhjkasdhjk ahjkhajkl",
     "150", "7.50", "20 %", "900.00"],
    ["2", "Product Bahsdhasdhkjashd ahsdhjk ashdjk ahjkdhjka hjkdlahjskld hjklasdjhkl ahjklads hjkadhjkasdhjk ahjkhajkl",
     "150", "7.50", "20 %", "900.00"],
    ["2", "Product Bahsdhasdhkjashd ahsdhjk ashdjk ahjkdhjka hjkdlahjskld hjklasdjhkl ahjklads hjkadhjkasdhjk ahjkhajkl",
     "150", "7.50", "20 %", "900.00"],
    ["2", "Product Bahsdhasdhkjashd ahsdhjk ashdjk ahjkdhjka hjkdlahjskld hjklasdjhkl ahjklads hjkadhjkasdhjk ahjkhajkl",
     "150", "7.50", "20 %", "900.00"],
    ["2", "Product Bahsdhasdhkjashd ahsdhjk ashdjk ahjkdhjka hjkdlahjskld hjklasdjhkl ahjklads hjkadhjkasdhjk ahjkhajkl",
     "150", "7.50", "20 %", "900.00"],
    ["2", "Product Bahsdhasdhkjashd ahsdhjk ashdjk ahjkdhjka hjkdlahjskld hjklasdjhkl ahjklads hjkadhjkasdhjk ahjkhajkl",
     "150", "7.50", "20 %", "900.00"],
    ["2", "Product Bahsdhasdhkjashd ahsdhjk ashdjk ahjkdhjka hjkdlahjskld hjklasdjhkl ahjklads hjkadhjkasdhjk ahjkhajkl",
     "150", "7.50", "20 %", "900.00"],
    ["2", "Product Bahsdhasdhkjashd ahsdhjk ashdjk ahjkdhjka hjkdlahjskld hjklasdjhkl ahjklads hjkadhjkasdhjk ahjkhajkl",
     "150", "7.50", "20 %", "900.00"],
    ["2", "Product Bahsdhasdhkjashd ahsdhjk ashdjk ahjkdhjka hjkdlahjskld hjklasdjhkl ahjklads hjkadhjkasdhjk ahjkhajkl",
     "150", "7.50", "20 %", "900.00"],
    ["2", "Product Bahsdhasdhkjashd ahsdhjk ashdjk ahjkdhjka hjkdlahjskld hjklasdjhkl ahjklads hjkadhjkasdhjk ahjkhajkl",
     "150", "7.50", "20 %", "900.00"],
    ["2", "Product Bahsdhasdhkjashd ahsdhjk ashdjk ahjkdhjka hjkdlahjskld hjklasdjhkl ahjklads hjkadhjkasdhjk ahjkhajkl",
     "150", "7.50", "20 %", "900.00"],

    ["2", "Product B", "150", "7.50", "20 %", "900.00"],
    ["3", "Product C", "200", "10.00", "20 %", "1200.00"],
    ["4", "Product D", "250", "12.50", "20 %", "1500.00"],
    ["5", "Product E", "300", "15.00", "20 %", "1800.00"],
    ["1", "Product A", "100", "5.00", "20 %", "600.00"],
    ["2", "Product B", "150", "7.50", "20 %", "900.00"],
    ["3", "Product C", "200", "10.00", "20 %", "1200.00"],
    ["4", "Product D", "250", "12.50", "20 %", "1500.00"],
    ["5", "Product E", "300", "15.00", "20 %", "1800.00"],
    ["1", "Product A", "100", "5.00", "20 %", "600.00"],
    ["2", "Product B", "150", "7.50", "20 %", "900.00"],
    ["3", "Product C", "200", "10.00", "20 %", "1200.00"],
    ["4", "Product D", "250", "12.50", "20 %", "1500.00"],
    ["5", "Product E", "300", "15.00", "20 %", "1800.00"],
    ["1", "Product A", "100", "5.00", "20 %", "600.00"],
    ["2", "Product B", "150", "7.50", "20 %", "900.00"],
    ["3", "Product C", "200", "10.00", "20 %", "1200.00"],
    ["4", "Product D", "250", "12.50", "20 %", "1500.00"],
    ["5", "Product E", "300", "15.00", "20 %", "1800.00"],
    ["1", "Product A", "100", "5.00", "20 %", "600.00"],
    ["2", "Product B", "150", "7.50", "20 %", "900.00"],
    ["3", "Product C", "200", "10.00", "20 %", "1200.00"],
    ["4", "Product D", "250", "12.50", "20 %", "1500.00"],
    ["5", "Product E", "300", "15.00", "20 %", "1800.00"],
    ["1", "Product A", "100", "5.00", "20 %", "600.00"],
    ["2", "Product B", "150", "7.50", "20 %", "900.00"],
    ["3", "Product C", "200", "10.00", "20 %", "1200.00"],
    ["4", "Product D", "250", "12.50", "20 %", "1500.00"],
    ["5", "Product E", "300", "15.00", "20 %", "1800.00"],
    ["1", "Product A", "100", "5.00", "20 %", "600.00"],
    ["2", "Product B", "150", "7.50", "20 %", "900.00"],
    ["3", "Product C", "200", "10.00", "20 %", "1200.00"],
    ["4", "Product D", "250", "12.50", "20 %", "1500.00"],
    ["5", "Product E", "300", "15.00", "20 %", "1800.00"],
    ["1", "Product A", "100", "5.00", "20 %", "600.00"],
    ["2", "Product B", "150", "7.50", "20 %", "900.00"],
    ["3", "Product C", "200", "10.00", "20 %", "1200.00"],
    ["4", "Product D", "250", "12.50", "20 %", "1500.00"],
    ["5", "Product E", "300", "15.00", "20 %", "1800.00"],
    ["1", "Product A", "100", "5.00", "20 %", "600.00"],
    ["2", "Product B", "150", "7.50", "20 %", "900.00"],
    ["3", "Product C", "200", "10.00", "20 %", "1200.00"],
    ["4", "Product D", "250", "12.50", "20 %", "1500.00"],
    ["5", "Product E", "300", "15.00", "20 %", "1800.00"],
    ["1", "Product A", "100", "5.00", "20 %", "600.00"],
    ["2", "Product B", "150", "7.50", "20 %", "900.00"],
    ["3", "Product C", "200", "10.00", "20 %", "1200.00"],
    ["4", "Product D", "250", "12.50", "20 %", "1500.00"],
    ["5", "Product E", "300", "15.00", "20 %", "1800.00"],
    ["1", "Product A", "100", "5.00", "20 %", "600.00"],
    ["2", "Product Bahsdhasdhkjashd ahsdhjk ashdjk ahjkdhjka hjkdlahjskld hjklasdjhkl ahjklads hjkadhjkasdhjk ahjkhajkl",
        "150", "7.50", "20 %", "900.00"],
    ["3", "Product C", "200", "10.00", "20 %", "1200.00"],
    ["4", "Product D", "250", "12.50", "20 %", "1500.00"],
    ["5", "Product E", "300", "15.00", "20 %", "1800.00"],
]

# Column widths and alignments
widths = [10, 70, 20, 30, 20, 30]
alignments = ['C', 'L', 'R', 'R', 'C', 'R']

# Add rows to the table
for product in products:
    current_y = pdf.get_y()
    if current_y + 10 > pdf.h - 25:
        pdf.add_page()
        pdf.set_xy(25, 50)  # Reset position at the top of the new page
    add_table_row(pdf, product, widths, alignments)

# Save the pdf with name .pdf
pdf_file_path = 'multi_page_table.pdf'
pdf.output(pdf_file_path)
print(f"PDF generated: {pdf_file_path}")

thanks in advance for any tips/help

andersonhc commented 3 months ago

Let me introduce you to our table feature, it will definitely make your life easier.

You can replace most of your code by something like this:

with pdf.table(
    width=180,
    col_widths=(10, 70, 20, 30, 20, 30),
    text_align=("C", "L", "R", "R", "C", "R"),
) as table:
    for product in products:
        row = table.row()
        for field in product:
            row.cell(field)
andersonhc commented 3 months ago

@tekkadan95 please let us know if that solution works for you and we can close the issue.

Lucas-C commented 2 weeks ago

No comment received for 2 months. I'm closing this now