NETReporter 1.6.0

NETReporter

Knihovna pro generování reportů (PDF, SVG, HTML, JSON) z deklarativních JSON šablon a JSON dat.

Šablona popisuje stránku, sekce, rozvržení, styly a bindingy na data; knihovna ji zvaliduje, vyhodnotí nad daty a vykreslí. PDF a SVG kreslí QuestPDF, grafy OxyPlot.

Instalace

Balíček je v privátním NuGet feedu Advisor Software:

dotnet nuget add source https://nuget.advisor-soft.com/v3/index.json --name AdvisorSoft
dotnet add package NETReporter

Požadavky: .NET 10.

Rychlý start

using NetReporter;

var renderer = new ReportRenderer();

await using var template = File.OpenRead("template.json");
await using var data = File.OpenRead("data.json");
await using var pdf = await renderer.RenderAsync(template, data);   // výchozí formát je PDF

await using var file = File.Create("report.pdf");
await pdf.CopyToAsync(file);

S DI kontejnerem (ReportRenderer se registruje jako singleton):

services.AddNetReporter();                        // ReportRenderer, TemplateSchemaParser, TemplateAnalyzer, VisualTreeBuilder
services.AddNetReporter<MyPipelineObserver>();    // + vlastní IReportPipelineObserver

var renderer = serviceProvider.GetRequiredService<ReportRenderer>();

RenderAsync bere šablonu a data jako Stream a vrací Stream s výstupem (pozice 0). Při neplatné šabloně vyhodí InvalidOperationException s diagnostikami (zpráva + JSON cesta).

Šablona v kostce

