Skip to content

Commit 9b300dc

Browse files
committed
API refactoring
1 parent c3e309f commit 9b300dc

51 files changed

Lines changed: 2687 additions & 1587 deletions

Some content is hidden

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

AGENTS.md

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,17 @@ src/FlexRender.Core/ # Core library (0 external dependencies)
2525
Configuration/ # ResourceLimits, FlexRenderOptions
2626
Layout/ # Two-pass flexbox layout engine (LayoutEngine, LayoutNode, LayoutSize)
2727
Units/ # Unit, UnitParser, PaddingValues, PaddingParser
28-
Loaders/ # FileResourceLoader, Base64ResourceLoader, EmbeddedResourceLoader, HttpResourceLoader
28+
Loaders/ # FileResourceLoader, Base64ResourceLoader, EmbeddedResourceLoader
2929
Parsing/Ast/ # Template, CanvasSettings, TemplateElement, TextElement, FlexElement, etc.
3030
TemplateEngine/ # TemplateProcessor, ExpressionLexer, ExpressionEvaluator
3131
Values/ # TemplateValue hierarchy (StringValue, NumberValue, etc.)
3232
3333
src/FlexRender.Yaml/ # YAML template parser (-> Core + YamlDotNet)
3434
Parsing/ # TemplateParser, YamlPreprocessor
35+
src/FlexRender.Http/ # HTTP resource loader (-> Core)
3536
src/FlexRender.Skia/ # SkiaSharp renderer (-> Core + SkiaSharp)
36-
Abstractions/ # IFlexRenderer, IFontLoader, IImageLoader, IFontManager
37-
Rendering/ # SkiaRenderer, TextRenderer, FontManager, ColorParser, RotationHelper
37+
Abstractions/ # ISkiaRenderer, IFontLoader, IImageLoader, IFontManager
38+
Rendering/ # SkiaRenderer, TextRenderer, FontManager, ColorParser, RotationHelper, BmpEncoder
3839
Loaders/ # FontLoader, ImageLoader
3940
Providers/ # IContentProvider<T,O>, ImageProvider
4041
src/FlexRender.QrCode/ # QR code provider (-> Skia + QRCoder)
@@ -54,16 +55,16 @@ examples/ # Example YAML templates
5455

5556
```
5657
FlexRender.Core (0 external deps)
57-
^ ^
58-
| |
59-
FlexRender.Yaml FlexRender.Skia (YamlDotNet) (SkiaSharp)
60-
^ ^
61-
| |
62-
FlexRender.QrCode FlexRender.Barcode (QRCoder)
63-
| |
64-
FlexRender.DependencyInjection (Microsoft.Extensions.DI)
65-
|
66-
FlexRender.MetaPackage (references all)
58+
^ ^ ^
59+
| | |
60+
FlexRender.Yaml FlexRender.Http FlexRender.Skia (YamlDotNet) (SkiaSharp)
61+
^ ^
62+
| |
63+
FlexRender.QrCode FlexRender.Barcode (QRCoder)
64+
| |
65+
FlexRender.DependencyInjection (Microsoft.Extensions.DI)
66+
|
67+
FlexRender.MetaPackage (references all)
6768
```
6869

6970
## SkiaSharp Native Assets on Linux
@@ -94,19 +95,38 @@ YAML Template
9495
-> SkiaRenderer (traverse LayoutNode tree -> draw to SKBitmap via SkiaSharp)
9596
```
9697

98+
### FlexRenderBuilder API
99+
100+
The builder pattern provides modular configuration without mandatory DI:
101+
102+
```csharp
103+
// Without DI
104+
var render = new FlexRenderBuilder()
105+
.WithHttpLoader()
106+
.WithBasePath("./templates")
107+
.WithSkia(skia => skia
108+
.WithQr()
109+
.WithBarcode())
110+
.Build();
111+
112+
byte[] png = await render.RenderFile("receipt.yaml", data);
113+
114+
// With DI
115+
services.AddFlexRender(builder => builder
116+
.WithSkia(skia => skia.WithQr().WithBarcode()));
117+
```
118+
97119
### Template Caching
98120

99-
Templates can be parsed once and cached, then expanded with different data at render time:
121+
Templates can be parsed once and cached, then rendered with different data:
100122

101123
```csharp
102124
// Parse once (at startup)
103125
var parser = new TemplateParser();
104-
var template = parser.Parse(yaml);
105-
templateCache["receipt"] = template;
126+
_templates["receipt"] = await parser.ParseFileAsync("receipt.yaml");
106127

