Skip to content

About

Design paginated PDF reports from formancy forms, Formancy Data datasets or your own JSON, in native Angular and React editors. Source-available; free to evaluate and develop, paid in production.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

58 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Formancy Report

Paginated PDF reports, designed inside your Angular or React application.

Formancy Report is a paid, source-available module that designs and renders paginated PDF reports. It builds on the Apache-2.0 formancy packages and runs in your application: you host it, and you hand it the data.

The Studio: a bar with the starter picker on Quotation and the builder switch on React; the React builder's toolbar, outline with “Image: logo” first in its report header, design view and report properties; and the Preview, “Preview: printed.”, with the Offerte under the firm's mark on white paper.

The quotation starter in the React builder, and beside it the Preview: the PDF the runtime and the renderer print from the same session, drawn by PDF.js on white paper, down to “Total CHF 3'143.00” (0010, 0011).

Taken by a script from the built Studio and held to the text it shows, as every picture under The Studio is.

What it does

  • Design a report in a native React or Angular builder, with undo, and see what is wrong before anything renders (0012, 0009).
  • Print it as a paginated PDF, in Node or in a browser, from your application's JSON. Totals are exact, values are formatted for the report's locale, and a report that cannot be drawn whole is refused, never truncated (0010, 0011).
  • Read its data from Formancy Data 0.1.0, optionally and best-effort (0016).
  • Print a formancy form's submissions, each against the exact form version that collected it: its labels in the report's language, a field the version moved refused rather than printed wrong, and a number by its digits or not at all. Your application hands over the version and the submission (0042).
  • Start a report from a formancy form version: a first draft, the same for the same version, that names the version and reads its submissions, with notes of what it left out and assumed (0045).
  • Review a formancy form's new version for a report: a version that moved, removed or retyped a field the report reads is refused until a resolution is applied, and anything else prints. Each resolution is a report-core command your application applies in one undo step -- a rename the form's author declared first, after which the versions that keep the field there print unreviewed -- and none is applied by itself (0048).
  • Say where a render's data came from, in the manifest kept beside the PDF: the Data forms it read, or each submission of a formancy form with its form's id and the version that collected it, held to the syntax of identifiers (0041).
  • QR codes, refused where they may not scan (0020).
  • A Swiss QR-bill, its receipt and payment part at the foot of the last page, held to SIX's rules, and edited field by field in either builder: added from SIX's example, and warned of while it holds SIX's examples (0022, 0039).
  • An EPC QR code for a SEPA credit transfer, held to the EPC's rules, with Austria's frame as an option (0025).
  • An EN 16931 e-invoice, the German hybrid invoice: the Factur-X / ZUGFeRD XML is written from the values the page prints, and a final render is a PDF/A-3b that embeds it. It may name the parties' electronic addresses, the seller's contact and its business process, which a German business invoice does not need. Or it is an XRechnung 3.0 for a German public buyer, in CII: its XML, which your application takes from the resolved document and sends, is the invoice, and its PDF a copy labelled so on every page. France is not claimed. In a gate of its own, Mustang and KoSIT judge the XML of the repository's samples, an XRechnung's in KoSIT's XRechnung scenario, and veraPDF and Mustang the hybrids' PDFs (0027, 0031, 0037, 0038). Either kind is edited field by field in either builder, its terms in groups and its VAT categories a list, and an IBAN typed into the e-invoice, its QR-bill or its EPC QR code is the account all of them pay, in one step, where every one of them writes it in the report. Only its totals and its lines are held to the page: a seller, a date or a bank the page prints in its own words is yours to change with it. An e-invoice is edited and never made from nothing: a starter gives one (0044).
  • Fonts: a report maps its sans, serif and mono tokens to TrueType fonts your application approves, pinned by digest; the renderer reads each with report-spec's reader, embeds it from its bytes with the layout tables fontkit would run removed, and refuses a character the face lacks and a weight or slant it does not map. It prints Latin, Greek and Cyrillic, with their punctuation and symbols, and refuses every other script, right-to-left, shaping and CJK among them, in any face. A token left unmapped prints in Roboto (sans) or is refused; a QR-bill's slip keeps Liberation Sans (0040, 0043).
  • Images: a PNG, a JPEG, or an SVG logo drawn as vectors, made of shapes alone: no text, no embedded picture. Where your application gives the editor a store for them, a person adds one in either builder, and your application keeps its bytes (0030, 0033).
  • The Studio, a playground with both builders and the PDF they print, beside them or in the design view's place, online at https://formancy.ai/report/ (Try it, 0018).

