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

122 views Asked by At

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.

1

There are 1 answers

0
User051209 On BEST ANSWER

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 variable data is exactly your variable.
  • I have made some modifications to your function simple_table()
  • I have added the function get_num_of_lines_in_multicell() which is not very different from the function present in this post.

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

enter image description here