pythonpython-3.xpyfpdf

How to set a constant height and constant width for every cell in a FPDF table in python


I'm making a script that makes a table out of a list of lists from a database.
I want to have some control over every cell so I don't want to use the with pdf.table() as table: as shown in the fpdf docs (https://py-pdf.github.io/fpdf2/Tables.html).
So I came up with an idea to make my own tables using multi_cells and cells but I encountered a problem with finding the right constant height for every cell so that the table would actually look like a table.
Currently the table looks like this: It's not even and doesn't look like a normal table.

Also I need to find a way to set a constant width for every cell so that every value fits in them. I want the table to look something like this: This table was created using the with pdf.table() as table (from the docs).

To summarize I want to find a constant height and constant width for cells so that the longest value from the list fits in the cells perfectly using for loops so that I can have some control over the cells that I create.

This is the current code relative to the first image:

from fpdf import FPDF
data = [
['Lp.', 'Nazwa Uslugi/Towaru', 'Jm', 'Ilosc', 'Cena Netto', 'Wartosc Netto', 'VAT', 'Kwota VAT' , 'Wartosc brutto'],
['1', 'maka', 'szt.', '10kg', '  11,32  zl ', '  11,32  zl ', '23%', '  2,60  zl ', '  13,92  zl '],
['2', 'ryz', 'szt.', '15kg', '  10,00  zl ', '  10,00  zl ', '8%', '  0,80  zl ', '  10,80  zl '],
['3', 'makaron', 'szt.', '5kg', '  3,60  zl ', '  3,60  zl ', '23%', '  0,83  zl ', '  4,43  zl '],
['4', 'kasza', 'szt.', '8kg', '  2,00  zl ', '  2,00  zl ', '8%', '  0,16  zl ', '  2,16  zl '],
['5', 'cukier', 'szt.', '20kg', '  20,00  zl ', '  20,00  zl ', '8%', '  1,60  zl ', '  21,60  zl '],
['6', 'soda', 'szt.', '3kg', '  1,00  zl ', '  1,00  zl ', '23%', '  0,23  zl ', '  1,23  zl '],
['7', 'smietana', 'szt.', '1l', '  15,00  zl ', '  15,00  zl ', '23%', '  3,45  zl ', '  18,45  zl '],
['8', 'bulka tarta', 'szt.', '2kg', '  1,56  zl ', '  1,56  zl ', '23%', '  0,36  zl ', '  1,92  zl']]

def simple_table(spacing=2):
    pdf = FPDF()
    pdf.add_font('Segoe Ui', '', r'C:\Windows\Fonts\seguibl.ttf')
    pdf.add_page()
    pdf.set_font("Segoe Ui")
    counter = 0
    
    col_width = pdf.w / 10
    row_height = pdf.font_size
    for row in data:
        counter += 1
        new_x = 10
        for item in row:
            if counter == 1:
                pdf.set_fill_color(128, 128, 128)
                pdf.multi_cell(col_width, row_height,
                     txt=item, border=1, align="C", fill=True)
                new_x += col_width
                pdf.set_xy(new_x, 10)
            else:
                pdf.cell(col_width, row_height*spacing,
                     txt=item, border=1)
        pdf.ln(row_height*spacing)
        
    pdf.output('testing.pdf')
simple_table()

I tried fixing the placement but couldn't figure it out.


Solution

  • Try to use the following code:

    from fpdf import FPDF
    
    data = [
        ['Lp.', 'Nazwa Uslugi/Towaru', 'Jm', 'Ilosc', 'Cena Netto', 'Wartosc Netto', 'VAT', 'Kwota VAT', 'Wartosc brutto'],
        ['1', 'maka', 'szt.', '10kg', '  11,32  zl ', '  11,32  zl ', '23%', '  2,60  zl ', '  13,92  zl '],
        ['2', 'ryz', 'szt.', '15kg', '  10,00  zl ', '  10,00  zl ', '8%', '  0,80  zl ', '  10,80  zl '],
        ['3', 'makaron', 'szt.', '5kg', '  3,60  zl ', '  3,60  zl ', '23%', '  0,83  zl ', '  4,43  zl '],
        ['4', 'kasza', 'szt.', '8kg', '  2,00  zl ', '  2,00  zl ', '8%', '  0,16  zl ', '  2,16  zl '],
        ['5', 'cukier', 'szt.', '20kg', '  20,00  zl ', '  20,00  zl ', '8%', '  1,60  zl ', '  21,60  zl '],
        ['6', 'soda', 'szt.', '3kg', '  1,00  zl ', '  1,00  zl ', '23%', '  0,23  zl ', '  1,23  zl '],
        ['7', 'smietana', 'szt.', '1l', '  15,00  zl ', '  15,00  zl ', '23%', '  3,45  zl ', '  18,45  zl '],
        ['8', 'bulka tarta', 'szt.', '2kg', '  1,56  zl ', '  1,56  zl ', '23%', '  0,36  zl ', '  1,92  zl']]
    
    CELL_WIDTH = 20
    
    def get_num_of_lines_in_multicell(pdf, message):
        global CELL_WIDTH
        # divide the string in words
        words = message.split(" ")
        line = ""
        n = 1
        for word in words:
            line += word + " "
            line_width = pdf.get_string_width(line)
            # In the next if it is necessary subtract 1 to the WIDTH
            if line_width > (CELL_WIDTH-1) * 2 - 1:
                # the multi_cell() insert a line break
                n += 2
                # reset of the string
                line = word + " "
            elif line_width > CELL_WIDTH-1:
                # the multi_cell() insert a line break
                n += 1
                # reset of the string
                line = word + " "
        return n
    
    def simple_table(spacing=2):
        global CELL_WIDTH
        pdf = FPDF()
    
        #pdf.add_font('Segoe Ui', '', r'C:\Windows\Fonts\seguibl.ttf')
        pdf.set_font('Times')
    
        pdf.add_page()
        #pdf.set_font("Segoe Ui")
        counter = 0
    
        col_width = pdf.w / 10
        CELL_WIDTH = col_width
        row_height = pdf.font_size
    
        for row in data:
            counter += 1
            new_x = 10
            saveY = 0
            for item in row:
                if counter == 1:
                    pdf.set_xy(new_x, 10)
                    pdf.set_fill_color(128, 128, 128)
                    num_lines = get_num_of_lines_in_multicell(pdf, item)
                    print(f"Num lines for '{item}' = {num_lines}")
                    if num_lines == 1:
                        pdf.multi_cell(col_width, row_height*3, txt=item, border=1, align="C", fill=True)
                    elif num_lines == 2:
                        pdf.multi_cell(col_width, (row_height*3)/2, txt=item, border=1, align="C", fill=True)
                    else:
                        pdf.multi_cell(col_width, row_height, txt=item, border=1, align="C", fill=True)
                    if pdf.get_y() > saveY:
                        saveY = pdf.get_y()
                    new_x += col_width
                    pdf.set_xy(new_x, 10)
                else:
                    pdf.cell(col_width, row_height * spacing, txt=item, border=1)
            if counter == 1:
                pdf.set_xy(10, saveY)
            else:
                pdf.ln(row_height * spacing)
    
        pdf.output('testing.pdf')
    
    simple_table()
    

    In the code:

    The content of the file testing.pdf (sorry for the font that is different from yours) is:

    enter image description here