Planned and not here: the server and its jobs, reviewed AI changes, a published PDF viewer, and a consistent read from Formancy Data.

Status, 22 October 2026: 0.2.0, a developer preview, is released. Its eight packages are on npm, with provenance, and the GitHub release carries the signed SBOM and release report of the run that published them. What 0.2.0 holds and leaves out is in the changelog. Since then main holds 0.3.0's first work, under Unreleased there, @formancy/report-forms among it, which no release has published yet. The report format is still version "0", the draft: a report written now is not promised to be read by a later release (0004).

Start with the getting-started guide: a quotation printed as a PDF in Node and in a browser, an XRechnung's XML taken from what the runtime resolved, the editor in a React page that saves and previews, and in an Angular page. The install gate compiles and runs its code against the packed packages (0015).

Try it

The Studio is online at https://formancy.ai/report/, and runs from a clone with Node 22.13 or later and corepack:

git clone https://github.com/sharkysan/formancy.ai-report.git
cd formancy.ai-report
corepack enable pnpm
pnpm install
pnpm demo

pnpm demo builds the packages and the Studio, then serves the built Studio at http://localhost:4394. Pick a starter or a blank report, edit it in either builder, and the Preview prints the PDF, Download PDF included. The starters' data is synthetic and built in. The page asks no other site for anything, and the browser gate fails if it does (0023).

The online Studio is main's, deployed on every push to main, so it can be ahead of the latest release. Nothing here checks what the address serves (RELEASING.md). The product's pages stand beside it, from https://formancy.ai/report/en/.

The Studio

apps/studio is the playground, never published as a package (0013). The Preview stands beside the builder where the window has room for it, and in the design view's place where it has not (0029). Add image… adds a PNG, a JPEG or an SVG from your computer, kept in the page and sent nowhere (0033).

The Studio with the builder switch on Angular: the Angular builder with Undo offered and the edited line “Zahlbar innert 10 Tagen netto.” selected in its design view and open in its properties, and the Preview printing the same line on white paper.

The quotation's payment line edited in React to “Zahlbar innert 10 Tagen netto.”, then the switch moved to Angular. The Angular builder draws the same editor over the same session, so the edit is there, still selected, and so is its Undo; the Preview prints the edited line. Switching frameworks keeps the document, its history, the selection and the zoom (0012).

The builder and the Preview: the outline of the equipment register with the Category column selected, its value set to Description, a warning in the builder's problems, and the register printed on white paper, grouped by site.

The equipment register with its Category column set to show the description instead. The builder says what that leaves behind before anything is rendered: “field "category" of the detail dataset is neither printed, drawn, ordered by, totalled nor read, and would be fetched for nothing”. A warning renders as written, so the Preview still prints the register grouped by site, with “Basel (12 items)” heading its group (0009).

The builder with the Book value row of the outline selected and focused, its properties showing the condition and an error in the builder's problems, beside the Preview saying it was refused by the runtime, with the reason and a button “Select Book value”.

The Book value column given the condition row.value > 1000, which reads a row where a column's condition has none. The builder marks it as an error, and the runtime refuses to print the preview rather than guess: “Preview: refused by the runtime, 1 problem.”, with its reason, “row has a value only in a table's cells and what an aggregate reads from each row”. Its button, “Select Book value”, pressed with Enter, selected the column and put the keyboard on its outline row, with the condition in its properties (0010).

More: At a phone's width The Studio at a phone's width: the bar with the starter picker on Service inspection and the builder switch on React, and under it the builder, its toolbar wrapped over several rows and the Preview as its one pane, “Preview: printed.”, with the top of the first page of the service inspection report on white paper.

At 390 pixels wide, the service inspection starter as the Studio opens. The bar's controls take a line each, and the builder shows one pane at a time, chosen from its toolbar: it opens on the Preview, standing in the design view's place, with its first page at the width there is, “Page 1 of 2” (0029). Nothing scrolls sideways, which the Studio's browser gate measures down to 320 pixels.

Every picture in this section is taken by node scripts/pictures.mjs, in Chromium, from the built Studio driven through its controls, never by hand. pnpm --filter @formancy/report-studio test:pictures walks the same states and fails when a picture's elements no longer say what they said when it was taken, or when a caption quotes what they do not say, and pnpm test:repo holds every caption to its picture's record. CI runs both, in its browser job. Neither looks at the pixels: a change of colour or layout alone is seen only by looking. And a caption is held to the text its elements hold, not to how much of that text a pane has scrolled or cut out of the picture.

