Skip to Content
Course content

102: Reading and Writing PDF Files

Click on the "Edit" button in the top corner of the screen to edit your slide content.

PDFs are a nightmare to work with if you try to treat them like text files. They aren't linear streams of characters; they're more like a set of drawing instructions that tell a viewer exactly where to place a glyph on a coordinate plane. Because of this, you usually need different libraries for creating PDFs and reading them.

Today, we're going to build a simple automated invoice generator. We'll use fpdf2 to generate the document and pypdf to read a "Terms and Conditions" snippet from an existing PDF to append it to our invoice. I prefer these two because they're actively maintained and don't require you to install heavy external C-libraries.

Setting up the document structure

First, we need to initialize our PDF object. I like to keep my layout logic separate from my data, so I'll start by defining the basic page settings. If you haven't already, you'll need to pip install fpdf2 pypdf.

from fpdf import FPDF

class InvoicePDF(FPDF):
    def header(self):
        self.set_font("helvetica", "B", 15)
        self.cell(0, 10, "SERVICE INVOICE", border=False, ln=True, align="C")
        self.ln(10)

pdf = InvoicePDF()
pdf.add_page()
pdf.set_font("helvetica", size=12)

The coordinate trap

Here is where I almost tripped up. When I first started with PDF libraries, I assumed the coordinate system worked like HTML or most GUI frameworks—where (0,0) is the top-left corner. I tried to place a logo at the top by setting the coordinates to 0,0, and my image completely vanished from the page.

In the PDF world, the origin (0,0) is actually the bottom-left corner. While fpdf2 handles much of this for us with the cell() and ln() methods (which move the "cursor" down the page), if you start using set_xy() for absolute positioning, you have to keep that bottom-up logic in mind. I've learned the hard way to just stick to relative positioning unless I'm building a complex form.

Let's add some actual line items to our invoice:

items = [
    ("Software Consulting", "10 hours", "$1,200"),
    ("API Integration", "5 hours", "$600"),
    ("Documentation", "2 hours", "$200"),
]

# Create a simple table header
pdf.set_font("helvetica", "B", 12)
pdf.cell(80, 10, "Description", border=1)
pdf.cell(40, 10, "Quantity", border=1)
pdf.cell(40, 10, "Price", border=1)
pdf.ln()

# Add the items
pdf.set_font("helvetica", size=12)
for desc, qty, price in items:
    pdf.cell(80, 10, desc, border=1)
    pdf.cell(40, 10, qty, border=1)
    pdf.cell(40, 10, price, border=1)
    pdf.ln()

Pulling content from another PDF

Now, we don't want to hardcode our legal "Terms and Conditions" into every script. Let's assume we have a file called legal_terms.pdf. We'll use pypdf to extract the text from that file and inject it into our generated invoice. This is a common pattern when you have standardized company headers or footers stored in separate documents.

from pypdf import PdfReader

# Read the external PDF
reader = PdfReader("legal_terms.pdf")
page = reader.pages[0]
terms_text = page.extract_text()

# Add the extracted text to our invoice
pdf.ln(20)
pdf.set_font("helvetica", "I", 8)
pdf.multi_cell(0, 5, f"Terms and Conditions:\n{terms_text}")

pdf.output("final_invoice.pdf")

I used multi_cell here instead of cell. If you use a standard cell for a long paragraph, the text will just run off the right edge of the page into the void. multi_cell handles the line wrapping for you, which is a lifesaver when dealing with extracted text of unknown length.




📋 Practical Task

Build a Monthly Expense Summary PDF

Write a Python script that does the following:

  • Creates a list of tuples containing expense data (e.g., ("Internet", 75.00), ("Electricity", 120.00), ("Cloud Hosting", 20.00)).
  • Uses fpdf2 to generate a PDF report.
  • The report must have a bold title "Monthly Expense Report" centered at the top.
  • It should list each expense and its cost in a formatted table.
  • It must calculate the total sum of all expenses and print "Total: $[sum]" at the bottom of the table in bold.
  • Save the resulting file as expense_report.pdf.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.