Skip to main content

Architecture

A map of the source tree grouped by responsibility, not by import order — useful context if you're modifying the package itself rather than just consuming it.

Editor (src/Designer.tsx + src/components/)

Designer.tsx owns selection state, the tab bar (Fields/Data/Style/ Filter/Page), clipboard (copy/paste), keyboard shortcuts, and every mutation on Template/Binding[] (add/remove/reorder schemas, update a binding, resize page bands…). It renders two children:

  • PageCanvas.tsx — the actual page: one <Rnd> (react-rnd) per schema for drag/resize, the header/footer/margin bands drawn in red, the grid, marquee (box) selection, zoom controls. Delegates what a field actually looks like to FieldBox/ (one small component per schema.type).
  • The side panelFieldList.tsx, Toolbar.tsx, and, once a field is selected, PropertyPanel.tsx — a thin dispatcher to one PropertyPanel<Type>.tsx per schema type, each split into a "Data" and a "Style" tab. BindingEditor.tsx (the generic path/array/ section/chart/kpi binding editor) and PropertyPanelFields.tsx (shared X/Y/width/height inputs) are reused across several of them.

Selection, editing, and binding all live in the same React tree — no module bridge, no imperative API between the canvas and the panel.

Bindings and templates (src/bindings/, src/tableColumns.ts)

bindings.ts is pure logic over strings/plain objects, with no third-party dependency:

  • resolveToken/renderTemplate — evaluate a {token}/ {FUNCTION(...)} template against the real JSON document. This is what turns a TextSchema.content or a KpiSchema.title/subtitle into the string that actually gets drawn. Formatting inside DATE/ CURRENCY is intentionally independent of the Designer's own UI language (locale prop) — it's part of the generated report's content, written by whoever authors the template, not the tool's chrome.
  • buildInputs — turns the whole JSON document + Binding[] into a flat Record<schemaName, string> (or a stringified 2D array, for tables) that the canvas preview and generate.ts both read from.
  • resolveChartItems/aggregateChartItems — resolve a chart's Binding against the real array, apply filters, group the tail into "Other" past topN.
  • resolveKpiValue — resolve a kpi-type Binding into a single aggregated number (sum/count/avg/min/max).
  • describeBinding/describeBindingShort — short human-readable summaries used only in the editor UI.

tableColumns.ts holds the pure functions that keep a TableSchema's head/content/footer/columnStyles in sync with its Binding (array) when a column is added/removed/reordered/reformatted from the panel.

PDF generation (src/pdf/)

generate.ts is the entry point (generatePdf(template, data, bindings, options?)) — pure JS, no DOM, safe to run in Node. For each schema it resolves the value via buildInputs/resolveToken (or resolveKpiValue for a KPI with a kpi binding) and delegates the actual drawing to a per-type module:

  • drawTable.ts — header/body/footer rows, per-column style overrides, pagination when a table doesn't fit on one page.
  • drawSection.ts — repeats the group of member fields once per item of the bound array, growing/paginating with the rest of the body.
  • drawChart.ts — pie/donut/bar, legend placement (font size configurable), color palette (src/chartColors.ts).
  • drawKpi.ts — the colored card + Material Symbols icon path (src/materialIcons.ts), configurable font sizes/icon size/corner radius (defaults in src/kpiFormat.ts).

Supporting modules: pagination.ts (splitting the body across pages against headerHeight/footerHeight/marginLeft/marginRight — see src/zones.ts for how the editor classifies a field into header/footer/ margin/body by position alone), fontUtils.ts (embedding a custom TTF via fontkit, normalizeFontBytes), backgroundImage.ts (turning an uploaded PDF/PNG/JPEG into the page's background PNG), color.ts, resolvers.ts, and pdfWorker.ts (wiring up pdf.js's worker for PdfPreview, browser-only).

Only downloadPdf, Designer, PdfPreview*, and the UI components touch the DOM. Everything else under src/pdf/, src/bindings/, and src/types/ is safe to import in a Node backend — see Backend integration and Server-only usage.

UI language (src/i18n/)

The Designer's own UI text (buttons, tabs, warnings, placeholders) comes from a small dictionary — en.ts (canonical, default) and pt-BR.ts (typed against it, so a missing key is a compile error, not a silent blank string). I18nProvider/useT/useLocale (React context) wire the <Designer locale="en" | "pt-BR"> prop through to every component; a component used standalone, without <Designer> on top, still renders correct (English) text via the context's default value. This only covers the editor's chrome — it never touches how {DATE(...)}/{CURRENCY(...)} format the generated report's own content.

Two entry points (src/index.ts vs. src/server.ts)

src/index.ts exports the full public API — React components (Designer, PdfPreview, PdfPreviewModal, the UI kit) plus generation/bindings/types. src/server.ts mirrors the non-React subset only — same generation/bindings/types, zero references to react anywhere in its compiled output — built as a separate bundle (dist/ server.{js,cjs,d.ts}) exposed via the json-pdf-designer/server subpath. Keeping them as two hand-maintained lists (rather than one generated from the other) means an export accidentally added to only one of them is easy to spot in review — see the comment at the top of src/index.ts.