py-pdf/fpdf2

Feature request: new and more functional TOC class

CayhanBoran opened this issue · 4 comments

Hey everyone,
I am trying to create a table of contents with different font style for landscape format. The aligments of dots in the texts are not aligning. I tried different formats, but the only best option is using the Courier format.

used function for table of contents:

        pdf.set_font("Arial", size=12)
        for section in outline:

            link = pdf.add_link(page=section.page_number, x=80, y=0)
            if pdf.get_y() >= 170: # passing the header for the next page
                pdf.page += 1
                pdf.set_y(35)
                pdf.set_x(10)
                pdf.cell(w=pdf.epw,
                         h=pdf.font_size,
                         text=f'{" " * section.level * 2} {section.name} {"." * (60 - section.level*2 - len(section.name))} {section.page_number}',
                         new_x="LMARGIN",
                         new_y="NEXT",
                         align="C",
                         link=link)  # type: ignore
            else:
                pdf.cell(w=pdf.epw,
                         h=pdf.font_size,
                         text=f'{" " * section.level * 2} {section.name} {"." * (60 - section.level*2 - len(section.name))} {section.page_number}',
                         new_x="LMARGIN",
                         new_y="NEXT",
                         align="C",
                         link=link)  # type: ignore

If I make it with Courier and font size 12:

courier_font

If I make it with Arial and font size 12:

arial_font

If you could help me and solve this issue of font style in table of contents, I would be really happy.
Thanks!

Hi @CayhanBoran!

Basically your issue is that Courrier is a monospace (or fixed-width) font: https://en.wikipedia.org/wiki/Monospaced_font
Whereas Arial is not monospace, leading to varying alignements in the resulting table of content.

You could try using a render function that right-aligns page numbers, instead of using dots, like this:

def render_toc_with_right_aligned_page_numbers(pdf, outline):
    pdf.set_font("Helvetica", size=16)
    for section in outline:
        link = pdf.add_link(page=section.page_number)
        pdf.cell(text=f'{" " * section.level * 2} {section.name}', link=link, new_x="LEFT")
        pdf.cell(text=f"{section.page_number}", link=link, w=pdf.epw, align="R")
        pdf.ln()

Does this answer solve your issue? 🙂

Yes solves partially, I wanted to use Helvetica or Arial with dots, but if that is not the case. I guess I will not use dots in the table of contents.
Thanks.

The underlying issue is that using dots to align the page number is a very ugly hack, which was cobbled together decades ago, and never fixed.

Ideally, someone would submit a PR creating a new and more functional TOC class, which contains adaptable TocEntry instances.
Desirable features might be:

  • Text part of each TocEntry implemented as a TextRegion, so it can use varying fonts for multilingual entries, possible spanning several lines.
  • Ability to vary the total width of the TOC, in absolute terms or relative to the page width.
  • Right aligned page numbers column with separately configurable font.
  • Multicolumn TOCs.
    • Autowrapping.
    • With configuration to wrap between individual entries, or between higher level chapter headings.
  • Configurable visual element to connect text and page number columns.
    • Automatically adapting to the available width between text and page number.
    • This could be dots, dashes, a line, some graphic, etc, in a configurable color (or nothing).
    • With multiline entries, configurable whether the connection (and page number) vertically aligns with the top or bottom of the entry.
  • All the bells and whistles I didn't think about.

Any takers?

Just a note: following @gmischler comment, I renamed this issue into Feature request: new and more functional TOC class and added the up-for-grabs label.