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

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á nastavit ResourcePolicy = ResourcePolicy.Server (zdroje ze souborů zakázané, stahování jen z veřejných adres, limity velikosti a času).

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.

- **Behavior change** ReportServer (ToDo #198): the storage settings (`ReportServer:Storage:*`, in the environment `REPORTSERVER_STORAGE_*`: `Enabled`, `Provider`, `Bucket`, `Region`, `ServiceUrl`, `AccessKeyId`, `SecretAccessKey`, `SessionToken`, `Prefix`, `RenderRetentionDays`, `RenderRetentionCleanupMinutes`) are read only from the process configuration (environment variables: in the cluster the configmap and the secret) and no longer from the table `ConfigEntries`. Until now the API copied them there at its first start and the stored value won over the configuration, so a changed `REPORTSERVER_STORAGE_*` had no effect and the access keys lay in the database in plain text. The page *Config* of the web UI and the endpoints `GET/PUT /admin/config/{scope}...`, `POST /admin/config/reload` and `POST /admin/config/reseed/storage[/overwrite]` are removed, together with the contracts `ConfigEntryResponse` and `UpdateConfigEntryRequest`. A change of the settings takes a restart of the API (in the cluster a rollout). **Before updating, compare the stored values with the configuration** (`SELECT "Key", "Value" FROM "ConfigEntries" WHERE "Scope" = 'ReportServer:Storage' AND "Key" <> 'SecretAccessKey'`): the stored value was in force until now, so the behavior changes where it differs, and an access key missing from the secret turns the storage off. The table `ConfigEntries` stays because it holds state marks (the seeded bundled content, the legacy import mark); the old storage rows stay in it unused and a later migration deletes them, so a rollback to the previous image keeps working. - **Behavior change** Template editor, ReportServer web UI and account pages (ToDo #183): the user interface is available in English and Czech. The language comes from the culture cookie (the switch `CS | EN` in the user menu and the header of the editor, in the header of the web and on the account pages; `GET culture/set`, cookie on the path `/`, so one choice covers the editor, the web and the account pages), then from the browser's `Accept-Language`; Czech when neither says English. A browser that asks for English now gets the English interface where it always got Czech. All texts are `.resx` resources read through `IStringLocalizer` (Czech is the neutral resource, English the `.en.resx` next to it; the new test `LocalizationResourceTests` keeps the keys and `{0}` placeholders equal in both languages and fails on Czech text left in `.razor`, `.cshtml` and `.js`). Scripts get their texts from `window.nrI18n` (`JsStrings`, `SharedJsStrings`), the Syncfusion components get Czech texts for the controls the editor uses (`SfResources`, the uploader, dialogs, the color picker, the speech input), and the emoji picker has an English catalog (`Data/emoji-picker-en.json`, `tools/IconPacks emoji-picker` writes both). The number and date formats of the interface follow the language (Czech: `12,5`, English: `12.5`), the template JSON stays invariant. Not translated: the ReportServer API (error messages stay Czech), the diagnostics of the library (`NRP…`), prompts and replies of the AI assistant, sample templates, blocks and themes, the default template of the editor and the texts a template contains. `NetReporterUserAdministration`, `SmtpEmailSender`, `AssistantService` and a few more services take an `IStringLocalizer` (optional where tests build them by hand); `CzechIdentityErrorDescriber` is now `LocalizedIdentityErrorDescriber`; `Claim`, `Description` and `Points` of `NetReporterIdentityHostOptions` are keys of the account resource (a text without a key is shown as written). The ReportServer API answers in Czech whatever the language of the machine it runs on. - Template editor (ToDo #197) fix: the JSON editor could show the template without the node `id`s the editor had added, and selecting then went wrong (a click into the JSON selected nothing or another node, a node selected in the tree or in the preview was marked at the wrong place of the text). The editor adds an `id` to every node without one when it analyzes a loaded template, but Monaco is created asynchronously: it took its first text from the hidden `<textarea>` when it finished loading, while the component recorded the value current at that moment, so a value that arrived in between (the first analysis of the start template, typically when a draft was offered) never reached Monaco. `JsonEditor` (shared by the editor and the ReportServer UI) now passes the initial text with the create call and sends any newer value right after creating Monaco; text typed into the fallback field while Monaco loads is kept. - Template editor (ToDo #197): a paste into the JSON editor of the template (Ctrl+V) is written at once when the result is valid JSON, as when switching the tab: the analysis adds an `id` to the pasted nodes and rebuilds the tree, so a pasted template or node can be selected right away in the JSON, the tree and the preview. Before, the pasted text waited for a tab or format switch, and a template without `id`s could not be selected until then. A paste that leaves the JSON invalid (in the middle of an edit) still waits. `JsonEditor` has the new callback `OnPaste`. - **Behavior change** ReportServer (ToDo #190, migration `RemoveUserAssistantSettings`): the endpoints `GET/PUT /me/assistant-settings` and `GET/PUT/DELETE /me/assistant-keys` and their tables `UserAssistantSettings` and `UserAssistantKeys` are removed. They held the provider choice and the encrypted keys of users, which nothing has read since 3.4.0 (the owner manages the models); the migration deletes the stored keys. An editor older than 3.4.0 no longer works against this API. - Template editor (ToDo #190): an assistant run checks the model configuration with ReportServer before it starts (a conditional `GET`, `304` when nothing changed; runs started within 2 s share one request, after a failure the known configuration is used for 10 s). A model change the owner saved in another editor pod thus applies to the next run at once instead of after up to 30 s; the operations offered in the panel still refresh every 30 s. - ReportServer API and UI: command-line arguments (`--Key=value`) are part of the configuration again and take precedence over the environment variables. `UseEnvironmentVariablesOnly` dropped them together with the other default sources; it now keeps them after the environment variables, as the editor and the MCP server already did. The containers start without arguments, so a deployment does not change. - Tests (ToDo #188): the host tests (ReportServer API, template editor, MCP server) run in parallel, the full suite takes about 3 minutes instead of about 10. The test hosts pass their configuration as command-line arguments (`UseSetting` of `WebApplicationFactory`) instead of setting process environment variables under one lock, the MCP tests are no longer a serial collection, at most half the cores (2 to 8, `REPORTSERVER_TESTS_MAX_API_HOSTS`) API hosts run at once, and each API host copies its database from a migrated template (`CREATE DATABASE … TEMPLATE`) instead of running the migrations. - **Behavior change** HTML output (ToDo #195, #196): the HTML looks like the PDF, which is the reference. - Borders: `border*.width` is in points like in the PDF (it was millimetres, so `"width": 1` was about 2.8 times thicker). A border lies on the middle of the edge and takes no space, as in the PDF. A solid side is drawn as a `box-shadow` (half outside, half inside the edge) because browsers round the width of a CSS border down to whole pixels. A dashed, dotted or double side stays a CSS border, and the space it takes is subtracted from the margin and the padding of that side. - Alignment: `align` on a `Stack`, `Box` or `Absolute` no longer writes `text-align`, which the browser passed down to every descendant while the PDF does not. The children go into an inner column that shrinks to the widest child and moves left, centre or right, as in the PDF. `alignVertical` works on containers and text nodes (a flex column) instead of the `vertical-align` that had no effect on blocks. `align` (and `imageAlign`) moves images, codes and icons. - Tables: class `nr-table` (it was missing, so the base CSS never applied and cells had the browser spacing). Column weights become percentage widths with `table-layout: fixed`. The grey default grid `#ddd`, the bold centred header and the middle vertical alignment of the browser are gone. The cell padding (`cellPadding*`) is resolved as in the PDF and `rowMinHeightMm` is the cell `height` (`min-height` did nothing on a cell). - Text: the default font is Lato with line height 1.2, the PDF default. A font that the PDF renderer does not have (neither registered nor installed on the server) is left out of `font-family`, so the browser uses Lato like the PDF instead of a font that only the reader has. `lineHeight` is passed to the descendants through `--nr-lh`. `bold` is weight 600 (the PDF draws the SemiBold face), and `fontWeight` names map to numbers (`SemiBold` was invalid CSS). Spaces and line breaks in a text are kept (`white-space: pre-wrap`). Paragraphs and the body have no browser margins, links have no browser colour and underline, and a binding shown instead of its value is `#cc0000` like in the PDF. - Paragraph spans carry only text properties. The box of the paragraph (padding, border) was repeated on every span; margin and padding in a span style no longer reach the HTML either (`NRP030` and its quick fix say so). - A solid `Line` is a block as thick as the line (it took the height of a text line), a dashed one a border of a zero-height block. A `Spacer` without a height is empty. - Barcodes and 2D codes use the same SVG as the PDF (it was built in pixels, which gave another aspect ratio and quiet zone). A code with a height and no width keeps its size and stays left. - The page: on screen the body has the width of the printable area, so the text wraps like in the PDF whatever the window width. The X-ray mode of the debug HTML set this width only while switched on. In print the `@page` margins are no longer doubled by the inline margin of the body. An empty element is written with an end tag (`<div></div>`), a self-closing `<div />` swallowed the elements after it. - Debug HTML (`DebugMode`, the editor preview) shows the page as a sheet of paper centred on a grey area, at least one page high. Page layers and watermarks lie on the sheet of the first page. X-ray only adds outlines and the printable area and no longer changes the layout. - PDF: in a table cell with `rowMinHeightMm`, `cellVerticalAlign` and `alignVertical` now move the content within the whole minimum height (the minimum height was applied inside the alignment, so the text stayed at the top). - Template editor: the HTML preview gets the Lato font of the PDF from `GET preview-fonts/{face}.ttf` (the faces of the QuestPDF font archive next to the application; anonymous, CORS `*` for the sandboxed preview frame, cached for a week), so it wraps the text like the PDF even when the browser has no Lato. 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