What the starters print

Each starter's PDF, and each invoice sample's, printed from its synthetic data and drawn by PDF.js. report-render-pdf's pictures.test.ts fails when a page stops printing or drawing what its picture shows, or its caption quotes what the page does not print. It does not see a drawing changed inside its own box, a rule, a border, an image or the colour of text. The quotation and the invoices print an invented firm's mark, an SVG your application hands over like any other asset (0032).

The quotation starter, an Offerte under the firm's mark: line items in Swiss francs, then Zwischensumme, MWST 8.1 %, Rundung and Total CHF 3'143.00

The service inspection starter, page 2: the frame headed Defect found, the photo from the data, and the sign-off above Inspector's signature kept with it

The equipment register starter, grouped by site: Basel (12 items) heads its group, and Subtotal Basel 12 closes it

The invoice starter, a Rechnung under the firm's mark: its line items, then Zwischensumme, MWST 8.1 % and Total CHF 1'416.52, and across the foot of the page its QR-bill, the Empfangsschein and the Zahlteil with its Swiss QR Code, each for CHF 1 416.52

The invoice starter is in German. The same invoice comes in French, Italian and English, with the slip's headings in SIX's words for each (0024).

The invoice in French, Italian and English

The invoice in French, a Facture under the firm's mark: its line items, then Sous-total, TVA 8,1 % and Total 1'416.52 CHF, and across the foot of the page its QR-bill, the Récépissé and the Section paiement with its Swiss QR Code, each for CHF 1 416.52

The invoice in Italian, a Fattura under the firm's mark: its line items, then Subtotale, IVA 8,1 % and Totale CHF 1416.52, and across the foot of the page its QR-bill, the Ricevuta and the Sezione pagamento with its Swiss QR Code, each for CHF 1 416.52

The invoice in English, an Invoice under the firm's mark: its line items, then Subtotal, VAT 8.1% and Total CHF 1'416.52, and across the foot of the page its QR-bill, the Receipt and the Payment part with its Swiss QR Code, each for CHF 1 416.52

The euro invoice starter, a German Rechnung under the firm's mark: its line items in euro, then Nettobetrag, Umsatzsteuer 19 % and Rechnungsbetrag 1.132,44 €, and under them the Bankverbindung, IBAN: DE83 0123 4567 0012 3456 78, beside the EPC QR code that pays it

The euro invoice prints beside its EPC QR code what the code holds, for the payer to compare with what the banking app read (0025). Samples from Austria, France, Italy, Spain, the Netherlands and Ireland sell parts for the boiler the starter services, at each country's standard VAT rate. No native speaker has reviewed their words, and they do not print all that each country's invoicing law asks of an invoice (0028).

The euro invoice from Austria, France, Italy, Spain, the Netherlands and Ireland

The euro invoice from Austria, a Rechnung under the firm's mark: the boiler's parts, then Nettobetrag, USt 20 % and Gesamtbetrag € 629,44, and under them the Bankverbindung, IBAN: AT93 8012 3000 1234 5678, beside the EPC QR code in Austria's frame, Zahlen mit Code up its side

The euro invoice from France, a Facture under the firm's mark: the boiler's parts, then Total HT, TVA 20 % and Total TTC 629,44 €, and under them the Coordonnées bancaires, IBAN : FR76 0123 4000 1200 0123 4567 880, beside the EPC QR code that pays it

The euro invoice from Italy, a Fattura under the firm's mark: the boiler's parts, then Imponibile, IVA 22% and Totale fattura 639,93 €, the notice Copia di cortesia, and under them the Coordinate bancarie, IBAN: IT53 N000 0000 0120 0001 2345 678, beside the EPC QR code that pays it

The euro invoice from Spain, a Factura under the firm's mark: the boiler's parts, then Base imponible, IVA 21 % and Total factura 634,68 €, and under them the Datos bancarios, IBAN: ES25 0000 0012 7000 1234 5678, beside the EPC QR code that pays it

The euro invoice from the Netherlands, a Factuur under the firm's mark: the boiler's parts, then Totaal excl. btw, Btw 21% and Totaal incl. btw € 634,68, and under them the Bankgegevens, IBAN: NL17 FMNC 0123 4567 89, beside the EPC QR code that pays it

The euro invoice from Ireland, an Invoice under the firm's mark: the boiler's parts, then Subtotal, VAT 23% and Total due €645.17, and under them the Bank details, IBAN: IE30 FMNC 0000 0012 3456 78, beside the EPC QR code that pays it

