Examples / reporting / python

Reporting

A complete, runnable python program — 224 lines, shipped in your download.

Demonstrates Reporting

Same example, other languages: activex_cpp c cpp cpp_linux csharp php vbnet

examples/python/reporting/18_invoice_pro.py 224 lines
# ============================================================================
#  LumasReport example 18 -- Professional FRAMED invoice (Python port)
#  A polished, print-ready invoice built from the banded model + the SECTION-
#  BOUNDED line feature (scope="section"): horizontal rules auto-span the band
#  WIDTH, vertical rules auto-span the band HEIGHT. Money columns right-aligned;
#  totals use inline SUM(Qty*Price). Exports to PDF/HTML/SVG/TEXT + CSV/XLSX/XLS.
# ============================================================================
import sys, os, ctypes
import os, sys
# the wrapper lives at <root>/wrappers/python (source checkout) or beside
# the example tree (shipped package) -- find it without a hard-coded path
_d = os.path.dirname(os.path.abspath(__file__))
for _ in range(6):
    for _c in (os.path.join(_d, 'wrappers', 'python'), _d):
        if os.path.isfile(os.path.join(_c, 'lumaspdf.py')):
            sys.path.insert(0, _c)
            break
    else:
        _d = os.path.dirname(_d)
        continue
    break
import lumaspdf as L

HERE = os.path.dirname(os.path.abspath(__file__))

PDF_DEMO_KEY = b"LUMAS-LumasReportExamples-DD5D40E0"
RPT_DEMO_KEY = b"LRPT2-TFIwMgECAAIAAgIAAQADBAD___9_BAEAAAUBAAAGCAAAAAAAAAAAAAcQAEx1bWFzUnB0RXhhbXBsZXM.sneCg6LX_J59AcqzUZPx1WqQ7jGQZyji4z0WMm_vIoycNySw12vGVoKV76gk3-yQtx3LfZZepGxoDdQAgf5MAA"

# Colours are COLORREF 00BBGGRR (low byte = red).
NAVY  = "005F3A1F"   # brand / rules / totals
INK   = "00222222"   # near-black body text
GREY  = "00808080"   # muted labels
GRID  = "00B9B9B9"   # table grid lines
HAIR  = "00D8D8D8"   # hairline row rules
SHADE = "00F4F1EC"   # zebra row tint
WHITE = "00FFFFFF"


def csv_data():
    return (
        "Item,Qty,Price\n"
        "Precision Widget Assembly,4,42.50\n"
        "Gadget Control Module,2,149.50\n"
        "Shielded Signal Cable (3m),10,4.75\n"
        "Universal Power Adapter,3,28.00\n"
        "Steel Mounting Bracket,12,3.25\n"
        "Thermal Interface Kit,5,11.20\n"
    )


# The five vertical column dividers, section-scoped so each spans its band.
def col_grid(tag):
    return (
        '   <line name="' + tag + 'a" orient="v" scope="section" x="0"   width="0.35" color="' + GRID + '"/>\n'
        '   <line name="' + tag + 'b" orient="v" scope="section" x="95"  width="0.35" color="' + GRID + '"/>\n'
        '   <line name="' + tag + 'c" orient="v" scope="section" x="117" width="0.35" color="' + GRID + '"/>\n'
        '   <line name="' + tag + 'd" orient="v" scope="section" x="149" width="0.35" color="' + GRID + '"/>\n'
        '   <line name="' + tag + 'e" orient="v" scope="section" x="182" width="0.35" color="' + GRID + '"/>\n'
    )


