NETReporter 1.7.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.

Rozhraní (IReportRenderer, ITemplateTools) a sdílené typy (RenderOptions, diagnostiky…) jsou v balíčku NETReporter.Abstractions, který se nainstaluje sám. Aplikace, která report jen zadává a vykreslí ho ReportServer, použije místo této knihovny tenký klient NETReporter.Client (bez QuestPDF a dalších těžkých závislostí; přihlášení API tokenem): viz docs/user/pouziti-knihovny.md a průvodce klientem v repozitáři.

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, Emoji (barevné emoji Twemoji jako vektor), Icon (ikony FontAwesome Free, s licencí i Pro, jako vektor), 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
RenderFormat.Png obrázek stránky bezeztrátově (bílé neprůhledné pozadí); RenderAsync vrací první stránku, všechny RenderPngPagesAsync
RenderFormat.Jpeg obrázek stránky ztrátově, bez průhlednosti; RenderAsync vrací první stránku, všechny RenderJpegPagesAsync

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 });

Stejně funguje RenderPngPagesAsync (bezeztrátově, ImageQuality se ignoruje) a RenderJpegPagesAsync (ztrátově, kvalita ImageQuality).

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 = [ IsdocWriter.CreateAttachment(isdocInvoice) ],   // ISDOC z dat faktury (NetReporter.Isdoc), nebo vlastní new PdfAttachment { ... }
    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. IsdocWriter složí ISDOC 6.0.2 z IsdocInvoice (součty a rekapitulaci DPH dopočítá, UUID je stabilní, výstup je ověřený proti oficiálnímu XSD); 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/18-emoji-a-ikony.md emoji a ikony FontAwesome – vestavěné sady, Pro a stahování verzí, licence
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.

