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.