pythonalgorithmpdfpyfpdf

Calculate the number of rows filled by a string in a multi_cell of the library PyFPDF


I'm using the method multicell() of the class FPDF (library PyFPDF) and, to manage it in my real application, I have to use the method get_string_width().

Description of my code

The code and its output

from fpdf import FPDF
import webbrowser

CELL_WIDTH = 20

# function that calculates the number of lines inside multicel
def get_num_of_lines_in_multicell(pdf, message):
    n = 1
    msg_width = pdf.get_string_width(message)
    while msg_width > n * CELL_WIDTH:
        n += 1
    return n

def main():
    pdf=FPDF()
    pdf.add_page()
    pdf.set_font('Arial','',8)

    cell_message1 = "Hello World! I'm trying to use multi_cell() method"
    pdf.multi_cell(CELL_WIDTH, 4, cell_message1, 1, 0)

    # call the function get_num_of_lines_in_multicell() and print the number of lines inside the multicell
    print(f"num lines in the multi_cell = {get_num_of_lines_in_multicell(pdf, cell_message1)}")

    # creation of the PDF
    pdf.output('multi_cell.pdf','F')

    # open the PDF
    webbrowser.open_new('multi_cell.pdf')

main()

The output of the file on the standard output is the followed:

num lines in the multi_cell = 4

But the multicell inside the PDF file created contains the message on 5 LINES and not 4 LINES. Below I show the PDF content:

Content of the pdf created

Question

Where is the error in the function get_num_of_lines_in_multicell(pdf, message) which return 4 lines, while in the file created the lines are 5?


Solution

  • Not correct use of get_string_width()

    The function get_num_of_lines_in_multicell() doesn't use correctly the method get_string_width(). This method is called only one time on the whole string. This is correct if the string would be inserted in a normal cell, but not if it is split between multiple rows like the multi_cell() method does.

    How to fix the function get_num_of_lines_in_multicell()

    To solve the problem it is necessary to consider all the words present in the message and apply get_string_width() to a string that is built by adding one word at a time.
    The correct function get_num_of_lines_in_multicell() is shown below along with the entire program:

    from fpdf import FPDF
    import webbrowser
    
    CELL_WIDTH = 20
    
    # correct function which calculates the number of lines inside multicell
    def get_num_of_lines_in_multicell(pdf, message):
        # 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:
                # the multi_cell() insert a line break
                n += 1
                # reset of the string
                line = word + " "
        return n
    
    def main():
        pdf=FPDF()
        pdf.add_page()
        pdf.set_font('Arial','',8)
    
        cell_message1 = "Hello World! I'm trying to use multi_cell() method"
        pdf.multi_cell(CELL_WIDTH, 4, cell_message1, 1, 0)
        print(f"correct num lines in the multi_cell = {get_num_of_lines_in_multicell(pdf, cell_message1)}")
    
        cell_message2 = "Hello World! I'm trying to use multi_cell() method. Now check where is"
        pdf.multi_cell(CELL_WIDTH, 4, cell_message2, 1, 0)
        print(f"correct num lines in the multi_cell = {get_num_of_lines_in_multicell(pdf, cell_message2)}")
    
        pdf.output('multi_cell.pdf','F')
        webbrowser.open_new('multi_cell.pdf')
    
    main()
    

    The output of the program

    The output of the execution of the program is:

    correct num lines in the multi_cell = 5
    correct num lines in the multi_cell = 7
    

    And the file multi_cell.pdf contains the following cells:

    pdf with 2 multi_cell