Skip to content

Commit 47819a0

Browse files
authored
Large fonts refactoring (#4)
1 parent d32a7b2 commit 47819a0

202 files changed

Lines changed: 4526 additions & 519 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/release.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ jobs:
7575
src/FlexRender.QrCode/FlexRender.QrCode.csproj \
7676
src/FlexRender.Barcode/FlexRender.Barcode.csproj \
7777
src/FlexRender.SvgElement/FlexRender.SvgElement.csproj \
78+
src/FlexRender.Content.Markdown/FlexRender.Content.Markdown.csproj \
79+
src/FlexRender.Content.Html/FlexRender.Content.Html.csproj \
7880
src/FlexRender.DependencyInjection/FlexRender.DependencyInjection.csproj \
7981
src/FlexRender.MetaPackage/FlexRender.MetaPackage.csproj; do
8082
dotnet pack "$project" \

Directory.Packages.props

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
</PropertyGroup>
55
<ItemGroup>
66
<!-- Production dependencies -->
7-
<PackageVersion Include="AwesomeAssertions" Version="9.3.0" />
7+
<PackageVersion Include="AwesomeAssertions" Version="9.4.0" />
88
<PackageVersion Include="xunit.v3" Version="3.2.2" />
99
<PackageVersion Include="YamlDotNet" Version="16.3.0" />
1010
<PackageVersion Include="SkiaSharp" Version="3.119.2" />
@@ -18,19 +18,21 @@
1818
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="2.1.7" />
1919
<PackageVersion Include="SixLabors.Fonts" Version="2.1.3" />
2020
<PackageVersion Include="Svg.Skia" Version="3.4.1" />
21+
<PackageVersion Include="Markdig" Version="1.1.1" />
22+
<PackageVersion Include="HtmlAgilityPack" Version="1.12.4" />
2123
<!-- Microsoft.Extensions: version 10.x for net10.0, version 8.x for net8.0 -->
2224
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.2" Condition="'$(TargetFramework)' == 'net10.0'" />
2325
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.2" Condition="'$(TargetFramework)' == 'net10.0'" />
2426
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.2" Condition="'$(TargetFramework)' == 'net10.0'" />
2527
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" Condition="'$(TargetFramework)' == 'net8.0'" />
2628
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" Condition="'$(TargetFramework)' == 'net8.0'" />
2729
<PackageVersion Include="Microsoft.Extensions.Options" Version="8.0.2" Condition="'$(TargetFramework)' == 'net8.0'" />
28-
<PackageVersion Include="System.CommandLine" Version="2.0.2" />
30+
<PackageVersion Include="System.CommandLine" Version="2.0.3" />
2931
<!-- Build dependencies -->
30-
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="10.0.102" />
32+
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="10.0.103" />
3133
<!-- Test dependencies -->
32-
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
34+
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
3335
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
34-
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
36+
<PackageVersion Include="coverlet.collector" Version="8.0.0" />
3537
</ItemGroup>
3638
</Project>

FlexRender.slnx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@
3232
<Folder Name="/src/ResourceLoaders/">
3333
<Project Path="src/FlexRender.Http/FlexRender.Http.csproj" />
3434
</Folder>
35+
<Folder Name="/src/Content/">
36+
<Project Path="src/FlexRender.Content.Markdown/FlexRender.Content.Markdown.csproj" />
37+
<Project Path="src/FlexRender.Content.Html/FlexRender.Content.Html.csproj" />
38+
</Folder>
3539
<Folder Name="/src/SvgElement/">
3640
<Project Path="src/FlexRender.SvgElement.Skia.Render/FlexRender.SvgElement.Skia.Render.csproj" />
3741
<Project Path="src/FlexRender.SvgElement/FlexRender.SvgElement.csproj" />

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ dotnet add package FlexRender.QrCode
8888
dotnet add package FlexRender.Barcode
8989
dotnet add package FlexRender.SvgElement
9090

91+
# Content parsers (optional)
92+
dotnet add package FlexRender.Content.Markdown
93+
dotnet add package FlexRender.Content.Html
94+
9195
# CLI tool
9296
dotnet tool install -g flexrender-cli
9397
```

docs/wiki/API-Reference.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ Builder for configuring and creating `IFlexRender` instances. Defined in `FlexRe
7979
| `WithFilter(ITemplateFilter)` | Register a custom template filter for inline expressions. Works alongside built-in filters (enabled by default) |
8080
| `WithoutDefaultLoaders()` | Remove default File and Base64 loaders (sandboxed mode) |
8181
| `WithoutDefaultFilters()` | Remove all 8 built-in filters (enabled by default), leaving only custom-registered filters |
82+
| `WithContentParser(IContentParser)` | Register a content parser for `type: content` elements |
83+
| `WithMarkdown()` | Enable Markdown content parsing (`format: markdown`) |
84+
| `WithHtml()` | Enable HTML content parsing (`format: html`) |
8285
| `Build()` | Create the configured `IFlexRender` instance |
8386

8487
### Usage
@@ -104,6 +107,13 @@ var render = new FlexRenderBuilder()
104107
.WithBarcode())
105108
.Build();
106109

