0

I hope you'r doing fine.

I want to generate a PDF using pyfpdf.

I need to generate a table of contents on the first page.

As you know, new pages are created at the end of the PDF. So, my table of content is generated at the end of the PDF.

I can't generate the table of contents at the start of the process because it contains links to go the pages. To do that i need to know what are the pages number so i need to generate all other page before the table of contents.

To do that, I tried to create a blank page at the beginning and then create other pages and finally return to the first page to add the table of contents. But the first page is still blank ...

from fpdf import FPDF
from datetime import date

today = date.today()
today = today.strftime("%Y/%m/%d")

hardwareModel = ["hardwareModel1", "hardwareModel2", "hardwareModel3"]
snapshotNumber = ["N13100","N13101","N13102"]
firmwareVersion = ["1.8.0","1.8.1","1.8.2"]
websiteVersion = ["2.4.0","2.4.0","2.4.0"]
unifiedPackage = ["#38-20220624","#39-20220701","#40-20220829"]
stageDatabaseVersion = ["4.3.12","4.3.13","4.3.13"]
dateArray = ["2022/06/24","2022/07/01",today]

tableOfContents = []

class PDF(FPDF):
    def __init__(self):
        super().__init__()
        self.currentIndex = -1
        
    def titlePage(self, redScale, greenScale, blueScale, string):
        pdf.set_text_color(redScale, greenScale, blueScale)
        #Title
        self.cell(0, 10, txt = string , align = "L")
        self.drawLine(128, 196, 28, 1, 10, 50, 200, 50)

    def drawLine(self, redScale, greenScale, blueScale, lineWidth, x1, y1, x2, y2):
        pdf.set_draw_color(redScale, greenScale, blueScale)
        pdf.set_line_width(lineWidth)
        pdf.line(x1, y1, x2, y2)
    
    def tableOfContentsGenerator(self):
        self.page = 1
        print(self.page_no())
        self.set_y(0)
        self.set_text_color(0, 0, 0)
        print(self.get_y())
        self.ln(50)
        print(self.get_y())
        for elem in tableOfContents:
            newLink = self.add_link()
            self.set_link(newLink,y=0.0,page=elem[0])
            self.cell(0,10,elem[1],link=newLink)
            self.ln(5)
        
    def header(self):
        if self.currentIndex > -1 and self.currentIndex <= len(snapshotNumber)-1:
            tableOfContents.append((pdf.page_no(), hardwareModel[self.currentIndex] + "_" + snapshotNumber[self.currentIndex] + " (" + dateArray[self.currentIndex] + ")"))
            self.image('image.jpg', 0 , 0 , 210)
            pdf.set_font("helvetica", size = 20)
            self.ln(30)
            self.titlePage(0, 84, 136, hardwareModel[self.currentIndex] + "_" + str(snapshotNumber[self.currentIndex]) + " (" + today + ")")
            if self.currentIndex == len(snapshotNumber)-1:
                self.tableOfContentsGenerator()
        elif self.currentIndex == -1:
            pass
            
        self.currentIndex += 1

pdf = PDF()

pdf.set_title("titleTest")

pdf.add_page()
for elem in snapshotNumber:
    pdf.add_page()

pdf.output("test.pdf")

Any help would be much appreciated.

Best regards

Rayon Magique
  • 130
  • 2
  • 15

1 Answers1

1

This can easily be done with fpdf2:

from fpdf import FPDF, TitleStyle


def p(pdf, text, **kwargs):
    "Inserts a paragraph"
    pdf.multi_cell(
        w=pdf.epw,
        h=pdf.font_size,
        txt=text,
        new_x="LMARGIN",
        new_y="NEXT",
        **kwargs,
    )

def render_toc(pdf, outline):
    pdf.y += 50
    pdf.set_font("Helvetica", size=16)
    pdf.underline = True
    p(pdf, "Table of contents:")
    pdf.underline = False
    pdf.y += 20
    pdf.set_font("Courier", size=12)
    for section in outline:
        link = pdf.add_link()
        pdf.set_link(link, page=section.page_number)
        p(pdf, f'{" " * section.level * 2} {section.name} {"." * (60 - section.level*2 - len(section.name))} {section.page_number}', align="C", link=link)

pdf = FPDF()
pdf.set_font("Helvetica")
pdf.set_section_title_styles(
    # Level 0 titles:
    TitleStyle(
        font_family="Times",
        font_style="B",
        font_size_pt=24,
        color=128,
        underline=True,
        t_margin=10,
        l_margin=10,
        b_margin=0,
    ),
)
pdf.add_page()
pdf.set_y(50)
pdf.set_font(size=40)
p(pdf, "Doc Title", align="C")
pdf.set_font(size=12)
pdf.insert_toc_placeholder(render_toc)
for i in range(10):
    pdf.start_section(f"Title {i}")
    p(pdf, "Lorem ipsum dolor sit amet, consectetur adipiscing elit,"
           " sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.")
pdf.output("toc-example.pdf")
Lucas Cimon
  • 1,859
  • 2
  • 24
  • 33
  • As an extension, how could you modify this method to add a TOC to an existing PDF? From my understanding fpdf2 can't open PDF files; so could you somehow use it in conjunction with PyPDF to read a directory of files, create a TOC, then append it as a page to a PDF merged using PyPDF? – Colton Campbell Mar 08 '23 at 20:59