using System.Text.Json; using Inspectron.Epson.PrintServer.Printers.Utils; using Inspectron.Epson.TemplateEngine.DataBinding; using Inspectron.Epson.TemplateEngine.Parsing; namespace Inspectron.Epson.TemplateEngine.Rendering; public class TemplateRenderer { private readonly int _lineWidth; private readonly int _bigFontLineWidth; private readonly LayoutEngine _layout = new(); public TemplateRenderer(int lineWidth, int bigFontLineWidth) { _lineWidth = lineWidth; _bigFontLineWidth = bigFontLineWidth; } public List Render(ReceiptNode receipt, DataContext context) { var commands = new List(); RenderChildren(receipt.Children, context, commands); return commands; } private void RenderChildren(List children, DataContext context, List commands) { for (int i = 0; i < children.Count; i++) { var node = children[i]; if (node is IfNode ifNode) { var evaluator = new ExpressionEvaluator(context); bool condition = evaluator.EvaluateCondition(ifNode.Test); if (condition) { RenderChildren(ifNode.Children, context, commands); // Skip following else if (i + 1 < children.Count && children[i + 1] is ElseNode) i++; } else { // Check for following else if (i + 1 < children.Count && children[i + 1] is ElseNode elseNode) { RenderChildren(elseNode.Children, context, commands); i++; } } } else { RenderNode(node, context, commands); } } } private void RenderNode(TemplateNode node, DataContext context, List commands) { switch (node) { case LineNode line: RenderLine(line, context, commands); break; case ColumnsNode columns: RenderColumns(columns, context, commands); break; case RowNode row: RenderRow(row, context, commands); break; case SeparatorNode separator: RenderSeparator(separator, commands); break; case CutNode: commands.Add(new PrintCommand("") { IsCut = true }); break; case FeedNode feed: RenderFeed(feed, commands); break; case ForeachNode foreachNode: RenderForeach(foreachNode, context, commands); break; case TableNode table: RenderTable(table, context, commands); break; case ElseNode: // Handled by IfNode processing in RenderChildren break; } } private void RenderLine(LineNode line, DataContext context, List commands) { var evaluator = new ExpressionEvaluator(context); string text = evaluator.Evaluate(line.Text ?? ""); int effectiveWidth = line.Big ? _bigFontLineWidth : _lineWidth; if (line.Wrap && text.Length > effectiveWidth) { var wrappedLines = _layout.WrapText(text, effectiveWidth, line.WrapIndent); foreach (var wrappedLine in wrappedLines) { var cmd = CreateCommand(wrappedLine, line.Bold, line.Big, line.Tall, line.Red, line.LineSpacing); commands.Add(cmd); } } else { text = _layout.AlignText(text, line.Align, effectiveWidth); var cmd = CreateCommand(text, line.Bold, line.Big, line.Tall, line.Red, line.LineSpacing); commands.Add(cmd); } } private void RenderColumns(ColumnsNode columns, DataContext context, List commands) { var evaluator = new ExpressionEvaluator(context); string left = evaluator.Evaluate(columns.Left ?? ""); string right = evaluator.Evaluate(columns.Right ?? ""); int effectiveWidth = columns.Big ? _bigFontLineWidth : _lineWidth; if (columns.Wrap) { var lines = _layout.FormatTwoColumnsWithWrap(left, right, effectiveWidth); foreach (var line in lines) { var cmd = CreateCommand(line, columns.Bold, columns.Big, columns.Tall, columns.Red, columns.LineSpacing); commands.Add(cmd); } } else { string text = _layout.FormatTwoColumns(left, right, effectiveWidth); var cmd = CreateCommand(text, columns.Bold, columns.Big, columns.Tall, columns.Red, columns.LineSpacing); commands.Add(cmd); } } private void RenderRow(RowNode row, DataContext context, List commands) { var evaluator = new ExpressionEvaluator(context); int effectiveWidth = row.Big ? _bigFontLineWidth : _lineWidth; bool anyWrap = row.Columns.Any(c => c.Wrap); if (anyWrap) { var columnData = row.Columns.Select(c => ( text: evaluator.Evaluate(c.Text ?? ""), width: ResolveColumnWidth(c, effectiveWidth), align: c.Align, wrap: c.Wrap )).ToList(); var lines = _layout.FormatMultiColumnWithWrap(columnData, effectiveWidth); foreach (var line in lines) { var cmd = CreateCommand(line, row.Bold, row.Big, row.Tall, row.Red, row.LineSpacing); commands.Add(cmd); } } else { var columnData = row.Columns.Select(c => ( text: evaluator.Evaluate(c.Text ?? ""), width: ResolveColumnWidth(c, effectiveWidth), align: c.Align )).ToList(); string text = _layout.FormatMultiColumn(columnData, effectiveWidth); var cmd = CreateCommand(text, row.Bold, row.Big, row.Tall, row.Red, row.LineSpacing); commands.Add(cmd); } } private void RenderSeparator(SeparatorNode separator, List commands) { string text = _layout.CreateSeparator(separator.Character, _lineWidth); commands.Add(new PrintCommand(text)); } private void RenderFeed(FeedNode feed, List commands) { for (int i = 0; i < feed.Lines; i++) { commands.Add(new PrintCommand("")); } } private void RenderForeach(ForeachNode foreachNode, DataContext context, List commands) { var evaluator = new ExpressionEvaluator(context); // Resolve the items collection var itemsElement = context.Resolve(foreachNode.Items); if (itemsElement == null || itemsElement.Value.ValueKind != JsonValueKind.Array) return; var array = itemsElement.Value; int count = array.GetArrayLength(); for (int i = 0; i < count; i++) { var item = array[i]; var childContext = context.CreateChildScope(); childContext.SetVariable(foreachNode.Var, item); childContext.SetLoopVariable("$index", i); childContext.SetLoopVariable("$first", i == 0); childContext.SetLoopVariable("$last", i == count - 1); RenderChildren(foreachNode.Children, childContext, commands); } } private void RenderTable(TableNode table, DataContext context, List commands) { var evaluator = new ExpressionEvaluator(context); int tableContentWidth = _lineWidth - table.Columns.Count - 1; var columnDefs = table.Columns.Select(c => ( text: evaluator.Evaluate(c.Text ?? ""), width: ResolveColumnWidth(c, tableContentWidth), align: c.Align )).ToList(); // Header rows from headerItems or column text var headerRows = new List>(); if (!string.IsNullOrEmpty(table.HeaderItems)) { var headerArray = context.Resolve(table.HeaderItems); if (headerArray != null && headerArray.Value.ValueKind == JsonValueKind.Array) { foreach (var headerItem in headerArray.Value.EnumerateArray()) { var row = new List(); foreach (var col in table.Columns) { var childContext = context.CreateChildScope(); childContext.SetVariable(table.Var ?? "item", headerItem); var childEval = new ExpressionEvaluator(childContext); row.Add(childEval.Evaluate(col.Text ?? "")); } headerRows.Add(row); } } } else if (columnDefs.Any(c => !string.IsNullOrEmpty(c.text))) { headerRows.Add(columnDefs.Select(c => c.text).ToList()); } // Data rows var dataRows = new List>(); if (!string.IsNullOrEmpty(table.Items)) { var itemsArray = context.Resolve(table.Items); if (itemsArray != null && itemsArray.Value.ValueKind == JsonValueKind.Array) { foreach (var dataItem in itemsArray.Value.EnumerateArray()) { var childContext = context.CreateChildScope(); childContext.SetVariable(table.Var ?? "item", dataItem); var childEval = new ExpressionEvaluator(childContext); var row = new List(); foreach (var child in table.Children) { if (child is LineNode lineChild) { row.Add(childEval.Evaluate(lineChild.Text ?? "")); } } // If no line children, use column defs if (row.Count == 0) { foreach (var col in table.Columns) { row.Add(childEval.Evaluate(col.Text ?? "")); } } dataRows.Add(row); } } } var wrapFlags = table.Columns.Select(c => c.Wrap).ToList(); var lines = _layout.FormatTable(columnDefs, headerRows, dataRows, _lineWidth, wrapFlags); foreach (var line in lines) { commands.Add(new PrintCommand(line)); } } private static int ResolveColumnWidth(ColumnDef col, int availableWidth) { if (col.Width.HasValue) return col.Width.Value; if (col.WidthPercent.HasValue) return (int)(availableWidth * col.WidthPercent.Value / 100.0); return 0; } private static PrintCommand CreateCommand(string text, bool bold, bool big, bool tall, bool red, int? lineSpacing) { return new PrintCommand(text, isBig: big, isBold: bold) { IsTall = tall, IsRed = red, SetLineSpacing = lineSpacing }; } }