py-pdf / fpdf2

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

Page Break after every cell if multicell crosses a page break. #120

Closed BConquest closed 3 years ago

BConquest commented 3 years ago

I am having weird issues with multicell inserting a page break after every cell if it crosses a page break.

import logging

from fpdf import fpdf

fpdf.LOGGER.setLevel(logging.DEBUG)
fpdf.LOGGER.addHandler(logging.StreamHandler())

data = (
    ("First name", "Last name", "Age", "City"),
    ("Jules", "Smith", "34", "San Juan"),
    ("Mary", "Ramos", "45", "Orlando"),
    ("Carlson", "Banks", "19", "Los Angeles"),
    ("Lucas", "Cimon", "31", "Saint-Mahturin-sur-Loire"),
)

pdf = fpdf.FPDF()
pdf.add_page()
pdf.set_font("Times", size=16)
line_height = pdf.font_size * 2
col_width = pdf.epw / 4  # distribute content evenly
for i in range(5):
    for row in data:
        for datum in row:
            pdf.multi_cell(col_width, line_height, datum, border=1, ln=3, max_line_height=pdf.font_size)
        pdf.ln(line_height)

pdf.output('table_with_cells.pdf')

table_with_cells.pdf

This seems related to issue #111, thanks for the cool lib.

Lucas-C commented 3 years ago

Interesting, thank you for the issue report!

I opened https://github.com/PyFPDF/fpdf2/pull/121 to solve this case

I plan on merging it & generating a new release this week-end.

WalidGharianiEAGLE commented 3 years ago

First of, Awesome light weight library! So Im using the latest release and I also come across the same issue. Here is an example to replicate

import pandas as pd 
from fpdf import FPDF

index=range(0,19)
columns=['A']
test = pd.DataFrame(index=index, columns=columns)
test['A']="test_lin_o_00000_001"
test['B'] = '3'
test['C'] = '4'
test['D'] = "test_lin_o_00000"
test['E'] = '7'
test['F'] = "test_lin_o_00000"
test['G'] = "test_lin"
test['H'] = "test_lin"

test_list = list(zip(test ["A"] ,test["B"],test ["C"],
                   test ["D"],test["E"],test ["F"],test["G"],
                   test["H"]))
test_titles = (("A","B","C","D","E","F","G","H"))
columns_test = [test_titles ] 

class CustomPDF(FPDF):

    def header(self):
        # Set up a logo
        self.image('https://upload.wikimedia.org/wikipedia/commons/7/70/Example.png', 160, 3, 25)
        self.set_font('Arial', 'B', 30)
        # Line break
        self.ln(20)

    def footer(self):
        self.set_y(-10)
        self.set_font('Arial', 'I', 8)

        # Add a page number
        page = 'Page ' + str(self.page_no()) + '/{nb}'
        self.cell(0, 10, page, 0, 0, 'C')

def create_pdf(pdf_path):
    pdf = CustomPDF()
    pdf.alias_nb_pages()
    pdf.add_page()
    pdf.set_margins(20, 20)
    pdf.set_text_color(50, 139, 168)
    pdf.set_font('Arial', '', 13)
    pdf.cell(0, 6, f'{"test"}')
    pdf.ln(5)
    pdf.set_text_color(0, 0,0)
    pdf.set_font('Times','B',size=7)
    line_height = pdf.font_size * 3
    col_width = pdf.epw / 8 
    for row in columns_test :
        for datum in row:
            pdf.multi_cell(col_width, line_height, datum, border=1, ln=3, max_line_height=pdf.font_size,align='C')
        pdf.ln(line_height)
    pdf.set_font('Times',size=7)
    line_height = pdf.font_size * 10 
    col_width = pdf.epw / 8 
    for row in test_list:
        for datum in row:
            pdf.multi_cell(col_width, line_height, datum, border=1, ln=3, max_line_height=pdf.font_size,align='C')
        pdf.ln(line_height)
    pdf.output(pdf_path)

if __name__ == '__main__':
    create_pdf('test.pdf')

What I noticed is that this have to do with the test['A'] containing large characters . If you drop some characters eg. test["A"] = test['A'].str.replace("test_", "") and redo the same thing it will works but I do need those original columns. Is here is way around this?

Lucas-C commented 3 years ago

Hi @WalidGharianiEAGLE!

What you need here is an unbreakable-section.

Interestingly, your example revealed a minor bug related to the page breaks logic, on some very specific conditions, when the overflow check compares two values that are equal. You can find the details, and a usage example of unbreakable(), in this PR: https://github.com/PyFPDF/fpdf2/pull/155/files