Escape

Reports have bands, not pages

Drawing a report is the easy part. The hard part is deciding what repeats, what totals, and where page two starts. Report writers settled that decades ago with bands. HTML-to-PDF dropped the answer, and every PDF generator since has had to find it again.

The problem every PDF generator solves again

Take a customer statement that runs to three pages. The column labels have to come back at the top of page two. Each customer needs a subtotal under their own rows. The grand total must not land alone at the top of an otherwise empty page three.

Not one of those is about drawing. They’re decisions about when something prints. Render a statement to HTML and print it through a headless browser, and those decisions get scattered. The template computes subtotals in a loop. CSS hints at where breaks may fall. A page number lives in a header template with its own rules. Each one is a small fix, and each fix is local to one report.

What a band is

A band is a strip of layout attached to one moment in the walk over the data. Each band answers a single question: when does this print?

  • The report header prints once, before any row. The report footer prints once, after the last one, which is where totals for the whole document belong.
  • A group header prints when a new value of the grouping key starts, and its group footer prints when that value ends. A subtotal per customer lives there.
  • The detail band prints once per row.
  • The empty band prints once when no rows remain, and takes the place of every group and detail row.
  • The page header and page footer print once per page, on targets that have pages.

Crystal Reports and JasperReports both lay a report out in bands like these, under their own names. The idea has outlived both tools because it puts the decision where it belongs. You don’t say “draw the subtotal at y = 640.” You say “the subtotal closes the customer,” and the renderer works out the rest.

In quario that’s a group with an aggregate and a footer. Here’s the week group from the timesheet template, cut down to the parts that matter:

timesheet.report.json
{ "groups": [{
  "name": "week",
  "by": "[email protected]",
  "aggregates": { "hours": "sum:[email protected]", "from": "first:[email protected]", "to": "last:[email protected]" },
  "header": [{ "type": "text", "value": "Week {{ week.key }}", "style": { "size": 11, "bold": true } }],
  "footer": [{ "type": "text", "value": "Week {{ week.key }} total: {{ week.hours }}", "style": { "size": 8 } }]
}] }

Nothing in it mentions a page. The definition says when the week opens, when it closes and what it adds up. The engine computes the aggregates from the data, so the total is never arithmetic someone typed into a template.

Why it works

Once pagination and totals belong to the model, they stop being tricks played on one output. The same structure lands on three very different surfaces.

On a PDF, @quario/pdf paginates the document itself, with no headless browser. A group’s header travels with its first row, and repeats at the top of every page the group continues onto. The last data row keeps its total block, so a total can’t land alone. The definition declares none of this. A definition that described its own page breaks would stop working on Letter the moment it worked on A4.

On a Word document, @quario/docx is a flow target. It writes real Word tables and lets Word turn the pages, with the table’s header row repeating after every break. Each group header opens with a heading, so the navigation pane mirrors the report’s own grouping.

On a workbook, @quario/xlsx flattens the walk onto a grid of native cells. Amounts stay numbers a reader can sort, filter, and sum. A group break that turns a page in the PDF starts a new worksheet here. The page bands become the sheet’s print header and footer, since a worksheet still has paper someone may print it on.

Three outputs, one definition, and no target-specific branch in it. That’s only possible because the definition describes the report’s structure rather than one surface’s geometry.

What quario changes from the classics

Bands aren’t new. Two things about how quario carries them are.

The definition is JSON you keep in the repository. Crystal Reports stores a report as a binary .rpt file. JasperReports uses an XML file that a visual designer usually writes for you. Either way the layout tends to live in a tool rather than in review. A quario definition is a plain file you diff, version and read in a pull request, next to the code that feeds it.

Expressions parse into closures. JasperReports compiles a report’s expressions into Java classes before it fills the report. quario has no eval, no new Function and no code generation anywhere in the render path. An expression like {{ round(region.subtotal / $.revenue * 100, 1) }} becomes a tree of closures when the definition compiles. It’s never source text that something runs.

That’s what makes a definition safe to accept from somewhere you don’t control, including an AI agent. The guarantee is narrow, and it should stay narrow. Nothing an agent writes into a definition can execute. Whether its numbers are the right ones is still a question for whoever reviews it.

Where bands are the wrong tool

Bands describe documents that run from top to bottom over rows of data. Plenty of layouts aren’t that.

A dashboard is a two-dimensional arrangement that someone filters and refreshes. Its tiles have no “when does this print” answer, because nothing prints. A chart library and a grid of components serve it better.

A free-form layout, like a brochure, a poster or a landing page, places elements by eye. Text wraps around a photo, and a heading overlaps an image. Nothing repeats per row, and there are no groups to total. A design tool or plain HTML and CSS is the honest choice there.

The test is simple. If the document has rows, and the rows decide what repeats and what adds up, it has bands whether you name them or not. Naming them is what lets one definition become a PDF, a Word document and a workbook without anyone redrawing it.

Try it on your own JSON.

quario is free to evaluate, with no time limit — output carries a watermark until you license a deployment.

Start free