Back to Homepage
Services

Backend Engineering, Document Automation

Industry

Real Estate

Year

2022-2024

The 45-minute exposé

One property exposé took the marketing team 45 minutes in InDesign. The platform had more than 200 active listings, and every change to a listing meant building its document again by hand. Within weeks the team was behind, and the backlog only grew. The listings were live. The documents that belonged to them were not.

THE CHALLENGE

Manual layout, 200 listings, no template

Every exposé had the same parts: a cover with a hero image and a title block that had to survive titles from 10 to 90 characters, a factsheet with configurable fields, and up to eight further pages of descriptions, floor plans and galleries. The marketing team rebuilt all of it in InDesign, one listing at a time, 45 minutes each. An updated price or a new photo meant the whole document again. Nothing was templated and nothing was versioned, so no two people produced quite the same layout, and every new hire drifted a little further from the brand.

THE SOLUTION

A generator instead of a converter

The quick route would have been HTML to PDF with wkhtmltopdf or WeasyPrint: write the exposé as a web page and let a browser engine lay it out. It works for simple documents and fails at exactly this one: sub-millimetre placement of images against the A4 spec, SVG logos at fixed coordinates, and footers that need a page total the engine does not have yet. I chose ReportLab instead and wrote a CompanyPDF class, about 1,700 lines of Python, that draws the document procedurally. Platypus flows the text and paginates, the cover measures its title and shrinks the font until it fits its box, and a small canvas subclass holds every page back until the last one exists. The price is more code and no CSS. The return is output that is identical on every server, with no browser engine to install, patch or keep in sync.

The cover's title loop: measure, shrink by half a point, measure again, until the string fits the box or reaches 16 pt:

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)

Fit a title, then number the pages

Type a property title or pick a sample. The sheet is a scaled A4 page and runs the same loop as the generator: 42 pt down in 0.5 pt steps until the text fits the 515 pt box, never below 16 pt. Below it, watch the footers: every page says 'of ?' until save() runs.

Penthouse Suite with Panoramic Views
Berlin · 4 rooms · 142 m²
Price€ 1,250,000
Area142 m²
Rooms4
Font size
25.5 pt
Shrink steps
33
Text width
514 / 515 pt
Result
Fits on one line
Deferred page numbering7 pages numbered at save()
  1. Page 1 of 7
  2. Page 2 of 7
  3. Page 3 of 7
  4. Page 4 of 7
  5. Page 5 of 7
  6. Page 6 of 7
  7. Page 7 of 7

The canvas subclass behind 'Page X of Y'. showPage() buffers, save() numbers and writes:

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):
        # Buffer the page instead of emitting it
        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()
THE RESULT

The marketing team stopped making PDFs

The generator took over every exposé. Change a listing and the next request returns a new document; nobody opens InDesign, nobody checks whether the footer still says the right page count. The person who used to spend full days on property PDFs was moved to other work, and the brand looks the same on every document because exactly one place defines how it looks. Turnaround went from most of an hour to a few seconds, and the queue that had grown for weeks was simply gone.

KEY METRICS

<3sPer exposé, generated
6,000+Documents in 18 months
1,700Lines of Python
CLIENT FEEDBACK

We had one person whose week was property PDFs: new listings, price changes, new photos. That work is gone. The documents come out of the platform, they all look the same, and that role now does something else.

Managing Director

Real estate platform, operations

FOR YOUR PROJECT

  • When it applies

    You produce the same branded document again and again from structured data, and a person rebuilds it by hand after every change. Property exposés here; offers, reports and certificates have the same shape.

  • What to check

    Whether everything the document needs already lives in one record: title, price, fields, images, the link for the QR code. If parts of it exist only in a designer's file, the generator has nothing to read. Then count documents per month and minutes per document.

  • What it needs

    A Python backend developer, ReportLab, brand assets as SVG, and a test set of real titles from 10 to 90 characters. No browser engine on the server. This one is about 1,700 lines for a cover, a factsheet and up to eight content pages.

FAQ

TECHNOLOGY STACK

Django
Python

We have enjoyed working with Daniel for 10 years now. We highly appreciate his fast response times around the clock and his all-round knowledge. Whether server configurations or programming, he always has the right solution.

Manuel Kasbarian - CEO, SophistiX

Follow in the footsteps of Manuel and bring your vision to life.

Get In TouchGet In Touch