def build_xml(csv):
    dot = chr(0xB7)   # middle dot U+00B7
    s = (
        '<?xml version="1.0" encoding="UTF-8"?>\n'
        '<report name="InvoicePro" tagLangVersion="1">\n'
        ' <page width="210" height="297" marginLeft="14" marginTop="14" marginRight="14" marginBottom="16"/>\n'
        ' <datasources><datasource alias="d" provider="csv" conn="' + csv + '"/></datasources>\n'
        ' <variables><variable name="PageNo" init="1"/></variables>\n'
        ' <styles>\n'
        '  <style name="brand"  fontName="Helvetica" fontSize="20" bold="1" textColor="' + NAVY + '"/>\n'
        '  <style name="addr"   fontName="Helvetica" fontSize="8"  textColor="' + GREY + '"/>\n'
        '  <style name="title"  fontName="Helvetica" fontSize="30" bold="1" textColor="' + NAVY + '" hAlign="right"/>\n'
        '  <style name="mlbl"   fontName="Helvetica" fontSize="8.5" bold="1" textColor="' + GREY + '" hAlign="right"/>\n'
        '  <style name="mval"   fontName="Helvetica" fontSize="8.5" textColor="' + INK + '" hAlign="right"/>\n'
        '  <style name="billto" fontName="Helvetica" fontSize="8" bold="1" textColor="' + NAVY + '"/>\n'
        '  <style name="cust"   fontName="Helvetica" fontSize="9.5" textColor="' + INK + '"/>\n'
        '  <style name="colh"   fontName="Helvetica" fontSize="8.5" bold="1" textColor="' + WHITE + '"/>\n'
        '  <style name="colhr"  fontName="Helvetica" fontSize="8.5" bold="1" textColor="' + WHITE + '" hAlign="right"/>\n'
        '  <style name="cell"   fontName="Helvetica" fontSize="9.5" textColor="' + INK + '"/>\n'
        '  <style name="cellr"  fontName="Helvetica" fontSize="9.5" textColor="' + INK + '" hAlign="right"/>\n'
        '  <style name="tlbl"   fontName="Helvetica" fontSize="9.5" bold="1" textColor="' + INK + '" hAlign="right"/>\n'
        '  <style name="tval"   fontName="Helvetica" fontSize="9.5" textColor="' + INK + '" hAlign="right"/>\n'
        '  <style name="glbl"   fontName="Helvetica" fontSize="13" bold="1" textColor="' + WHITE + '"/>\n'
        '  <style name="gval"   fontName="Helvetica" fontSize="13" bold="1" textColor="' + WHITE + '" hAlign="right"/>\n'
        '  <style name="note"   fontName="Helvetica" fontSize="8.5" textColor="' + GREY + '"/>\n'
        '  <style name="foot"   fontName="Helvetica" fontSize="8" textColor="' + GREY + '"/>\n'
        '  <style name="footr"  fontName="Helvetica" fontSize="8" textColor="' + GREY + '" hAlign="right"/>\n'
        ' </styles>\n'
        ' <bands>\n'
        # ============ REPORT HEADER ============
        '  <band kind="reportheader" name="rh" height="42">\n'
        '   <text name="co"   x="0"  y="0"  w="110" h="9" style="brand" wordWrap="0">ACME Corporation</text>\n'
        '   <text name="a1"   x="0"  y="10" w="120" h="4" style="addr" wordWrap="0">123 Industrial Way  ' + dot + '  Springfield, IL 62704</text>\n'
        '   <text name="a2"   x="0"  y="14" w="120" h="4" style="addr" wordWrap="0">+1 (555) 018-2245  ' + dot + '  billing@acme.example</text>\n'
        '   <text name="ti"   x="92" y="0"  w="90"  h="13" style="title" wordWrap="0">INVOICE</text>\n'
        '   <text name="ml1"  x="108" y="15" w="40" h="4" style="mlbl" wordWrap="0">INVOICE #</text>\n'
        '   <text name="mv1"  x="150" y="15" w="32" h="4" style="mval" wordWrap="0">INV-1042</text>\n'
        '   <text name="ml2"  x="108" y="20" w="40" h="4" style="mlbl" wordWrap="0">ISSUE DATE</text>\n'
        '   <text name="mv2"  x="150" y="20" w="32" h="4" style="mval" wordWrap="0">2026-07-19</text>\n'
        '   <text name="ml3"  x="108" y="25" w="40" h="4" style="mlbl" wordWrap="0">DUE DATE</text>\n'
        '   <text name="mv3"  x="150" y="25" w="32" h="4" style="mval" wordWrap="0">2026-08-18</text>\n'
        '   <text name="bt"   x="0"  y="25" w="60" h="4" style="billto" wordWrap="0">BILL TO</text>\n'
        '   <text name="c1"   x="0"  y="29.5" w="95" h="4.5" style="cust" wordWrap="0">Globex Manufacturing Co.</text>\n'
        '   <text name="c2"   x="0"  y="33.5" w="95" h="4" style="addr" wordWrap="0">500 Commerce Blvd, Metropolis, NY 10001</text>\n'
        '   <line name="rht" orient="h" scope="section" vAlign="top"    width="0.3" color="' + HAIR + '"/>\n'
        '   <line name="rhb" orient="h" scope="section" vAlign="bottom" width="1.1" color="' + NAVY + '"/>\n'
        '  </band>\n'
        # ============ COLUMN CAPTIONS (navy bar) ============
        '  <band kind="pageheader" name="ph" height="8">\n'
        '   <shape name="bar" x="0" y="0" w="182" h="8" shape="0" backColor="' + NAVY + '"/>\n'
        '   <text name="hI" x="3"   y="2" w="88" h="5" style="colh"  wordWrap="0">DESCRIPTION</text>\n'
        '   <text name="hQ" x="97"  y="2" w="16" h="5" style="colhr" wordWrap="0">QTY</text>\n'
        '   <text name="hP" x="119" y="2" w="26" h="5" style="colhr" wordWrap="0">UNIT PRICE</text>\n'
        '   <text name="hA" x="151" y="2" w="29" h="5" style="colhr" wordWrap="0">AMOUNT</text>\n'
        + col_grid("phg") +
        ' </band>\n'
        # ============ DETAIL ROWS ============
        '  <band kind="detail" name="det" height="7" data="d">\n'
        '   <shape name="zebra" x="0" y="0" w="182" h="7" shape="0" backColor="' + SHADE + '" visible="RowNum % 2 = 0"/>\n'
        '   <text name="dI" x="3"   y="1.6" w="90" h="4" style="cell"  wordWrap="0">{{Item}}</text>\n'
        '   <text name="dQ" x="97"  y="1.6" w="16" h="4" style="cellr" wordWrap="0">{{Qty}}</text>\n'
        '   <text name="dP" x="119" y="1.6" w="26" h="4" style="cellr" wordWrap="0">{{expr: FORMATNUM(\'#,##0.00\', Price)}}</text>\n'
        '   <text name="dA" x="151" y="1.6" w="29" h="4" style="cellr" wordWrap="0">{{expr: FORMATNUM(\'#,##0.00\', Qty*Price)}}</text>\n'
        + col_grid("dg") +
        '   <line name="drb" orient="h" scope="section" vAlign="bottom" width="0.2" color="' + HAIR + '"/>\n'
        '  </band>\n'
        # ============ SUMMARY ============
        '  <band kind="summary" name="sm" height="46">\n'
        '   <line name="stop" orient="h" scope="section" vAlign="top" width="0.6" color="' + NAVY + '"/>\n'
        '   <text name="nh" x="0" y="4"  w="95" h="4" style="billto" wordWrap="0">NOTES</text>\n'
        '   <text name="n1" x="0" y="8.5" w="100" h="4" style="note" wordWrap="0">Payment due within 30 days. Bank transfer to</text>\n'
        '   <text name="n2" x="0" y="12"  w="100" h="4" style="note" wordWrap="0">ACME Corp ' + dot + ' IBAN GB00 ACME 0000 1042 ' + dot + ' Ref INV-1042.</text>\n'
        '   <text name="s1l" x="100" y="4"  w="45" h="4.5" style="tlbl" wordWrap="0">Subtotal</text>\n'
        '   <text name="s1v" x="149" y="4"  w="31" h="4.5" style="tval" wordWrap="0">{{expr: FORMATNUM(\'#,##0.00\', SUM(Qty*Price))}}</text>\n'
        '   <text name="s2l" x="100" y="9.5" w="45" h="4.5" style="tlbl" wordWrap="0">Tax (8.5%)</text>\n'
        '   <text name="s2v" x="149" y="9.5" w="31" h="4.5" style="tval" wordWrap="0">{{expr: FORMATNUM(\'#,##0.00\', SUM(Qty*Price)*0.085)}}</text>\n'
        '   <shape name="gbar" x="100" y="16" w="82" h="10" shape="0" backColor="' + NAVY + '"/>\n'
        '   <text name="gl" x="104" y="18.5" w="40" h="6" style="glbl" wordWrap="0">TOTAL</text>\n'
        '   <text name="gv" x="149" y="18.5" w="29" h="6" style="gval" wordWrap="0">{{expr: FORMATNUM(\'#,##0.00\', SUM(Qty*Price)*1.085)}}</text>\n'
        '   <text name="gc" x="100" y="28" w="82" h="4" style="footr" wordWrap="0">USD ' + dot + ' Total items {{expr: COUNT()}}</text>\n'
        '  </band>\n'
        # ============ PAGE FOOTER ============
        '  <band kind="pagefooter" name="pf" height="12">\n'
        '   <line name="pft" orient="h" scope="section" vAlign="top" width="0.3" color="' + GRID + '"/>\n'
        '   <text name="ty" x="0"   y="3" w="120" h="4" style="foot"  wordWrap="0">Thank you for your business.  Questions? billing@acme.example</text>\n'
        '   <text name="pg" x="120" y="3" w="62"  h="4" style="footr" wordWrap="0">Page {{var:PageNo}} of {{var:TotalPages}}</text>\n'
        '  </band>\n'
        ' </bands>\n'
        '</report>\n'
    )
    return s


