Document model

A Document contains metadata such as title and author, followed by blocks. Blocks include sections, paragraphs, equations, figures, tables, lists, code, quotes, and notes. A section can contain further blocks and subsections. You describe what each block means once; each renderer chooses the output syntax.

Sections and rich text

In C++, section() returns a Node&. Keep that reference when you add several blocks to a section. References remain valid while their parent document or section exists:

briefpp::Document doc;
auto& summary = doc.section("Summary").label("summary");
summary.paragraph().text("The result is ").strong("positive")
    .text(". See ").reference("summary").text(" for context.");

Python has the same builder methods:

from briefpp import Document

doc = Document()
summary = doc.section("Summary").label("summary")
summary.paragraph().text("The result is ").strong("positive")

Paragraphs support text, emphasis, strong, code, math, link(text, url), reference(label), and citation(key). Use those methods on section.heading(), figure.caption(), table.cell(), or list.item() for rich text in those positions. For plain text, the simple string overloads are shorter. Add a bibliography entry for every citation key; rendering raises an error if a key has no entry.

Bibliography and citations

Entries belong to the document and appear in a References section after the body. Add the entry once, then cite its key from a paragraph, caption, table cell, heading, or list item. The URL is optional.

doc.bibliography_entry("smith2025", "A. Smith", "A study of reports", "2025",
                       "https://example.org/study");
doc.paragraph().text("The method follows ").citation("smith2025").text(".");
doc.bibliography_entry("smith2025", "A. Smith", "A study of reports", "2025",
                       "https://example.org/study")
doc.paragraph().text("The method follows ").citation("smith2025").text(".")

Keys must start with an ASCII letter or digit and then contain only letters, digits, underscores, hyphens, or periods. Duplicate keys are rejected. When appending a document fragment, its bibliography entries are merged; conflicting entries with the same key are rejected. Brief++ formats entries from author, title, and year; it does not import BibTeX or CSL databases.

Writing prose

You can write whole paragraphs as prose instead of chaining inline builders:

doc.paragraph(R"(The results were stable across the three runs.
Further measurements would help confirm the trend.)");
from pathlib import Path

doc.paragraph(Path("summary.txt").read_text(encoding="utf-8"))

The text in summary.txt is treated as ordinary paragraph text. Brief++ does not parse Markdown, reStructuredText, or another manuscript format from that string. To make a link or emphasize a phrase, add those parts with the inline builder methods. raw(Backend::Markdown, content) in C++ or raw(Backend.MARKDOWN, content) in Python inserts Markdown only into the Markdown output, so use it when one backend needs its own syntax.

Tables and lists

For a table with headers, set columns before rows. Every row must have the same number of cells:

summary.table().columns("Metric", "Value")
    .row("Count", "12")
    .row("Mean", "4.2");

For a headerless table, the first row establishes its width. To add rich cell content, call column_count(n) or columns(...) first, then use cell(). The renderer rejects an incomplete row:

table = summary.table().column_count(2)
table.cell().text("Status")
table.cell().strong("Passed")

list(true) in C++ and list(True) in Python make numbered lists; use false or False for bullets. The zero-argument item() gives you a rich text builder and can contain a nested list:

checks = summary.list()
item = checks.item()
item.strong("Verify")
item.list().item("Check the units")

definition_list().item(term, description) adds a plain definition. For rich terms and descriptions, call definition_item() and fill its term and description builders.

Reusable blocks and format-specific content

DocumentFragment is an alias of Document. In C++, doc << fragment copies the fragment’s blocks. In Python, call doc.append(fragment). This lets you build repeated sections independently before adding them to a report.

label(id) gives a block an identifier and reference(id) refers to it. Repeated role(name) calls add semantic roles. HTML renders roles as CSS classes; LaTeX can map them to environments through LatexRenderer.role_environment. Other renderers keep the content but ignore those roles. Use raw(Backend::Latex, content) in C++ or raw(Backend.LATEX, content) in Python only for content meant for one backend. Other backends omit that raw block.

Ordinary text is escaped by each renderer. Math expressions and raw blocks pass through, so write math in the syntax expected by the target format. Labels should use identifiers accepted by each output format you plan to generate. See Renderers and output behavior for details on format differences.