DEV Community

EbookForge
EbookForge

Posted on

Why relying on a raw LLM to build structured PDFs is a nightmare (and how I fixed it)

If you’ve ever tried to generate a polished, production-ready PDF directly from an LLM, you already know the pain.

You ask for a clean layout, maybe some headers and a specific margin, and the model confidently spits back an unformatted wall of markdown. Or worse, you try to pipe its output directly into a PDF library like ReportLab or PDFKit, and the layout breaks completely. The fonts are wrong, the page breaks happen in the middle of sentences, and any structured data you had is now a chaotic mess.

Here's why raw LLMs fail at structured PDF generation, and the pipeline I built to fix it.

The Problem: LLMs are writers, not designers

Large Language Models are incredible at generating text, summarizing data, and even reasoning through logic. But they have zero spatial awareness. When an LLM generates a document, it doesn't "see" a 8.5x11 inch page. It sees a continuous stream of tokens.

Trying to get an LLM to reliably output precise spacing, page breaks, or complex tables that fit perfectly on a printed page is like asking a novelist to typeset their own book while blindfolded.

The typical naive approach looks like this:

  1. Prompt the LLM to generate content.
  2. Tell it to output HTML.
  3. Pass the HTML to a PDF generator.

This breaks because LLMs hallucinate HTML tags, forget closing tags, and completely ignore pagination logic.

The Solution: A Decoupled Architecture

To fix this, I stopped treating the LLM as a designer and started treating it strictly as a content engine. The solution was a decoupled pipeline:

1. Structured Data Generation (JSON/Markdown)
Instead of asking the LLM for HTML or layout instructions, I prompt it to return strictly structured JSON or clean Markdown. I use function calling (or structured outputs) to enforce a strict schema. The LLM's only job is to provide the content (titles, paragraphs, data points).

2. The Templating Engine (HTML/CSS)
I created a robust templating layer (using Jinja2/Handlebars) that holds the actual design. This layer knows exactly how to handle margins, fonts, and headers. It takes the structured JSON from the LLM and maps it to predefined, heavily tested HTML components.

3. The PDF Rendering Engine
Finally, the rendered HTML is passed to a headless browser (like Puppeteer/Playwright) or a dedicated rendering engine (like WeasyPrint) that actually understands CSS paged media. This handles the page breaks, headers, and footers flawlessly.

The Result

By decoupling the content generation from the layout rendering, the nightmare stopped. The LLM focuses on what it does best (writing and structuring data), and the rendering engine handles the visual layout. No more broken tables, no more mid-sentence page breaks.

If you are building an AI-powered document generator, stop trying to make the LLM do everything. Build a pipeline.


Further Reading from EbookForge

If you're building document generation pipelines or working with AI-generated content, check out these related posts from our blog:

Ready to automate your document creation without the layout headaches? Try EbookForge.app today and let us handle the rendering pipeline for you.

Top comments (0)