107128
// Render many times (per request)
108-
var template = templateCache["receipt"];
109-
var bytes = renderer.RenderToPng(template, data); // Expander called internally
129+
byte[] png = await render.Render(_templates["receipt"], data);
110130
```
111131

112132
### Two-Pass Layout Engine
@@ -118,13 +138,15 @@ var bytes = renderer.RenderToPng(template, data); // Expander called internally
118138

119139
| Stage | Key Classes |
120140
|-------|------------|
141+
| Configuration | `FlexRenderBuilder`, `SkiaBuilder`, `FlexRenderOptions`, `ResourceLimits` |
142+
| Abstractions | `IFlexRender`, `IResourceLoader` |
121143
| Parsing | `TemplateParser`, `Template`, `CanvasSettings`, `TextElement`, `FlexElement`, `QrElement`, `BarcodeElement`, `ImageElement`, `SeparatorElement`, `EachElement`, `IfElement` |
122144
| Template Engine | `TemplateExpander`, `TemplateProcessor`, `ExpressionLexer`, `ExpressionEvaluator`, `TemplateContext` |
123145
| Layout | `LayoutEngine`, `LayoutNode`, `LayoutContext`, `LayoutSize`, `IntrinsicSize`, `Unit`, `UnitParser` |
124-
| Rendering | `SkiaRenderer`, `TextRenderer`, `FontManager`, `ColorParser`, `RotationHelper`, `BmpEncoder` |
146+
| Rendering | `SkiaRender` (IFlexRender impl), `SkiaRenderer`, `TextRenderer`, `FontManager`, `ColorParser`, `RotationHelper`, `BmpEncoder` |
125147
| Providers | `IContentProvider<T,O>`, `QrProvider`, `BarcodeProvider`, `ImageProvider` |
126-
| DI | `ServiceCollectionExtensions.AddFlexRender()`, `FlexRenderBuilder`, `FlexRenderOptions` |
127-
| Abstractions | `IFlexRenderer`, `ILayoutRenderer<T>`, `ITemplateParser` |
148+
| Loaders | `FileResourceLoader`, `Base64ResourceLoader`, `EmbeddedResourceLoader`, `HttpResourceLoader` |
149+
| DI | `ServiceCollectionExtensions.AddFlexRender()` |
128150
| Values | `TemplateValue` (abstract), `StringValue`, `NumberValue`, `BoolValue`, `NullValue`, `ArrayValue`, `ObjectValue` |
129151

130152
## Coding Conventions

FlexRender.slnx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
<Folder Name="/src/">
33
<Project Path="src/FlexRender.Core/FlexRender.Core.csproj" />
44
<Project Path="src/FlexRender.Yaml/FlexRender.Yaml.csproj" />
5+
<Project Path="src/FlexRender.Http/FlexRender.Http.csproj" />
56
<Project Path="src/FlexRender.Skia/FlexRender.Skia.csproj" />
67
<Project Path="src/FlexRender.QrCode/FlexRender.QrCode.csproj" />
78
<Project Path="src/FlexRender.Barcode/FlexRender.Barcode.csproj" />

README.md

Lines changed: 100 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ A .NET library for rendering images from YAML templates with flexbox-like layout
1313
- **Flexbox Layout** - Row/column direction, wrap, justify, align, gap
1414
- **Template Engine** - Variables, loops (`type: each`), conditionals (`type: if`)
1515
- **Multiple Content Types** - Text, images, QR codes, barcodes
16-
- **Output Formats** - PNG, JPEG, BMP
16+
- **Output Formats** - PNG, JPEG, BMP, Raw
1717
- **CLI Tool** - Render templates from command line
1818
- **AOT Compatible** - No reflection, works with Native AOT
1919
- **Modular Architecture** - Install only what you need
@@ -58,6 +58,7 @@ dotnet add package FlexRender
5858
| `FlexRender.Skia` | SkiaSharp renderer |
5959
| `FlexRender.QrCode` | QR code support |
6060
| `FlexRender.Barcode` | Barcode support |
61+
| `FlexRender.Http` | HTTP/HTTPS resource loading |
6162
| `FlexRender.DependencyInjection` | Microsoft DI integration |
6263

6364
```bash
@@ -87,13 +88,6 @@ dotnet add package SkiaSharp.NativeAssets.Linux.NoDependencies
8788
dotnet tool install -g FlexRender.Cli
8889
```
8990

90-
### Dependency Injection
91-
92-
```csharp
93-
// Register all FlexRender services
94-
services.AddFlexRender();
95-
```
96-
9791
## Quick Start
9892

9993
### 1. Create a template (receipt.yaml)
@@ -149,43 +143,67 @@ layout:
149143
### 2. Render with code
150144
151145
```csharp
152-
using FlexRender.Parsing;
153-
using FlexRender.Rendering;
154-
using FlexRender.Values;
155-
156-
var parser = new TemplateParser();
157-
var renderer = new SkiaRenderer();
158-
159-
var template = parser.ParseFile("receipt.yaml");
146+
// Build renderer with fluent API
147+
var render = new FlexRenderBuilder()
148+
.WithBasePath("./templates")
149+
.WithSkia(skia => skia
150+
.WithQr()
151+
.WithBarcode())
152+
.Build();
160153

