Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
102: Reading and Writing PDF Files
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
fpdf2to 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.
There are no comments for now.