Python binding

Use the Python binding when a script already has the values for a report, such as results from a simulation or measurements in a table. Install briefpp with pip install briefpp (Python 3.9 or newer). The package contains a compiled extension and the C++ headers. Prebuilt wheels cover CPython 3.9 through 3.14 on Linux x86-64, Windows x64, and macOS Intel and Apple Silicon. Building from source requires a C++17 compiler and CMake; pip installs the Python build requirements automatically.

Your first Python report

Save this as report.py and run python report.py:

from briefpp import Document

doc = Document().title("Atmospheric Simulation").author("Simulation Team")
doc.section("Model").equation(r"\frac{dP}{dz} = -\rho g", "hydrostatic")

results = doc.section("Results")
results.figure("density.png").caption("Density profile").label("density").width(0.8)
results.table().columns(["Parameter", "Value"]).row(["Temperature", "273.15 K"])
results.paragraph().text("See ").reference("density").text(" for the profile.")

print(doc.render("md"))
doc.write("report.html")

print shows the Markdown representation in your terminal. write saves the HTML representation to a file. You can change the extension to .rst, .tex, .typ, .txt, or .json to save another representation. A figure path is included in the generated file; the image itself is not copied. Keep density.png alongside the output or adjust the path for your site.

Build a report from Python data

You can add rows in a loop rather than assembling output text yourself:

from briefpp import Document

readings = [("Morning", 18.2), ("Noon", 24.7), ("Evening", 20.1)]
doc = Document().title("Temperature log")
results = doc.section("Readings")
table = results.table().columns(["Time", "Temperature (°C)"])
for time, temperature in readings:
    table.row([time, f"{temperature:.1f}"])
results.paragraph("Measurements were taken at one location.")
doc.write("temperatures.md")

Use columns before row when the table has headers. A table without headers can start with row; its first row sets the number of columns. All later rows must have the same width. If you need rich text inside a cell, set column_count(n) or columns first and fill cells one at a time:

table = doc.section("Checks").table().columns(["Check", "Status"])
table.cell().text("Calibration")
table.cell().strong("Passed")

Add styled text, lists, and reusable content

Text builders let the renderer apply the right syntax for each output format:

section = doc.section("Interpretation")
section.paragraph().text("The change was ").emphasis("small").text("; see ") \
    .link("source data", "https://example.org/data")
steps = section.list(True)
steps.item("Collect readings")
steps.item("Check units")

Use label("name") on a node and reference("name") in a paragraph to link to it. The same inline methods work on captions, headings, and list items. Sections can contain subsections. Use DocumentFragment when you want to build a group of blocks separately and copy them into a document:

from briefpp import DocumentFragment

fragment = DocumentFragment()
fragment.paragraph("Measurements were reviewed by two readers.")
doc.append(fragment)

Render and customize output

Document.render(format) returns a string. It accepts md or markdown, rst, tex or latex, html, typ or typst, txt or text, and json. Document.write(path) selects a renderer from the file extension: .md, .rst, .tex, .html, .typ, .txt, or .json. Pass a string path.

Document and section nodes provide section, paragraph, equation, figure, table, code_block, list, definition_list, horizontal_rule, page_break, quote, admonition, warning, note, and raw. A node’s label and role methods set semantic IDs and roles. Paragraphs and other text nodes have text, emphasis, strong, code, math, link, reference, and citation. Rich text builders also come from section.heading(), figure.caption(), table.cell(), list.item(), and definition_list.definition_item(). Keep a reference to a section when adding several blocks to it. Returned builders retain their owning document while they are in use.

To cite a source, register it on the document before rendering:

doc.bibliography_entry("smith2025", "A. Smith", "A study of reports", "2025",
                       "https://example.org/study")
doc.paragraph().text("See ").citation("smith2025").text(" for details.")

The optional URL appears in the References section. Missing citation keys raise ValueError when you render or write the document.

HTML output uses a responsive stylesheet and MathJax by default. Use HtmlRenderer().clean_html().render(doc) for plain HTML without CSS or scripts, or HtmlRenderer().stylesheet("report.css").render(doc) for a custom stylesheet. mathjax_source("path/to/tex-chtml.js") selects a local MathJax script if the report must work without the CDN.

Tables accept lists of strings with columns(["A", "B"]) and row(["1", "2"]). For rich cells, call column_count(n) or columns(...) first, then add content with cell().text(...).strong(...). Use list(True) for a numbered list and list(False) for bullets. DocumentFragment is an alias of Document; doc.append(fragment) copies its blocks into doc.

For renderer settings, instantiate LatexRenderer, HtmlRenderer, or TypstRenderer and call render(doc). For example:

from briefpp import LatexRenderer

latex = LatexRenderer().document_class("article").font_size(11).render(doc)

The other renderer classes are MarkdownRenderer, RstRenderer, PlainTextRenderer, and JsonRenderer. Backend-specific content can be added with doc.raw(Backend.LATEX, r"\newpage") after importing Backend.

The bundled C++ include directory is returned by briefpp.get_include(). The C++ library remains header-only and does not require the Python extension when used directly through CMake. See Document model for more about the document structure and Renderers and output behavior for output behavior.