namespace Inspectron.Epson.TemplateEngine.Rendering; public class LayoutEngine { public string AlignText(string text, string alignment, int lineWidth) { if (string.IsNullOrEmpty(text)) return text ?? ""; if (text.Length >= lineWidth) return text; return alignment.ToLowerInvariant() switch { "center" => CenterText(text, lineWidth), "right" => text.PadLeft(lineWidth), _ => text // left-aligned is default (no padding) }; } private static string CenterText(string text, int lineWidth) { int totalPadding = lineWidth - text.Length; int leftPadding = totalPadding / 2; return new string(' ', leftPadding) + text; } public string FormatTwoColumns(string left, string right, int lineWidth) { left ??= ""; right ??= ""; int halfWidth = lineWidth / 2; return left.PadRight(halfWidth) + right.PadLeft(lineWidth - halfWidth); } public List FormatTwoColumnsWithWrap(string left, string right, int lineWidth) { left ??= ""; right ??= ""; int halfWidth = lineWidth / 2; // Try simple format first if (left.Length + right.Length <= lineWidth) { int spaces = lineWidth - left.Length - right.Length; if (spaces < 1) spaces = 1; return new List { left + new string(' ', spaces) + right }; } // Wrap left side, right-align right on first line var leftLines = WrapText(left, halfWidth); var result = new List(); for (int i = 0; i < leftLines.Count; i++) { if (i == 0) { int spaces = lineWidth - leftLines[i].Length - right.Length; if (spaces < 1) { result.Add(leftLines[i]); result.Add(right.PadLeft(lineWidth)); } else { result.Add(leftLines[i] + new string(' ', spaces) + right); } } else { result.Add(leftLines[i]); } } return result; } public string FormatMultiColumn(List<(string text, int width, string align)> columns, int lineWidth) { var parts = new List(); // Calculate widths: distribute remaining width among columns without explicit width 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; foreach (var (text, width, align) in columns) { int colWidth = width > 0 ? width : defaultWidth; if (colWidth <= 0) colWidth = 1; string formatted = align.ToLowerInvariant() switch { "center" => CenterInWidth(text ?? "", colWidth), "right" => (text ?? "").PadLeft(colWidth), _ => (text ?? "").PadRight(colWidth) }; // Truncate if too long if (formatted.Length > colWidth) formatted = formatted[..colWidth]; parts.Add(formatted); } return string.Concat(parts); } public List 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>(); 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 { 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(); for (int lineIdx = 0; lineIdx < maxLines; lineIdx++) { var parts = new List(); 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 WrapText(string text, int maxWidth, int indent = 0) { var lines = new List(); if (string.IsNullOrEmpty(text) || maxWidth <= 0) { lines.Add(text ?? ""); return lines; } if (text.Length <= maxWidth) { lines.Add(text); return lines; } var words = text.Split(' '); string currentLine = ""; bool isFirst = true; foreach (var word in words) { int available = isFirst ? maxWidth : maxWidth - indent; if (available <= 0) available = 1; if (currentLine.Length == 0) { currentLine = word; } else if (currentLine.Length + 1 + word.Length <= available) { currentLine += " " + word; } else { if (isFirst) { lines.Add(currentLine); isFirst = false; } else { lines.Add(new string(' ', indent) + currentLine); } currentLine = word; } } if (currentLine.Length > 0) { if (isFirst) lines.Add(currentLine); else lines.Add(new string(' ', indent) + currentLine); } return lines; } public string CreateSeparator(char character, int lineWidth) { return new string(character, lineWidth); } public List FormatTable( List<(string text, int width, string align)> columns, List> headerRows, List> dataRows, int lineWidth, List? wrapFlags = null) { var result = new List(); // Calculate column widths var colWidths = CalculateTableColumnWidths(columns, lineWidth); bool hasWrap = wrapFlags != null && wrapFlags.Any(w => w); // Top border result.Add(FormatTableBorder(colWidths)); // Header rows foreach (var row in headerRows) { if (hasWrap) result.AddRange(FormatTableRows(row, colWidths, columns, wrapFlags!)); else result.Add(FormatTableRow(row, colWidths, columns)); } // Separator between header and data if (headerRows.Count > 0 && dataRows.Count > 0) { result.Add(FormatTableBorder(colWidths)); } // Data rows foreach (var row in dataRows) { if (hasWrap) result.AddRange(FormatTableRows(row, colWidths, columns, wrapFlags!)); else result.Add(FormatTableRow(row, colWidths, columns)); } // Bottom border result.Add(FormatTableBorder(colWidths)); return result; } private List CalculateTableColumnWidths(List<(string text, int width, string align)> columns, int lineWidth) { // Account for borders: |col1|col2|col3| = columns.Count + 1 border chars int availableWidth = lineWidth - columns.Count - 1; int totalExplicit = columns.Where(c => c.width > 0).Sum(c => c.width); int unspecifiedCount = columns.Count(c => c.width <= 0); int remaining = availableWidth - totalExplicit; int defaultWidth = unspecifiedCount > 0 ? remaining / unspecifiedCount : 0; var widths = new List(); foreach (var (_, width, _) in columns) { widths.Add(width > 0 ? width : Math.Max(defaultWidth, 1)); } return widths; } private string FormatTableBorder(List colWidths) { return "+" + string.Join("+", colWidths.Select(w => new string('-', w))) + "+"; } private string FormatTableRow(List cells, List colWidths, List<(string text, int width, string align)> columns) { var parts = new List(); for (int i = 0; i < colWidths.Count; i++) { string cell = i < cells.Count ? cells[i] : ""; string align = i < columns.Count ? columns[i].align : "left"; parts.Add(FormatCellContent(cell, colWidths[i], align)); } return "|" + string.Join("|", parts) + "|"; } private List FormatTableRows(List cells, List colWidths, List<(string text, int width, string align)> columns, List wrapFlags) { // Wrap each cell that has wrap enabled var allColLines = new List>(); 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 { cell.Length > colWidths[i] ? cell[..colWidths[i]] : cell }); } int maxLines = allColLines.Max(l => l.Count); var result = new List(); for (int lineIdx = 0; lineIdx < maxLines; lineIdx++) { var parts = new List(); 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) { if (text.Length > width) text = text[..width]; return align.ToLowerInvariant() switch { "center" => CenterInWidth(text, width), "right" => text.PadLeft(width), _ => text.PadRight(width) }; } private static string CenterInWidth(string text, int width) { if (string.IsNullOrEmpty(text)) return new string(' ', width); if (text.Length >= width) return text[..width]; int totalPadding = width - text.Length; int leftPadding = totalPadding / 2; int rightPadding = totalPadding - leftPadding; return new string(' ', leftPadding) + text + new string(' ', rightPadding); } }