{
  "version": "1.0",
  "meta": { "name": "Faktura", "culture": "cs-CZ", "palette": { "primary": "#1A73E8" } },
  "page": { "size": "A4", "orientation": "Portrait", "marginsMm": { "top": 15, "right": 12, "bottom": 15, "left": 12 } },
  "styles": {
    "base": { "font": "Arial", "fontSize": 9 },
    "h1": { "fontSize": 16, "bold": true, "color": "primary" }
  },
  "content": {
    "root": {
      "type": "Stack",
      "gapMm": 3,
      "items": [
        { "type": "Text", "text": "Faktura {{$.Number}}", "styleRef": "h1" },
        { "type": "Text", "text": "Vystaveno: {{$.Issued:dd.MM.yyyy}}, celkem {{$.Total:C2}}" },
        {
          "type": "Table",
          "columns": [
            { "key": "name", "header": "Položka", "width": "3*" },
            { "key": "price", "header": "Cena", "width": "1*", "align": "Right" }
          ],
          "rows": {
            "source": "$.Items",
            "as": "item",
            "cells": {
              "name": { "text": "{{item.Name}}" },
              "price": { "text": "{{item.Price:N2}}" }
            }
          }
        }
      ]
    }
  },
  "pageFooter": {
    "root": { "type": "PageNumbers", "mode": "Current", "showTotal": true, "separator": " / " }
  }
}
  • Sekce: pageBackground, pageHeader, reportHeader, content, reportFooter, firstPageFooter, pageFooter, pageOverlay.
  • Vodoznak a razítko: kořenové watermark (text nebo obrázek, podmínka visibleIf, průhlednost v barvě) na každé stránce PDF, SVG, HTML i JSON; z kódu RenderOptions.Watermark.
  • Rozvržení: Stack, Row, Box, Absolute, Spacer, PageBreak; libovolný uzel lze opakovat nad kolekcí přes "repeat": { "source": "$.Items", "as": "item" }; "keepTogether": true (u Stack, Row, Box, Text, Paragraph, Table) zabrání zalomení uzlu přes stránku – nevejde-li se na zbytek stránky, přesune se celý na další.
  • Obsah: Text, Paragraph, HttpLink, Image, Line, PageNumbers, Barcode, Code2D (QR, DataMatrix…), PaymentQr (QR platba SPAYD a EPC s kontrolou IBAN), Checkbox, Table (skupiny, souhrny, stránkování), Chart.
  • Binding: {{$.Cesta.K.Hodnote}} (kořen dat), {{alias.Pole}} (položka repeat/tabulky/grafu), formát za dvojtečkou ({{$.Total:N2}}, {{$.Date:dd.MM.yyyy}}), C# výrazy s prefixem # ({{#item.Qty * item.Price}}), systémové proměnné now, page.number, page.total. Formátuje se podle meta.culture.
  • Styly: pojmenované styly v styles (base platí pro vše), odkaz styleRef a inline style; písmo, barvy (#RRGGBB nebo název z meta.palette), okraje a výplně v mm, rámečky, zarovnání. Styl lze vybrat podle dat – styleRef může obsahovat binding, jehož výsledkem je jméno stylu ("styleRef": "{{# item.Overdue ? \"overdue\" : \"\" }}"); tabulky mají styl řádku rows.styleRef a buňky rows.cells.<key>.styleRef / style.

Kompletní popis formátu je v přibalené dokumentaci (viz níže) – kapitola 02_template_format.md.

Grafy

{
  "type": "Chart",
  "chartType": "Column",
  "source": "$.Months",
  "as": "m",
  "category": "{{m.Month}}",
  "series": [
    { "name": "2025", "value": "m.Revenue", "color": "primary" },
    { "name": "Plán", "value": "#m.Revenue * 1.1", "dashed": true }
  ],
  "valueAxis": { "format": "N0" },
  "legend": { "position": "Top" }
}

Typy Column, Bar, Line, Area, Pie, Donut; více sérií, paleta, legenda, osy, popisky hodnot, skládání. Kombinovaný graf: série může přepsat typ ("type": "Line") a čáry mohou mít druhou osu vpravo ("axis": "Secondary" + secondaryValueAxis). Grafy jsou vektorové ve všech výstupech. Podrobnosti: 02_template_format.md §7.11 a 17-charts.md.

Výstupní formáty

await using var output = await renderer.RenderAsync(template, data, new RenderOptions { Format = RenderFormat.Svg });
Formát Popis
RenderFormat.Pdf výchozí; plnohodnotné PDF se stránkováním
RenderFormat.Svg stejný layout a stránkování jako PDF; jeden SVG se stránkami pod sebou (data-pages, <svg data-page="N">)
RenderFormat.Html HTML náhled bez stránkování (vhodný pro editor/prohlížeč)
RenderFormat.Json strukturovaný výsledek renderu – vyhodnocené texty, data grafů, metadata obrázků a kódů; pro automatické a AI kontroly
RenderFormat.Webp obrázek stránky se stejným layoutem jako PDF (ztrátový, bílé pozadí); RenderAsync vrací první stránku, všechny RenderWebpPagesAsync

Každou SVG stránku zvlášť vrací RenderSvgPagesAsync:

IReadOnlyList<string> pages = await renderer.RenderSvgPagesAsync(template, data);

Do SVG se nevkládají fonty – prohlížeč použije písmo nainstalované v systému (pozice znaků zůstanou zachované).

Každou stránku jako obrázek WebP vrací RenderWebpPagesAsync (rozlišení RenderOptions.ImageDpi 1–600, výchozí 288, kvalita ImageQuality; všechny stránky jsou najednou v paměti):

IReadOnlyList<byte[]> pages = await renderer.RenderWebpPagesAsync(template, data, new RenderOptions { ImageDpi = 150 });

Ladění šablon: new RenderOptions { Format = RenderFormat.Html, RenderBindingsAsExpressions = true } zobrazí místo hodnot samotné binding výrazy. RenderOptions.DebugMode přidá k HTML výstupu ladicí atributy (data-nr-type s typem uzlu u prvků s id), ladicí CSS a skript pro vizuální editor šablon; výstup bez něj se nemění.

Limit stránek: PDF/SVG render skončí RenderPageLimitExceededException po překročení RenderOptions.MaxPages (výchozí 10 000, 0 = bez limitu) – ochrana před šablonou se zacykleným layoutem. CancellationToken se kontroluje i během generování stránek. Všechny formáty se renderují souběžně: od QuestPDF 2026.9 je souběžné generování PDF/SVG v jednom procesu bezpečné a ReportRenderer ho neserializuje, takže propustnost roste s počtem vláken.

Metadata PDF

Vlastnosti dokumentu (Název, Autor, Předmět, Klíčová slova, Aplikace, Jazyk) se nastavují v meta.document. Hodnoty jsou texty s bindingy vyhodnocené nad daty; prázdný výsledek se nezapíše, klíčová slova mohou být pole a bez language se použije meta.culture. Metadata se zapisují jen do PDF.

"meta": {
  "name": "Faktura",
  "culture": "cs-CZ",
  "document": { "title": "Faktura {{$.Number}}", "author": "{{$.Supplier}}", "keywords": [ "faktura", "{{$.Number}}" ] }
}

Podrobnosti: 02_template_format.md §1.4.

Profesionální PDF

Hlavičkový papír, připojená PDF, PDF/A, přílohy a šifrování se doplňují do PDF po vygenerování (post-processing přes qpdf z balíčku QuestPDF; šablony bez těchto funkcí dávají stejné PDF jako dřív).

Hlavičkový papír a připojená PDF (šablona; fragment): page.letterhead vloží stránku PDF pod obsah stránek, meta.document.append připojí PDF za dokument. Zdroj je cesta k souboru, http(s) URL nebo data URI, nejčastěji {{$.Assets.Název}}:

"meta": { "assets": { "Hlavicka": "data:application/pdf;base64,JVBERi0…" },
          "document": { "append": [ "https://firma.cz/vop.pdf" ] } },
"page": { "size": "A4", "orientation": "Portrait", "marginsMm": { "top": 40, "right": 15, "bottom": 25, "left": 15 },
          "letterhead": { "src": "{{$.Assets.Hlavicka}}", "pages": "FirstAndRest" } }

pages: All (výchozí), First, FirstAndRest (první stránka 1. stránkou podkladu, ostatní 2. stránkou). Nedostupný zdroj render nezastaví: PDF vznikne bez něj a RenderOptions.OnBindingDiagnostics (nebo pozorovatel) dostane varování NRP036 s JSON cestou zdroje; kontrolujte je, než fakturu odešlete. Server, který renderuje cizí šablony, má zakázat zdroje ze souborů: AllowLocalPdfSources = false.

PDF/A-3b s přílohou (ISDOC), vstupní data v PDF a heslo:

var options = new RenderOptions
{
    PdfConformance = PdfConformance.PdfA3B,
    Attachments = [ new PdfAttachment { FileName = "FV-2026-001.isdoc", Content = isdocBytes, Relationship = PdfAttachmentRelationship.Alternative } ],
    EmbedData = true,                                   // vstupní JSON jako data.json (null = podle meta.document.embedData)
};

// šifrování (AES-256) nejde spojit s PDF/A
var secured = new RenderOptions { Encryption = new PdfEncryption { UserPassword = "ctenar", OwnerPassword = "vlastnik", AllowModification = false } };

PDF/A-2b přílohy nepovoluje. Volby PDF u jiného formátu než PDF a neslučitelné volby jsou ArgumentException. Knihovna ISDOC negeneruje; PDF/A ověřte nástrojem veraPDF. Podrobnosti: user/pouziti-knihovny.md a dev/07_renderer_questpdf.md.

Validace šablony

Šablonu lze zkontrolovat bez renderu. Parser ji ověří proti vestavěnému schématu a vrátí diagnostiky s JSON cestou k chybě:

using NetReporter.Common;
using NetReporter.Template.Parsing;

var parser = new TemplateSchemaParser();                  // výchozí ParserValidationMode.Strict
var result = parser.Parse(templateJson);

if (!result.Success)
{
    foreach (var d in result.Diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error))
        Console.WriteLine($"{d.JsonPath}: {d.Message} ({d.Code})");
}
  • Strict hlásí neznámé vlastnosti a chybný obsah stylů (neznámá vlastnost, špatný typ, neplatný enum) jako chybu, Compat jako varování (new TemplateSchemaParser(validationMode: ParserValidationMode.Compat); při renderu new RenderOptions { ValidationMode = ParserValidationMode.Compat }, varování pak dostane pozorovatel v ParseCompletedContext.Diagnostics).
  • Diagnostiky mají stabilní Code a JsonPath (např. $.content.root.items[2].rows.source).
  • Schéma šablon (typy uzlů, vlastnosti, enumy) vrací TemplateSchemaProvider.Create().