110+
// With content parsers
111+
var render = new FlexRenderBuilder()
112+
.WithMarkdown()
113+
.WithHtml()
114+
.WithSkia(skia => skia.WithQr().WithBarcode())
115+
.Build();
116+
107117
// Sandboxed (no file system access)
108118
var render = new FlexRenderBuilder()
109119
.WithoutDefaultLoaders()

docs/wiki/Element-Reference.md

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ For rendering options (antialiasing, format settings), see [[Render-Options]].
1212

1313
## Common Properties (TemplateElement)
1414

15-
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.
15+
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.
1616

1717
> **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.
1818
@@ -739,13 +739,16 @@ Renders text content with font styling, alignment, wrapping, overflow handling,
739739
|----------|-----------|------|---------|--------------|----------|-------------|
740740
| Content | `content` | string | `""` | Any string, may contain `{{variable}}` expressions | No | The text to render. |
741741
| Font | `font` | string | `"main"` | Any key defined in the `fonts` section | No | Font reference name. Falls back to `"default"` if `"main"` is not defined. |
742+
| 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. |
742743
| Size | `size` | string | `"1em"` | px, em, % | No | Font size. |
743744
| Color | `color` | string | `"#000000"` | Hex color (#rgb or #rrggbb) | No | Text color. |
744745
| 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. |
745746
| Wrap | `wrap` | bool | `true` | true, false | No | Whether text wraps to multiple lines when exceeding width. |
746747
| Overflow | `overflow` | TextOverflow | `ellipsis` | ellipsis, clip, visible | No | How overflowing text is handled when `maxLines` is reached or `wrap` is `false`. |
747748
| MaxLines | `maxLines` | int? | `null` | Any positive integer, or null for unlimited | No | Maximum number of lines. Text beyond this limit is truncated per `overflow`. |
748749
| LineHeight | `lineHeight` | string | `""` | Multiplier, px, em, or empty string | No | Line spacing for multi-line text. |
750+
| 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. |
751+
| FontStyle | `fontStyle` or `font-style` | FontStyle | `Normal` | `normal`, `italic`, `oblique` | No | Font style for selecting font variant. |
749752

750753
```yaml
751754
# Font size in pixels
@@ -834,6 +837,30 @@ Renders text content with font styling, alignment, wrapping, overflow handling,
834837
wrap: true
835838
```
836839

840+
**fontWeight and fontStyle examples:**
841+
842+
```yaml
843+
# Font weight and style variants
844+
- type: text
845+
content: "Bold text"
846+
fontWeight: bold
847+
848+
- type: text
849+
content: "Italic text"
850+
fontStyle: italic
851+
852+
- type: text
853+
content: "Light italic"
854+
fontWeight: light
855+
fontStyle: italic
856+
857+
- type: text
858+
content: "Semi-bold"
859+
fontWeight: 600
860+
```
861+
862+
> **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.
863+
837864
### Complete Example: Multi-line Truncated Description
838865

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

14201450
---
14211451

1452+
## Content Element (Control Flow)
1453+
1454+
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.
1455+
1456+
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.
1457+
1458+
```yaml
1459+
- type: content
1460+
source: "{{body}}"
1461+
format: markdown
1462+
```
1463+
1464+
### Properties
1465+
1466+
| Property | YAML Name | Type | Default | Valid Values | Expression | Description |
1467+
|----------|-----------|------|---------|--------------|-----------|-------------|
1468+
| Source | `source` | string | `""` | Any string, typically `{{variable}}` | Yes | The formatted text to parse. Usually bound to a data variable. |
1469+
| Format | `format` | string | `""` | `markdown`, `html`, or any registered parser name | Yes | The content format. Must match a registered `IContentParser.FormatName`. |
1470+
1471+
### Supported Formats
1472+
1473+
| Format | Package | Builder Method | Library |
1474+
|--------|---------|----------------|---------|
1475+
| `markdown` | `FlexRender.Content.Markdown` | `.WithMarkdown()` | Markdig |
1476+
| `html` | `FlexRender.Content.Html` | `.WithHtml()` | HtmlAgilityPack |
1477+
1478+
### Element Mapping
1479+
1480+
Content parsers convert formatted text into standard FlexRender elements:
1481+
1482+
| Source Format | Produces |
1483+
|---------------|----------|
1484+
| Bold text (`**bold**` or `<b>`) | `TextElement { FontWeight = Bold }` |
1485+
| Italic text (`*italic*` or `<i>`) | `TextElement { FontStyle = Italic }` |
1486+
| Headings (`# H1` or `<h1>`) | `TextElement { FontWeight = Bold, Size = "2em" }` |
1487+
| Lists (`- item` or `<ul>`) | `FlexElement` with bullet-prefixed children |
1488+
| Blockquote (`>` or `<blockquote>`) | `FlexElement { Padding, Background }` |
1489+
| Horizontal rule (`---` or `<hr>`) | `SeparatorElement` |
1490+
| Image (`![](url)` or `<img>`) | `ImageElement` |
1491+
| Code (`` `code` `` or `<code>`) | `TextElement { Background = "#f0f0f0" }` |
1492+
1493+
### Example: Markdown Content
1494+
1495+
```yaml
1496+
template:
1497+
name: "receipt"
1498+
1499+
canvas:
1500+
fixed: width
1501+
width: 400
1502+
background: "#ffffff"
1503+
1504+
layout:
1505+
- type: text
1506+
content: "Order Receipt"
1507+
fontWeight: bold
1508+
size: "1.5em"
1509+
padding: "16"
1510+
1511+
- type: content
1512+
source: "{{orderDetails}}"
1513+
format: markdown
1514+
padding: "12 16"
1515+
```
1516+
1517+
Data:
1518+
```json
1519+
{
1520+
"orderDetails": "## Items\n\n- Widget A — $9.99\n- **Gadget B** — $24.99\n\n> Total: **$34.98**"
1521+
}
1522+
```
1523+
1524+
### Example: HTML Content with Inline Styles
1525+
1526+
```yaml
1527+
- type: content
1528+
source: "{{productInfo}}"
1529+
format: html
1530+
padding: "8"
1531+
```
1532+
1533+
Data:
1534+
```json
1535+
{
1536+
"productInfo": "<p>Price: <b style=\"color: #E91E63; font-size: 1.3em;\">$29.99</b></p>"
1537+
}
1538+
```
1539+
1540+
### Registration
1541+
1542+
```csharp
1543+
var render = new FlexRenderBuilder()
1544+
.WithMarkdown() // FlexRender.Content.Markdown
1545+
.WithHtml() // FlexRender.Content.Html
1546+
.WithSkia()
1547+
.Build();
1548+
```
1549+
1550+
### Template Caching
1551+
1552+
The `content` element is expanded at render time (like `each` and `if`), so parsed templates can be safely cached and rendered with different data.
1553+
1554+
---
1555+
14221556
## each (Control Flow)
14231557

14241558
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.

docs/wiki/Getting-Started.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ Install only what you need:
4646
| `FlexRender.SvgElement.Skia.Render` | SVG elements for Skia | Svg.Skia |
4747
| `FlexRender.SvgElement.Svg.Render` | SVG elements for SVG output | None |
4848
| `FlexRender.SvgElement` | SvgElement meta-package (all renderers) | Svg.Skia |
49+
| `FlexRender.Content.Markdown` | Markdown content parsing for `type: content` | Markdig |
50+
| `FlexRender.Content.Html` | HTML content parsing for `type: content` | HtmlAgilityPack |
4951
| `FlexRender.HarfBuzz` | HarfBuzz text shaping for Arabic/Hebrew | SkiaSharp.HarfBuzz |
5052
| `FlexRender.Http` | HTTP/HTTPS resource loading | None |
5153
| `FlexRender.DependencyInjection` | Microsoft DI integration | Microsoft.Extensions.DI |
@@ -192,6 +194,8 @@ Native rendering via SkiaSharp. Best quality, widest feature set.
192194

193195
```csharp
194196
var render = new FlexRenderBuilder()
197+
.WithMarkdown() // Markdown content parsing
198+
.WithHtml() // HTML content parsing
195199
.WithSkia(skia => skia
196200
.WithQr() // QR code support
197201
.WithBarcode() // Barcode support
@@ -201,7 +205,7 @@ var render = new FlexRenderBuilder()
201205

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

207211
### ImageSharp Backend

0 commit comments

Comments
 (0)