diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 82bdedf..a532b27 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,6 +13,8 @@ permissions: contents: write pull-requests: write security-events: write + pages: write + id-token: write jobs: build: @@ -289,3 +291,49 @@ jobs: --notes "Pre-release from \`${{ github.head_ref }}\` branch." \ --prerelease \ ./artifacts/*.nupkg + + docs: + needs: release + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/master' && github.event_name == 'push' + + steps: + - uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: | + 8.0.x + 9.0.x + 10.0.x + + - name: Install DocFX + run: dotnet tool install -g docfx + + - name: Restore + run: dotnet restore + + - name: Build site + run: docfx docs/docfx.json + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: docs/_site + + docs-deploy: + needs: docs + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/master' && github.event_name == 'push' + concurrency: + group: pages + cancel-in-progress: false + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 0372e5e..114a306 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,7 @@ obj /_NCrunch*/*.cache /*.ncrunchsolution.user /.claude/settings.local.json + +# DocFX generated output +/docs/_site/ +/docs/api/ diff --git a/README.md b/README.md index 6f95b9e..16b6509 100644 --- a/README.md +++ b/README.md @@ -4,3 +4,9 @@ [![NuGet](https://img.shields.io/nuget/v/Tharga.Reporter)](https://www.nuget.org/packages/Tharga.Reporter) ![Nuget](https://img.shields.io/nuget/dt/Tharga.Reporter) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) + +**Docs:** [reporter.tharga.net](https://reporter.tharga.net) — guides and API reference. + +A .NET library for building and rendering PDF documents from declarative templates. Define a `Template` of `Section`s with text, images, barcodes, lines, rectangles, and tables, feed it data, and render to a PDF byte array. + +Supports .NET 8, .NET 9, and .NET 10. Windows-only at runtime. diff --git a/Tharga.Reporter/Tharga.Reporter.csproj b/Tharga.Reporter/Tharga.Reporter.csproj index f1a14d4..d67b2f1 100644 --- a/Tharga.Reporter/Tharga.Reporter.csproj +++ b/Tharga.Reporter/Tharga.Reporter.csproj @@ -7,7 +7,7 @@ Thargelion AB Tharga Reporter Tharga Reporter generates PDF and prints pdf-documents and send then m diretly to the printer. - http://thargelion.se/wp-content/uploads/2019/11/Thargelion-White-Icon-150.png + https://thargelion.net/assets/component-reporter.png True https://github.com/Tharga/Reporter README.md diff --git a/docs/CNAME b/docs/CNAME new file mode 100644 index 0000000..78f1b41 --- /dev/null +++ b/docs/CNAME @@ -0,0 +1 @@ +reporter.tharga.net diff --git a/docs/articles/data-and-rendering.md b/docs/articles/data-and-rendering.md new file mode 100644 index 0000000..f443f8c --- /dev/null +++ b/docs/articles/data-and-rendering.md @@ -0,0 +1,176 @@ +# Data and rendering + +A template is just the layout. The actual values — invoice number, customer name, line items — come from a `DocumentData` instance passed to the `Renderer`. This page covers the data model, the `Renderer`, and the available page formats. + +## DocumentData + +Build a `DocumentData` and add key/value pairs: + +```csharp +var data = new DocumentData(); +data.Add("InvoiceNumber", "INV-00042"); +data.Add("CustomerName", "Bob Lablow"); +``` + +Any text element whose `Value` contains `{Key}` tokens will have those tokens replaced at render time: + +```csharp +section.Pane.ElementList.Add(new Text { Value = "Invoice {InvoiceNumber}" }); +// renders as: "Invoice INV-00042" +``` + +Pass the data to the `Renderer`: + +```csharp +var pdf = new Renderer(template, data).GetPdfBinary(PageSize.A4); +``` + +If a placeholder references an unknown key, it renders empty. Use `Text.HideValue` to suppress an entire element when a particular key is empty. + +## Built-in placeholders + +The renderer fills these automatically — you don't need to add them to `DocumentData`: + +| Token | Resolves to | +|---|---| +| `{Page}` | Current page number | +| `{TotalPages}` | Total pages in the document | +| `{Section}` | Current section name | + +So `"Page {Page} of {TotalPages}"` produces `"Page 1 of 3"`. + +## Table data + +For `Table` elements, add a `DocumentDataTable` with the same name as the table's `DataTableName`: + +```csharp +var lines = new DocumentDataTable("InvoiceLines"); +lines.Rows.Add(new DocumentDataTableData(new Dictionary +{ + { "Item", "Widget" }, { "Qty", "2" }, { "Price", "10.00" } +})); +lines.Rows.Add(new DocumentDataTableData(new Dictionary +{ + { "Item", "Gadget" }, { "Qty", "1" }, { "Price", "25.00" } +})); + +var data = new DocumentData(); +data.Add(lines); +``` + +Each `DocumentDataTableData` becomes a row; the dictionary keys must match the `ColumnName` on each `TableColumn` you defined. + +For visual grouping, use `DocumentDataTableGroup` between data rows — it renders as a styled separator with its `Content` as the label. + +## Renderer + +The `Renderer` takes a `Template` plus the optional knobs: + +```csharp +var renderer = new Renderer( + template, + documentData: data, + includeBackgroundObjects: true, + documentProperties: properties, + debug: false +); + +byte[] pdf = renderer.GetPdfBinary(PageSize.A4); +``` + +- **`documentData`** — placeholder + table data. `null` for templates with no placeholders. +- **`includeBackgroundObjects`** — set `false` to omit elements marked `IsBackground = true`. Useful when the foreground is rendered onto pre-printed paper. +- **`documentProperties`** — fills the PDF metadata: `Author`, `Title`, `Subject`, `Creator`. +- **`debug`** — draws guides for element bounds, reference points, and the page grid. Off for production. + +## DocumentProperties + +PDF metadata seen by the reader: + +```csharp +var props = new DocumentProperties +{ + Title = "Invoice INV-00042", + Author = "Acme Corp", + Subject = "Customer invoice", + Creator = "MyApp 1.2.0" +}; +``` + +## PageFormat + +`PageFormat` accepts the PdfSharp `PageSize` enum directly (implicit conversion): + +```csharp +renderer.GetPdfBinary(PageSize.A4); +renderer.GetPdfBinary(PageSize.Letter); +renderer.GetPdfBinary(PageSize.A5); +``` + +For a custom size, pass millimetres: + +```csharp +renderer.GetPdfBinary(new PageFormat(customWidth: 100, customHeight: 150)); +``` + +There's also a built-in `PlasticCard` (85x54mm): + +```csharp +renderer.GetPdfBinary(PageFormat.PlasticCard); +``` + +## End-to-end example + +A two-page invoice with a header, body table, footer page numbers, and metadata: + +```csharp +using PdfSharp; +using Tharga.Reporter; +using Tharga.Reporter.Entity; +using Tharga.Reporter.Entity.Element; + +var section = new Section +{ + Margin = new UnitRectangle { Left = "1cm", Top = "2cm", Right = "1cm", Bottom = "2cm" } +}; + +section.Header.Height = "2cm"; +section.Header.ElementList.Add(new Text { Value = "Invoice {InvoiceNumber}", FontSize = "18pt" }); + +var table = new Table +{ + Width = "100%", + DataTableName = "Lines", + Columns = + { + new TableColumn { Title = "Item", ColumnName = "Item", Width = "60%" }, + new TableColumn { Title = "Qty", ColumnName = "Qty", Width = "20%" }, + new TableColumn { Title = "Price", ColumnName = "Price", Width = "20%" }, + } +}; +section.Pane.ElementList.Add(table); + +section.Footer.Height = "1cm"; +section.Footer.ElementList.Add(new Text { Value = "Page {Page} of {TotalPages}" }); + +var data = new DocumentData(); +data.Add("InvoiceNumber", "INV-00042"); + +var lines = new DocumentDataTable("Lines"); +lines.Rows.Add(new DocumentDataTableData(new Dictionary +{ + { "Item", "Widget" }, { "Qty", "2" }, { "Price", "10.00" } +})); +data.Add(lines); + +var props = new DocumentProperties +{ + Title = "Invoice INV-00042", + Author = "Acme Corp" +}; + +var pdf = new Renderer(new Template(section), data, documentProperties: props) + .GetPdfBinary(PageSize.A4); + +File.WriteAllBytes("invoice.pdf", pdf); +``` diff --git a/docs/articles/elements.md b/docs/articles/elements.md new file mode 100644 index 0000000..8ef6749 --- /dev/null +++ b/docs/articles/elements.md @@ -0,0 +1,149 @@ +# Elements + +All visual content lives in one of the section's `ElementList`s — `Header.ElementList`, `Pane.ElementList`, or `Footer.ElementList`. Every element supports the standard positioning properties (`Left`, `Top`, `Right`, `Bottom`, `Width`, `Height`) as `UnitValue` strings, plus `IsBackground` to control draw order. + +## Text + +Single-line text. Doesn't wrap — anything that exceeds the bounds is clipped. + +```csharp +section.Pane.ElementList.Add(new Text +{ + Value = "Invoice {InvoiceNumber}", + FontSize = "14pt", + TextAlignment = TextBase.Alignment.Right +}); +``` + +`Value` supports the `{Key}` placeholder syntax — see [Data and rendering](data-and-rendering.md). The `HideValue` property suppresses output when a referenced data key is empty — useful for optional headers. + +## TextBox + +Multi-line text that wraps and can span multiple pages. + +```csharp +section.Pane.ElementList.Add(new TextBox +{ + Value = "Long descriptive copy that needs to wrap across many lines.", + Width = "100%", + Top = "5cm" +}); +``` + +Use a `TextBox` whenever the content length is unbounded. Combine with `ReferencePoint` (below) to stack content that grows. + +## Image + +Embed an image from a URL, file path, or stream. + +```csharp +section.Header.ElementList.Add(new Image +{ + Source = "https://example.com/logo.png", + Left = "0", Top = "0", + Width = "5cm" +}); +``` + +`Source` accepts an HTTP URL, a local file path, or anything `System.Drawing.Image.FromStream`/`FromFile` understands. + +## BarCode + +A Code 39 barcode rendered via Aspose.BarCode. + +```csharp +section.Pane.ElementList.Add(new BarCode +{ + Code = "1234567890", + Left = "8mm", + Right = "8mm", + Top = "18mm", + Bottom = "18mm" +}); +``` + +The barcode fills the box you give it; size the box to the print area the scanner expects. + +## Line + +Straight line. Without explicit position, draws across the full width. + +```csharp +section.Pane.ElementList.Add(new Line +{ + Thickness = "1mm", + Color = Color.MidnightBlue +}); + +section.Pane.ElementList.Add(new Line +{ + Top = "10cm", + Thickness = "0.5mm", + Color = Color.Black +}); +``` + +## Rectangle + +Outlined or filled rectangle. + +```csharp +section.Pane.ElementList.Add(new Rectangle +{ + Left = "25%", Top = "25%", + Width = "50%", Height = "50%", + BorderColor = Color.Green, + BackgroundColor = Color.LightYellow +}); +``` + +Set only `BorderColor` for outline, only `BackgroundColor` for solid fill, or both. + +## Table + +Renders tabular data from a `DocumentDataTable`. Tables grow across pages and support grouping rows. + +```csharp +var table = new Table +{ + Width = "100%", + DataTableName = "InvoiceLines", + Columns = + { + new TableColumn { Title = "Item", ColumnName = "Item", Width = "60%" }, + new TableColumn { Title = "Qty", ColumnName = "Qty", Width = "20%", HeaderAlignment = Table.Alignment.Right, ContentAlignment = Table.Alignment.Right }, + new TableColumn { Title = "Price", ColumnName = "Price", Width = "20%", HeaderAlignment = Table.Alignment.Right, ContentAlignment = Table.Alignment.Right }, + } +}; + +section.Pane.ElementList.Add(table); +``` + +The matching table data goes onto the `DocumentData` — see [Data and rendering](data-and-rendering.md). + +## ReferencePoint + +A logical container that holds child elements, with optional vertical stacking. Use it when you want a group of elements to flow as one block and grow downward. + +```csharp +var rp = new ReferencePoint +{ + Stack = ReferencePoint.StackMethod.Vertical, + Top = "3cm" +}; +rp.ElementList.Add(new Text { Value = "First line" }); +rp.ElementList.Add(new TextBox { Value = "Body paragraph that wraps." }); +rp.ElementList.Add(new Text { Value = "Final line" }); + +section.Pane.ElementList.Add(rp); +``` + +With `Stack = Vertical`, each child is anchored directly below the previous one, so a growing `TextBox` shifts the elements after it instead of overlapping them. + +## Z-order and `IsBackground` + +Elements draw in the order they're added to the `ElementList`. To put something behind the rest, set `IsBackground = true` on the element — backgrounds render in a pre-pass so foreground content overlays them. + +## Next + +- [Data and rendering](data-and-rendering.md) — `DocumentData`, table data, the full `Renderer` surface, and choosing a `PageFormat`. diff --git a/docs/articles/getting-started.md b/docs/articles/getting-started.md new file mode 100644 index 0000000..c4f227c --- /dev/null +++ b/docs/articles/getting-started.md @@ -0,0 +1,81 @@ +# Getting started + +## Install + +For a typical .NET 8 / 9 / 10 application: + +``` +dotnet add package Tharga.Reporter +``` + +Tharga.Reporter targets `net8.0`, `net9.0`, and `net10.0`. At runtime it uses `System.Drawing.Common`, `Aspose.BarCode`, and `PdfSharp.MigraDoc`, so it expects a Windows host. + +## Namespaces + +The core types span three namespaces — import them all at the top of the file that builds your document: + +```csharp +using Tharga.Reporter; // Renderer, PageFormat +using Tharga.Reporter.Entity; // Template, Section, DocumentData, DocumentProperties +using Tharga.Reporter.Entity.Element; // Text, TextBox, Image, BarCode, Line, Rectangle, Table +``` + +- `Tharga.Reporter` — `Renderer` and `PageFormat`, the rendering entry points. +- `Tharga.Reporter.Entity` — the document model: `Template`, `Section`, units, document data, font. +- `Tharga.Reporter.Entity.Element` — the visual elements you stack inside a section's pane, header, or footer. + +## Minimal example + +The shortest useful program is: build a `Section`, add an element, wrap it in a `Template`, hand it to `Renderer.GetPdfBinary`: + +```csharp +using PdfSharp; +using Tharga.Reporter; +using Tharga.Reporter.Entity; +using Tharga.Reporter.Entity.Element; + +var section = new Section(); +section.Pane.ElementList.Add(new Text { Value = "Hello, world!" }); + +var template = new Template(section); +var renderer = new Renderer(template); +var pdfBytes = renderer.GetPdfBinary(PageSize.A4); + +File.WriteAllBytes("hello.pdf", pdfBytes); +``` + +`GetPdfBinary` returns the PDF as a `byte[]`, so you can stream it to a HTTP response, save it to disk, or hand it to a print pipeline. + +## Adding margins, a header, and a footer + +`Section` exposes a `Margin`, a `Header`, a `Pane`, and a `Footer`. Each of the three areas has an `ElementList`: + +```csharp +using System.Drawing; +using PdfSharp; +using Tharga.Reporter; +using Tharga.Reporter.Entity; +using Tharga.Reporter.Entity.Element; + +var section = new Section +{ + Margin = new UnitRectangle { Left = "1cm", Top = "2cm", Right = "1cm", Bottom = "2cm" } +}; +section.Header.Height = "2cm"; +section.Header.ElementList.Add(new Text { Value = "Invoice", FontSize = "18pt" }); + +section.Pane.ElementList.Add(new Text { Value = "Body content here." }); + +section.Footer.Height = "1cm"; +section.Footer.ElementList.Add(new Text { Value = "Page {Page} of {TotalPages}" }); + +var pdf = new Renderer(new Template(section)).GetPdfBinary(PageSize.A4); +``` + +Units accept `cm`, `mm`, `pt`, `in`, and `%` (relative to the parent area). The `{Page}` and `{TotalPages}` tokens are built-in placeholders filled by the renderer. + +## Next steps + +- [Templates](templates.md) — how `Template`, `Section`, `Header`, `Pane`, and `Footer` fit together; multi-section documents. +- [Elements](elements.md) — the catalog of element types and their key properties. +- [Data and rendering](data-and-rendering.md) — `DocumentData` for placeholder substitution and table data, plus the full `Renderer` and `PageFormat` surface. diff --git a/docs/articles/index.md b/docs/articles/index.md new file mode 100644 index 0000000..75346ba --- /dev/null +++ b/docs/articles/index.md @@ -0,0 +1,8 @@ +# Articles + +Guides for building with Tharga.Reporter. + +- **[Getting started](getting-started.md)** — install, namespaces, and a minimal `Template` → `Renderer` → PDF example. +- **[Templates](templates.md)** — `Template`, `Section`, the `Header` / `Pane` / `Footer` areas, multi-section documents, and margins. +- **[Elements](elements.md)** — `Text`, `TextBox`, `Image`, `BarCode`, `Line`, `Rectangle`, `Table`, and `ReferencePoint` for stacking. +- **[Data and rendering](data-and-rendering.md)** — `DocumentData` placeholder substitution, table data, `Renderer`, `PageFormat`, and `DocumentProperties`. diff --git a/docs/articles/templates.md b/docs/articles/templates.md new file mode 100644 index 0000000..4fb4f0d --- /dev/null +++ b/docs/articles/templates.md @@ -0,0 +1,123 @@ +# Templates + +A document is a `Template` containing one or more `Section`s. Each `Section` has three areas — `Header`, `Pane`, `Footer` — plus a `Margin`, optional `DefaultFont`, and an optional `Name`. + +## Template + +```csharp +var section = new Section(); +section.Pane.ElementList.Add(new Text { Value = "Hello" }); + +var template = new Template(section); +``` + +For multi-section documents, build the `Template` with the first section and add more via `SectionList`: + +```csharp +var first = new Section(); +var second = new Section(); + +var template = new Template(first); +template.SectionList.Add(second); +``` + +Each section in the list renders as one logical block — usually one page, but elements like `TextBox` and `Table` can grow across pages. + +## Section areas + +A `Section` is laid out top-to-bottom: + +``` ++--------------------------------+ +| Header | Height controlled by section.Header.Height ++--------------------------------+ +| | +| Pane | Fills the remaining space +| | ++--------------------------------+ +| Footer | Height controlled by section.Footer.Height ++--------------------------------+ +``` + +Each area is an `ElementList` you push visual elements onto: + +```csharp +section.Header.Height = "2cm"; +section.Header.ElementList.Add(new Text { Value = "Statement of Account" }); + +section.Pane.ElementList.Add(new TextBox +{ + Value = "Body text that wraps across lines and pages.", + Width = "100%" +}); + +section.Footer.Height = "1cm"; +section.Footer.ElementList.Add(new Text { Value = "Page {Page} of {TotalPages}" }); +``` + +## Margins + +`Section.Margin` is a `UnitRectangle`. The default is zero on all sides: + +```csharp +section.Margin = new UnitRectangle +{ + Left = "1cm", + Top = "2cm", + Right = "1cm", + Bottom = "2cm", +}; +``` + +Element coordinates (`Left`, `Top`, `Right`, `Bottom`, `Width`, `Height`) are measured **inside** the section's margins. + +## Units + +Positions and sizes are strings parsed by `UnitValue`: + +| Suffix | Meaning | +|---|---| +| `cm` | centimetres | +| `mm` | millimetres | +| `pt` | points (1/72 inch) | +| `in` | inches | +| `%` | percentage of the parent area's width or height | + +So `Left = "50%"` means halfway across the parent area, and `Width = "100%"` fills it. + +## Default font + +Set `Section.DefaultFont` to provide a fallback for all text elements in that section: + +```csharp +section.DefaultFont = new Font +{ + Name = "Arial", + Size = "12pt", + Color = Color.Black +}; +``` + +Individual elements can override by setting their own `Font` property. + +## Naming sections + +Set `Section.Name` if you want to reference a specific section in `DocumentData` table mappings or downstream tooling. The name is preserved in the XML serialization (`Template.ToXml()` / `Template.Load(xml)`). + +## XML round-trip + +Templates can be persisted as XML: + +```csharp +XmlDocument xml = template.ToXml(); +// ... persist xml ... + +Template loaded = Template.Load(xml); +``` + +This is useful when the template is authored by a designer separately from the C# call site, or when templates ship as resources alongside the data that fills them. + +## Next + +- [Elements](elements.md) — the visual primitives you place inside `Header.ElementList`, `Pane.ElementList`, and `Footer.ElementList`. +- [Data and rendering](data-and-rendering.md) — feeding `DocumentData` into the renderer. diff --git a/docs/articles/toc.yml b/docs/articles/toc.yml new file mode 100644 index 0000000..533cb41 --- /dev/null +++ b/docs/articles/toc.yml @@ -0,0 +1,10 @@ +- name: Overview + href: index.md +- name: Getting started + href: getting-started.md +- name: Templates + href: templates.md +- name: Elements + href: elements.md +- name: Data and rendering + href: data-and-rendering.md diff --git a/docs/docfx.json b/docs/docfx.json new file mode 100644 index 0000000..683f2c9 --- /dev/null +++ b/docs/docfx.json @@ -0,0 +1,53 @@ +{ + "metadata": [ + { + "src": [ + { + "src": "..", + "files": [ + "Tharga.Reporter/Tharga.Reporter.csproj" + ] + } + ], + "dest": "api", + "includePrivateMembers": false, + "disableGitFeatures": false, + "disableDefaultFilter": false, + "noRestore": false, + "namespaceLayout": "flattened", + "memberLayout": "samePage", + "EnumSortOrder": "alphabetic", + "allowCompilationErrors": false + } + ], + "build": { + "content": [ + { + "files": ["**/*.{md,yml}"], + "exclude": ["_site/**", "obj/**"] + } + ], + "resource": [ + { + "files": ["CNAME"] + } + ], + "output": "_site", + "globalMetadata": { + "_appName": "Tharga.Reporter", + "_appTitle": "Tharga.Reporter", + "_appLogoPath": "https://thargelion.net/assets/component-reporter.png", + "_appFaviconPath": "https://thargelion.net/assets/component-reporter.png", + "_enableSearch": true, + "_disableContribution": false, + "_gitContribute": { + "repo": "https://github.com/Tharga/Reporter", + "branch": "master" + } + }, + "template": ["default", "modern", "templates/thg"], + "postProcessors": ["ExtractSearchIndex"], + "keepFileLink": false, + "disableGitFeatures": false + } +} diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..5816fea --- /dev/null +++ b/docs/index.md @@ -0,0 +1,53 @@ +--- +_layout: landing +--- + +# Tharga.Reporter + +A .NET library for **building and rendering PDF documents** from declarative templates. Define a `Template` of `Section`s with text, images, barcodes, lines, rectangles, and tables, feed it data, and render to a PDF byte array. + +Useful when you want to generate receipts, invoices, member cards, labels, reports, or any other structured PDF without hand-writing PdfSharp/MigraDoc code. + +## Package + +| Package | Target | What it does | +|---|---|---| +| [Tharga.Reporter](https://www.nuget.org/packages/Tharga.Reporter) | net8.0 / net9.0 / net10.0 | Template model (`Section`, `Pane`, `Header`, `Footer`), elements (`Text`, `TextBox`, `Image`, `BarCode`, `Line`, `Rectangle`, `Table`), and a `Renderer` that produces a PDF. | + +> Tharga.Reporter is Windows-only at runtime — it uses `System.Drawing.Common` and `Aspose.BarCode` under the hood. + +## Quick start + +``` +dotnet add package Tharga.Reporter +``` + +```csharp +using Tharga.Reporter; +using Tharga.Reporter.Entity; +using Tharga.Reporter.Entity.Element; +using PdfSharp; + +var section = new Section(); +section.Pane.ElementList.Add(new Text { Value = "Hello, world!" }); + +var template = new Template(section); +var renderer = new Renderer(template); +var pdfBytes = renderer.GetPdfBinary(PageSize.A4); + +File.WriteAllBytes("hello.pdf", pdfBytes); +``` + +See [Getting started](articles/getting-started.md) for the full setup. + +## What's in the box + +- **Template model** — `Template` wraps one or more `Section`s; each `Section` has a `Header`, `Pane`, `Footer`, and `Margin`. See [Templates](articles/templates.md). +- **Elements** — `Text`, `TextBox`, `Image`, `BarCode`, `Line`, `Rectangle`, `Table`, and `ReferencePoint` for stacking. See [Elements](articles/elements.md). +- **Unit system** — positions and sizes use a string-based unit grammar (`"1cm"`, `"10mm"`, `"50%"`) via `UnitValue` and `UnitRectangle`. Percentages are relative to the parent area. +- **Data substitution** — `DocumentData` lets templates contain `{Placeholder}` tokens that get filled at render time. Tables work the same way for repeating rows. See [Data and rendering](articles/data-and-rendering.md). +- **Page formats** — `PageFormat` wraps PdfSharp's `PageSize` and adds custom millimetre sizes plus a built-in `PlasticCard` (85x54mm). + +## Repo + +[github.com/Tharga/Reporter](https://github.com/Tharga/Reporter) — source, issues, releases. diff --git a/docs/templates/thg/layout/_master.tmpl b/docs/templates/thg/layout/_master.tmpl new file mode 100644 index 0000000..acac4c1 --- /dev/null +++ b/docs/templates/thg/layout/_master.tmpl @@ -0,0 +1,160 @@ +{{!Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license.}} +{{! Copy of templates/modern/layout/_master.tmpl from DocFX 2.78.5 with two targeted fixes for absolute-URL handling on the favicon and navbar logo. The original prepends _rel (e.g. "../") to _appFaviconPath and _appLogoPath unconditionally, which turns an absolute URL into "../https://..." on sub-pages. The fix moves _rel into the fallback path only, so absolute URLs are rendered as-is. Keep _appFaviconPath / _appLogoPath absolute (or unset to use the fallback). }} +{{!include(/^public/.*/)}} +{{!include(favicon.ico)}} +{{!include(logo.svg)}} + + + + + {{#redirect_url}} + + {{/redirect_url}} + {{^redirect_url}} + {{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}} + + + {{#_description}}{{/_description}} + {{#description}}{{/description}} + + + + + + {{#_noindex}}{{/_noindex}} + {{#_enableSearch}}{{/_enableSearch}} + {{#_disableNewTab}}{{/_disableNewTab}} + {{#_disableTocFilter}}{{/_disableTocFilter}} + {{#docurl}}{{/docurl}} + + + + + + + + + + + + + + + + + + {{#_googleAnalyticsTagId}} + + + {{/_googleAnalyticsTagId}} + {{/redirect_url}} + + + {{^redirect_url}} + +
+ {{^_disableNavbar}} + + {{/_disableNavbar}} +
+ +
+ {{^_disableToc}} +
+
+
+
Table of Contents
+ +
+
+ +
+
+
+ {{/_disableToc}} + +
+
+ {{^_disableToc}} + + {{/_disableToc}} + + {{^_disableBreadcrumb}} + + {{/_disableBreadcrumb}} +
+ +
+ {{!body}} +
+ + {{^_disableContribution}} +
+ {{#sourceurl}} + {{__global.improveThisDoc}} + {{/sourceurl}} + {{^sourceurl}}{{#docurl}} + {{__global.improveThisDoc}} + {{/docurl}}{{/sourceurl}} +
+ {{/_disableContribution}} + + {{^_disableNextArticle}} + + {{/_disableNextArticle}} + +
+ + {{^_disableAffix}} +
+ +
+ {{/_disableAffix}} +
+ + {{#_enableSearch}} +
+ {{/_enableSearch}} + +
+
+
+ {{{_appFooter}}}{{^_appFooter}}Made with docfx{{/_appFooter}} +
+
+
+ + {{/redirect_url}} + diff --git a/docs/templates/thg/public/main.css b/docs/templates/thg/public/main.css new file mode 100644 index 0000000..79e3b7f --- /dev/null +++ b/docs/templates/thg/public/main.css @@ -0,0 +1,10 @@ +/* Custom style overrides for the Tharga.Reporter documentation site. + Loaded after the modern template's docfx.min.css, so these win. */ + +/* The source logo (https://thargelion.net/assets/component-reporter.png) is 150x150; + constrain its display size in the navbar instead of resizing the image file. */ +#logo, +.navbar-brand img { + height: 32px; + width: auto; +} diff --git a/docs/toc.yml b/docs/toc.yml new file mode 100644 index 0000000..019419e --- /dev/null +++ b/docs/toc.yml @@ -0,0 +1,6 @@ +- name: Home + href: index.md +- name: Articles + href: articles/ +- name: API + href: api/ diff --git a/plan/feature.md b/plan/feature.md new file mode 100644 index 0000000..e2c5b82 --- /dev/null +++ b/plan/feature.md @@ -0,0 +1,51 @@ +# Feature: Documentation site + PackageIconUrl migration + +## Goal +Set up a DocFX documentation site at `reporter.tharga.net` and migrate `` to `https://thargelion.net/assets/component-reporter.png`. Both items shipped together because they share a release cycle and the icon ends up referenced from both the NuGet listing and the docs site navbar. + +## Source requests +- `Move PackageIconUrl to thargelion.net/assets` → Tharga.Reporter ([Requests.md:1670](file:///c:/Users/danie/SynologyDrive/Documents/Notes/Tharga/Requests.md#L1670)) +- `Documentation sites under tharga.net` → Tharga.Reporter ([Requests.md:1790](file:///c:/Users/danie/SynologyDrive/Documents/Notes/Tharga/Requests.md#L1790)) + +## Reference +**Tharga.Console** ([PR #35](https://github.com/Tharga/Console/pull/35)) — pattern to mirror for both items. Console already pairs the icon migration with the docs site in a single feature branch. + +## Scope + +### PackageIconUrl +Replace `http://thargelion.se/wp-content/uploads/2019/11/Thargelion-White-Icon-150.png` in [Tharga.Reporter.csproj](Tharga.Reporter/Tharga.Reporter.csproj#L10) with `https://thargelion.net/assets/component-reporter.png`. Verify URL resolves before committing. + +### Docs site +- `docs/` source tree mirroring Console's layout: `docfx.json`, `CNAME`, `index.md`, `toc.yml`, `articles/` (4 articles + index + toc), `templates/thg/` overlay. +- Use the **absolute-URL + template overlay** approach (option b in the trap warning) — matches Console exactly, lets the logo URL live on `thargelion.net` rather than needing a local copy. +- 4 articles: `getting-started.md`, `templates.md`, `elements.md`, `data-and-rendering.md`. Topics reflect the public API surface (Renderer, Template, Section, Pane, elements, DocumentData, PageFormat). +- CNAME → `reporter.tharga.net` (DNS is pre-req meta-repo work and assumed to be in place). + +### README.md +Add docs link line above the existing badges/description, mirroring Console's format. + +### Build pipeline +Add to [.github/workflows/build.yml](.github/workflows/build.yml): +- `pages: write` and `id-token: write` to the workflow-level `permissions`. +- `docs` job: `needs: release`, installs DocFX, builds `docs/docfx.json`, uploads pages artifact. +- `docs-deploy` job: `needs: docs`, deploys to `github-pages` environment. + +Both jobs gated on `github.ref == 'refs/heads/master' && github.event_name == 'push'` so docs only publish after a successful release (per "CI: Publish docs after release succeeds" pattern at [Requests.md:1854](file:///c:/Users/danie/SynologyDrive/Documents/Notes/Tharga/Requests.md#L1854)). + +## Out of scope (user-side) +- DNS configuration for `reporter.tharga.net` → `tharga.github.io` (assumed already done; otherwise pages will deploy under the github.io URL until DNS is set). +- Uploading `component-reporter.png` to `thargelion.net/assets/` (meta-repo pre-req). +- Enabling GitHub Pages in repo settings (one-time setup). + +## Acceptance criteria +- `` references `https://thargelion.net/assets/component-reporter.png` and the URL resolves (HTTP 200). +- `docfx docs/docfx.json` builds the site locally with no errors. +- `dotnet build -c Release` and `dotnet test -c Release` still pass. +- Workflow YAML is syntactically valid; `docs` + `docs-deploy` chained after `release`. +- README links to `https://reporter.tharga.net`. +- On merge to master, a patch release publishes to NuGet (with new icon URL) **and** docs deploy to `reporter.tharga.net`. + +## Done condition +- All acceptance criteria met. +- PR merged, release published, docs site live and verified via `curl https://reporter.tharga.net/articles/` returning the absolute-URL logo unchanged (no `../https://...` corruption). +- Both Reporter requests marked Done in `Requests.md`. diff --git a/plan/plan.md b/plan/plan.md new file mode 100644 index 0000000..bc2e679 --- /dev/null +++ b/plan/plan.md @@ -0,0 +1,50 @@ +# Plan: Documentation site + PackageIconUrl migration + +## Steps + +- [x] 1. Create feature branch `feature/docs-and-icon` from master +- [x] 2. Create `plan/feature.md` and `plan/plan.md` +- [ ] 3. Update `` in [Tharga.Reporter.csproj](Tharga.Reporter/Tharga.Reporter.csproj) → `https://thargelion.net/assets/component-reporter.png`. Verify URL via curl. +- [ ] 4. Create docs root files: + - `docs/docfx.json` (api metadata from Tharga.Reporter.csproj; modern + thg template; absolute logo URL) + - `docs/CNAME` = `reporter.tharga.net` + - `docs/index.md` (landing — overview, packages, quick start, what's in the box) + - `docs/toc.yml` +- [ ] 5. Write 4 articles + index + toc: + - `getting-started.md` — install, namespaces, minimal example + - `templates.md` — Template / Section / Header / Pane / Footer hierarchy, multi-section + - `elements.md` — Text, TextBox, Image, BarCode, Line, Rectangle, Table, ReferencePoint + - `data-and-rendering.md` — DocumentData substitution, Renderer, PageFormat, PdfDocumentProperties + - `articles/index.md` + `articles/toc.yml` +- [ ] 6. Add template overlay (Console option b — absolute-URL trap fix): + - `docs/templates/thg/layout/_master.tmpl` (copied from Console) + - `docs/templates/thg/public/main.css` (logo navbar sizing) +- [ ] 7. Update [README.md](README.md) — add `**Docs:** [reporter.tharga.net](https://reporter.tharga.net)` line. +- [ ] 8. Update [.github/workflows/build.yml](.github/workflows/build.yml): + - Add `pages: write` and `id-token: write` to top-level permissions + - Append `docs` job (needs: release, install docfx, build site, upload-pages-artifact) + - Append `docs-deploy` job (needs: docs, deploy-pages, environment github-pages) +- [ ] 9. Validate locally: + - `curl -I https://thargelion.net/assets/component-reporter.png` returns 200 + - `dotnet tool install -g docfx` if missing + - `docfx docs/docfx.json` builds cleanly + - `dotnet build -c Release` passes + - `dotnet test -c Release` passes +- [ ] 10. Commit + push feature branch, create PR `feature/docs-and-icon → master` + +## User-side follow-ups (not done by Claude) +- [ ] Verify DNS for `reporter.tharga.net` is configured to point at `tharga.github.io` +- [ ] Enable GitHub Pages in repo settings (Source: GitHub Actions) +- [ ] Confirm `component-reporter.png` is uploaded to `thargelion.net/assets/` +- [ ] Merge PR — release publishes with new icon, docs deploy to subdomain +- [ ] Mark both requests Done in `Requests.md` + +## Decisions + +- **Pair both requests in one feature** — user instruction; matches Console's PR #35 pattern. +- **Reference project**: Tharga.Console (user-specified). Inherit absolute-URL logo + template-overlay approach (option b). +- **Article count**: 4. Mirrors Console/Mcp/Runtime; covers the four logical surfaces of Reporter — install/setup, structure, elements, render output. +- **Docs CI integration**: jobs live inside `build.yml`, `needs: release`. No separate `docs.yml`. Per the "CI: Publish docs after release succeeds" pattern, this is the canonical layout. + +## Last session +_(in progress)_