317 lines
11 KiB
C#
317 lines
11 KiB
C#
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<PrintCommand> Render(ReceiptNode receipt, DataContext context)
|
|
{
|
|
var commands = new List<PrintCommand>();
|
|
RenderChildren(receipt.Children, context, commands);
|
|
return commands;
|
|
}
|
|
|
|
private void RenderChildren(List<TemplateNode> children, DataContext context, List<PrintCommand> 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<PrintCommand> 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<PrintCommand> 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<PrintCommand> 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<PrintCommand> 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<PrintCommand> commands)
|
|
{
|
|
string text = _layout.CreateSeparator(separator.Character, _lineWidth);
|
|
commands.Add(new PrintCommand(text));
|
|
}
|
|
|
|
private void RenderFeed(FeedNode feed, List<PrintCommand> commands)
|
|
{
|
|
for (int i = 0; i < feed.Lines; i++)
|
|
{
|
|
commands.Add(new PrintCommand(""));
|
|
}
|
|
}
|
|
|
|
private void RenderForeach(ForeachNode foreachNode, DataContext context, List<PrintCommand> 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<PrintCommand> 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<List<string>>();
|
|
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<string>();
|
|
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<List<string>>();
|
|
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<string>();
|
|
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
|
|
};
|
|
}
|
|
}
|