The e-invoice starter, a German Rechnung under the firm's mark, whose final render is a PDF/A-3b that embeds its XML: its line items in euro, then Nettobetrag 743,30 €, Umsatzsteuer 19 % auf 635,80 €, Umsatzsteuer 7 % auf 107,50 € and Rechnungsbetrag 871,63 €, the totals the XML holds, and under them the Bankverbindung beside the EPC QR code that pays it

The e-invoice starter is the euro invoice's firm invoicing a German business as an EN 16931 e-invoice, "Rechnung (ZUGFeRD)": the page prints each total the XML holds, computed once for both (0027), and either builder edits it field by field (0044). Its three other samples and the XRechnung's are the e-invoice gate's, not the Studio's.

The site visit starter, generated from version 2 of a formancy form: a line for each answer, from Objekt: Bahnhofplatz 1, 3011 Bern, the inspector, the ticked boxes and the matrix under their headings, Elektrik: [x] among them, the findings one table under Befunde, and the page counted, 1 / 1

The site visit is the report @formancy/report-forms generates from a formancy form version, a first draft: its labels from that version in the report's language, a group a section, the repeater asked for its table, and a submission of that version read into it. Its notes, which no page shows, list what it left out and assumed (0045).

Render a report

Your application resolves a report against the data it may show, then renders it. Nothing here fetches data or follows an address: the report names its datasets and assets, and your application hands over both. Here report is the quotation starter, data its synthetic data and logo its mark's bytes and digest, handed over when the runtime asks for it:

import { resolveReport } from '@formancy/report-runtime'
import type { ImageResolver } from '@formancy/report-runtime'
import { renderPdf, RenderRefusal } from '@formancy/report-render-pdf'

const images: ImageResolver = (ref) => (ref.asset === 'logo' && ref.digest === logo.digest ? { mediaType: 'image/svg+xml', bytes: logo.bytes } : undefined)
const resolution = resolveReport(report, data, { mode: 'final', parameters: {}, images, creationDate: '2026-10-15T08:00:00Z' })
if (!resolution.ok) throw new Error(resolution.errors.map((error) => `${error.code} at ${error.path}`).join('; '))
const pdf: Uint8Array = await renderPdf(resolution.document, { maxPages: 50 })

resolveReport refuses, with every reason at once, a report the editor's diagnostics refuse, data that breaks its contract, an asset your application has no bytes for (missing-asset) and, in a final render, incomplete data. renderPdf rejects with a RenderRefusal whose code says what stopped it: too-many-pages, content-too-large, invalid-asset, cancelled or unsupported. Neither returns part of a document. The install gate runs these lines from the packed packages (scripts/install-fixture/consume.ts).

Packages

Package What it is
@formancy/report-spec The report format, version 0 (a draft): its types, validation with stable error codes, a canonical hash, and the rules of a QR-bill, an EPC QR code, an e-invoice, an SVG, an image's bytes and a font's (0007).
@formancy/report-core Commands over a report, as functions and as data, several as one batch, a session that commits only what validates, with exact undo and redo, and one compilation of every expression for the diagnostics and the runtime (0008, 0009, 0048).
@formancy/report-runtime Resolves a report against its data into a renderer-neutral document, exact numbers as strings and totals rounded once, and writes an e-invoice's XML, or refuses with every reason; its manifest says what a render was resolved from, the data's source among it (0010, 0041).
@formancy/report-render-pdf Draws that document with pdfmake, in Node and through a browser entry, and refuses what it cannot draw whole rather than return a partial PDF (0005, 0011).
@formancy/report-data-provider Reads a report's data from a Formancy Data 0.1.0 server, labelled best-effort (0016).
@formancy/report-forms Reads a report's data from a formancy form's submissions, against the exact version that collected each, as your application hands them over, host-captured (0042); and starts a report from a form version, a first draft whose notes list what it left out (0045); and reviews a form's new version for a report, a version that moved, removed or retyped a field it reads blocking until a resolution -- a declared rename first, report-core's commands as data -- is applied (0048).
@formancy/report-builder-core The editor model both builders render, with no framework, in English, Swiss German and Swiss French; BUILDER_SCENARIOS is what every builder must do (0012).
@formancy/report-builder-react The builder for React 19.
@formancy/report-builder-angular The builder for Angular 22: standalone components, signals and OnPush, no zone.js.

Beside them, apps/studio is the Studio, apps/pages the product's pages, static HTML in four languages (0021), and @formancy/report-corpus the private test support that proves a rendered PDF (0003); none is published.

