-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathllms.txt
More file actions
660 lines (522 loc) · 31.2 KB
/
Copy pathllms.txt
File metadata and controls
660 lines (522 loc) · 31.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
# FlexRender
> A modular .NET library for rendering images from YAML templates with flexbox layout. Render-backend agnostic with SkiaSharp as the default backend. AOT-compatible, no reflection.
**For LLM Agents:** This is a concise overview. For comprehensive reference, see [`llms-full.txt`](llms-full.txt).
## Project Structure
```
src/FlexRender.Core/ # Core library (0 external dependencies)
Abstractions/ # ILayoutRenderer<T>, ITemplateParser, IResourceLoader
Configuration/ # ResourceLimits, FlexRenderOptions
Layout/ # Two-pass flexbox layout engine (LayoutEngine, LayoutNode, LayoutSize)
Units/ # Unit, UnitParser, PaddingValues, PaddingParser
Loaders/ # FileResourceLoader, Base64ResourceLoader, EmbeddedResourceLoader
Parsing/Ast/ # Template, CanvasSettings, TemplateElement, TextElement, FlexElement, etc.
Parsing/Nodes/ # Format-neutral node model (TemplateMapping, TemplateSequence, TemplateScalar)
Parsing/Engine/ # Shared parsing engine (TemplateEngine, ElementParsers, ChartParsers, ShapeParsers, KnownProperties)
TemplateEngine/ # TemplateProcessor, ExpressionLexer, ExpressionEvaluator
Values/ # TemplateValue hierarchy (StringValue, NumberValue, etc.)
src/FlexRender.Yaml/ # YAML facade: YamlDotNet -> neutral nodes -> Core engine (-> Core + YamlDotNet)
Parsing/ # TemplateParser (facade), YamlNodeConverter
src/FlexRender.Xml/ # XML facade: XDocument -> neutral nodes -> Core engine (-> Core only)
Parsing/ # XmlTemplateParser, XmlNodeConverter
src/FlexRender.Http/ # HTTP resource loader (-> Core)
src/FlexRender.Skia.Render/ # SkiaSharp renderer (-> Core + SkiaSharp)
Abstractions/ # ISkiaRenderer, IFontLoader, IImageLoader, IFontManager
Rendering/ # SkiaRenderer, TextRenderer, FontManager, ColorParser, RotationHelper, BmpEncoder
Loaders/ # FontLoader, ImageLoader
Providers/ # IContentProvider<T,O>, ImageProvider
src/FlexRender.Skia/ # Skia backend meta-package (renderer + providers)
src/FlexRender.QrCode.Skia.Render/ # QR provider for Skia (-> Skia + QRCoder)
src/FlexRender.QrCode.Svg.Render/ # QR provider for SVG (-> Svg)
src/FlexRender.QrCode.ImageSharp.Render/ # QR provider for ImageSharp (-> ImageSharp + QRCoder)
src/FlexRender.QrCode/ # QR meta-package (references all renderers)
src/FlexRender.Barcode.Skia.Render/ # Barcode provider for Skia
src/FlexRender.Barcode.Svg.Render/ # Barcode provider for SVG
src/FlexRender.Barcode.ImageSharp.Render/ # Barcode provider for ImageSharp
src/FlexRender.Barcode/ # Barcode meta-package (references all renderers)
src/FlexRender.SvgElement.Skia.Render/ # SvgElement provider for Skia (-> Svg.Skia)
src/FlexRender.SvgElement.Svg.Render/ # SvgElement provider for SVG (native)
src/FlexRender.SvgElement/ # SvgElement meta-package (references all renderers)
src/FlexRender.HarfBuzz/ # HarfBuzz text shaping (-> Skia + SkiaSharp.HarfBuzz)
src/FlexRender.ImageSharp.Render/ # ImageSharp renderer (-> Core + SixLabors.ImageSharp)
Rendering/ # ImageSharpRenderingEngine, ImageSharpTextRenderer, ImageSharpFontManager
src/FlexRender.ImageSharp/ # ImageSharp backend meta-package (renderer + providers)
src/FlexRender.Svg.Render/ # SVG output renderer (-> Core)
src/FlexRender.Svg/ # SVG backend meta-package (renderer + providers)
src/FlexRender.Content.Markdown/ # Markdown content parser (-> Core + Markdig)
src/FlexRender.Content.Html/ # HTML content parser (-> Core + HtmlAgilityPack)
src/FlexRender.Content.Ndc/ # NDC (ATM receipt) content parser (-> Core)
src/FlexRender.DependencyInjection/ # Microsoft.Extensions.DI integration
src/FlexRender.MetaPackage/ # Meta-package (core + all backends + DI)
src/FlexRender.Cli/ # CLI tool (System.CommandLine, uses all packages)
tests/FlexRender.Tests/ # Unit + snapshot tests
tests/FlexRender.Cli.Tests/ # CLI integration tests
examples/ # Example YAML templates (see "Example Templates" section below)
```
## Rendering Pipeline
```
YAML Template
-> TemplateParser (YAML -> AST: Template with CanvasSettings + element tree)
+ KnownProperties (validate YAML keys, warn on unknown properties with "Did you mean?" suggestions)
-> TemplateExpander (expand EachElement/IfElement to concrete elements based on data) [async only]
-> TemplateProcessor (resolve {{variable}} expressions in element properties) [async only]
-> LayoutEngine (two-pass: MeasureAllIntrinsics -> ComputeLayout -> LayoutNode tree)
-> SkiaRenderer (traverse LayoutNode tree -> draw to SKBitmap via SkiaSharp)
OR ImageSharpRenderer (traverse LayoutNode tree -> draw via SixLabors.ImageSharp)
```
The entire pipeline is **async-only**. There are no synchronous `Expand()`, `Process()`, `Measure()`, or `Render()` methods. All public API methods return `Task` or `Task<T>`.
Templates can be parsed once and cached for reuse with different data.
## Element Types
Sixteen element types, each a sealed class extending `TemplateElement`:
| Type | Key Properties |
|------|---------------|
| **text** | content, font, fontFamily, fontWeight (thin/light/normal/medium/semi-bold/bold/extra-bold/black or 100-900), fontStyle (normal/italic/oblique), size, color, align (left/center/right/start/end), wrap, overflow (ellipsis/clip/visible), maxLines, lineHeight |
| **flex** | direction (row/column), wrap, gap, rowGap, columnGap, justify, align, alignContent, overflow, children |
| **image** | src, width, height, fit (fill/contain/cover/none) |
| **qr** | data, size, errorCorrection (L/M/Q/H), foreground -- requires `FlexRender.QrCode.*` (Skia/Svg/ImageSharp) |
| **barcode** | data, format (code128/code39/ean13/ean8/upc), width, height, showText, foreground -- requires `FlexRender.Barcode.*` (Skia/Svg/ImageSharp) |
| **svg** | src, content, width, height, fit (fill/contain/cover/none) -- requires `FlexRender.SvgElement.*` (Skia/Svg) |
| **separator** | orientation (horizontal/vertical), style (dotted/dashed/solid), thickness, color |
| **rect** | fill (hex color OR gradient object), stroke, stroke-width (px, default 0), opacity (0..1), radius (px/em corner rounding) -- flex box drawn as a filled/stroked rectangle |
| **circle** | fill (hex color OR gradient object), stroke, stroke-width, opacity, size (shorthand setting width & height = diameter) -- flex box drawn as a circle |
| **ellipse** | fill (hex color OR gradient object), stroke, stroke-width, opacity -- flex box drawn as an ellipse filling its width/height |
| **draw** | shapes (ordered list painted in list order); shape kinds: line, polyline, rect, circle, path (abs commands M/L/Q/C/Z); absolute coords relative to element top-left; capped by `MaxShapesPerDraw` (default 1000). Box shapes accept gradient fill objects |
| **chart** | chart-type (bar/line/area/pie/donut/scatter/bubble/gauge/progress/sparkline/heatmap/radar, default bar), width/height (required), categories (x-axis/slice labels; radar spokes), series (list of {label?, data}; data = inline number array, OR `[[x,y],...]`/`[[x,y,r],...]` tuples for scatter/bubble, OR `{{ expr }}` resolving to a number array), palette (named default/ocean/sunset/forest/mono/vivid OR explicit `["#hex",...]`), theme (light/dark/minimal override), legend (top/bottom/left/right/none, default bottom), title; bar only: horizontal, stacked; line/area/sparkline: smooth, points; pie/donut: labels (percent/value/none); gauge/progress only: value, max (default 100), label (centered caption); heatmap only: x-labels, y-labels, cell-values (print number per cell). Sparkline is a chrome-free inline line. Heatmap: each series is a grid ROW, x-labels/y-labels label columns/rows (x-labels falls back to categories), cell color encodes value. Radar: categories are spokes, each series is a closed polygon. Empty/missing series renders a "no data" placeholder, never an error. Capped by `MaxSeriesPerChart` (50) and `MaxDataPointsPerSeries` (10000) |
| **table** | array, as, columns (key/label/width/grow/align/format), rows, headerFont/headerFontWeight/headerFontStyle/headerFontFamily/headerColor/headerSize/headerBackground -- expands to flex tree |
| **each** | array, as, children -- iteration over data arrays |
| **if** | condition, equals/notEquals, then, elseIf, else -- conditional rendering |
| **content** | source, format, options -- embeds formatted text (Markdown/HTML/NDC) parsed into AST subtree at render time. Requires content parser: `.WithMarkdown()` or `.WithHtml()` or `.WithNdc()` |
All elements share common properties from `TemplateElement`: padding, margin, background, opacity, box-shadow, rotate, display (flex/none), position, top, right, bottom, left, aspectRatio, minWidth, maxWidth, minHeight, maxHeight, text-direction, border, border-top, border-right, border-bottom, border-left, border-width, border-color, border-style, border-radius.
Element-level `rotate` supports named values (`none`, `left`, `right`, `flip`) and arbitrary degrees (e.g., `"30"`). Rotation is rendered for: text, image, qr, barcode. Canvas-level `rotate` applies post-render rotation to the entire output.
All elements (except flex containers) have flex-item properties: grow, shrink, basis, order, alignSelf, width, height.
### Borders
- `border`: shorthand for all sides -- `"width style color"` (e.g., `"2 solid #333"`, `"1 dashed"`, `"3"`)
- `border-top`, `border-right`, `border-bottom`, `border-left`: per-side shorthand overrides
- `border-width`, `border-color`, `border-style`: individual property overrides for all sides
- `border-radius`: corner rounding (px, em, %)
- Border styles: `solid`, `dashed`, `dotted`, `none`
- Borders consume layout space (added to element size alongside padding)
### Order
- `order`: integer (default `0`). Controls visual display order of flex items. Lower values appear first. Negative values supported. Items with equal order preserve source order (stable sort).
### Positioning
- `position`: `static` (default), `relative`, `absolute`
- `top`, `right`, `bottom`, `left`: inset values for positioned elements (px, %, em)
- Relative: offset from normal flow position
- Absolute: removed from flow, positioned relative to nearest flex container
### Aspect Ratio
- `aspectRatio`: float (width / height). When one dimension is known, the other is computed automatically.
### Overflow
- FlexElement-only: `overflow`: `visible` (default), `hidden` -- clips content at container bounds when set to `hidden`
### Auto Margins
- `margin: "auto"` -- all sides auto (centers both axes)
- `margin: "0 auto"` -- horizontal centering within flex container
- Auto margins on main axis consume free space before justify-content
- Auto margins on cross axis override align-items/align-self
## RTL (Right-to-Left) Support
- Canvas `text-direction`: `ltr` (default), `rtl` -- sets default text direction for the entire template
- Element `text-direction`: `ltr`, `rtl`, or null (inherit from parent/canvas) -- per-element override
- Text `align`: `start` and `end` are logical values that resolve based on direction:
- `start` resolves to `left` in LTR, `right` in RTL
- `end` resolves to `right` in LTR, `left` in RTL
- Row layout is mirrored in RTL: items flow right-to-left instead of left-to-right
- Column layout is unaffected by direction
- HarfBuzz text shaping: optional `FlexRender.HarfBuzz` package provides proper Arabic/Hebrew glyph shaping via `.WithHarfBuzz()` on the Skia builder
- Arabic font support: use an Arabic-capable font (e.g., Noto Sans Arabic) in the `fonts` section for Arabic text rendering. Combine with `text-direction: rtl` and HarfBuzz for full Arabic support
## Fonts
**Two registration formats** in the `fonts:` YAML section:
- **Dictionary format** (legacy): `fonts: { default: "path.ttf", heading: "path.ttf" }`
- **List format** (recommended): `fonts: [ "path.ttf", { path: "path.ttf", name: heading, fallback: "Arial" } ]`
- First unnamed font automatically becomes `default`/`main`
- Simple strings and objects with `path`/`name`/`fallback` can be mixed
**Text element font properties:**
- `font`: registered font name (default: `main`)
- `fontFamily`: CSS-like family name -- searches registered fonts by FamilyName, then system fonts
- `fontWeight`: `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
- `fontStyle`: `normal`, `italic`, `oblique`
**Resolution priority:** `font` (registered name) > `fontFamily` (family name) > fallback (default)
**Automatic sibling discovery:** when `fontWeight`/`fontStyle` is used, scans the font file's directory for `.ttf`/`.otf` siblings with matching family name and weight/style (within +/-100 units)
**Table header font properties:** `headerFont`, `headerFontWeight`, `headerFontStyle`, `headerFontFamily` (aliases: `header-font`, `header-fontWeight`, `header-fontStyle`, `header-fontFamily`)
- Variable fonts are NOT supported (SkiaSharp 3.x limitation) -- use separate static font files per weight
- System fonts are used when no custom font is registered
## Non-Uniform Padding
Padding and margin support CSS-like shorthand with 1 to 4 space-separated values:
- `"20"` -- all sides = 20
- `"20 40"` -- top/bottom = 20, left/right = 40
- `"20 40 30"` -- top = 20, left/right = 40, bottom = 30
- `"20 40 30 10"` -- top = 20, right = 40, bottom = 30, left = 10
Values can use px, %, or em units (e.g., `"10px 5% 2em 20"`).
## Expression Syntax
- `{{variable}}` -- simple substitution
- `{{user.name}}` -- dot notation path
- `{{items[0]}}` -- array index
- `{{@index}}`, `{{@first}}`, `{{@last}}` -- loop variables (inside `type: each`)
- `{{price * quantity}}` -- arithmetic (`+`, `-`, `*`, `/`)
- `{{total > 1000}}` -- comparison (`==`, `!=`, `<`, `>`, `<=`, `>=`)
- `{{!active}}` -- logical NOT
- `{{name || 'Guest'}}` -- truthy coalescing (fallback for null, "", 0, false)
- `{{a && b}}` -- logical AND (returns first falsy or last value)
- `{{nickname ?? name ?? 'Guest'}}` -- null coalescing
- `{{price | currency}}` -- filters (8 built-in: currency, currencySymbol, number, upper, lower, trim, truncate, format)
- `{{total * 1.1 | currency}}` -- combined arithmetic and filter
- Literals: `true`, `false`, `null` (e.g., `{{status == null}}`, `{{active == true}}`)
### Culture Support
- `template.culture`: optional culture string in YAML template (e.g., `"ru-RU"`)
- `RenderOptions.Culture`: per-call CultureInfo override
- Priority: RenderOptions.Culture > template.culture > InvariantCulture
- Affects built-in filters (enabled by default): currency, currencySymbol, number, format
### Visual Effects
- `opacity`: float (0.0-1.0), default 1.0 -- element transparency, affects element and all children
- `box-shadow`: `"offsetX offsetY blurRadius color"` -- drop shadow behind element (e.g., `"4 4 8 #00000040"`)
- `background` supports CSS gradients: `linear-gradient(to right, #ff0000, #0000ff)`, `radial-gradient(#ffffff, #000000)` with direction keywords, angle degrees, and color stops with optional percentage positions
## Control Flow (AST-Level)
### Iteration
```yaml
- type: each
array: items # path to data array
as: item # optional variable name
children:
- type: text
content: "{{item.name}}"
```
### Conditional
Operators: truthy (no key), `equals`, `notEquals`, `in`, `notIn`, `contains`, `greaterThan`, `greaterThanOrEqual`, `lessThan`, `lessThanOrEqual`, `hasItems`, `countEquals`, `countGreaterThan`.
```yaml
# Truthy check
- type: if
condition: isPremium
then: [...]
else: [...]
# Equality (numbers, strings, bool, arrays, null)
- type: if
condition: status
equals: "paid" # or notEquals: "cancelled"
then: [...]
# In list
- type: if
condition: role
in: ["admin", "moderator"]
then: [...]
# Numeric comparison
- type: if
condition: total
greaterThan: 1000
then: [...]
# Array checks
- type: if
condition: items
hasItems: true # or countGreaterThan: 5
then: [...]
# Else-if chain
- type: if
condition: status
equals: "paid"
then: [...]
elseIf:
condition: status
equals: "pending"
then: [...]
else: [...]
```
### Content Embedding
```yaml
- type: content
source: "{{body}}" # text with formatting (from data)
format: markdown # or "html" -- must match registered parser
```
Requires content parser registration: `.WithMarkdown()` (Markdig) or `.WithHtml()` (HtmlAgilityPack).
Converts formatted text into FlexRender AST elements at render time (bold → FontWeight.Bold, italic → FontStyle.Italic, headings, lists, etc.).
Content source resolution: `text:` prefix → plain text; `{{variable}}` → `string` or `BytesValue`; everything else delegates to IResourceLoader chain (`data:`, `base64:`, `file:`, `http://`, `embedded://`).
Binary sources pass `ReadOnlyMemory<byte>` directly to `IBinaryContentParser` implementations (e.g., NDC parser).
**Important:** Content element expansion is async-only. The `ExpandAsync()` method must be used; there is no synchronous `Expand()` alternative.
#### NDC Content
```yaml
# Load NDC data from a local .bin file
- type: content
source: "file:ndc-data/receipt.bin"
format: ndc
options:
columns: 40
font_family: "JetBrains Mono"
charsets:
I:
font: bold
font_style: bold
encoding: qwerty-jcuken
uppercase: true
J:
encoding: qwerty-jcuken
"2":
font: default
```
Requires NDC parser registration: `.WithNdc()` (FlexRender.Content.Ndc). Parses binary NDC ATM printer data streams. Source can be `file:path`, `{{variable}}` (with `BytesValue`), or `data:` URI. Options: `columns` (default 40), `input_encoding` (`latin1`/`utf-8`/`ascii`), `charsets` with per-charset `encoding` (`qwerty-jcuken`), `font_style`, `uppercase`, `color`.
## YAML Validation
The parser validates all YAML property names against known properties for each element type. Unknown properties trigger a `TemplateParseException` with a "Did you mean?" suggestion using Levenshtein distance:
```
Unknown property 'colour' for element type 'text'. Did you mean 'color'?
```
This catches typos like `colour` → `color`, `backgrund` → `background`, `dierction` → `direction`.
## Supported Units
- `px` -- pixels (default when no unit specified)
- `%` -- percentage of parent size
- `em` -- relative to font size
- `auto` -- automatic sizing
## Template YAML Format
```yaml
template:
name: "receipt"
version: 1
culture: "ru-RU" # optional: culture for number/date formatting
fonts: # list format (recommended) or dictionary format
- "assets/fonts/Inter-Regular.ttf" # first unnamed = default/main
- "assets/fonts/Inter-Bold.ttf"
canvas:
fixed: width # width | height | both | none
width: 300
height: 0 # 0 = auto when not fixed
background: "#ffffff"
rotate: "none" # none | left | right | flip | degrees
text-direction: ltr # ltr | rtl (text/layout direction)
layout:
- type: text
content: "Hello {{name}}"
font: "main"
size: "16px"
color: "#000000"
padding: "10 20"
- type: separator
style: dashed
color: "#cccccc"
thickness: 2
- type: rect
width: 100
height: 50
fill: "#4A90D9"
stroke: "#333333"
stroke-width: 2
radius: 4
- type: circle
size: 40
fill: "#e74c3c"
- type: ellipse
width: 120
height: 60
fill: "#2ecc71"
- type: rect
width: 100
height: 100
fill:
gradient: linear
colors: ["#f00", "#00f"]
angle: 45
- type: draw
width: 400
height: 200
shapes:
- line: {x1: 0, y1: 100, x2: 400, y2: 50, stroke: "#333", stroke-width: 2}
- circle: {cx: 200, cy: 75, r: 30, fill: "#e74c3c"}
- path: {d: "M 0 0 L 100 50 Q 150 0 200 50 Z", fill: "#2ecc71"}
- type: flex
direction: row
gap: "10"
children:
- type: text
content: "Left"
grow: 1
- type: text
content: "Right"
```
## Configurable Resource Limits
The `ResourceLimits` class centralizes all security limits. Defaults are safe-by-default.
| Property | Default | Purpose |
|----------|---------|---------|
| MaxTemplateFileSize | 1 MB | YAML template file size |
| MaxDataFileSize | 10 MB | JSON data file size |
| MaxTemplateNestingDepth | 100 | Expression nesting |
| MaxRenderDepth | 100 | Render tree recursion |
| MaxImageSize | 10 MB | Image loading |
| MaxFlexLines | 1000 | Maximum flex lines when wrapping |
| MaxShapesPerDraw | 1000 | Maximum shapes in a single `draw` element |
| MaxSeriesPerChart | 50 | Maximum series in a single `chart` element |
| MaxDataPointsPerSeries | 10000 | Maximum data points per chart series |
Configure via builder: `builder.WithLimits(limits => { limits.MaxRenderDepth = 200; })`
## Builder API
Templates may be authored in YAML (`FlexRender.Yaml`) or XML (`FlexRender.Xml`, via `XmlTemplateParser` / `RenderXml`). Both produce the same `Template` AST, with identical validation and resource limits. See `docs/wiki/Xml-Syntax.md` for the XML<->YAML mapping.
### Without DI (Skia)
```csharp
var render = new FlexRenderBuilder()
.WithHttpLoader(configure: opts => {
opts.Timeout = TimeSpan.FromSeconds(60);
opts.MaxResourceSize = 20 * 1024 * 1024;
})
.WithBasePath("./templates")
.WithLimits(limits => limits.MaxRenderDepth = 200)
.WithMarkdown() // optional: Markdown content parsing
.WithHtml() // optional: HTML content parsing
.WithNdc() // optional: NDC ATM receipt parsing
.WithSkia(skia => skia
.WithQr()
.WithBarcode()
.WithHarfBuzz())
.Build();
byte[] png = await render.RenderFile("receipt.yaml", data);
```
### Without DI (ImageSharp -- pure .NET, no native deps)
```csharp
var render = new FlexRenderBuilder()
.WithBasePath("./templates")
.WithImageSharp(imageSharp => imageSharp.WithQr().WithBarcode())
.Build();
byte[] png = await render.RenderFile("receipt.yaml", data);
```
> **Note:** ImageSharp now supports QR codes and barcodes via `FlexRender.QrCode.ImageSharp.Render` and `FlexRender.Barcode.ImageSharp.Render`. SVG elements are not supported in ImageSharp. It is NOT AOT-compatible.
### SVG Output with ImageSharp Raster Backend
```csharp
var render = new FlexRenderBuilder()
.WithSvg(svg => svg.WithRasterBackend(
ImageSharpFlexRenderBuilderExtensions.CreateRendererFactory()))
.Build();
```
### With DI
```csharp
services.AddFlexRender(builder => builder
.WithBasePath("/app/templates")
.WithSkia(skia => skia.WithQr().WithBarcode()));
// Inject IFlexRender
public class MyService(IFlexRender render)
{
public Task<byte[]> Generate(ObjectValue data)
=> render.RenderFile("template.yaml", data);
}
```
### Sandboxed (no file access)
```csharp
var render = new FlexRenderBuilder()
.WithoutDefaultLoaders()
.WithoutDefaultFilters() // Remove all 8 built-in filters (enabled by default)
.WithFilter(new MyFilter()) // Register custom filters
.WithEmbeddedLoader(typeof(Program).Assembly)
.WithSkia()
.Build();
```
## CLI Tool
### Install as dotnet tool
```bash
dotnet tool install -g flexrender-cli
```
After installation the `flexrender` command is available globally:
```bash
flexrender render template.yaml -d data.json -o output.png
flexrender validate template.yaml
flexrender info template.yaml
flexrender watch template.yaml -d data.json -o preview.png
flexrender debug-layout template.yaml -d data.json
```
### Native AOT binary
The CLI is fully AOT-compatible. Build a standalone native binary (no .NET runtime required):
```bash
dotnet publish src/FlexRender.Cli -c Release -r <RID> /p:PublishAot=true
```
Common RIDs: `osx-arm64`, `osx-x64`, `linux-x64`, `linux-arm64`, `win-x64`.
### Distribution channels
| Channel | Install | Requires .NET? | Use case |
|---------|---------|---------------|----------|
| dotnet tool | `dotnet tool install -g flexrender-cli` | Yes | .NET developers |
| GitHub Release | Download binary from Releases | No | Everyone else (CI, Docker, non-.NET) |
| Build from source | `dotnet publish /p:PublishAot=true -r <RID>` | Build-time only | Custom builds |
Native AOT binaries are attached to every GitHub Release for: osx-arm64, linux-x64, win-x64.
### Run from source (development)
```bash
dotnet run --project src/FlexRender.Cli -- render template.yaml -d data.json -o output.png
```
Global options: `-v`/`--verbose`, `--fonts <dir>`, `--scale <float>`, `--backend <skia|imagesharp>`
The `--backend` option selects the rendering backend: `skia` (default, full features) or `imagesharp` (pure .NET, no native deps, QR/barcode support with `FlexRender.QrCode.ImageSharp.Render` and `FlexRender.Barcode.ImageSharp.Render`, no SVG elements).
Note: The `--scale` option uses invariant culture -- always use `.` as the decimal separator (e.g., `--scale 2.0`).
## Example Templates
Reference these YAML files for real-world template patterns. Each file is a complete, working template.
### Complete Templates (examples/)
| File | Description | Key Features |
|------|-------------|-------------|
| `examples/receipt.yaml` | Thermal receipt | Basic text, separator, flex row, data binding |
| `examples/receipt-dynamic.yaml` | Receipt with loops | `type: each` iteration, `@index`/`@last` variables |
| `examples/ticket.yaml` | Event ticket | Horizontal layout, QR code, background colors |
| `examples/label.yaml` | Product label | Compact layout, barcode, border-radius |
| `examples/table-invoice.yaml` | Invoice with table | `type: table` element, columns, header styling |
| `examples/expressions-demo.yaml` | Expression showcase | Arithmetic, filters, null coalescing, formatting |
| `examples/visual-effects.yaml` | Visual effects | Gradients, box-shadow, opacity, border-radius |
| `examples/svg-icons.yaml` | SVG icons | Inline SVG elements, vector shapes |
| `examples/showcase.yaml` | Full feature showcase | All element types, 14 sections, HTTP images |
| `examples/showcase-capabilities.yaml` | Renderer comparison | Conditional rendering per backend (`type: if` with `renderer`) |
| `examples/showcase-all-features.yaml` | All features showcase | Every supported feature in one template |
| `examples/markdown-content.yaml` | Markdown content demo | `type: content` with `format: markdown`, dynamic body |
| `examples/html-content.yaml` | HTML content demo | `type: content` with `format: html`, inline styles |
### Cookbook (docs/wiki/Cookbook.md)
9 practical recipes for common use cases: simple receipt, dynamic receipt with loops, table invoice, NDC ATM receipt, receipt with QR code, shipping label, event ticket, conditional content, and multi-language template. Each recipe is a complete, copy-paste-ready YAML template with sample data.
### Per-Feature Examples (examples/visual-docs/)
Minimal, focused templates -- one feature per file:
| Category | Files | What They Show |
|----------|-------|----------------|
| `text/` | align, wrap, maxLines, lineHeight | Text alignment, wrapping, truncation |
| `justify/` | start, end, center, space-between, space-around, space-evenly | Flex justify-content modes |
| `align/` | start, end, center, stretch, baseline | Flex align-items modes |
| `direction/` | row, column, row-reverse, column-reverse, rtl-* | Flex direction + RTL |
| `wrap/` | wrap, nowrap, wrap-reverse | Flex wrapping behavior |
| `image/` | contain, cover, fill, none | Image fit modes |
| `position/` | absolute-*, relative-offset, overlay, badge, floating-label | CSS-style positioning |
| `border/` | solid, styles, per-side, radius | Border rendering |
| `separator/` | styles, thickness, orientation | Separator element options |
| `canvas/` | fixed-*, background, rotate-* | Canvas settings |
| `qr/` | colors, errorCorrection | QR code options |
| `barcode/` | colors, showText | Barcode options |
| `order/` | basic, negative | Flex item ordering |
### Renderer-Specific Projects (examples/*RenderExample/)
| Project | Backend | Shows |
|---------|---------|-------|
| `examples/SkiaRenderExample/` | Skia | Receipt with QR + barcode, `FlexRenderBuilder` setup |
| `examples/ImageSharpRenderExample/` | ImageSharp | Product label, pure .NET rendering |
| `examples/SvgRenderExample/` | SVG | Business card, vector output |
## Coding Conventions
- .NET 10, C# latest, `Nullable=enable`, `TreatWarningsAsErrors=true`
- AOT compatible -- no reflection, `GeneratedRegex`, `IsAotCompatible=true`
- `sealed` all leaf classes; `sealed record` for tokens; `readonly record struct` for value types
- XML documentation on all public APIs
- Guard clauses: `ArgumentNullException.ThrowIfNull()`, `ArgumentException.ThrowIfNullOrWhiteSpace()`
- Switch-based dispatch for element types (not base class properties)
- TDD approach; xUnit with `[Fact]`/`[Theory]`; naming: `Method_Scenario_Expected`
## How to Add a New Element Type
1. Create sealed AST class in `Parsing/Ast/` extending `TemplateElement`
2. Add to `ElementType` enum
3. Register parser in `TemplateEngine._elementParsers` dictionary (Core; parser function in `Parsing/Engine/ElementParsers.cs`)
4. Add layout calculation via switch pattern matching in `LayoutEngine`
5. Add rendering in `SkiaRenderer.RenderNode()` or create a provider
6. Write tests for each step
## NuGet Package Dependencies
| Package | Depends On | External Deps |
|---------|-----------|---------------|
| FlexRender.Core | -- | (none) |
| FlexRender.Yaml | Core | YamlDotNet 16.3.0 |
| FlexRender.Xml | Core | (none) |
| FlexRender.Http | Core | (none) |
| FlexRender.Skia.Render | Core | SkiaSharp 3.119.2 |
| FlexRender.Skia | Skia.Render + providers | SkiaSharp 3.119.2 |
| FlexRender.QrCode.Skia.Render | Skia.Render | QRCoder 1.7.0 |
| FlexRender.QrCode.Svg.Render | Svg.Render | QRCoder 1.7.0 |
| FlexRender.QrCode.ImageSharp.Render | ImageSharp.Render | QRCoder 1.7.0 |
| FlexRender.QrCode | All renderers | -- |
| FlexRender.Barcode.Skia.Render | Skia.Render | (none) |
| FlexRender.Barcode.Svg.Render | Svg.Render | (none) |
| FlexRender.Barcode.ImageSharp.Render | ImageSharp.Render | (none) |
| FlexRender.Barcode | All renderers | -- |
| FlexRender.HarfBuzz | Skia.Render | SkiaSharp.HarfBuzz 3.119.2, HarfBuzzSharp 8.3.1.3 |
| FlexRender.ImageSharp.Render | Core | SixLabors.ImageSharp, SixLabors.ImageSharp.Drawing, SixLabors.Fonts |
| FlexRender.ImageSharp | ImageSharp.Render + providers | SixLabors.ImageSharp, SixLabors.ImageSharp.Drawing, SixLabors.Fonts |
| FlexRender.SvgElement.Skia.Render | Skia.Render | Svg.Skia |
| FlexRender.SvgElement.Svg.Render | Svg.Render | (none) |
| FlexRender.SvgElement | All renderers | -- |
| FlexRender.Svg.Render | Core | (none) |
| FlexRender.Svg | Svg.Render + providers | (none) |
| FlexRender.DependencyInjection | Core | Microsoft.Extensions.DI |
| FlexRender.MetaPackage | All | -- |
| FlexRender.Content.Markdown | Core | Markdig 1.1.1 |
| FlexRender.Content.Html | Core | HtmlAgilityPack 1.12.4 |
| FlexRender.Content.Ndc | Core | (none) |
| flexrender-cli | All | System.CommandLine |
**Linux/Docker:** SkiaSharp requires native libraries. Add `SkiaSharp.NativeAssets.Linux` (or `.NoDependencies` for minimal containers) to your executable project to avoid `DllNotFoundException: libSkiaSharp`. When using `FlexRender.HarfBuzz`, also add `HarfBuzzSharp.NativeAssets.Linux` to avoid `DllNotFoundException: libHarfBuzzSharp`.