Carmen B.
Carmen B.

Reputation: 349

Problems creating PDF with tables and multicells in FPDF Python

I am trying to create a table with the FPDF library that is capable of expanding the height of the cells when the text is very large.

I am going to show the code that I have, which is the same that appears on the official documentation page:

from fpdf import FPDF

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 udfhisudhf fughdiufhg fduihgsdiufg dfghsdifugh fdiguhdsfiug fdughdifugh dfhgsdiufhg"),
)

pdf = FPDF()
pdf.add_page()
pdf.set_font("Times", size=10)
line_height = pdf.font_size * 2.5
col_width = pdf.epw / 4 
for row in data:
    for datum in row:
        pdf.multi_cell(col_width, line_height, datum, border=1, ln=3)
    pdf.ln(line_height)
pdf.output('table_with_cells.pdf')

this is the result:

THE PDF RESULT

The last column of the last row is taller because it has more content, that's fine, but I would like the height of all columns in that row to be the same dynamically. Something like this:

I WOULD LIKE THIS RESULT

How can I get it?

Note: The data that will be in the table comes from the database, so I can't know exactly the length of each string

Upvotes: 2

Views: 13211

Answers (5)

iKindOfCode
iKindOfCode

Reputation: 29

I had a hard time solving a similar problem and had issues with all the above solutions.

self.pdf points to an FPDF instance. Data is a two-dimensional array widths is one-dimensional array e.g. [25, 75] (first column gets 25% of page and second gets 75%)

I found a rather simple solution where i first insert all the text without border, and then insert the border afterwards. Using output=MethodReturnValue.HEIGHT i get the height of the printed multi_cell and i use this value to update the max_height variable.

def build_table(self, data, col_count, widths=None, col_header_index=None, first_row_as_header=None):
    self.pdf.set_font("helvetica", "", 10)
    widths = widths if widths != None else [100/col_count for _ in range(col_count)]
    for i, row in enumerate(data):
        max_height = 0
        for j, col in enumerate(row):
            w = (widths[j] / 100) * self.pdf.epw
            h = self.pdf.multi_cell(w=w, h=5, border=False, txt=col, new_x="RIGHT" if j < col_count - 1 else "LMARGIN", new_y="TOP", output=MethodReturnValue.HEIGHT)
            max_height = h if h > max_height else max_height
        for j, col in enumerate(row):
            w = (widths[j] / 100) * self.pdf.epw
            h = max_height
            self.pdf.multi_cell(w=w, h=h, border=True,new_x="RIGHT" if j < col_count - 1 else "LMARGIN", new_y="TOP" if j < col_count - 1 else "NEXT")

Upvotes: 0

jayesef
jayesef

Reputation: 791

Before starting adding any multi_cell to the new row, you should determine the height of the highest cell, which can be obtained by calling multi_cell with the option split_only=True ( as indicated in the last line of the documentation https://pyfpdf.github.io/fpdf2/Tables.html ). In your example this would translate in something like

for row in data:
    n_lines = max([pdf.multi_cell(col_width, line_height, datum, border=1, ln=3,
                                  split_only=True)
                   for datum in row])
    next_line_height = pdf.font_size * (n_lines + 1.5)
    for datum in row:
        pdf.multi_cell(col_width, next_line_height, datum, border=1, ln=3)
    pdf.ln(line_height)

Upvotes: 0

ahmed hamza
ahmed hamza

Reputation: 21

You can draw a table from scratch, and assign text fonts based on the size.

Something like this:

arr_1=['Avg OEE','Avg AV','Avg Qualtiy','Avg Performance',]
j=0
for st in arr_1:  #draw text
    pdf.set_y(42+j*7)
    pdf.set_x(4)
    pdf.set_font('Arial', 'I',12) #change 12 with the font you want
    pdf.set_text_color(255, 255, 255)
    # pdf.cell(1)
    pdf.set_fill_color(255, 255, 255)
    pdf.cell(0, 0, st, 0, 0, 'L', fill=False)
    j=j+1    
 for i in range(5):#draw horizantal line
    pdf.set_y(39+i*7)
    pdf.set_x(2)
    pdf.cell(53, .1, '', 0, 0, 'C', fill=True)

y=39
pdf.set_y(y)#draw vert line
pdf.set_x(2)
pdf.cell(.1,28, '', 0, 0, 'C', fill=True)

pdf.set_y(y)
pdf.set_x(40)
pdf.cell(.1, 28, '', 0, 0, 'C', fill=True)

pdf.set_y(y)
pdf.set_x(55)
pdf.cell(.1, 28, '', 0, 0, 'C', fill=True)enter code here

Upvotes: 1

GambuTheWizard
GambuTheWizard

Reputation: 1

Weirdly in my case this line is denoted as undefined, which makes the whole code not working:

line_height = lh_list[j] #choose right height for current row

Upvotes: 0

JackColo_Ben4
JackColo_Ben4

Reputation: 447

probably it's to late for you, but since we are at it...

Assumptions:
As always with FPDF, in order to adjust 'line_height' and 'col_width' parameters to use in cell/multicell methods...you MUST know something about the content of your data, at least the max length of the longest word in your table. Otherwise...you have to set some reasonable max boundaries according to your knowledge.
For instance, using your data, for setting the line_height... if ironically a 'first name' were composed by 75 chars (or more), multiply the font_size for 2.5 will not be enough and the final table printed on the pdf will be unreadable. But you can be pretty sure that you will nobody in the world has a first name that long. =) Therefore you can be confident that 2.5 is a reasonable factor, even if you don't know all elements in the first column of your table.

Anyway...
the only solution here, is count previously the number of words of each cell, defining a new,good,unique line_height for each data rows containing a string with many "N" words. This new line_height depends on the number of words of your long string. Finally, you can easily create your table choosing the proper line_height, according to your current row.

from fpdf import FPDF

data = (
    ("First name", "Last name", "Age", "City"),
    ("Jules", "Smith", "34", "San Juan"),
    ("Mary", "Ramos", "45", "Orlando"),
    ("Carlson", "Banks", "19", "Los Angeles"),
    ("Milano Roma Firenze Venezia Genova Napoli Livorno Bergamo Siracusa \       
    Rimini Pisa Bologna Brescia Torino"),
    )
pdf = FPDF()
pdf.add_page()
pdf.set_font("Times", size=10)
line_height = pdf.font_size * 2.5
col_width = pdf.epw / 4.5

lh_list = [] #list with proper line_height for each row
use_default_height = 0 #flag

#create lh_list of line_heights which size is equal to num rows of data
for row in data:
    for datum in row:
        word_list = datum.split()
        number_of_words = len(word_list) #how many words
        if number_of_words>2: #names and cities formed by 2 words like Los Angeles are ok)
            use_default_height = 1
            new_line_height = pdf.font_size * (number_of_words/2) #new height change according to data 
    if not use_default_height:
        lh_list.append(line_height)
    else:
        lh_list.append(new_line_height)
        use_default_height = 0

#create your fpdf table ..passing also max_line_height!
for j,row in enumerate(data):
    for datum in row:
        line_height = lh_list[j] #choose right height for current row
        pdf.multi_cell(col_width, line_height, datum, border=1,align='L',ln=3, 
        max_line_height=pdf.font_size)
    pdf.ln(line_height)

pdf.output('table_with_cells.pdf')

enter image description here

Upvotes: 5

Related Questions