Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ jobs:
src/FlexRender.QrCode/FlexRender.QrCode.csproj \
src/FlexRender.Barcode/FlexRender.Barcode.csproj \
src/FlexRender.SvgElement/FlexRender.SvgElement.csproj \
src/FlexRender.Content.Markdown/FlexRender.Content.Markdown.csproj \
src/FlexRender.Content.Html/FlexRender.Content.Html.csproj \
src/FlexRender.DependencyInjection/FlexRender.DependencyInjection.csproj \
src/FlexRender.MetaPackage/FlexRender.MetaPackage.csproj; do
dotnet pack "$project" \
Expand Down
12 changes: 7 additions & 5 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
</PropertyGroup>
<ItemGroup>
<!-- Production dependencies -->
<PackageVersion Include="AwesomeAssertions" Version="9.3.0" />
<PackageVersion Include="AwesomeAssertions" Version="9.4.0" />
<PackageVersion Include="xunit.v3" Version="3.2.2" />
<PackageVersion Include="YamlDotNet" Version="16.3.0" />
<PackageVersion Include="SkiaSharp" Version="3.119.2" />
Expand All @@ -18,19 +18,21 @@
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="2.1.7" />
<PackageVersion Include="SixLabors.Fonts" Version="2.1.3" />
<PackageVersion Include="Svg.Skia" Version="3.4.1" />
<PackageVersion Include="Markdig" Version="1.1.1" />
<PackageVersion Include="HtmlAgilityPack" Version="1.12.4" />
<!-- Microsoft.Extensions: version 10.x for net10.0, version 8.x for net8.0 -->
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.2" Condition="'$(TargetFramework)' == 'net10.0'" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.2" Condition="'$(TargetFramework)' == 'net10.0'" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.2" Condition="'$(TargetFramework)' == 'net10.0'" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" Condition="'$(TargetFramework)' == 'net8.0'" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" Condition="'$(TargetFramework)' == 'net8.0'" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="8.0.2" Condition="'$(TargetFramework)' == 'net8.0'" />
<PackageVersion Include="System.CommandLine" Version="2.0.2" />
<PackageVersion Include="System.CommandLine" Version="2.0.3" />
<!-- Build dependencies -->
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="10.0.102" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="10.0.103" />
<!-- Test dependencies -->
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
<PackageVersion Include="coverlet.collector" Version="8.0.0" />
</ItemGroup>
</Project>
4 changes: 4 additions & 0 deletions FlexRender.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
<Folder Name="/src/ResourceLoaders/">
<Project Path="src/FlexRender.Http/FlexRender.Http.csproj" />
</Folder>
<Folder Name="/src/Content/">
<Project Path="src/FlexRender.Content.Markdown/FlexRender.Content.Markdown.csproj" />
<Project Path="src/FlexRender.Content.Html/FlexRender.Content.Html.csproj" />
</Folder>
<Folder Name="/src/SvgElement/">
<Project Path="src/FlexRender.SvgElement.Skia.Render/FlexRender.SvgElement.Skia.Render.csproj" />
<Project Path="src/FlexRender.SvgElement/FlexRender.SvgElement.csproj" />
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ dotnet add package FlexRender.QrCode
dotnet add package FlexRender.Barcode
dotnet add package FlexRender.SvgElement

# Content parsers (optional)
dotnet add package FlexRender.Content.Markdown
dotnet add package FlexRender.Content.Html

# CLI tool
dotnet tool install -g flexrender-cli
```
Expand Down
10 changes: 10 additions & 0 deletions docs/wiki/API-Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ Builder for configuring and creating `IFlexRender` instances. Defined in `FlexRe
| `WithFilter(ITemplateFilter)` | Register a custom template filter for inline expressions. Works alongside built-in filters (enabled by default) |
| `WithoutDefaultLoaders()` | Remove default File and Base64 loaders (sandboxed mode) |
| `WithoutDefaultFilters()` | Remove all 8 built-in filters (enabled by default), leaving only custom-registered filters |
| `WithContentParser(IContentParser)` | Register a content parser for `type: content` elements |
| `WithMarkdown()` | Enable Markdown content parsing (`format: markdown`) |
| `WithHtml()` | Enable HTML content parsing (`format: html`) |
| `Build()` | Create the configured `IFlexRender` instance |