Podrobnosti: kapitola 03_parser_and_validation.md.

Data, která šablona čte

Chybějící datové pole se vykreslí prázdné a nikdo o tom nezjistí. Analyzátor zjistí ze šablony (bez renderu), která pole dat čte, a složí z nich JSON Schema nebo ukázková data:

using NetReporter.Template.DataContract;

var contract = new TemplateDataContractAnalyzer().Analyze(templateJson);
foreach (var field in contract.EnumerateFields())
    Console.WriteLine($"{field.Path}  {field.Kind}");          // $.Invoice.Items[*].Total  Number

string schema = contract.ToJsonSchema();                        // JSON Schema 2020-12, bez povinných polí
string sample = new TemplateSampleDataGenerator().GenerateJson(contract);   // deterministická ukázková data

Druh pole se odvodí z použití, formátu a názvu (názvový odhad kontrakt označí); C# výrazy {{# …}} se čtou jen podle zápisu. Podrobnosti: user/pouziti-knihovny.md a kapitola 05_binding_system.md.

Sledování pipeline

IReportPipelineObserver dostává události OnPipelineStarted, OnParseStarted/Completed, OnRenderStarted/Completed (doba, velikost výstupu) a OnError (fáze, kategorie, stabilní kód chyby) – vhodné pro metriky a logování. Registruje se přes AddNetReporter<TObserver>() nebo konstruktor ReportRenderer. Knihovna loguje přes ILogger, pokud předáte ILoggerFactory.

