Skip to content

Generate PDF

SØAD includes built-in support for PDF generation using Flying Saucer. This recipe demonstrates how to render an HTML view as a styled PDF document—ideal for invoices, reports, or certificates.


Generate PDF from HTML

invoice.py
from utils import render

class Invoice(object):
    def view(self, ctx):
        # Set values to be rendered inside the HTML view
        ctx.output["invoice_no"] = "INV-2024-005"
        ctx.output["customer_name"] = "Ahmad Bin Ali"
        ctx.output["items"] = [
            {"desc": "Product A", "qty": 2, "price": 100},
            {"desc": "Product B", "qty": 1, "price": 250}
        ]
        grand_total = 0
        for item in ctx.output["items"]:
            total_price = item.get("qty") * item.get("price")
            item["total_price"] = total_price
            grand_total = grand_total + total_price
        ctx.output["total"] = grand_total

        # Generate the PDF from the HTML view
        ctx.go_to = render.as_pdf(ctx, "invoice", attachment=False)
invoice.html
<html>

<head>
  <style>
    body {
      font-family: sans-serif;
    }

    h1 {
      text-align: center;
    }

    table {
      width: 100%;
      border-collapse: collapse;
      margin-top: 1em;
    }

    th,
    td {
      border: 1px solid #ccc;
      padding: 8px;
      text-align: left;
    }

    .right {
      text-align: right;
    }
  </style>
</head>

<body>
  <h1>Invoice</h1>
  <p><strong>Invoice No:</strong> {{invoice_no}}</p>
  <p><strong>Customer:</strong> {{customer_name}}</p>

  <table>
    <thead>
      <tr>
        <th>Description</th>
        <th>Qty</th>
        <th>Price</th>
        <th>Total</th>
      </tr>
    </thead>
    <tbody>
      {{#each items}}
      <tr>
        <td>{{desc}}</td>
        <td>{{qty}}</td>
        <td class="right">{{price}}</td>
        <td class="right">{{total_price}}</td>
      </tr>
      {{/each}}
    </tbody>
    <tfoot>
      <tr>
        <th colspan="3" class="right">Total:</th>
        <th class="right">{{total}}</th>
      </tr>
    </tfoot>
  </table>
</body>

</html>

View the PDF

The user can open this transaction via:

/t/example/invoice

The browser open the rendered PDF content.


Generate PDF using API

While SØAD provides built-in PDF rendering via Flying Saucer for HTML-to-PDF conversion, you can also use OpenPDF for programmatic PDF generation—ideal for simple layout control, table drawing, or dynamic document building without relying on HTML.

invoice.py
from utils import render

from com.lowagie.text import Document, Paragraph, Font, FontFactory
from com.lowagie.text.pdf import PdfWriter
from java.io import ByteArrayOutputStream

class Invoice(object):
    def pdf(self, ctx):
        output_stream = ByteArrayOutputStream()

        document = Document()
        PdfWriter.getInstance(document, output_stream)

        document.open()
        title_font = FontFactory.getFont(FontFactory.HELVETICA_BOLD, 16)
        normal_font = FontFactory.getFont(FontFactory.HELVETICA, 12)

        document.add(Paragraph("Invoice Summary", title_font))
        document.add(Paragraph("Customer: Ahmad Bin Ali", normal_font))
        document.add(Paragraph("Invoice #: INV-2024-005", normal_font))
        document.add(Paragraph("Total: RM450", normal_font))

        document.close()

        ctx.go_to = render.as_blob(
            ctx,
            output_stream.toByteArray(),
            "application/pdf",
            "invoice.pdf",
            attachment=False 
        )

View the PDF

The user can open this transaction via:

/t/example/invoice/pdf

The browser open the rendered PDF content.

Do you know?

  • Use attachment=True to force download instead of displaying inline.
  • For simple HTML-to-PDF, prefer Flying Saucer for better styling support.