- HTML and JSON output: the sections `pageBackground`, `pageOverlay` and `firstPageFooter` are rendered in every format now. HTML draws them as page layers (`div.nr-page-background` and `div.nr-page-overlay` are `position:fixed` over the page, the footer sits at the bottom of the first page inside the page margins; the print CSS defines the variables `--nr-page-h` and `--nr-margin-*`), JSON writes `sections.pageBackground`, `sections.pageOverlay` and `sections.firstPageFooter` (and the entries of `documentTree`) only for a template that has them. The bindings in these sections are evaluated, so `validate_data` and the render diagnostics of `format=json` and `format=html` report their problems too. - Templates: the local variable `loop` in the content of `repeat`, in the rows of a table and in the rows of a chart: `loop.index` (from 1), `index0`, `first`, `last`, `odd`, `even` and `count` (the count after the filter), so `{{loop.index}}.` numbers items and `rows.styleRef: "{{# loop.odd ? \"zebra\" : \"\" }}"` stripes rows. A nested loop shadows it, an alias called `loop` wins, and the data contract does not treat it as a data field. - Templates: table columns have `visibleIf` (a condition with a binding, evaluated once per table; a hidden column is not rendered, the weights are shared by the rest, column sums can still be used; all columns hidden gives a warning and no table; the debug mode shows all columns). Charts have `filter` and `sortBy` of the rows with the rules of `repeat.filter` and `repeat.sortBy`. Warnings `NRP031` and the error `NRP032` apply to them as to `repeat` and `rows`. - Templates: `PaymentQr` has more keys. SPAYD: `altAccounts` (`ALT-ACC`, alternative accounts with an optional BIC, at most 2), `notificationType` (`Phone` or `Email`) with `notificationAddress` (`NT` and `NTA`, written only together), `retryDays` (`X-PER`, 0-30) and `crc32` (the optional `CRC32` checksum as the last attribute: CRC-32 of the header and the other attributes sorted by key and value, 8 uppercase hex digits). EPC: `purpose` (the four-letter ISO 20022 purpose code on the ninth line). An invalid value is left out with a warning, a static one is `NRP033`; the keys of the other standard are ignored. - Bindings: `Slovy(amount, currency, language)` knows more currencies and a second language. Currencies: `CZK` (default), `EUR`, and new `USD` (dolar), `GBP` (libra šterlinků, penny), `PLN` (zlotý, groš), `CHF` (frank, rap) and `HUF` (forint, fillér) with the Czech noun forms by the last numeral. The new third parameter `language` is `cs` (default, independent of the culture of the template) or `en` (also a culture such as `en-US`): `Slovy(1234.56, "EUR", "en")` is "one thousand two hundred thirty-four euros and fifty-six cents". Any other language or currency is an error reported as a warning with the JSON path, as before. - Data contract: the generated sample data of an object with the numeric fields `Total` and `Vat` (a table row whose summary sums the column `vat`, or a summary object) gives `Vat` as 21 % of `Total` rounded to cents, no longer the same amount as `Total`, so the sample VAT summary differs from the total one. The contract itself is unchanged (`Vat` is listed because the summary reads it first and, like every field of the contract, it is optional). - New output formats `RenderFormat.Png` (lossless) and `RenderFormat.Jpeg` (lossy): the same layout and paging as the PDF, like WebP. `ReportRenderer.RenderPngPagesAsync` and `RenderJpegPagesAsync` return every page as an image, `RenderAsync` with `Format = Png` or `Jpeg` returns the first page. `RenderOptions.ImageDpi` (1-600, default 288) applies to all three image formats and `ImageQuality` to the lossy ones (WebP, JPEG); PNG ignores it. The PNG file has an alpha channel, but the page background is opaque white. PDF-only options and template features are rejected or reported (`NRP035`) the same way as for WebP. - Templates: `PageNumbers` has the new properties `prefix` and `suffix`, a text before and after the page numbers in every mode (`"prefix": "Strana "` with `"showTotal": true, "separator": " z "` writes "Strana 3 z 5"). The texts, like `separator`, may contain a binding (`"{{$.Labels.Page}} "`) and the data contract reads the fields in them. The block "Patička „Strana X z Y“" of the template editor uses it instead of a separate text column. - Templates: `watermark` has the new properties `pages` (`All` by default, or `First` for the first page only) and `behind` (`true` puts the watermark under the content). PDF and SVG compose a first-page watermark only for page 1 and put a `behind` watermark on the page background layer above `pageBackground`; HTML puts a first-page watermark in an absolutely positioned layer of the height of the page (`--nr-page-h`), so the print does not repeat it, and a `behind` one in a layer with `z-index:-1`. The JSON output shows the watermark regardless of both. - New: ISDOC 6.0.2 export of an invoice. `IsdocWriter` (`NetReporter.Isdoc`: `ToXml`, `ToBytes`, `Write`, `FileNameFor` and `CreateAttachment`, which returns a `PdfAttachment` for PDF/A-3b with `Relationship = Alternative`) writes an `IsdocInvoice` (supplier and customer `IsdocParty`, `IsdocInvoiceLine` with prices without VAT and the VAT rate, `IsdocPayment`, all document types of ISDOC, a non-payer, a credit note with negative quantities) as ISDOC. The totals, the VAT recapitulation by rate and the amount to pay are calculated bottom-up by `IsdocInvoice.CalculateTotals()` (the explicit `BaseAmount`, `VatAmount` and `VatRecap` of a printed invoice take precedence, `PaidDeposits` and `RoundPayable` are supported); the UUID defaults to a stable one derived from the supplier company id and the number (a repeated import does not duplicate the document); the account number and the bank code come from a Czech IBAN, the house number from the end of the street. An invalid document is an `ArgumentException`. The output is validated against the official `isdoc-invoice-6.0.2.xsd` in the tests. Purely additive, the renderer does not use it. - Fix: HTML output: the base CSS (and the print page CSS) was written to `<style>` as escaped text, so the child selector `.nr-keep-together>*` was written as `.nr-keep-together&gt;*` and did not apply in the browser. The CSS is now written unescaped (CDATA that is removed after serialization, as the debug CSS already was). - Templates: the new content nodes `Emoji` and `Icon`, drawn as **vectors** so that the result is the same in PDF, SVG, WebP and HTML and does not depend on the fonts of the machine (a Docker image has only Lato; text with `fontFallbacks` renders an emoji as an empty space there). `Emoji` (`emoji`, `sizeMm`) draws a color emoji from the bundled Twemoji 15.0 set (3720 emoji, found by code points; a variation selector does not matter; skin tones, flags and ZWJ sequences work). `Icon` (`name`, `variant`, `version`, `sizeMm`) draws a Font Awesome icon filled with `style.color` (black without it; a `#AARRGGBB` color keeps its transparency as `fill-opacity`): `house`, `fa-house`, an alias such as `home` or copied classes such as `fa-solid fa-house` all work, the variant defaults to `solid`, `regular` or `brands` by what the icon has, `light`, `thin`, `duotone` and `sharp-*` come with Font Awesome Pro, `version` picks `7`, `6` or an exact version. The size defaults to 1.2 times the font size (as for `Checkbox`), an icon keeps its aspect ratio. Font Awesome Free 7.3.1 (1992 icons) is bundled, about 2 MB of the package together with Twemoji; licenses (CC BY 4.0, SIL OFL, MIT) are in the new `THIRD-PARTY-NOTICES.md` of the package. Diagnostics: `NRP039` an emoji is not in the set (a static value is checked by the validation, a value from the data at render time), `NRP040` an icon is in no available pack (a typo, or a Pro icon without the Pro pack), `NRP041` the requested variant or version is not available and the nearest one was used (`light` becomes `regular`, `duotone` becomes `solid`); an unknown emoji or icon draws an empty space of the node size so that the layout does not change. Icons are not checked statically because a Pro icon is known only to the host. - Library: `RenderOptions.Icons` (`IIconCatalog`, default `IconCatalog.Default` with Font Awesome Free and Twemoji) and the new `NetReporter.Icons` namespace: `IconCatalog` (`FindIcon`, `TryGetEmojiSvg`, `Create(cacheDirectory)` and `Reload()` for packs downloaded to a cache directory), `IconPack`, `EmojiPack`, `IconPackBuilder` and `FontAwesomeInstaller`. The installer downloads Font Awesome from npm into the cache: Free from `registry.npmjs.org`, **Pro with the user's license key from `npm.fontawesome.com`**, by an exact version or by a major version (`7` is the highest stable 7.x; Font Awesome 6 and 7 are supported). The key goes only into the `Authorization` header of `npm.fontawesome.com`, never into a URL, a log, an exception message or a file, and a template cannot set it. Only https and the two npm hosts are allowed, the URL of the package must be on the same host, redirects are followed by hand and only to an allowed host (the Pro registry answers a tarball request with a `307` to a pre-signed `dl.fontawesome.com` URL: that one host is allowed as a redirect target only and the key is never sent to it; error messages never contain a path or query because the signature is in it), the download has a size (200 MB) and time (5 minutes) limit, only the metadata are read from the package and the result is written atomically. The metadata of Font Awesome Pro 7 are 115 MB, so `icon-families.json` is read as a stream icon by icon (`Utf8JsonReader`), not as one document; a Pro 7.3.1 pack (4349 icons in 17 variants) has 6.5 MB on disk and about 100 MB in memory after loading. The secondary layer of a duotone icon is recognized by the `fa-secondary` class (Font Awesome 6) and by `opacity=".4"` on the path (Font Awesome 7 Pro). Pro files must not be redistributed, so the cache stays outside the repository and the Docker images. `IRenderSink` has the new default method `Symbol(style, ResolvedSymbol, elementId)` (the default draws the SVG as an `Image`), so custom sinks keep working; `HtmlRenderSink` and `JsonRenderSink` override it. The packs are generated by `tools/IconPacks`. Docs: `02_template_format.md` sections 7.13 and 7.14 and the new chapter `18-emoji-a-ikony.md`. - Template editor: **emoji and icons**. The tree toolbar has the buttons Emoji and Icon and the inspector of an `Emoji` or `Icon` node has **Změnit emoji… / Změnit ikonu…**; they open a picker (`SymbolPickerDialog`): emoji by group, with skin tones and a search in Czech and English without diacritics (Czech names and keywords from Unicode CLDR 48, order and groups from emojibase-data 17, `Data/emoji-picker.json`, generated by `tools/IconPacks emoji-picker`), Font Awesome icons by name, alias, label and search terms with a filter of the variant and of the version. A double click or **Vložit** inserts the node as one undo step, **Použít** changes the selected node. The variant is written to the template only when it differs from the automatic one, so the JSON stays short. Previews come from `GET symbols/emoji/{key}.svg` and `GET symbols/icon/{variant}/{name}.svg` (one SVG each, strict input checks, `nosniff` and a CSP; Pro previews are `Cache-Control: private`, and the editor has no sign-in yet, so anyone who reaches it sees them). The **FontAwesome** button of the picker shows the built-in Free version, the downloaded packs and whether the license key is set (never its value), and has the buttons **Stáhnout Pro 7**, **Stáhnout Pro 6** (disabled without a key) and **Stáhnout Free 6**; the key is never typed into the editor: it is read from the environment variable `FONTAWESOME_PRO_TOKEN` (or `FONTAWESOME_NPM_AUTH_TOKEN`, or `FA_TOKEN`), `NETREPORTER_ICONS_DIR` sets the cache directory (default `/tmp/netreporter-icons` in a container) and `FONTAWESOME_VERSIONS=7` installs the missing Pro versions at start. The preview and the gallery thumbnails use the same catalog, so Pro icons render there too. Sample: `ikony-a-emoji` (with sample data). Deployment: the optional key `FONTAWESOME_PRO_TOKEN` of the secret `netreporter-secrets` (the 5th column `optional` of `04-secret-map.txt`: `deploy.sh` skips an empty value instead of failing). - Template editor and ReportServer UI: new look. Compact, industrial design (13 px base font, dense tables and property grids, 4 px grid, indigo accent, Inter) in the spirit of the Syncfusion Essential UI Kit, with a light and a dark mode (the choice is remembered; Monaco switches too). Template editor: a new shell with an app bar (**Načíst šablonu**, **Uložit šablonu**, **Reset** and the theme switch moved there from the tabs), the structure tree in a side panel, the tabs fill the window height (no more `calc(100vh - ...)`), the Page tab is a set of cards, and the Syncfusion theme is `tailwind3` (was `bootstrap5`). ReportServer UI: a sidebar with the sections, a header with the API / DB / S3 status badges and a split sign-in page; the pages use status badges, alerts and dense tables instead of inline styles and emoji. Both applications are meant for a desktop screen in landscape. The shared stylesheet `wwwroot/css/nr-ui.css` and `wwwroot/js/nr-theme.js` exist in two identical copies (a test checks they match). - New package **`NETReporter.Abstractions`**: the shared contract of the library and of the new client. It holds the interfaces `IReportRenderer` (all public overloads of `RenderAsync` and `Render*PagesAsync`, implemented by `ReportRenderer`) and `ITemplateTools` (`ValidateTemplateAsync`, `ValidateDataAsync`, `GetDataContractAsync`, `GenerateSampleDataAsync`; the new class `TemplateTools` implements it locally: validation by the parser, data validation by a render to JSON that never calls the application's observer and never touches the disk, data contract and sample data) with the result types `TemplateValidationResult`, `TemplateDataValidationResult` and `TemplateSampleDataResult`, and the types the library moved there without changing their namespaces: `RenderOptions` (and `RenderFormat`, `ImageQuality`, `BindingDiagnosticsCallback`), `PdfConformance`, `PdfAttachment`, `PdfEncryption`, `RenderPageLimitExceededException`, `RenderOptionsValidator` (now public), `Diagnostic`, `DiagnosticSeverity`, `DiagnosticCodes`, `BindingDiagnostic`, `BindingDiagSeverity`, `IReportPipelineObserver` with its contexts, `ParserValidationMode`, the data contract types (`TemplateDataContract`, `DataField`, `SampleDataOptions`…) and `IIconCatalog` with the icon data records. `NETReporter` references the package (same version) and forwards every moved type (`TypeForwardedTo`), so source and binary compatibility is kept; the only visible change is that `IconGlyph.ToSvg(color)` is now an extension method of `NetReporter.Icons.IconGlyphExtensions` (calls look the same). `AddNetReporter()` also registers `IReportRenderer` and `ITemplateTools` (`TryAdd`, so an earlier registration, for example the client, wins). The Abstractions package has no dependency on QuestPDF, Roslyn, OxyPlot or ZXing. - New option `RenderOptions.AllowLocalImageSources` (default `true`): with `false` the renderer never reads an image or an SVG from a file (`Image.src`, watermark image, `{{$.Assets.*}}` with a path), draws the placeholder and, in the JSON output, reports the source as `unavailable` with the reason `local-sources-disabled` whether the file exists or not. Data URIs and http(s) addresses are not affected. It joins `AllowLocalPdfSources` for servers that render foreign templates. - New package **`NETReporter.Client`**: a thin client of ReportServer with the same interfaces. `ReportServerRenderer` (`IReportRenderer`) renders on the server through `POST render/ad-hoc` (and `render/ad-hoc/pages`) and `ReportServerTemplateTools` (`ITemplateTools`) calls `/template-tools`; both authenticate with an **API token** (`Authorization: Bearer nrp_…`). All formats and all `RenderOptions` work (PDF/A, attachments, encryption, watermark, resolution…), `OnBindingDiagnostics` and the pipeline observer are supported (the diagnostics are read from the render job; the observer gets simplified events, the parse stage happens on the server). Registration: `services.AddNetReporterClient(options => …)` or `AddNetReporterClient(IConfiguration)` (section `NetReporterClient`: `BaseAddress`, `ApiToken`, `Timeout`) replaces the local `IReportRenderer` and `ITemplateTools` whatever the order of `AddNetReporter()`, validates the settings at start and returns the `IHttpClientBuilder`; without DI use `NetReporterClient.CreateHttpClient(options)`. Invalid options are rejected locally with `ArgumentException` before anything is sent; server errors map to the exceptions of the local renderer (`RenderPageLimitExceededException`, `OperationCanceledException`, `ArgumentException` for options) and to the new `ReportServerException : InvalidOperationException` (`Code`, `StatusCode`, `Diagnostics`, `RenderJobId`; for example `TEMPLATE_VALIDATION_FAILED`, `AUTH_UNAUTHORIZED`, `REPORTSERVER_UNAVAILABLE`, `REPORTSERVER_TIMEOUT`). The remote render differs from the local one in what the server decides: files are never read, the server caps pages, resolution and attachments, `RenderOptions.Icons` is not used and at most 50 diagnostics are returned. Docs: the new chapter `client-usage` (`doc/netreporter-knihovna/user/pouziti-klienta.md`, also in the package). - Packaging: the three packages share the metadata, the release notes taken from this `CHANGELOG.md` and the version (`src/Packaging.props`, MinVer); publish them together, the order is `NETReporter.Abstractions`, `NETReporter`, `NETReporter.Client` (`nugetpub.ps1` does it). - ReportServer API: **render options and all formats**. `POST /render/ad-hoc` takes the optional `options` (`RenderOptionsRequest`: `format` pdf, html, json, svg, webp, png or jpeg, `renderBindingsAsExpressions`, `debugMode`, `maxPages`, `maxBindingDiagnostics`, `validationMode`, `watermark`, `pdfConformance`, `attachments` as base64, `embedData`, `encryption`, `imageDpi`, `imageQuality`) and returns the output in the right content type; the new `POST /render/ad-hoc/pages` returns every page of an SVG, WebP, PNG or JPEG render as JSON (SVG as text, images as base64) with the binding diagnostics inline. The options are validated before a job is created (`400 RENDER_INVALID_OPTIONS`; attachments over the limit `413`); a page limit overrun is `400 RENDER_PAGE_LIMIT_EXCEEDED` with `maxPages`. Only a PDF without encryption is cached and stored (the cache key includes the options that change the output; without options it is the same as before), the other outputs answer `X-Render-Cache: bypass`. New response headers `X-Render-Format`, `X-Render-Page-Count`, `X-Render-Binding-Diagnostics` (the count; the details are in the job detail) and `X-Render-Binding-Diagnostics-Omitted`. `RenderJob` has the new column `Format` (migration `AddRenderJobFormat`, default `pdf`), returned by the job detail and list. New limits `SECURITY_REQUESTLIMITS_MAX_RENDER_PAGES` (10000), `…_MAX_IMAGE_RENDER_PAGES` (100), `…_MAX_IMAGE_DPI` (300) and `…_MAX_RENDER_ATTACHMENT_BYTES` (5 MiB). - ReportServer API: **template tools** `POST /template-tools/validate`, `/validate-data`, `/data-contract` and `/sample-data` (User policy, render limiter, payload limit): they only call `ITemplateTools`, so the result equals the local one; nothing is stored. - ReportServer API and UI: **API tokens**. A user creates tokens on the new UI page **API tokeny** (name and expiry of 30, 90 or 365 days, none or a custom date); the plain text is shown once and only its SHA-256 hash is stored (`ApiTokens`, migration `AddApiTokens`, the prefix `nrp_` and 8 characters identify it in lists). A token has the rights of its owner (no scopes), is checked on every request without caching (a revoked or expired token and a deactivated owner stop working at once) and sees only the owner's templates and jobs. `GET/POST /auth/tokens`, `DELETE /auth/tokens/{id}` and, for administrators, `GET /admin/api-tokens?userId=` and `DELETE /admin/api-tokens/{id}`; at most 20 active tokens per user; the page management works only with a JWT (a token cannot create or revoke tokens, `403`). Deleting a user deletes the tokens, the audit log (category `ApiToken`) never contains the token. The users page links to the tokens of each user; the jobs table shows the output format. - **Behavior changes** of ReportServer API: (1) the server never reads files for a render: `AllowLocalPdfSources` and `AllowLocalImageSources` are forced to `false` for ad-hoc and stored templates alike, so a letterhead or an image given as a file path now renders without it (the warning `NRP036` for PDF sources, the placeholder for images) and only data URIs and http(s) addresses work; (2) the render rate limit is counted per user (authenticated principal, fallback to the client address) instead of one limit for everybody, and the rate limiter runs after authentication; (3) the authentication scheme of the API is a policy scheme that sends `Authorization: Bearer nrp_…` to the API token handler and everything else to JWT (the 401 and 403 bodies are unchanged); (4) a render cancelled by the client is recorded as `Failed` with `RENDER_CANCELLED` instead of staying in `Running` (ToDo #64). - ReportServer UI: error messages of the API are shown as text (the camelCase body of the API was not read, so the raw JSON appeared). 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