def export_one(job, target, path):
    if L.rptExportA(job, target, path.encode('latin-1')) != 0:
        print("  wrote " + path)
    else:
        print("  EXPORT FAILED: " + path)


def main():
    pdf, eng = boot_engine()
    if not eng:
        return
    try:
        csv = os.path.join(HERE, "18_items.csv")
        write_text(csv, csv_data())
        lrpt = os.path.join(HERE, "18_invoice.lrpt")
        write_text(lrpt, build_xml(csv))

        job = L.rptOpenReportA(eng, lrpt.encode('latin-1'))
        if not job:
            print("open failed"); dump_err(eng); return
        try:
            if L.rptRender(job) == 0:
                print("render failed"); dump_err(eng); return
            print("rendered %d page(s); exporting:" % L.rptGetPageCount(job))
            export_one(job, L.RPT_EXP_PDF, os.path.join(HERE, "18_invoice.pdf"))
            export_one(job, L.RPT_EXP_HTML, os.path.join(HERE, "18_invoice.html"))
            export_one(job, L.RPT_EXP_SVG, os.path.join(HERE, "18_invoice.svg"))
            export_one(job, L.RPT_EXP_TEXT, os.path.join(HERE, "18_invoice.txt"))
            export_one(job, L.RPT_EXP_CSV, os.path.join(HERE, "18_invoice.csv"))
            export_one(job, L.RPT_EXP_XLSX, os.path.join(HERE, "18_invoice.xlsx"))
            export_one(job, L.RPT_EXP_XLS, os.path.join(HERE, "18_invoice.xls"))
        finally:
            L.rptCloseReport(job)
    finally:
        L.rptDeleteEngine(eng)
        L.pdfDeletePDF(pdf)


# --- shared boilerplate (mirror of _shared.inc) -----------------------------

def boot_engine():
    pdf = L.pdfNewPDF()
    if not pdf:
        print("pdfNewPDF failed"); return None, None
    L.pdfSetLicenseKey(pdf, PDF_DEMO_KEY)
    L.rptSetRptLicenseKeyA(pdf, RPT_DEMO_KEY)
    eng = L.rptCreateEngineA(pdf, None)
    if not eng:
        print("rptCreateEngine failed"); return pdf, None
    return pdf, eng


def write_text(path, content):
    with open(path, "wb") as f:
        f.write(content.encode('latin-1'))


def dump_err(eng):
    info = L.TRptErrorInfoC()
    if L.rptGetLastError(eng, ctypes.byref(info)) != 0:
        if info.Code != 0:
            print("  ! rpt error %d [%s] at %s: %s" % (
                info.Code, info.Module_.decode('latin-1', 'replace'),
                info.Location.decode('latin-1', 'replace'),
                info.Msg.decode('latin-1', 'replace')))


if __name__ == "__main__":
    main()

This file is in the SDK at examples/python/reporting/18_invoice_pro.py. The build fails if this page and that file ever differ.