1

I am having an issue calculating the correct height of a multicell in FPDF. I am running into issues of overlapping when using pdf.ln() as the content in the cell is overflowing. What can I do to dynamically solve this for any length of the string?

itemname = "This is a thing this is a thing this is a thing"
lineheight = pdf.font_size * 2.5
pdf.multi_cell(col_width * 9, line_height, textname, border=0, align="L",ln=3, max_line_height=7)
pdf.ln()
pdf.multi_cell(col_width * 9, line_height, "Shipping Charges", border=0, align="L",ln=3, max_line_height=7)

This is giving the result as shown in the image, if I add more content to the item name the shipping cost, giftwrap and item name and placed on top of it enter image description here

Derik002
  • 145
  • 13
  • To know the number of lines a MultiCell will take, you need a function that implements the same logic as MultiCell itself. For PHP, the NbLines() function of the [Table with MultiCells](http://www.fpdf.org/en/script/script3.php) script does exacty that. – Olivier Jun 28 '21 at 12:09

1 Answers1

0

I took a different approach. It is a little more involved, but you can add each cell in a row and let it take however much space it needs. Find the bottom of the cell and keep track of it. As you add cells to the row, figure out which is the lowest, and then start the next line at that position. That way, the next row will be far enough down to accommodate the tallest cell in the previous row.

Modifying Bvalgard's code (mostly, I replaced multi_cell with my add_cell function and track the lowest_point as I go):

def add_cell(pdf, width, line_height, content, border, align, lowest_point, highest_point):
    pdf.multi_cell(width, line_height, content, border=border, align=align, ln=0, max_line_height=pdf.font_size*1.25)
    current_x = pdf.get_x()
    current_y = pdf.get_y()
    if current_y > lowest_point:
        lowest_point = current_y
        print("new lowest point:", lowest_point)
    pdf.set_xy(current_x, highest_point)
    print("row x", pdf.get_x(), "row y", pdf.get_y())
    return lowest_point

def create_table(pdf, table_data, title='', data_size = 10, title_size=12, align_data='L', align_header='L', cell_width='even', x_start='x_default',emphasize_data=[], emphasize_style=None, emphasize_color=(0,0,0)):
    """
    table_data: 
                list of lists with first element being list of headers
    title: 
                (Optional) title of table (optional)
    data_size: 
                the font size of table data
    title_size: 
                the font size fo the title of the table
    align_data: 
                align table data
                L = left align
                C = center align
                R = right align
    align_header: 
                align table data
                L = left align
                C = center align
                R = right align
    cell_width: 
                even: evenly distribute cell/column width
                uneven: base cell size on lenght of cell/column items
                int: int value for width of each cell/column
                list of ints: list equal to number of columns with the widht of each cell / column
    x_start: 
                where the left edge of table should start
    emphasize_data:  
                which data elements are to be emphasized - pass as list 
                emphasize_style: the font style you want emphaized data to take
                emphasize_color: emphasize color (if other than black) 
    
    """
    default_style = pdf.font_style
    if emphasize_style == None:
        emphasize_style = default_style
    # default_font = pdf.font_family
    # default_size = pdf.font_size_pt
    # default_style = pdf.font_style
    # default_color = pdf.color # This does not work

    # Get Width of Columns
    def get_col_widths():
        col_width = cell_width
        if col_width == 'even':
            col_width = pdf.epw / len(data[0]) - 1  # distribute content evenly   # epw = effective page width (width of page not including margins)
        elif col_width == 'uneven':
            col_widths = []

            # searching through columns for largest sized cell (not rows but cols)
            for col in range(len(table_data[0])): # for every row
                longest = 0 
                for row in range(len(table_data)):
                    cell_value = str(table_data[row][col])
                    value_length = pdf.get_string_width(cell_value)
                    if value_length > longest:
                        longest = value_length
                col_widths.append(longest + 4) # add 4 for padding
            col_width = col_widths



                    ### compare columns 

        elif isinstance(cell_width, list):
            col_width = cell_width  # TODO: convert all items in list to int        
        else:
            # TODO: Add try catch
            col_width = int(col_width)
        return col_width

    # Convert dict to lol
    # Why? because i built it with lol first and added dict func after
    # Is there performance differences?
    if isinstance(table_data, dict):
        header = [key for key in table_data]
        data = []
        for key in table_data:
            value = table_data[key]
            data.append(value)
        # need to zip so data is in correct format (first, second, third --> not first, first, first)
        data = [list(a) for a in zip(*data)]

    else:
        header = table_data[0]
        data = table_data[1:]

    line_height = pdf.font_size * 2.5
    # line_height = pdf.font_size * 10

    col_width = get_col_widths()
    pdf.set_font(size=title_size)

    # Get starting position of x
    # Determin width of table to get x starting point for centred table
    if x_start == 'C':
        table_width = 0
        if isinstance(col_width, list):
            for width in col_width:
                table_width += width
        else: # need to multiply cell width by number of cells to get table width 
            table_width = col_width * len(table_data[0])
        # Get x start by subtracting table width from pdf width and divide by 2 (margins)
        margin_width = pdf.w - table_width
        # TODO: Check if table_width is larger than pdf width

        center_table = margin_width / 2 # only want width of left margin not both
        x_start = center_table
        pdf.set_x(x_start)
    elif isinstance(x_start, int):
        pdf.set_x(x_start)
    elif x_start == 'x_default':
        x_start = pdf.set_x(pdf.l_margin)


    # TABLE CREATION #

    # add title
    if title != '':
        pdf.multi_cell(0, line_height, title, border=0, align='j', ln=3, max_line_height=pdf.font_size)
        pdf.ln(line_height) # move cursor back to the left margin

    pdf.set_font(size=data_size)
    # add header
    y1 = pdf.get_y()
    if x_start:
        x_left = x_start
    else:
        x_left = pdf.get_x()
    x_right = pdf.epw + x_left

    lowest_point = pdf.get_y()
    highest_point = pdf.get_y()
    print("header x", pdf.get_x(), "header y", pdf.get_y())

    if  not isinstance(col_width, list):
        if x_start:
            pdf.set_x(x_start)
        for datum in header:
            # pdf.multi_cell(col_width, line_height, datum, border=0, align=align_header, ln=3, max_line_height=pdf.font_size)
            lowest_point = add_cell(pdf, col_width, line_height, datum, 0, align_header, lowest_point, highest_point)
            x_right = pdf.get_x()
        # pdf.ln(line_height) # move cursor back to the left margin
        # y2 = pdf.get_y()
        # pdf.line(x_left,y1,x_right,y1)
        # pdf.line(x_left,y2,x_right,y2)
        # lowest_point += line_height/2.0

        pdf.line(x_left,highest_point,x_right,highest_point)
        pdf.line(x_left,lowest_point,x_right,lowest_point)
        for row in data:
            # if x_start: # not sure if I need this
            pdf.set_xy(x_start, lowest_point)
            highest_point = lowest_point
            for datum in row:
                if datum in emphasize_data:
                    pdf.set_text_color(*emphasize_color)
                    pdf.set_font(style=emphasize_style)
                    # pdf.multi_cell(col_width, line_height, datum, border=0, align=align_data, ln=3, max_line_height=pdf.font_size)
                    lowest_point = add_cell(pdf, col_width, line_height, datum, 0, align_header, lowest_point, highest_point)
                    pdf.set_text_color(0,0,0)
                    pdf.set_font(style=default_style)
                else:
                    # pdf.multi_cell(col_width, line_height, datum, border=0, align=align_data, ln=3, max_line_height=pdf.font_size) # ln = 3 - move cursor to right with same vertical offset # this uses an object named pdf
                    lowest_point = add_cell(pdf, col_width, line_height, datum, 0, align_header, lowest_point, highest_point)
            # pdf.ln(line_height) # move cursor back to the left margin
            # pdf.ln(line_height)
            lowest_point += line_height/2.0
            pdf.line(x_left,lowest_point,x_right,lowest_point)
            print("making line at", lowest_point)
        pdf.set_xy(x_left, lowest_point)
    else:
        if x_start:
            pdf.set_x(x_start)
        for i in range(len(header)):
            datum = header[i]
            # pdf.multi_cell(col_width[i], line_height, datum, border=0, align=align_header, ln=3, max_line_height=pdf.font_size)
            lowest_point = add_cell(pdf, col_width[i], line_height, datum, 0, align_header, lowest_point, highest_point)
            x_right = pdf.get_x()
        # pdf.ln(line_height) # move cursor back to the left margin
        # y2 = pdf.get_y()
        # pdf.line(x_left,y1,x_right,y1)
        # pdf.line(x_left,y2,x_right,y2)
        # lowest_point += line_height/2.0

        pdf.line(x_left,highest_point,x_right,highest_point)
        pdf.line(x_left,lowest_point,x_right,lowest_point)
        for i in range(len(data)):
            # if x_start:
            #     pdf.set_x(x_start)
            pdf.set_xy(x_start, lowest_point)
            highest_point = lowest_point
            row = data[i]
            for i in range(len(row)):
                datum = row[i]
                if not isinstance(datum, str):
                    datum = str(datum)
                adjusted_col_width = col_width[i]
                if datum in emphasize_data:
                    pdf.set_text_color(*emphasize_color)
                    pdf.set_font(style=emphasize_style)
                    # pdf.multi_cell(adjusted_col_width, line_height, datum, border=0, align=align_data, ln=3, max_line_height=pdf.font_size)
                    lowest_point = add_cell(pdf, adjusted_col_width, line_height, datum, 0, align_header, lowest_point, highest_point)
                    pdf.set_text_color(0,0,0)
                    pdf.set_font(style=default_style)
                else:
                    # pdf.multi_cell(adjusted_col_width, line_height, datum, border=0, align=align_data, ln=3, max_line_height=pdf.font_size) # ln = 3 - move cursor to right with same vertical offset # this uses an object named pdf
                    lowest_point = add_cell(pdf, adjusted_col_width, line_height, datum, 0, align_header, lowest_point, highest_point)
            lowest_point += line_height/2.0
            pdf.line(x_left,lowest_point,x_right,lowest_point)
        pdf.set_xy(x_left, lowest_point)
    y3 = pdf.get_y()
    pdf.line(x_left,y3,x_right,y3)
    pdf.ln()


pdf = FPDF(orientation='L', unit='mm', format='A4')
pdf.add_page()

cell_widths = "even"

create_table(pdf, table_data = my_table_data, data_size=10, align_header='L', align_data='L', cell_width=cell_widths, x_start='C') 

cthelin7
  • 51
  • 6