### Usage
Expand All @@ -104,6 +107,13 @@ var render = new FlexRenderBuilder()
.WithBarcode())
.Build();

// With content parsers
var render = new FlexRenderBuilder()
.WithMarkdown()
.WithHtml()
.WithSkia(skia => skia.WithQr().WithBarcode())
.Build();

// Sandboxed (no file system access)
var render = new FlexRenderBuilder()
.WithoutDefaultLoaders()
Expand Down
136 changes: 135 additions & 1 deletion docs/wiki/Element-Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ For rendering options (antialiasing, format settings), see [[Render-Options]].

## Common Properties (TemplateElement)

All 10 element types (`flex`, `text`, `image`, `svg`, `qr`, `barcode`, `separator`, `table`, `each`, `if`) inherit these properties from the base `TemplateElement` class. You can use any of them on any element.
All 11 element types (`flex`, `text`, `image`, `svg`, `qr`, `barcode`, `separator`, `table`, `content`, `each`, `if`) inherit these properties from the base `TemplateElement` class. You can use any of them on any element.

> **Expression support:** All properties on all element types accept `{{expressions}}`. This includes typed properties like `opacity` (float), `grow`/`shrink` (float), `order` (int), `wrap` (bool on text, FlexWrap on flex), and enum properties like `display`, `position`, `align`. See [[Template-Expressions]] for details.