Licence, in one table

Source-available, not open source. The formancy packages this module builds on stay Apache-2.0; this module does not. It is on Formancy Data's terms.

Use Terms
Read the source and fork the repository Free
Evaluate, develop and test — including in companies Free
Run in production, internally or customer-facing Paid licence
Redistribute inside another software product Separate commercial agreement
Customer data, report templates and generated documents Remain the customer's

LICENSE.md has the terms. It is Version 1, in force from 14 October 2026, and says at the top that no lawyer has reviewed it. A production licence is requested from info@formancy.ai, as its section 9 says.

Why a paid module exists beside an Apache-2.0 project is decision 0001. Running @formancy/report-data-provider in production needs a Formancy Data production licence as well, for the @formancy/data-client it depends on (0016).

How it relates to formancy.ai and Formancy Data

Report depends on the released @formancy/* packages at exact versions and copies none of their code (0002). A report is its own document, with pages, flowing tables, groups and totals, not a widened formancy form (0007). Formancy Data is optional: without it, your application supplies the data. With it, one package reads a published Data form's records, as the person whose token your application supplies, so Data's policy decides every answer (0016). Data never depends on Report. A formancy form's submissions are read through @formancy/spec's own validator, hash and labels, along scopes a test holds to formancy's own data paths, by a package that depends on formancy alone, never on Data (0042).

Development

Node 22.13 or later, which pdfjs-dist 6.4 needs, and pnpm through corepack.

corepack enable pnpm
pnpm install
pnpm build
pnpm test

While working on the Studio, pnpm --filter @formancy/report-studio dev serves it on port 4394, once pnpm build has built the packages it imports.

CI runs the gates of .github/workflows/gates.yml on every pull request, and a release runs them before it publishes. Every run ends in a release report of what it was tested on and what it found, and a release publishes only what its own run's report passed (0014). CLAUDE.md gives each gate's command and the bar a change meets; Verification says what each gate proves and what it does not.

What it is tested with

Tested with Declared as
@formancy/data-client 0.1.0 0.1.0
@formancy/data-core (transitive) 0.1.0
@formancy/expressions 0.7.0 0.7.0
@formancy/spec (transitive) 0.5.0
@formancy/spec 0.7.0 0.7.0
@formancy/themes 0.7.0 0.7.0
tslib, by @formancy/report-builder-angular 2.8.1 ^2.8.0
pdfkit, by @formancy/report-render-pdf 0.19.1 0.19.1
pdfmake, by @formancy/report-render-pdf 0.3.11 0.3.11
uqr, by @formancy/report-render-pdf 0.1.3 0.1.3
@noble/hashes, by @formancy/report-spec 2.4.0 2.4.0
@angular/core, a peer of @formancy/report-builder-angular 22.2.2, in its suites ^22.0.0
react, a peer of @formancy/report-builder-react 19.3.1, in its suites ^19.0.0
react-dom, a peer of @formancy/report-builder-react 19.3.1, in its suites ^19.0.0
Node, the workspace >=22.13.0
Node, CI 22
pnpm 12.4.2, through corepack pnpm@12.4.2, the root's packageManager
Chromium 156.0.8078.4, the chromium-headless-shell Playwright 1.64.1 pins (revision 1248)
typescript 6.0.3 ~6.0.3
vitest 5.0.4 ^5.0.1
pdfjs-dist 6.4.299 6.4.299
jsdom 30.1.2
axe-core 4.14.0
playwright 1.64.1 ^1.63.0

What a run actually ran -- the Node of each job, the React and Angular each install host resolved at both ends of the builders’ peer ranges, and the Chromium the browser gate launched -- is in the release report every CI run produces and every release attaches, signed.

Documents

  • docs/getting-started.md — from an empty npm project to a PDF and the editor in your page, its code compiled and run by the install gate.
  • docs/README.md — the architecture, in arc42's sections.
  • docs/decisions/ — every decision somebody could helpfully undo, with what verifies it.
  • CHANGELOG.md — what changed and why.
  • CONTRIBUTING.md and CLA.md — contributions are taken under a CLA, checked on every pull request.
  • SECURITY.md — how to report a vulnerability.
  • RELEASING.md — how a release is cut, with provenance, a signed SBOM and the signed release report.
  • docs/release/ — what the release report holds and what fails it, with the release gates and the known limitations.

About

Design paginated PDF reports from formancy forms, Formancy Data datasets or your own JSON, in native Angular and React editors. Source-available; free to evaluate and develop, paid in production.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages