templates tested
This commit is contained in:
@@ -1,115 +1,116 @@
|
|||||||
<receipt>
|
<receipt>
|
||||||
<line align="center">{{CompanyName}}</line>
|
<line align="center">{{CompanyName}}</line>
|
||||||
<line align="center">{{Address1}}</line>
|
<line align="center">{{Address1}}</line>
|
||||||
<line align="center">{{Address2}}</line>
|
<line align="center">{{Address2}}</line>
|
||||||
<line align="center">{{Phone}}</line>
|
<line align="center">{{Phone}}</line>
|
||||||
<line />
|
<line />
|
||||||
<line />
|
<line />
|
||||||
|
|
||||||
<if test="IsDebtor">
|
<if test="IsDebtor">
|
||||||
<line>Debitorenrechnung</line>
|
<line>Debitorenrechnung</line>
|
||||||
</if>
|
</if>
|
||||||
|
|
||||||
<columns left="{{ReceiptLabel}}" right="{{DateTime:HH:mm dd.MM.yyyy}}" bold="true" />
|
<columns left="{{ReceiptLabel}}" right="{{DateTime:HH:mm dd.MM.yyyy}}" bold="true" />
|
||||||
<line>Guests: {{Guests}}</line>
|
<line>Guests: {{Guests}}</line>
|
||||||
<line />
|
<line />
|
||||||
|
|
||||||
|
|
||||||
<foreach items="Items" var="item">
|
<foreach items="Items" var="item">
|
||||||
<columns left="{{item.Quantity}}x {{item.Description}}" right="{{item.PriceDisplay}}" wrap="true" />
|
<columns left="{{item.Quantity}}x {{item.Description}}" right="{{item.PriceDisplay}}" wrap="true" />
|
||||||
<foreach items="item.SubItems" var="sub">
|
<foreach items="item.SubItems" var="sub">
|
||||||
<line> - {{sub}}</line>
|
<line> - {{sub}}</line>
|
||||||
</foreach>
|
</foreach>
|
||||||
</foreach>
|
</foreach>
|
||||||
|
|
||||||
<line />
|
<line />
|
||||||
<line align="right">---------</line>
|
<line align="right">---------</line>
|
||||||
<line />
|
<line />
|
||||||
|
|
||||||
<line align="center" big="true" bold="true">Summe: {{Total:F2}} {{Currency}}</line>
|
<line align="center" big="true" bold="true">Summe: {{Total:F2}} {{Currency}}</line>
|
||||||
<line />
|
<line />
|
||||||
|
|
||||||
<if test="TotalInAlternateCurrency">
|
<if test="TotalInAlternateCurrency">
|
||||||
<line align="right">{{TotalInAlternateCurrency:F2}} {{AlternateCurrency}}</line>
|
<line align="right">{{TotalInAlternateCurrency:F2}} {{AlternateCurrency}}</line>
|
||||||
<line />
|
<line />
|
||||||
</if>
|
</if>
|
||||||
|
|
||||||
<if test="DiscountInfo">
|
<if test="DiscountInfo">
|
||||||
<columns left="{{DiscountInfo.Description}}" right="{{DiscountInfo.Amount:F2}} {{DiscountInfo.Currency}}" wrap="true" />
|
<columns left="{{DiscountInfo.Description}}" right="{{DiscountInfo.Amount:F2}} {{DiscountInfo.Currency}}" wrap="true" />
|
||||||
<line />
|
<line />
|
||||||
</if>
|
</if>
|
||||||
|
|
||||||
<foreach items="SplitPayments" var="sp">
|
<foreach items="SplitPayments" var="sp">
|
||||||
<line align="right">{{sp.PaymentMethod}}: {{sp.Amount:F2}} {{sp.Currency}}</line>
|
<line align="right">{{sp.PaymentMethod}}: {{sp.Amount:F2}} {{sp.Currency}}</line>
|
||||||
</foreach>
|
</foreach>
|
||||||
<if test="HasSplitPayments">
|
<if test="HasSplitPayments">
|
||||||
<line />
|
<line />
|
||||||
</if>
|
</if>
|
||||||
|
|
||||||
<columns left="{{PaymentMethod}}" right="{{PaymentAmount:F2}} {{Currency}}" bold="true" />
|
<columns left="{{PaymentMethod}}" right="{{PaymentAmount:F2}} {{Currency}}" bold="true" />
|
||||||
<line />
|
<line />
|
||||||
|
|
||||||
<foreach items="TaxBreakdown" var="tax">
|
<foreach items="TaxBreakdown" var="tax">
|
||||||
<if test="$first">
|
<if test="$first">
|
||||||
<row>
|
<row>
|
||||||
<col width="11" align="left">MwSt %</col>
|
<col width="25%" align="left">MwSt %</col>
|
||||||
<col width="11" align="right">Brutto</col>
|
<col width="25%" align="right" wrap="true" >Brutto</col>
|
||||||
<col width="11" align="right">Netto</col>
|
<col width="25%" align="right" wrap="true">Netto</col>
|
||||||
<col align="right">MwSt</col>
|
<col width="25%" align="right">MwSt</col>
|
||||||
</row>
|
</row>
|
||||||
</if>
|
</if>
|
||||||
<row>
|
<row>
|
||||||
<col width="11" align="left">{{tax.Category}}:{{tax.Rate}}%</col>
|
<col width="25%" align="left">{{tax.Category}}:{{tax.Rate}}%</col>
|
||||||
<col width="11" align="right"> {{tax.Gross:F2}} {{tax.Currency}}</col>
|
<col width="25%" align="right"> {{tax.Gross:F2}} {{tax.Currency}}</col>
|
||||||
<col width="11" align="right"> {{tax.Net:F2}} {{tax.Currency}}</col>
|
<col width="25%" align="right"> {{tax.Net:F2}} {{tax.Currency}}</col>
|
||||||
<col align="right"> {{tax.TaxAmount:F2}} {{tax.Currency}}</col>
|
<col width="25%" align="right"> {{tax.TaxAmount:F2}} {{tax.Currency}}</col>
|
||||||
</row>
|
</row>
|
||||||
</foreach>
|
</foreach>
|
||||||
|
|
||||||
<if test="!HasTaxableCategories">
|
<if test="!HasTaxableCategories">
|
||||||
<line>Nicht mehrwertsteuerpflichtig</line>
|
<line>Nicht mehrwertsteuerpflichtig</line>
|
||||||
</if>
|
</if>
|
||||||
|
|
||||||
<line />
|
<line />
|
||||||
|
|
||||||
<columns left="Bedient von:" right="{{WaiterName}}" />
|
<columns left="Bedient von:" right="{{WaiterName}}" />
|
||||||
<if test="Terminal">
|
<if test="Terminal">
|
||||||
<columns left="Terminal:" right="{{Terminal}}" />
|
<columns left="Terminal:" right="{{Terminal}}" />
|
||||||
</if>
|
</if>
|
||||||
<columns left="Tisch:" right="{{TableNumber}}" />
|
<columns left="Tisch:" right="{{TableNumber}}" />
|
||||||
<line />
|
<line />
|
||||||
<line />
|
<line />
|
||||||
|
|
||||||
<line align="center">{{VatNumber}}</line>
|
<line align="center">{{VatNumber}}</line>
|
||||||
<line />
|
<line />
|
||||||
|
|
||||||
<foreach items="TerminalReceipts" var="tr">
|
<foreach items="TerminalReceipts" var="tr">
|
||||||
<line align="center">{{tr.ReceiptType}}</line>
|
<line align="center">{{tr.ReceiptType}}</line>
|
||||||
<line align="center">{{tr.BookingType}}</line>
|
<line align="center">{{tr.BookingType}}</line>
|
||||||
<line align="center">{{tr.PaymentSystem}}</line>
|
<line align="center">{{tr.PaymentSystem}}</line>
|
||||||
<line>{{tr.TransactionNumber}}</line>
|
<line>{{tr.TransactionNumber}}</line>
|
||||||
<columns left="{{tr.TransactionDateTime:dd.MM.yyyy}}" right="{{tr.TransactionDateTime:HH:mm:ss}}" />
|
<columns left="{{tr.TransactionDateTime:dd.MM.yyyy}}" right="{{tr.TransactionDateTime:HH:mm:ss}}" />
|
||||||
<columns left="Trm-Id:" right="{{tr.TerminalId}}" />
|
<columns left="Trm-Id:" right="{{tr.TerminalId}}" />
|
||||||
<columns left="AID:" right="{{tr.AID}}" />
|
<columns left="AID:" right="{{tr.AID}}" />
|
||||||
<columns left="Trx. Seq-Cnt:" right="{{tr.TransactionSeqCount}}" />
|
<columns left="Trx. Seq-Cnt:" right="{{tr.TransactionSeqCount}}" />
|
||||||
<columns left="Trx. Ref-No:" right="{{tr.TransactionRefNo}}" />
|
<columns left="Trx. Ref-No:" right="{{tr.TransactionRefNo}}" />
|
||||||
<columns left="Auth. Code:" right="{{tr.AuthCode}}" />
|
<columns left="Auth. Code:" right="{{tr.AuthCode}}" />
|
||||||
<columns left="Acq-Id:" right="{{tr.AcquirerId}}" />
|
<columns left="Acq-Id:" right="{{tr.AcquirerId}}" />
|
||||||
<columns left="EFT {{tr.Currency}}:" right="{{tr.EftAmount:F2}}" />
|
<columns left="EFT {{tr.Currency}}:" right="{{tr.EftAmount:F2}}" />
|
||||||
<columns left="Trinkgeld {{tr.Currency}}:" right="{{tr.TipAmount:F2}}" />
|
<columns left="Trinkgeld {{tr.Currency}}:" right="{{tr.TipAmount:F2}}" />
|
||||||
<columns left="Total-EFT {{tr.Currency}}:" right="{{tr.TotalEftAmount:F2}}" />
|
<columns left="Total-EFT {{tr.Currency}}:" right="{{tr.TotalEftAmount:F2}}" />
|
||||||
<separator />
|
<separator />
|
||||||
<line />
|
<line />
|
||||||
</foreach>
|
</foreach>
|
||||||
|
|
||||||
<line align="center">{{ThankYouMessage}}</line>
|
<line align="center">{{ThankYouMessage}}</line>
|
||||||
<line align="center">{{GoodbyeMessageLine1}}</line>
|
<line align="center">{{GoodbyeMessageLine1}}</line>
|
||||||
<line align="center">{{GoodbyeMessageLine2}}</line>
|
<line align="center">{{GoodbyeMessageLine2}}</line>
|
||||||
|
|
||||||
<if test="IsDebtor">
|
<if test="IsDebtor">
|
||||||
<line />
|
<line />
|
||||||
<line />
|
<line />
|
||||||
<line />
|
<line />
|
||||||
<separator />
|
<separator />
|
||||||
<line>Unterschrift</line>
|
<line>Unterschrift</line>
|
||||||
</if>
|
</if>
|
||||||
</receipt>
|
</receipt>
|
||||||
|
|||||||
@@ -47,7 +47,9 @@ public class ColumnDef
|
|||||||
{
|
{
|
||||||
public string? Text { get; set; }
|
public string? Text { get; set; }
|
||||||
public int? Width { get; set; }
|
public int? Width { get; set; }
|
||||||
|
public double? WidthPercent { get; set; }
|
||||||
public string Align { get; set; } = "left";
|
public string Align { get; set; } = "left";
|
||||||
|
public bool Wrap { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SeparatorNode : TemplateNode
|
public class SeparatorNode : TemplateNode
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Globalization;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
|
||||||
namespace Inspectron.Epson.TemplateEngine.Parsing;
|
namespace Inspectron.Epson.TemplateEngine.Parsing;
|
||||||
@@ -92,9 +93,10 @@ public class TemplateParser
|
|||||||
var col = new ColumnDef
|
var col = new ColumnDef
|
||||||
{
|
{
|
||||||
Text = GetTextContent(colElement),
|
Text = GetTextContent(colElement),
|
||||||
Width = GetNullableIntAttr(colElement, "width"),
|
Align = GetAttr(colElement, "align", "left"),
|
||||||
Align = GetAttr(colElement, "align", "left")
|
Wrap = GetBoolAttr(colElement, "wrap")
|
||||||
};
|
};
|
||||||
|
ParseColumnWidth(colElement, col);
|
||||||
node.Columns.Add(col);
|
node.Columns.Add(col);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,9 +160,10 @@ public class TemplateParser
|
|||||||
var col = new ColumnDef
|
var col = new ColumnDef
|
||||||
{
|
{
|
||||||
Text = GetTextContent(colElement),
|
Text = GetTextContent(colElement),
|
||||||
Width = GetNullableIntAttr(colElement, "width"),
|
Align = GetAttr(colElement, "align", "left"),
|
||||||
Align = GetAttr(colElement, "align", "left")
|
Wrap = GetBoolAttr(colElement, "wrap")
|
||||||
};
|
};
|
||||||
|
ParseColumnWidth(colElement, col);
|
||||||
node.Columns.Add(col);
|
node.Columns.Add(col);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,4 +250,22 @@ public class TemplateParser
|
|||||||
if (attr == null) return null;
|
if (attr == null) return null;
|
||||||
return int.TryParse(attr.Value, out var v) ? v : null;
|
return int.TryParse(attr.Value, out var v) ? v : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void ParseColumnWidth(XElement element, ColumnDef col)
|
||||||
|
{
|
||||||
|
var attr = element.Attribute("width");
|
||||||
|
if (attr == null) return;
|
||||||
|
|
||||||
|
var value = attr.Value.Trim();
|
||||||
|
if (value.EndsWith('%'))
|
||||||
|
{
|
||||||
|
if (double.TryParse(value[..^1], NumberStyles.Float, CultureInfo.InvariantCulture, out var pct))
|
||||||
|
col.WidthPercent = pct;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (int.TryParse(value, out var w))
|
||||||
|
col.Width = w;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,6 +109,59 @@ public class LayoutEngine
|
|||||||
return string.Concat(parts);
|
return string.Concat(parts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<string> FormatMultiColumnWithWrap(
|
||||||
|
List<(string text, int width, string align, bool wrap)> columns, int lineWidth)
|
||||||
|
{
|
||||||
|
// Calculate widths
|
||||||
|
int totalExplicit = columns.Where(c => c.width > 0).Sum(c => c.width);
|
||||||
|
int unspecifiedCount = columns.Count(c => c.width <= 0);
|
||||||
|
int remaining = lineWidth - totalExplicit;
|
||||||
|
int defaultWidth = unspecifiedCount > 0 ? remaining / unspecifiedCount : 0;
|
||||||
|
|
||||||
|
var colWidths = columns.Select(c => c.width > 0 ? c.width : Math.Max(defaultWidth, 1)).ToList();
|
||||||
|
|
||||||
|
// Wrap or truncate each column's text into lines
|
||||||
|
var allColLines = new List<List<string>>();
|
||||||
|
for (int i = 0; i < columns.Count; i++)
|
||||||
|
{
|
||||||
|
var (text, _, _, wrap) = columns[i];
|
||||||
|
text ??= "";
|
||||||
|
int w = colWidths[i];
|
||||||
|
|
||||||
|
if (wrap)
|
||||||
|
allColLines.Add(WrapText(text, w));
|
||||||
|
else
|
||||||
|
allColLines.Add(new List<string> { text.Length > w ? text[..w] : text });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the tallest column
|
||||||
|
int maxLines = allColLines.Max(l => l.Count);
|
||||||
|
|
||||||
|
// Build each output line
|
||||||
|
var result = new List<string>();
|
||||||
|
for (int lineIdx = 0; lineIdx < maxLines; lineIdx++)
|
||||||
|
{
|
||||||
|
var parts = new List<string>();
|
||||||
|
for (int colIdx = 0; colIdx < columns.Count; colIdx++)
|
||||||
|
{
|
||||||
|
string cellText = lineIdx < allColLines[colIdx].Count ? allColLines[colIdx][lineIdx] : "";
|
||||||
|
int w = colWidths[colIdx];
|
||||||
|
string formatted = columns[colIdx].align.ToLowerInvariant() switch
|
||||||
|
{
|
||||||
|
"center" => CenterInWidth(cellText, w),
|
||||||
|
"right" => cellText.PadLeft(w),
|
||||||
|
_ => cellText.PadRight(w)
|
||||||
|
};
|
||||||
|
if (formatted.Length > w)
|
||||||
|
formatted = formatted[..w];
|
||||||
|
parts.Add(formatted);
|
||||||
|
}
|
||||||
|
result.Add(string.Concat(parts));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public List<string> WrapText(string text, int maxWidth, int indent = 0)
|
public List<string> WrapText(string text, int maxWidth, int indent = 0)
|
||||||
{
|
{
|
||||||
var lines = new List<string>();
|
var lines = new List<string>();
|
||||||
@@ -177,20 +230,26 @@ public class LayoutEngine
|
|||||||
List<(string text, int width, string align)> columns,
|
List<(string text, int width, string align)> columns,
|
||||||
List<List<string>> headerRows,
|
List<List<string>> headerRows,
|
||||||
List<List<string>> dataRows,
|
List<List<string>> dataRows,
|
||||||
int lineWidth)
|
int lineWidth,
|
||||||
|
List<bool>? wrapFlags = null)
|
||||||
{
|
{
|
||||||
var result = new List<string>();
|
var result = new List<string>();
|
||||||
|
|
||||||
// Calculate column widths
|
// Calculate column widths
|
||||||
var colWidths = CalculateTableColumnWidths(columns, lineWidth);
|
var colWidths = CalculateTableColumnWidths(columns, lineWidth);
|
||||||
|
|
||||||
|
bool hasWrap = wrapFlags != null && wrapFlags.Any(w => w);
|
||||||
|
|
||||||
// Top border
|
// Top border
|
||||||
result.Add(FormatTableBorder(colWidths));
|
result.Add(FormatTableBorder(colWidths));
|
||||||
|
|
||||||
// Header rows
|
// Header rows
|
||||||
foreach (var row in headerRows)
|
foreach (var row in headerRows)
|
||||||
{
|
{
|
||||||
result.Add(FormatTableRow(row, colWidths, columns));
|
if (hasWrap)
|
||||||
|
result.AddRange(FormatTableRows(row, colWidths, columns, wrapFlags!));
|
||||||
|
else
|
||||||
|
result.Add(FormatTableRow(row, colWidths, columns));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Separator between header and data
|
// Separator between header and data
|
||||||
@@ -202,7 +261,10 @@ public class LayoutEngine
|
|||||||
// Data rows
|
// Data rows
|
||||||
foreach (var row in dataRows)
|
foreach (var row in dataRows)
|
||||||
{
|
{
|
||||||
result.Add(FormatTableRow(row, colWidths, columns));
|
if (hasWrap)
|
||||||
|
result.AddRange(FormatTableRows(row, colWidths, columns, wrapFlags!));
|
||||||
|
else
|
||||||
|
result.Add(FormatTableRow(row, colWidths, columns));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bottom border
|
// Bottom border
|
||||||
@@ -245,6 +307,40 @@ public class LayoutEngine
|
|||||||
return "|" + string.Join("|", parts) + "|";
|
return "|" + string.Join("|", parts) + "|";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<string> FormatTableRows(List<string> cells, List<int> colWidths,
|
||||||
|
List<(string text, int width, string align)> columns, List<bool> wrapFlags)
|
||||||
|
{
|
||||||
|
// Wrap each cell that has wrap enabled
|
||||||
|
var allColLines = new List<List<string>>();
|
||||||
|
for (int i = 0; i < colWidths.Count; i++)
|
||||||
|
{
|
||||||
|
string cell = i < cells.Count ? cells[i] : "";
|
||||||
|
bool wrap = i < wrapFlags.Count && wrapFlags[i];
|
||||||
|
|
||||||
|
if (wrap)
|
||||||
|
allColLines.Add(WrapText(cell, colWidths[i]));
|
||||||
|
else
|
||||||
|
allColLines.Add(new List<string> { cell.Length > colWidths[i] ? cell[..colWidths[i]] : cell });
|
||||||
|
}
|
||||||
|
|
||||||
|
int maxLines = allColLines.Max(l => l.Count);
|
||||||
|
|
||||||
|
var result = new List<string>();
|
||||||
|
for (int lineIdx = 0; lineIdx < maxLines; lineIdx++)
|
||||||
|
{
|
||||||
|
var parts = new List<string>();
|
||||||
|
for (int colIdx = 0; colIdx < colWidths.Count; colIdx++)
|
||||||
|
{
|
||||||
|
string cellText = lineIdx < allColLines[colIdx].Count ? allColLines[colIdx][lineIdx] : "";
|
||||||
|
string align = colIdx < columns.Count ? columns[colIdx].align : "left";
|
||||||
|
parts.Add(FormatCellContent(cellText, colWidths[colIdx], align));
|
||||||
|
}
|
||||||
|
result.Add("|" + string.Join("|", parts) + "|");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private string FormatCellContent(string text, int width, string align)
|
private string FormatCellContent(string text, int width, string align)
|
||||||
{
|
{
|
||||||
if (text.Length > width)
|
if (text.Length > width)
|
||||||
|
|||||||
@@ -145,15 +145,36 @@ public class TemplateRenderer
|
|||||||
var evaluator = new ExpressionEvaluator(context);
|
var evaluator = new ExpressionEvaluator(context);
|
||||||
int effectiveWidth = row.Big ? _bigFontLineWidth : _lineWidth;
|
int effectiveWidth = row.Big ? _bigFontLineWidth : _lineWidth;
|
||||||
|
|
||||||
var columnData = row.Columns.Select(c => (
|
bool anyWrap = row.Columns.Any(c => c.Wrap);
|
||||||
text: evaluator.Evaluate(c.Text ?? ""),
|
|
||||||
width: c.Width ?? 0,
|
|
||||||
align: c.Align
|
|
||||||
)).ToList();
|
|
||||||
|
|
||||||
string text = _layout.FormatMultiColumn(columnData, effectiveWidth);
|
if (anyWrap)
|
||||||
var cmd = CreateCommand(text, row.Bold, row.Big, row.Tall, row.Red, row.LineSpacing);
|
{
|
||||||
commands.Add(cmd);
|
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)
|
private void RenderSeparator(SeparatorNode separator, List<PrintCommand> commands)
|
||||||
@@ -199,9 +220,10 @@ public class TemplateRenderer
|
|||||||
{
|
{
|
||||||
var evaluator = new ExpressionEvaluator(context);
|
var evaluator = new ExpressionEvaluator(context);
|
||||||
|
|
||||||
|
int tableContentWidth = _lineWidth - table.Columns.Count - 1;
|
||||||
var columnDefs = table.Columns.Select(c => (
|
var columnDefs = table.Columns.Select(c => (
|
||||||
text: evaluator.Evaluate(c.Text ?? ""),
|
text: evaluator.Evaluate(c.Text ?? ""),
|
||||||
width: c.Width ?? 0,
|
width: ResolveColumnWidth(c, tableContentWidth),
|
||||||
align: c.Align
|
align: c.Align
|
||||||
)).ToList();
|
)).ToList();
|
||||||
|
|
||||||
@@ -267,13 +289,21 @@ public class TemplateRenderer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var lines = _layout.FormatTable(columnDefs, headerRows, dataRows, _lineWidth);
|
var wrapFlags = table.Columns.Select(c => c.Wrap).ToList();
|
||||||
|
var lines = _layout.FormatTable(columnDefs, headerRows, dataRows, _lineWidth, wrapFlags);
|
||||||
foreach (var line in lines)
|
foreach (var line in lines)
|
||||||
{
|
{
|
||||||
commands.Add(new PrintCommand(line));
|
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)
|
private static PrintCommand CreateCommand(string text, bool bold, bool big, bool tall, bool red, int? lineSpacing)
|
||||||
{
|
{
|
||||||
return new PrintCommand(text, isBig: big, isBold: bold)
|
return new PrintCommand(text, isBig: big, isBold: bold)
|
||||||
|
|||||||
@@ -140,12 +140,20 @@ Multi-column layout with explicit column definitions. Each column is defined by
|
|||||||
|
|
||||||
| Attribute | Type | Default | Description |
|
| Attribute | Type | Default | Description |
|
||||||
|-----------|------|---------|-------------|
|
|-----------|------|---------|-------------|
|
||||||
| `width` | int | _(auto)_ | Column width in characters. Unspecified columns share remaining space equally. |
|
| `width` | int or string | _(auto)_ | Column width as a character count (`"10"`) or percentage of the row width (`"33.3%"`). Unspecified columns share remaining space equally. |
|
||||||
| `align` | `left` / `center` / `right` | `left` | Text alignment within the column |
|
| `align` | `left` / `center` / `right` | `left` | Text alignment within the column |
|
||||||
|
| `wrap` | `true` / `false` | `false` | Word-wrap text that exceeds column width. All columns expand vertically to match the tallest cell. |
|
||||||
|
|
||||||
**Examples:**
|
**Examples:**
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
|
<!-- Equal-width columns using percentages -->
|
||||||
|
<row>
|
||||||
|
<col width="33.3%" align="left">Name</col>
|
||||||
|
<col width="33.3%" align="right">Qty</col>
|
||||||
|
<col width="33.3%" align="right">Price</col>
|
||||||
|
</row>
|
||||||
|
|
||||||
<!-- Tax breakdown header -->
|
<!-- Tax breakdown header -->
|
||||||
<row>
|
<row>
|
||||||
<col width="10" align="left">MwSt %</col>
|
<col width="10" align="left">MwSt %</col>
|
||||||
@@ -161,10 +169,28 @@ Multi-column layout with explicit column definitions. Each column is defined by
|
|||||||
<col width="10" align="right">{{tax.Net:F2}} {{tax.Currency}}</col>
|
<col width="10" align="right">{{tax.Net:F2}} {{tax.Currency}}</col>
|
||||||
<col align="right">{{tax.TaxAmount:F2}} {{tax.Currency}}</col>
|
<col align="right">{{tax.TaxAmount:F2}} {{tax.Currency}}</col>
|
||||||
</row>
|
</row>
|
||||||
|
|
||||||
|
<!-- Row with word wrap on the first column -->
|
||||||
|
<row>
|
||||||
|
<col width="10" align="left" wrap="true">Very Long Product Name Here</col>
|
||||||
|
<col align="right">12.50</col>
|
||||||
|
</row>
|
||||||
|
<!-- Output (42-char width, col2 auto-fills remaining 32 chars):
|
||||||
|
Very Long 12.50
|
||||||
|
Product
|
||||||
|
Name Here -->
|
||||||
|
|
||||||
|
<!-- Both columns wrapping -->
|
||||||
|
<row>
|
||||||
|
<col width="20" align="left" wrap="true">{{item.Name}}</col>
|
||||||
|
<col width="22" align="left" wrap="true">{{item.Description}}</col>
|
||||||
|
</row>
|
||||||
```
|
```
|
||||||
|
|
||||||
In this example, the first three columns are 10 characters wide. The fourth column gets all remaining space (`lineWidth - 30`).
|
In this example, the first three columns are 10 characters wide. The fourth column gets all remaining space (`lineWidth - 30`).
|
||||||
|
|
||||||
|
When `wrap="true"` is set on a column, text that exceeds the column width wraps to multiple lines. All columns in the row expand vertically to match the tallest cell. Each wrapped line respects the column's `align` attribute.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### `<separator>`
|
### `<separator>`
|
||||||
@@ -244,10 +270,11 @@ If `headerItems` is not set, the text content of `<col>` elements is used as the
|
|||||||
|
|
||||||
| Attribute | Type | Default | Description |
|
| Attribute | Type | Default | Description |
|
||||||
|-----------|------|---------|-------------|
|
|-----------|------|---------|-------------|
|
||||||
| `width` | int | _(auto)_ | Column width in characters (excluding border characters) |
|
| `width` | int or string | _(auto)_ | Column width as a character count (`"10"`) or percentage of the content area (`"50%"`). Percentages are relative to the content width (line width minus border characters). Unspecified columns share remaining space equally. |
|
||||||
| `align` | `left` / `center` / `right` | `left` | Cell content alignment |
|
| `align` | `left` / `center` / `right` | `left` | Cell content alignment |
|
||||||
|
| `wrap` | `true` / `false` | `false` | Word-wrap cell text that exceeds column width. All columns in the row expand vertically to match the tallest cell. |
|
||||||
|
|
||||||
Column widths exclude border characters. With 3 columns, 4 border characters (`|`) are used, so the available content width is `lineWidth - 4`.
|
Column widths exclude border characters. With 3 columns, 4 border characters (`|`) are used, so the available content width is `lineWidth - 4`. Percentage widths are calculated against this content width.
|
||||||
|
|
||||||
**Examples:**
|
**Examples:**
|
||||||
|
|
||||||
@@ -275,8 +302,24 @@ Column widths exclude border characters. With 3 columns, 4 border characters (`|
|
|||||||
|Margherita | 12.50|
|
|Margherita | 12.50|
|
||||||
|Tiramisu | 8.00|
|
|Tiramisu | 8.00|
|
||||||
+--------------------+----------+ -->
|
+--------------------+----------+ -->
|
||||||
|
|
||||||
|
<!-- Table with word wrap -->
|
||||||
|
<table items="Products" var="p">
|
||||||
|
<col width="12" align="left" wrap="true">Name</col>
|
||||||
|
<col width="8" align="right">Price</col>
|
||||||
|
</table>
|
||||||
|
<!-- Output (cell text wraps, all columns expand vertically):
|
||||||
|
+------------+--------+
|
||||||
|
|Name | Price|
|
||||||
|
+------------+--------+
|
||||||
|
|Margherita | 12.50|
|
||||||
|
|Wiener | 8.00|
|
||||||
|
|Schnitzel | |
|
||||||
|
+------------+--------+ -->
|
||||||
```
|
```
|
||||||
|
|
||||||
|
When `wrap="true"` is set on a table `<col>`, cell text that exceeds the column width wraps to multiple lines. All columns in the row expand vertically to match the tallest cell, with empty cells padded with spaces. Each wrapped line respects the column's `align` attribute.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Control Flow
|
## Control Flow
|
||||||
@@ -632,7 +675,7 @@ Comment: Well done
|
|||||||
| `<receipt>` | _(root)_ | - |
|
| `<receipt>` | _(root)_ | - |
|
||||||
| `<line>` | 1 PrintCommand (or N if wrapping) | `align`, `bold`, `big`, `tall`, `red`, `wrap`, `wrapIndent`, `lineSpacing` |
|
| `<line>` | 1 PrintCommand (or N if wrapping) | `align`, `bold`, `big`, `tall`, `red`, `wrap`, `wrapIndent`, `lineSpacing` |
|
||||||
| `<columns>` | 1 PrintCommand (or N if wrapping) | `left`, `right`, `bold`, `big`, `tall`, `red`, `wrap`, `wrapIndent`, `lineSpacing` |
|
| `<columns>` | 1 PrintCommand (or N if wrapping) | `left`, `right`, `bold`, `big`, `tall`, `red`, `wrap`, `wrapIndent`, `lineSpacing` |
|
||||||
| `<row>` | 1 PrintCommand | `bold`, `big`, `tall`, `red`, `lineSpacing` + nested `<col>` |
|
| `<row>` | 1+ PrintCommand(s) (N if wrapping) | `bold`, `big`, `tall`, `red`, `lineSpacing` + nested `<col>` |
|
||||||
| `<separator>` | 1 PrintCommand | `char` |
|
| `<separator>` | 1 PrintCommand | `char` |
|
||||||
| `<cut />` | 1 PrintCommand (IsCut=true) | - |
|
| `<cut />` | 1 PrintCommand (IsCut=true) | - |
|
||||||
| `<feed />` | N PrintCommands (empty lines) | `lines` |
|
| `<feed />` | N PrintCommands (empty lines) | `lines` |
|
||||||
|
|||||||
Reference in New Issue
Block a user