Expand Down Expand Up @@ -739,13 +739,16 @@ Renders text content with font styling, alignment, wrapping, overflow handling,
|----------|-----------|------|---------|--------------|----------|-------------|
| Content | `content` | string | `""` | Any string, may contain `{{variable}}` expressions | No | The text to render. |
| Font | `font` | string | `"main"` | Any key defined in the `fonts` section | No | Font reference name. Falls back to `"default"` if `"main"` is not defined. |
| FontFamily | `fontFamily` or `font-family` | string | `""` | Any font family name | No | CSS-like font family name. Searches registered fonts by FamilyName metadata, then system fonts. |
| Size | `size` | string | `"1em"` | px, em, % | No | Font size. |
| Color | `color` | string | `"#000000"` | Hex color (#rgb or #rrggbb) | No | Text color. |
| Align | `align` | TextAlign | `left` | left, center, right, start (logical), end (logical) | No | Horizontal text alignment within the element. `start`/`end` resolve based on text direction. |
| Wrap | `wrap` | bool | `true` | true, false | No | Whether text wraps to multiple lines when exceeding width. |
| Overflow | `overflow` | TextOverflow | `ellipsis` | ellipsis, clip, visible | No | How overflowing text is handled when `maxLines` is reached or `wrap` is `false`. |
| MaxLines | `maxLines` | int? | `null` | Any positive integer, or null for unlimited | No | Maximum number of lines. Text beyond this limit is truncated per `overflow`. |
| LineHeight | `lineHeight` | string | `""` | Multiplier, px, em, or empty string | No | Line spacing for multi-line text. |
| FontWeight | `fontWeight` or `font-weight` | FontWeight | `Normal` | `thin` (100), `extra-light` (200), `light` (300), `normal` (400), `medium` (500), `semi-bold` (600), `bold` (700), `extra-bold` (800), `black` (900), or numeric 100-900 | No | Font weight for selecting font variant. CSS-compatible values. |
| FontStyle | `fontStyle` or `font-style` | FontStyle | `Normal` | `normal`, `italic`, `oblique` | No | Font style for selecting font variant. |

```yaml
# Font size in pixels
Expand Down Expand Up @@ -834,6 +837,30 @@ Renders text content with font styling, alignment, wrapping, overflow handling,
wrap: true
```

**fontWeight and fontStyle examples:**

```yaml
# Font weight and style variants
- type: text
content: "Bold text"
fontWeight: bold

- type: text
content: "Italic text"
fontStyle: italic

- type: text
content: "Light italic"
fontWeight: light
fontStyle: italic

- type: text
content: "Semi-bold"
fontWeight: 600
```

> **Font resolution priority:** `font` (registered name) > `fontFamily` (family name lookup) > fallback (default). When `fontFamily` is set, FlexRender searches registered fonts by FamilyName metadata, then system fonts. When `fontWeight` or `fontStyle` is set, FlexRender automatically scans the same directory as the resolved font file for sibling files with matching family name and weight/style (within +/-100 units). See [[Template-Syntax#fonts]] for details. Variable fonts are not supported -- use separate static `.ttf`/`.otf` files per weight.

### Complete Example: Multi-line Truncated Description

```yaml
Expand Down Expand Up @@ -1283,6 +1310,9 @@ Renders tabular data with configurable columns, optional header row, and support
| Columns | `columns` | column[] | -- | Array of column definitions | **Yes** | Column definitions. Must have at least one column. |
| Rows | `rows` | row[] | `[]` | Array of static row definitions | No | Static rows. Alternative to `array` for fixed data. |
| HeaderFont | `headerFont` or `header-font` | string? | `null` | Any font name from `fonts` section | No | Font for the header row. |
| HeaderFontWeight | `headerFontWeight` or `header-fontWeight` | string? | `null` | Font weight name or 100-900 | No | Font weight for the header row. |
| HeaderFontStyle | `headerFontStyle` or `header-fontStyle` | string? | `null` | normal, italic, oblique | No | Font style for the header row. |
| HeaderFontFamily | `headerFontFamily` or `header-fontFamily` | string? | `null` | Any font family name | No | CSS-like font family for the header row. |
| HeaderColor | `headerColor` or `header-color` | string? | `null` | Hex color | No | Text color for the header row. |
| HeaderSize | `headerSize` or `header-size` | string? | `null` | px, em, % | No | Font size for the header row. |
| HeaderBackground | `headerBackground` or `header-background` | string? | `null` | Hex color | No | Background color for the header row. |
Expand Down Expand Up @@ -1419,6 +1449,110 @@ Renders tabular data with configurable columns, optional header row, and support

---

## Content Element (Control Flow)

Embeds dynamically formatted text (Markdown, HTML, etc.) from template data. The `source` text is parsed at render time into a subtree of FlexRender elements using pluggable content parsers.

This is a **control-flow element** — like `each` and `if`, it is expanded during template processing and does not appear in the final render tree.

```yaml
- type: content
source: "{{body}}"
format: markdown
```

### Properties

| Property | YAML Name | Type | Default | Valid Values | Expression | Description |
|----------|-----------|------|---------|--------------|-----------|-------------|
| Source | `source` | string | `""` | Any string, typically `{{variable}}` | Yes | The formatted text to parse. Usually bound to a data variable. |
| Format | `format` | string | `""` | `markdown`, `html`, or any registered parser name | Yes | The content format. Must match a registered `IContentParser.FormatName`. |

### Supported Formats

| Format | Package | Builder Method | Library |
|--------|---------|----------------|---------|
| `markdown` | `FlexRender.Content.Markdown` | `.WithMarkdown()` | Markdig |
| `html` | `FlexRender.Content.Html` | `.WithHtml()` | HtmlAgilityPack |

### Element Mapping

Content parsers convert formatted text into standard FlexRender elements:

| Source Format | Produces |
|---------------|----------|
| Bold text (`**bold**` or `<b>`) | `TextElement { FontWeight = Bold }` |
| Italic text (`*italic*` or `<i>`) | `TextElement { FontStyle = Italic }` |
| Headings (`# H1` or `<h1>`) | `TextElement { FontWeight = Bold, Size = "2em" }` |
| Lists (`- item` or `<ul>`) | `FlexElement` with bullet-prefixed children |
| Blockquote (`>` or `<blockquote>`) | `FlexElement { Padding, Background }` |
| Horizontal rule (`---` or `<hr>`) | `SeparatorElement` |
| Image (`![](url)` or `<img>`) | `ImageElement` |
| Code (`` `code` `` or `<code>`) | `TextElement { Background = "#f0f0f0" }` |

### Example: Markdown Content

```yaml
template:
name: "receipt"

canvas:
fixed: width
width: 400
background: "#ffffff"

layout:
- type: text
content: "Order Receipt"
fontWeight: bold
size: "1.5em"
padding: "16"

- type: content
source: "{{orderDetails}}"
format: markdown
padding: "12 16"
```

Data:
```json
{
"orderDetails": "## Items\n\n- Widget A — $9.99\n- **Gadget B** — $24.99\n\n> Total: **$34.98**"
}
```

### Example: HTML Content with Inline Styles

```yaml
- type: content
source: "{{productInfo}}"
format: html
padding: "8"
```

Data:
```json
{
"productInfo": "<p>Price: <b style=\"color: #E91E63; font-size: 1.3em;\">$29.99</b></p>"
}
```

### Registration

```csharp
var render = new FlexRenderBuilder()
.WithMarkdown() // FlexRender.Content.Markdown
.WithHtml() // FlexRender.Content.Html
.WithSkia()
.Build();
```

### Template Caching

The `content` element is expanded at render time (like `each` and `if`), so parsed templates can be safely cached and rendered with different data.

---

## each (Control Flow)

Iterates over an array in the template data, rendering the `children` template once for each array item. This is the primary mechanism for dynamic, data-driven lists.
Expand Down
6 changes: 5 additions & 1 deletion docs/wiki/Getting-Started.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ Install only what you need:
| `FlexRender.SvgElement.Skia.Render` | SVG elements for Skia | Svg.Skia |
| `FlexRender.SvgElement.Svg.Render` | SVG elements for SVG output | None |
| `FlexRender.SvgElement` | SvgElement meta-package (all renderers) | Svg.Skia |
| `FlexRender.Content.Markdown` | Markdown content parsing for `type: content` | Markdig |
| `FlexRender.Content.Html` | HTML content parsing for `type: content` | HtmlAgilityPack |
| `FlexRender.HarfBuzz` | HarfBuzz text shaping for Arabic/Hebrew | SkiaSharp.HarfBuzz |
| `FlexRender.Http` | HTTP/HTTPS resource loading | None |
| `FlexRender.DependencyInjection` | Microsoft DI integration | Microsoft.Extensions.DI |
Expand Down Expand Up @@ -192,6 +194,8 @@ Native rendering via SkiaSharp. Best quality, widest feature set.

```csharp
var render = new FlexRenderBuilder()
.WithMarkdown() // Markdown content parsing
.WithHtml() // HTML content parsing
.WithSkia(skia => skia
.WithQr() // QR code support
.WithBarcode() // Barcode support
Expand All @@ -201,7 +205,7 @@ var render = new FlexRenderBuilder()

- **Formats:** PNG, JPEG, BMP, Raw
- **Requires:** `SkiaSharp.NativeAssets.Linux` on Linux/Docker
- **Optional:** `.WithHarfBuzz()` for Arabic/Hebrew text shaping
- **Optional:** `.WithHarfBuzz()` for Arabic/Hebrew text shaping, `.WithMarkdown()` / `.WithHtml()` for content parsing
- **Best for:** Desktop apps, servers with native library support

### ImageSharp Backend
Expand Down
Loading