161154
var data = new ObjectValue
162155
{
163156
["shopName"] = "My Shop",
164157
["total"] = 1500,
165158
["paymentUrl"] = "https://pay.example.com/123",
166-
["items"] = new ArrayValue(new TemplateValue[]
167-
{
159+
["items"] = new ArrayValue(
168160
new ObjectValue { ["name"] = "Product 1", ["price"] = 500 },
169-
new ObjectValue { ["name"] = "Product 2", ["price"] = 1000 }
170-
})
161+
new ObjectValue { ["name"] = "Product 2", ["price"] = 1000 })
171162
};
172163

173-
// Render to bitmap (async API)
174-
using var bitmap = await renderer.Render(template, data);
164+
// Render to PNG bytes
165+
byte[] pngBytes = await render.RenderFile("receipt.yaml", data);
166+
await File.WriteAllBytesAsync("receipt.png", pngBytes);
167+
```
168+
169+
### 3. With Dependency Injection
175170

176-
// Save to file
177-
using var image = SKImage.FromBitmap(bitmap);
178-
using var pngData = image.Encode(SKEncodedImageFormat.Png, 100);
179-
using var stream = File.OpenWrite("receipt.png");
180-
pngData.SaveTo(stream);
171+
```csharp
172+
// Program.cs
173+
services.AddFlexRender(builder => builder
174+
.WithBasePath("/app/templates")
175+
.WithSkia(skia => skia
176+
.WithQr()
177+
.WithBarcode()));
178+
179+
// In your service
180+
public class ReceiptService(IFlexRender render)
181+
{
182+
public async Task<byte[]> Generate(ReceiptData data)
183+
{
184+
var values = MapToObjectValue(data);
185+
return await render.RenderFile("receipt.yaml", values);
186+
}
187+
}
181188
```
182189

183-
### 3. Or use CLI
190+
### 4. Or use CLI
184191

185192
```bash
186193
flexrender render receipt.yaml -d data.json -o receipt.png
187194
```
188195

196+
## Output Formats
197+
198+
| Format | Extension | Description |
199+
|--------|-----------|-------------|
200+
| PNG | `.png` | Lossless compression, best quality |
201+
| JPEG | `.jpg` | Lossy compression, smaller file size |
202+
| BMP | `.bmp` | Uncompressed bitmap |
203+
| Raw | `.raw` | Raw BGRA pixel data (4 bytes per pixel) |
204+
205+
The Raw format outputs uncompressed pixel data in BGRA order (Blue, Green, Red, Alpha), 4 bytes per pixel, row by row from top to bottom. Useful for direct hardware integration or custom processing pipelines.
206+
189207
## Template Syntax
190208

191209
### Canvas Settings
@@ -382,57 +400,67 @@ flexrender watch template.yaml -d data.json -o preview.png
382400

383401
## API Reference
384402

385-
### TemplateParser
403+
### FlexRenderBuilder (recommended)
386404

387405
```csharp
406+
// Minimal setup
407+
var render = new FlexRenderBuilder()
408+
.WithSkia()
409+
.Build();
410+
411+
// Full configuration
412+
var render = new FlexRenderBuilder()
413+
.WithHttpLoader() // Enable HTTP resource loading
414+
.WithEmbeddedLoader(typeof(Program).Assembly) // Load from embedded resources
415+
.WithBasePath("./templates") // Base path for file resolution
416+
.WithLimits(limits => limits.MaxRenderDepth = 200)
417+
.WithSkia(skia => skia
418+
.WithQr() // Enable QR code support
419+
.WithBarcode()) // Enable barcode support
420+
.Build();
421+
422+
// Render from YAML file (requires FlexRender.Yaml package)
423+
byte[] png = await render.RenderFile("receipt.yaml", data);
424+
byte[] jpg = await render.RenderFile("receipt.yaml", data, ImageFormat.Jpeg);
425+
426+
// Render from YAML string
427+
byte[] png = await render.RenderYaml(yamlString, data);
428+
429+
// Render from parsed template (for caching)
388430
var parser = new TemplateParser();
389-
390-
// Parse from string
391-
Template template = parser.Parse(yamlString);
392-
393-
// Parse from file (with 1MB size limit)
394-
Template template = parser.ParseFile("template.yaml");
395-
396-
// Check supported element types
397-
IReadOnlyCollection<string> types = parser.SupportedElementTypes;
398-
// Returns: ["text", "qr", "barcode", "image", "flex", "separator", "each", "if"]
431+
var template = parser.Parse(yamlString);
432+
byte[] png = await render.Render(template, data);
433+
434+
// Sandboxed mode (no file system access)
435+
var sandboxed = new FlexRenderBuilder()
436+
.WithoutDefaultLoaders()
437+
.WithEmbeddedLoader(typeof(Program).Assembly)
438+
.WithSkia()
439+
.Build();
399440
```
400441

401-
### TemplateExpander
402-
403-
Templates with `type: each` and `type: if` are automatically expanded during rendering.
404-
For manual expansion (useful for template caching):
405-
406-
```csharp
407-
var expander = new TemplateExpander();
408-
409-
// Expand control flow elements with data
410-
Template expanded = expander.Expand(template, data);
411-
412-
// The expanded template has no Each/If elements - they're replaced with concrete elements
413-
// This allows parsing once and rendering with different data
414-
```
415-
416-
### SkiaRenderer
442+
### Dependency Injection
417443

418444
```csharp
419-
using var renderer = new SkiaRenderer();
420-
421-
// Set base font size (default: 12)
422-
renderer.BaseFontSize = 14f;
423-
424-
// Measure required size
425-
SKSize size = renderer.Measure(template, data);
445+
// Basic registration
446+
services.AddFlexRender(builder => builder
447+
.WithSkia(skia => skia.WithQr().WithBarcode()));
426448

