You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(ast): add ExprValue<T> struct for universal property type support
feat: introduce ExprValue<T> universal property type and TemplatePipeline
Centralize template expression resolution from 3 duplicated backend
preprocessors into a single Core pipeline. All element properties now
use ExprValue<T> which can hold either a typed literal or a raw
expression string, enabling expression support for non-string types
(int, float, bool, enum) that were previously silently lost.
Key changes:
- Add ExprValue<T> readonly struct with Resolve/Materialize pipeline
- Add TemplatePipeline orchestrating Expand -> Resolve -> Materialize
- Migrate all properties across 9 element types to ExprValue<T>
- Wire TemplatePipeline into Skia, ImageSharp, and SVG backends
- Strip backend preprocessors to font-only (delete SVG preprocessor)
- Add 38 new tests for ExprValue and TemplatePipeline
82 files changed, 5095 tests passing.
test: add ExprValue integration tests and expression-aware YAML parsing
Add YamlPropertyHelpers with expression detection for typed properties
(float, int, bool, enum) — preserves {{expressions}} instead of losing
them via TryParse. Update ElementParsers to use new helpers.
Add 19 integration tests covering expression support across all types.
docs: update wiki for ExprValue expression support in typed properties
Update Template-Expressions: add "Expressions in Typed Properties" section,
update processing order from 5 to 6 steps (Parse → Expand → Resolve →
Materialize → Layout → Render). Update Element-Reference with expression
support note on Common Properties section.
Copy file name to clipboardExpand all lines: docs/wiki/Element-Reference.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -14,6 +14,8 @@ For rendering options (antialiasing, format settings), see [[Render-Options]].
14
14
15
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.
16
16
17
+
> **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.
18
+
17
19
### Size Properties
18
20
19
21
Control the explicit dimensions and constraints of an element. All size properties accept values in `px`, `%`, `em`, or `auto`. Plain numbers without a suffix are treated as pixels.
Copy file name to clipboardExpand all lines: docs/wiki/Template-Expressions.md
+60-8Lines changed: 60 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,9 +1,10 @@
1
1
# Template Expressions
2
2
3
-
FlexRender provides a template engine with variable substitution, loops, and conditionals. Expressions are processed in two layers:
3
+
FlexRender provides a template engine with variable substitution, loops, and conditionals. Expressions are processed in three phases:
4
4
5
5
1.**AST-level** (`TemplateExpander`) -- expands `type: each` and `type: if` elements into concrete elements based on data. This enables template caching.
6
-
2.**Inline** (`TemplateProcessor`) -- resolves `{{variable}}` expressions in element property values after expansion.
6
+
2.**Inline** (`TemplatePipeline`) -- resolves `{{variable}}` expressions in all element property values after expansion.
7
+
3.**Materialization** -- resolved strings are parsed into their target types (float, int, bool, enum). This allows expressions to work in all property types, not just strings.
7
8
8
9
## Variable Substitution
9
10
@@ -27,7 +28,7 @@ Use `{{variable}}` syntax to insert data values into text and properties:
27
28
content: "{{orders[0].items[2].name}}"
28
29
```
29
30
30
-
Variables can be used in most string properties: `content`, `data`, `src`, `color`, and others.
31
+
Variables can be used in **all** element properties -- including typed properties like numbers (`opacity`, `maxLines`, `size`), booleans (`wrap`, `showText`), and enums (`align`, `display`, `position`). When a typed property contains `{{`, the value is preserved as an expression during parsing, resolved at render time, and then parsed into the target type.
31
32
32
33
## Inline Expressions
33
34
@@ -762,17 +763,68 @@ The `condition` field supports inline expressions with filters. This enables cas
762
763
763
764
---
764
765
766
+
## Expressions in Typed Properties
767
+
768
+
All element properties accept `{{expressions}}`, including typed properties like floats, integers, booleans, and enums. This enables fully data-driven templates where any aspect of the layout can be controlled by data.
769
+
770
+
```yaml
771
+
# Expressions in numeric properties
772
+
- type: text
773
+
content: "Dynamic opacity"
774
+
opacity: "{{theme.textOpacity}}"
775
+
maxLines: "{{layout.maxLines}}"
776
+
777
+
# Expressions in boolean properties
778
+
- type: barcode
779
+
data: "{{product.sku}}"
780
+
showText: "{{settings.showBarcodeText}}"
781
+
782
+
# Expressions in enum properties
783
+
- type: text
784
+
content: "Dynamic alignment"
785
+
align: "{{theme.alignment}}"
786
+
787
+
# Expressions in size properties
788
+
- type: qr
789
+
data: "{{payment.url}}"
790
+
size: "{{layout.qrSize}}"
791
+
```
792
+
793
+
How typed expressions work:
794
+
795
+
1. When a typed property contains `{{`, the parser preserves the raw string as an `ExprValue<T>` expression instead of parsing it immediately
796
+
2. After template expansion, the expression is resolved to a concrete string using the data context
797
+
3. The resolved string is then parsed into the target type (e.g., `"0.5"` becomes `float 0.5`, `"true"` becomes `bool true`, `"center"` becomes `TextAlign.Center`)
798
+
4. If parsing fails, the default value for that type is used (e.g., `1.0` for opacity, `null` for nullable properties)
799
+
800
+
This works with all expression features -- arithmetic, filters, conditionals, and null coalescing:
Understanding the processing order helps with debugging:
768
819
769
-
1. **Parse** -- YAML is parsed into an AST (Template with CanvasSettings + TemplateElement tree)
820
+
1. **Parse** -- YAML is parsed into an AST. Typed properties containing `{{` are preserved as expressions
770
821
2. **Expand** -- `type: each` and `type: if` elements are expanded based on data
771
-
3. **Process** -- `{{variable}}` expressions are resolved in element properties
772
-
4. **Layout** -- the flexbox engine computes positions and sizes
773
-
5. **Render** -- elements are drawn to the output image
822
+
3. **Resolve** -- `{{variable}}` expressions are resolved to concrete strings in all properties
823
+
4. **Materialize** -- resolved strings are parsed into typed values (float, int, bool, enum)
824
+
5. **Layout** -- the flexbox engine computes positions and sizes
825
+
6. **Render** -- elements are drawn to the output image
774
826
775
-
Template caching works because steps 1 (parse) and 2-5 (expand/process/layout/render) are separate. Parse once, then render many times with different data.
827
+
Template caching works because step 1 (parse) is separate from steps 2-6 (expand/resolve/materialize/layout/render). Parse once, then process with different data for each render.
0 commit comments