Services

Backend Engineering, Document Automation

Industry

Real Estate

Year

2022-2024

1,700 lines of Python that replaced an entire PDF design team.

A real estate platform needed branded, multi-page exposé documents for every property listing. Each PDF had to include dynamic content, high-resolution images, SVG logos, QR codes linking to the property page, branded gradients, and a consistent layout regardless of content length. The existing process involved manual InDesign work. Every listing change meant regenerating the PDF by hand. At 200+ active listings, the team fell behind within weeks.

01.
THE CHALLENGE

Manual Design at an Automated Scale

Each property exposé required a cover page with a hero image, title block with dynamic font sizing to handle titles from 10 to 90 characters, a factsheet with configurable fields, and up to eight additional pages of descriptions, floor plans, and image galleries. The marketing team was producing these manually in InDesign. A single exposé took 45 minutes. Updates to existing listings required regenerating the entire document. There was no version control, no templating, and styling inconsistencies crept in with every new hire.

45 minutes per exposé, no version control, and styling that drifted with every new hire. At 200 listings, the backlog was permanent.

02.
THE SOLUTION

Procedural Document Generation with ReportLab

I wrote a CompanyPDF class that generates multi-page A4 documents entirely in Python using ReportLab's Platypus flow engine. The cover page calculates available space and shrinks the title font in a binary-search loop until it fits the designated box, replicating CSS clamp() for PDF. Images are resized and positioned with sub-millimeter precision using coordinate math against the A4 spec. A custom NumberedCanvas subclass defers page numbering until the final save() call, enabling correct 'Page X of Y' rendering across variable-length documents.

Dynamic font sizing: a loop shrinks the title until it fits the available box:

Python
def _draw_title(self, canvas, title, box_width, box_y):
    font_size = 42  # Start large
    min_size = 16

    while font_size > min_size:
        w = canvas.stringWidth(title, self.font_bold, font_size)
        if w <= box_width:
            break
        font_size -= 0.5

    canvas.setFont(self.font_bold, font_size)
    canvas.drawString(self.margin_left, box_y, title)

Try It Yourself

Type a property title and watch the font size adjust to fit the cover page box. This is the exact algorithm the generator uses.

ACME REAL ESTATE
Penthouse Suite with Panoramic ViewsBerlin · 4 Rooms · 142 m²
PRICE€ 1,250,000
AREA142 m²
ROOMS4
42ptrange: 16–42pt

Production Challenges

Dynamic Font Sizing

Property titles range from 10 to 90 characters. A fixed font size either overflows the title box or leaves half the cover page empty. The generator uses a while loop that decreases the font size by 0.5pt increments until stringWidth reports a width that fits the box. A minimum font size of 16pt prevents unreadable results.

SVG Logo Injection

Client logos are stored as SVGs but ReportLab's canvas cannot render SVGs natively. The generator converts SVGs to ReportLab drawing objects using svglib, then positions them at exact coordinates on each page. Color overrides allow the same logo to appear in white on dark backgrounds and in the original brand color on light ones.

QR Code Generation

Each exposé includes a QR code linking to the property's web page. The code is generated inline using the qrcode library, rendered as an SVG, patched with a white background rectangle, then injected into the page at a fixed position. The entire process runs in memory. No temporary files touch the disk.

The NumberedCanvas subclass that enables 'Page X of Y' rendering:

Python
class NumberedCanvas(canvas.Canvas):
    """Defers page numbering until save()."""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._saved_pages = []

    def showPage(self):
        self._saved_pages.append(dict(self.__dict__))
        self._startPage()

    def save(self):
        total = len(self._saved_pages)
        for i, state in enumerate(self._saved_pages, 1):
            self.__dict__.update(state)
            self.setFont("Helvetica", 9)
            self.drawRightString(
                self._pagesize[0] - 30,
                20,
                f"Page {i} of {total}"
            )
            super().showPage()
        super().save()
03.
THE RESULT

From 45 Minutes to 3 Seconds

The automated generator produces a complete branded exposé in under 3 seconds. Updates propagate instantly: change a listing, the PDF regenerates on the next request. The marketing team stopped producing PDFs entirely. Styling consistency is now guaranteed. The system has generated over 6,000 documents across 18 months without a single layout failure. A debug mode with colored bounding boxes lets developers verify coordinate math visually during development.

KEY METRICS

0<3sGeneration Time
0,000+Documents Generated
0,700Python LOC
WHAT THE CLIENT SAYS

"We used to have a full-time person just updating property PDFs. Now it happens automatically and every document looks exactly right. We reallocated that role entirely."

Managing Director

Real Estate Platform · Operations

FAQ

Why ReportLab instead of wkhtmltopdf or WeasyPrint?

How does the dynamic font sizing work without CSS?

What happens with very long property descriptions?

TECHNOLOGY STACK