render method
Renders the table to a string.
Implementation
@override
String render() {
if (_headers.isEmpty && _rows.isEmpty) return '';
final columns = _headers.isNotEmpty
? _headers.length
: (_rows.isNotEmpty ? _rows.first.length : 0);
// Calculate column widths using rendered cell content so width/align styles are respected.
final widths = List<int>.filled(columns, 0);
if (_manualWidths != null) {
for (var i = 0; i < columns && i < _manualWidths!.length; i++) {
widths[i] = _manualWidths![i];
}
}
if (_manualWidths == null) {
void applyWidths(List<String> cells, int rowIndex) {
for (var c = 0; c < columns; c++) {
final raw = c < cells.length ? cells[c] : '';
final rendered = _renderCellValue(rowIndex, c, raw);
for (final line in rendered.split('\n')) {
final len = Style.visibleLength(line);
if (len > widths[c]) widths[c] = len;
}
}
}
if (_headers.isNotEmpty) {
applyWidths(_headers, headerRow);
}
for (var i = 0; i < _rows.length; i++) {
applyWidths(_rows[i], i);
}
}
// Adjust for fixed width if specified
if (_width != null && _manualWidths == null) {
final totalPadding = _padding * 2 * columns;
final borderCount = _borderColumn ? columns + 1 : 2;
final available = _width! - totalPadding - borderCount;
if (available > 0) {
final perColumn = available ~/ columns;
for (var i = 0; i < widths.length; i++) {
if (widths[i] < perColumn) widths[i] = perColumn;
}
// Distribute any remaining width across columns (left-to-right) so the
// rendered table matches the requested total width.
final used = widths.fold<int>(0, (sum, w) => sum + w);
var remaining = available - used;
var j = 0;
while (remaining > 0 && widths.isNotEmpty) {
widths[j % widths.length]++;
remaining--;
j++;
}
}
}
final pad = ' ' * _padding;
final b = _border;
// Helper to style border characters
String styleBorderText(String text) {
if (_borderStyle == null) return text;
return _renderConfig.configureStyle(_borderStyle!).render(text);
}
// Build horizontal border line
String buildBorder(String left, String mid, String right, String fill) {
final parts = widths.map((w) => fill * (w + _padding * 2));
final leftChar = _borderLeft ? left : '';
final rightChar = _borderRight ? right : '';
final midChar = _borderColumn ? mid : '';
return styleBorderText('$leftChar${parts.join(midChar)}$rightChar');
}
// Build a row with optional styling - handles multi-line cells
List<String> buildRow(List<String> cells, int rowIndex) {
// First, process each cell and split into lines
final cellLines = <List<String>>[];
var maxLines = 1;
for (var c = 0; c < columns; c++) {
final raw = c < cells.length ? cells[c] : '';
final styledContent = _renderCellValue(rowIndex, c, raw, widths[c]);
// Split by newlines to handle multi-line content
final lines = styledContent.split('\n');
cellLines.add(lines);
if (lines.length > maxLines) maxLines = lines.length;
}
// Build output rows
final outputRows = <String>[];
for (var lineIdx = 0; lineIdx < maxLines; lineIdx++) {
final parts = <String>[];
for (var c = 0; c < columns; c++) {
final lines = cellLines[c];
final line = lineIdx < lines.length ? lines[lineIdx] : '';
final visible = Style.visibleLength(line);
final fill = widths[c] - visible;
final fillCount = fill > 0 ? fill : 0;
final cellContent = '$pad$line${' ' * fillCount}$pad';
parts.add(cellContent);
}
final leftBorder = _borderLeft ? styleBorderText(b.left) : '';
final rightBorder = _borderRight ? styleBorderText(b.right) : '';
final colSep = _borderColumn ? styleBorderText(b.left) : '';
outputRows.add('$leftBorder${parts.join(colSep)}$rightBorder');
}
return outputRows;
}
final buffer = StringBuffer();
// Top border
if (_borderTop) {
buffer.writeln(
buildBorder(b.topLeft, b.middleTop ?? b.top, b.topRight, b.top),
);
}
// Header row
if (_headers.isNotEmpty) {
for (final line in buildRow(_headers, headerRow)) {
buffer.writeln(line);
}
// Header separator
if (_borderHeader) {
buffer.writeln(
buildBorder(
b.middleLeft ?? b.left,
b.middle ?? b.top,
b.middleRight ?? b.right,
b.top,
),
);
}
}
// Data rows with offset and height
final startRow = _offset.clamp(0, _rows.length);
var endRow = _rows.length;
if (_height != null) {
// Calculate available rows based on height
var usedLines = 0;
if (_borderTop) usedLines++;
if (_headers.isNotEmpty) usedLines++;
if (_borderHeader && _headers.isNotEmpty) usedLines++;
if (_borderBottom) usedLines++;
final availableRows = _height! - usedLines;
if (availableRows > 0 && startRow + availableRows < endRow) {
endRow = startRow + availableRows;
}
}
for (var i = startRow; i < endRow; i++) {
for (final line in buildRow(_rows[i], i)) {
buffer.writeln(line);
}
// Row separator (between rows, not after last)
if (_borderRow && i < endRow - 1) {
buffer.writeln(
buildBorder(
b.middleLeft ?? b.left,
b.middle ?? b.top,
b.middleRight ?? b.right,
b.top,
),
);
}
}
// Bottom border
if (_borderBottom) {
buffer.write(
buildBorder(
b.bottomLeft,
b.middleBottom ?? b.bottom,
b.bottomRight,
b.bottom,
),
);
}
return buffer.toString().trimRight();
}