RenderCompletedContext v OnRenderCompleted nese také BindingDiagnostics a OmittedBindingDiagnostics: chyby v bindingech (nedokončené {{, nečíselná hodnota grafu…) render nezastaví, takže je jinak neuvidíte. Shodné diagnostiky se slučují a počet omezuje RenderOptions.MaxBindingDiagnostics (výchozí 200, 0 sběr vypne). Při selhání renderu je (do chyby nasbírané) nese i PipelineError v OnError.

Dokumentace v balíčku

Balíček obsahuje dokumentaci knihovny a šablon ve složce docs/ (po instalaci v %USERPROFILE%\.nuget\packages\netreporter\<verze>\docs\, na Linuxu ~/.nuget/packages/netreporter/<verze>/docs/). Rozcestník je docs/README.md:

Soubor Obsah
docs/user/pouziti-knihovny.md průvodce použitím knihovny krok za krokem
docs/dev/02_template_format.md formát šablony – sekce, uzly, vlastnosti, repeat, tabulky, grafy
docs/dev/03_parser_and_validation.md parser a validace šablon, diagnostiky
docs/dev/04_style_system.md stylovací systém
docs/dev/05_binding_system.md binding a výrazy
docs/dev/17-charts.md grafy – chování a omezení
docs/dev/01_introduction.md, 06_runtime_pipeline.md, 07_renderer_questpdf.md, 08_program_flow_example.md, 09_extensibility.md architektura knihovny, pipeline, renderer, rozšiřitelnost

Seznam změn všech verzí je v souboru CHANGELOG.md v kořeni balíčku; změny konkrétní verze ukazuje NuGet v release notes balíčku.

Licence

MIT, © Advisor Software s.r.o.

Zdrojový kód, ukázkové šablony a změny verzí: repozitář projektu.

No packages depend on NETReporter.

- Templates: letterhead and appended PDFs for the PDF output. New `page.letterhead` (`{ "src": ..., "pages": "All" | "First" | "FirstAndRest" }`) puts a page of a PDF under the content of the pages: `All` (default) puts the first page of the letterhead under every page, `First` only under the first page, `FirstAndRest` puts the first page under the first page of the document and the second page of the letterhead under the others (a continuation sheet in one PDF from a designer). New `meta.document.append` (a string or an array of strings) appends PDFs after the document (terms and conditions), `meta.document.embedData` (bool) embeds the input data. A source is a file path, an http(s) URL or a data URI with bindings, most often `{{$.Assets.Name}}` (limits: 25 MB, 30 s, the `%PDF-` header). The letterhead is placed under all pages of the document and never under the appended pages (explicit target pages `1-N`; the number of pages comes from the page guard of the render). A smaller letterhead is centered, a larger one is scaled down. **An unavailable source never stops the render**: an empty binding result, an unreadable file, a non-PDF content, a too large file or a PDF that qpdf cannot read gives the warning `NRP036` with the JSON path of the source and the PDF is produced without it (a one-page letterhead with `FirstAndRest` is used for all pages with the same warning), so check the warnings before an invoice is sent. Validation: `page.letterhead` is checked against the new `letterheadProperties` section of the schema (a missing `src` `NRP011`, types `NRP014`, `pages` `NRP015`, unknown keys `NRP013`; an empty static `src` `NRP011`), a `meta.document.append` / `embedData` of a wrong type is the warning `NRP024`. The post-processing is done by QuestPDF `DocumentOperation` (the bundled qpdf) in one job in a private temporary directory that is always deleted; a template without these features gives byte-identical PDFs as before. New AST: `LetterheadNode`, `LetterheadPages`, `PageNode.Letterhead`, `DocumentMetaNode.Append` / `EmbedData`; `TemplateSchema.LetterheadProperties`. The pipeline has the new stage `PostProcess` (errors `NRP-PIPE-POSTPROCESS-FAILED` and `-CANCELED`; `OnRenderCompleted` gets the length of the final PDF). - `RenderOptions` for the PDF output: `PdfConformance` (`PdfA2B`, `PdfA3B`; XMP `pdfaid`; PDF/A-2b does not allow attachments), `Attachments` (`PdfAttachment`: `FileName`, `Content`, `MimeType`, `Description`, `Relationship`; an ISDOC to an invoice with PDF/A-3b; the MIME type is always written, `.isdoc` is XML), `EmbedData` (`null` = by the template, `true` always, `false` never; embeds the raw input JSON as `data.json` with the relationship `Source`) and `Encryption` (`PdfEncryption`: AES-256, `UserPassword`, `OwnerPassword`, the rights `AllowPrinting`, `AllowContentExtraction`, `AllowFillingForms`, `AllowModification`, `AllowAnnotation`, `AllowAssembly`, `EncryptMetadata`; a class so that `ToString()` never prints the passwords; the passwords are not logged and cannot be set from a template). `AllowLocalPdfSources` (default `true`) forbids PDF sources from files (a path is then `NRP036` without touching the disk): a server that renders foreign templates should turn it off; the MCP server over HTTP does. Invalid combinations are an `ArgumentException` (`ParamName == "options"`) before the pipeline starts: PDF options with another format than PDF (also with `RenderSvgPagesAsync`), encryption with PDF/A, attachments or `EmbedData = true` with PDF/A-2b, an empty, duplicate (case-insensitive) or path-like attachment name, the name `data.json` while the data are embedded, an empty owner password or equal passwords. The `{{` check of `RenderOptions.Watermark` moved into the same validator (same exception). - New diagnostics (runtime warnings, `BindingDiagnostic.Code`): `NRP035` a PDF-only feature of the template is ignored for another format (not for JSON), `NRP036` a letterhead or appended PDF cannot be used, `NRP037` `embedData` is skipped for PDF/A-2b, `NRP038` PDF/A with a foreign PDF (CMYK, fonts that are not embedded). `BindingDiagnostic` has the new optional `Code`; it is passed through the observer, `OnBindingDiagnostics`, the stored diagnostics of ReportServer render jobs (`RenderBindingDiagnosticResponse.Code`, additive) and the MCP `bindingDiagnostics[].code`. - New output format `RenderFormat.Webp`: a WebP image with the same layout as the PDF (lossy, white background). `ReportRenderer.RenderWebpPagesAsync` returns every page as an image, `RenderAsync` with `Format = Webp` returns the first page. `RenderOptions.ImageDpi` (1-600, default 288, outside the range `ArgumentOutOfRangeException`) and `RenderOptions.ImageQuality` (`Best` ... `VeryLow`, default `High`). All pages are held in memory at once (`MaxPages` applies). Template functions of the PDF output are ignored with `NRP035`. - MCP server: `render_report` and `render_report_from_files` accept `format=webp`: one page as an image the agent can see (an `ImageContentBlock` with `image/webp`), the new parameters `page` (1-based, default 1; a page beyond the last one is `InvalidParams` that names the page count) and `dpi` (36-300, default 130: an A4 page is about 1075 x 1520 px, below the 1568 px at which Claude scales an image). `pageCount` is the total number of pages, the new `RenderResult.Page` the returned page. A WebP over the inline limit goes as a link or a file and the summary suggests a lower `dpi`; the resource `netreporter://renders/{id}` is read as a blob for every non-text MIME type. The server instructions and the prompt suggest checking the layout with `format=webp` before the final PDF. **PDF sources from files are not read over HTTP** (only data URIs and http(s) URLs, a path is `NRP036`), like the local file tools. - Fix: the page count (`pageCount` of the MCP tools and the ReportServer render metrics) of a PDF that went through the post-processing was missing: qpdf writes the keys of the page tree dictionary in alphabetical order (`/Count` before `/Type`), which the old pattern did not read. The count is now read from the root page tree dictionary (without `/Parent`) with the keys in any order. - Console sample: the output format WebP (every page to `<template>-<n>.webp`). Template editor: the preview shows the warning `NRP036` for a letterhead or appended PDF that is unavailable, and the JSON editor knows `page.letterhead` and `meta.document.append` / `embedData`. - Deployment: the post-processing calls the native `libqpdf.so` of the QuestPDF package, which needs `libz.so.1` (present in `aspnet:10.0`, check chiseled images with `ldd`); do not install a system `qpdf` into the images. - Library: the data contract of a template. `TemplateDataContractAnalyzer` (`NetReporter.Template.DataContract`, registered by `AddNetReporter`) finds out which fields of the input data a template reads without rendering anything: it walks the parsed template like the runtime does (sections, `repeat`, table rows and summaries, charts, `visibleIf`, watermarks, `meta.document`), follows the aliases of `repeat.as`, `rows.as` and `Chart.as`, knows the table locals (`group`, `groupSums`, `groupSubtotal`, `totalSums`, counts) and the implicit fields of column sums, and reads C# expressions (`{{# ...}}`) by syntax only (best effort, nothing is compiled). The result `TemplateDataContract` is a tree of `DataField` (path such as `$.Invoice.Items[*].Total`, `Kind`, `KindSource` usage > format > name > default, `Hint` for group keys, image sources, URLs, codes and style names, `Format`, `UsedAt`, compared values) with `Issues` (an alias that is not defined at that place, fields that differ only in letter case) and `TemplateDiagnostics`; `ToJsonSchema()` writes a JSON Schema 2020-12 without `required` (a missing field renders as an empty value). `TemplateSampleDataGenerator` builds deterministic sample data from a contract (`SampleDataOptions`: `ArrayItemCount` 3, `NestedArrayItemCount` 2, `Culture`, `ReferenceDate`, `Indented`): dates from the reference date, amounts, a Czech company id with a valid check digit, a valid IBAN, addresses, an SVG image as a data URI, group keys that follow each other, values the template compares with in turn; Czech or English by `meta.culture`. Purely additive. - MCP server: the tools `get_data_contract` (the fields a template reads with kinds, where they are used, JSON Schema and the problems found) and `generate_sample_data` (a data document for a template; `arrayItemCount`, `referenceDate`), a step in the prompt `design_report_template` and in the server instructions. A data field that is missing is not a binding problem, so the contract is the way to check data against what the template expects. - Template editor: the preview toolbar has **Vygenerovat data** (generates sample data from the fields the template reads and renders the preview over them as custom data; asks before overwriting custom data) and **Datový kontrakt** (a dialog with the tree of the fields, their kinds and where they are used, the problems found and the buttons to copy the JSON Schema and to download `<template>.schema.json` and `<template>.sample-data.json`). - Template editor: templates can be stored in PostgreSQL instead of the folder on the server. Set `EDITOR_CONNECTION_STRING` (or `ConnectionStrings:EditorDb`); the editor creates the schema `editor` (own migration history, so it may share the database with ReportServer) and applies the migrations at start. The connection string of ReportServer is not taken over on purpose. Without the setting nothing changes (the folder `~/Documents/ReportTemplates`). The table `editor."Templates"` has an owner (`OwnerId`) and a visibility (`Private` / `Public`) so that private and public templates of several users can follow once sign-in exists: today the editor is single-user (`LocalEditorUserContext`), the queries already list own and foreign public templates and change only own ones. Saving checks a revision (an optimistic lock): when the template was changed meanwhile (another tab), the editor asks before it overwrites it; the same holds for the folder (the revision is the last write time). The template text is stored exactly as typed (`text`, not `jsonb`). An unreachable database does not stop the editor: every operation reports the error to the user and `/readyz` says `Degraded`. The template names are checked the same way for both stores (no path separators or characters that Windows does not allow in a file name; a name with `../` can no longer leave the folder). - Template editor: themes and "extract colors to the palette" on the Page tab. **Motiv** applies a theme (`themes/*.json`, format `netreporter-theme`) to the whole template as one undo step: the palette is overwritten or completed and the base styles are patched (font, size, weight, colors, line height; the template's own style names and aliases such as `title` for `h1` are patched, `base` is always created, `fontFallbacks` keep the template's own fonts after the theme's). Shipped themes: Firemní modrá, Klasický, Moderní and Úsporný tisk (all other palette entries to gray). **Vytáhnout barvy do palety…** replaces the colors written directly in the template (styles, inline styles of nodes, spans and table cells, borders, shadows, `Line`, watermark, chart series and palettes) with names in `meta.palette`, so that a theme or new corporate colors can recolor the template. The colors are grouped by value and use (the same black as text and as a border gets two names, `text` and `border`), named by role (`text`, `primary`, `accent`, `muted`, `border`, `light`, `chart1`…) or by hue (`red`, `lightBlue`, `red25` for 25 % opacity), the names can be edited, an existing entry with the same value is reused, invalid colors are only reported. A chart color written as hex without `#` (which the chart renderer could not read and drew in SteelBlue) becomes valid, so that chart changes; the result says so. The recommended palette roles are described in the template format chapter 1.2; a palette name must not look like a hex color. - Template editor: **Bloky**, a library of ready-made parts of a template. The toolbar of the tree has the menu Bloky: it inserts a block into the selected container (or after the selected node) as one undo step, saves the selected node as your own block and manages your blocks. A block (`snippets/*.json`, format `netreporter-snippet`) is a node with its children plus the styles it refers to, the palette colors it uses and the assets; the template keeps what it already has (a style with the same content is reused, a missing one is added, a different one stays the template's so that the block takes over its look and a theme works on it) and an information strip reports what was added and what was kept. Six blocks are shipped: Adresní blok dodavatel / odběratel, Hlavička faktury s logem, Rekapitulace DPH, QR platba, Podpisové pole and Patička "Strana X z Y" (asks before it goes into another section than the footer). Your own blocks are stored like the templates: in the database (table `editor."Snippets"` with owner and visibility, migration `AddSnippets`) or in the folder `snippets` next to the templates. A block with a node type the editor does not know is disabled. - Template editor: **Načíst šablonu** is now a gallery of templates with thumbnails. The cards come from three sources: the empty template (A4 with a palette of roles), the samples shipped with the editor (`sampleTemplates`, without the benchmark; `TemplateLibrary:SamplesFolder` and `TemplateLibrary:ExcludedSamples`) and your own templates from the store (folder or database). A card shows the first page rendered as a WebP image (over the sample data of the template, or data generated from its data contract), the title and description from `meta`, the page size and badges (sample data, invalid JSON); the search ignores case and diacritics and a filter switches between all, samples and your own templates. *Načíst i ukázková data* switches the preview to the sample data of the template (or the generated data) right after loading; loading is one undo step. The thumbnails are rendered lazily, at most `TemplateLibrary:ThumbnailParallelism` at a time (default 2, the wait in the queue does not count into `ThumbnailTimeoutSeconds`), once per template even for several browsers, and cached in memory and on disk (`TemplateLibrary:ThumbnailCacheFolder`, a new key whenever the template changes); a failed render shows a placeholder that the browser does not cache. The endpoint `GET /gallery/thumbnail/{source}/{name}` serves only the cards the gallery lists. Saving a template that was not loaded from the store under the same name (a sample, an uploaded file, a new name) over an existing template of that name now asks first. - Samples: fixed the data and templates that did not match each other (the analysis of the data contract found them). `odesilacilist`: the data used `Ol` and other field names than the template reads (`$.ol`, `poradi`, `predmet`, `mnozstvi`, `prototyp`), so the whole document rendered empty. `prohlaseni-mlcenlivost`: the template read `$.doc.qrData` and `$.Doc.Revision`, the data have `qrValue` and `revision`. `invoice`: the items have the explicit `Vat` (the same values as the fallback `Total x VatRate`). The sample `benchmark-extreme` still reads the row alias in the group header and summary, where it is not visible (the header renders as "Skupina "); it is left as it is so that the load of the benchmark does not change. Celý seznam změn: CHANGELOG.md v balíčku a https://dev.azure.com/advsoft/Knihovny/_git/NetReporter?path=/src/NetReporter/CHANGELOG.md

Version Downloads Last updated
3.9.2 3 10/28/2026
3.9.1 1 10/27/2026
3.9.0 6 10/20/2026
3.8.1 6 10/19/2026
3.8.0 4 10/19/2026
3.7.0 4 10/19/2026
3.6.0 6 10/17/2026
3.5.1 62 10/15/2026
3.5.0 4 10/15/2026
3.4.2 5 10/15/2026
3.4.0 4 10/12/2026
3.3.0 5 10/11/2026
3.2.2 5 10/10/2026
3.2.1 4 10/10/2026
3.2.0 4 10/10/2026
3.1.1 4 10/09/2026
3.1.0 5 10/09/2026
3.0.1 8 10/08/2026
3.0.0 5 10/08/2026
2.2.1 5 10/05/2026
2.2.0 4 10/05/2026
2.1.0 9 10/04/2026
2.0.1 8 10/04/2026
2.0.0 5 10/03/2026
1.7.0 6 10/03/2026
1.6.0 5 10/02/2026
1.5.0 12 10/02/2026
1.4.0 6 10/01/2026
1.3.0 4 10/01/2026
1.2.0 5 10/01/2026
1.1.1 6 09/30/2026
1.1.0 8 09/30/2026
1.0.10 4 09/29/2026
1.0.9 4 09/29/2026
1.0.8 16 09/29/2026
1.0.7 115 09/24/2026
1.0.6 9 09/24/2026
1.0.5 7 09/24/2026
1.0.4 11 09/24/2026
1.0.3 8 09/24/2026
1.0.2 13 09/23/2026
1.0.0 7 09/23/2026
0.9.1 13 09/23/2026
0.9.0 87 09/14/2026
0.8.1 25 02/20/2026
0.8.1-preview.1 34 02/13/2026
0.7.1-preview.2 32 02/10/2026
0.7.1-preview.1 33 02/10/2026
0.7.0 30 02/10/2026
0.0.6 28 02/01/2026
0.0.5 19 02/01/2026
0.0.0-alpha.0.17 35 02/01/2026