427-
// Render to canvas
428-
renderer.Render(canvas, template, data);
429-
renderer.Render(canvas, template, data, offset: new SKPoint(10, 10));
430-
431-
// Render to bitmap
432-
renderer.Render(bitmap, template, data);
433-
434-
// Async render via ILayoutRenderer<SKBitmap>
435-
using var bitmap = await renderer.Render(template, data);
449+
// With service provider access
450+
services.AddFlexRender((sp, builder) =>
451+
{
452+
var config = sp.GetRequiredService<IConfiguration>();
453+
builder
454+
.WithBasePath(config["FlexRender:BasePath"] ?? "./templates")
455+
.WithSkia(skia => skia.WithQr().WithBarcode());
456+
});
457+
458+
// Inject IFlexRender
459+
public class MyService(IFlexRender render)
460+
{
461+
public Task<byte[]> GenerateImage(ObjectValue data)
462+
=> render.RenderFile("template.yaml", data);
463+
}
436464
```
437465

438466
### TemplateValue Types
@@ -445,17 +473,15 @@ TemplateValue str = new StringValue("hello");
445473
// Number
446474
TemplateValue num = 42; // implicit from int
447475
TemplateValue num = 3.14; // implicit from double
448-
TemplateValue num = new NumberValue(42);
449476
450477
// Boolean
451478
TemplateValue flag = true; // implicit conversion
452-
TemplateValue flag = new BoolValue(true);
453479
454480
// Null
455481
TemplateValue nil = NullValue.Instance;
456482

457483
// Array
458-
var array = new ArrayValue(new TemplateValue[] { "a", "b", "c" });
484+
var array = new ArrayValue("a", "b", "c");
459485
int count = array.Count;
460486
TemplateValue first = array[0];
461487

examples/AstRenderExample/AstRenderExample.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
<ItemGroup>
88
<ProjectReference Include="..\..\src\FlexRender.Core\FlexRender.Core.csproj" />
99
<ProjectReference Include="..\..\src\FlexRender.Skia\FlexRender.Skia.csproj" />
10+
<ProjectReference Include="..\..\src\FlexRender.QrCode\FlexRender.QrCode.csproj" />
11+
<ProjectReference Include="..\..\src\FlexRender.Barcode\FlexRender.Barcode.csproj" />
1012
</ItemGroup>
1113

1214
</Project>

0 commit comments

Comments
 (0)