render method
Implementation
String render(
{TextLayout Function(PositionedCell) layoutFactory = _simpleLayoutFactory,
TextBorder? border}) {
border ??= TextBorder.DEFAULT;
final layouts = {for (var a in positionedCells) (a).cell: layoutFactory(a)};
final columnWidths = List<int>.filled(columnCount!, 1);
final columnBorderWidths = List<int>.filled(columnCount! + 1, 0);
final rowHeights = List<int>.filled(rowCount, 1);
final rowBorderHeights = List<int>.filled(rowCount + 1, 0);
for (final positionedCell in positionedCells) {
final rowIndex = positionedCell.rowIndex;
final columnIndex = positionedCell.columnIndex;
final cell = positionedCell.cell;
final canonicalStyle = positionedCell.canonicalStyle;
final layout = layouts[cell]!;
final columnSpan = cell.columnSpan;
if (columnSpan == 1) {
final currentWidth = columnWidths[columnIndex];
final contentWidth = layout.measureWidth();
if (contentWidth > currentWidth) {
columnWidths[columnIndex] = contentWidth;
}
}
final rowSpan = cell.rowSpan;
if (rowSpan == 1) {
final currentHeight = rowHeights[rowIndex];
final contentHeight = layout.measureHeight();
if (contentHeight > currentHeight) {
rowHeights[rowIndex] = contentHeight;
}
}
if ((columnIndex == 0 && tableStyle?.border == true ||
canonicalStyle.borderLeft == true) &&
(columnIndex > 0 || tableStyle?.borderStyle != BorderStyle.Hidden)) {
columnBorderWidths[columnIndex] = 1;
}
if ((columnIndex + columnSpan == columnCount &&
tableStyle?.border == true ||
canonicalStyle.borderRight == true) &&
(columnIndex + columnSpan < columnCount! ||
tableStyle?.borderStyle != BorderStyle.Hidden)) {
columnBorderWidths[columnIndex + columnSpan] = 1;
}
if ((rowIndex == 0 && tableStyle?.border == true ||
canonicalStyle.borderTop == true) &&
(rowIndex > 0 || tableStyle?.borderStyle != BorderStyle.Hidden)) {
rowBorderHeights[rowIndex] = 1;
}
if ((rowIndex + rowSpan == rowCount && tableStyle?.border == true ||
canonicalStyle.borderBottom == true) &&
(rowIndex + rowSpan < rowCount ||
tableStyle?.borderStyle != BorderStyle.Hidden)) {
rowBorderHeights[rowIndex + rowSpan] = 1;
}
}
final sortedColumnSpanCells = positionedCells
.where(((it) => it.cell.columnSpan > 1))
.toList()
..sort(
(a, b) => Comparable.compare(a.cell.columnSpan, b.cell.columnSpan));
for (final positionedCell in sortedColumnSpanCells) {
final columnIndex = positionedCell.columnIndex;
final cell = positionedCell.cell;
final layout = layouts[cell]!;
final columnSpan = cell.columnSpan;
final contentWidth = layout.measureWidth();
final columnSpanIndices = [
for (var i = columnIndex; i < columnIndex + columnSpan; i++) i
];
final currentSpanColumnWidth =
columnSpanIndices.map((e) => columnWidths[e]).fold(0, _plus);
final currentSpanBorderWidth = [
for (var i = columnIndex; i < columnIndex + columnSpan; i++) i
].map((e) => columnBorderWidths[e]).fold(0, _plus);
final currentSpanWidth = currentSpanColumnWidth + currentSpanBorderWidth;
final remainingSize = contentWidth - currentSpanWidth;
if (remainingSize > 0) {
// TODO change to distribute remaining size proportionally to the existing widths?
final commonSize = remainingSize ~/ columnSpan;
final extraSize = remainingSize - (commonSize * columnSpan);
for (var spanIndex = 0;
spanIndex < columnSpanIndices.length;
spanIndex++) {
final targetColumnIndex = columnSpanIndices[spanIndex];
final additionalSize =
(spanIndex < extraSize) ? commonSize + 1 : commonSize;
final currentWidth = columnWidths[targetColumnIndex];
final newWidth = currentWidth + additionalSize;
columnWidths[targetColumnIndex] = newWidth;
}
}
}
final sortedRowSpanCells = positionedCells
.where(((it) => it.cell.rowSpan > 1))
.toList()
..sort((a, b) => Comparable.compare(a.cell.rowSpan, b.cell.rowSpan));
for (final positionedCell in sortedRowSpanCells) {
final rowIndex = positionedCell.rowIndex;
final cell = positionedCell.cell;
final layout = layouts[cell]!;
final rowSpan = cell.rowSpan;
final contentHeight = layout.measureHeight();
final rowSpanIndices = [
for (var i = rowIndex; i < rowIndex + rowSpan; i++) i
];
final currentSpanRowHeight =
rowSpanIndices.map((it) => rowHeights[it]).fold(0, _plus);
final currentSpanBorderHeight = [
for (var i = rowIndex + 1; i < rowIndex + rowSpan; i++) i
].map((it) => rowBorderHeights[it]).fold(0, _plus);
final currentSpanHeight = currentSpanRowHeight + currentSpanBorderHeight;
final remainingSize = contentHeight - currentSpanHeight;
if (remainingSize > 0) {
// TODO change to distribute remaining size proportionally to the existing widths?
final commonSize = remainingSize ~/ rowSpan;
final extraSize = remainingSize - (commonSize * rowSpan);
var spanIndex = 0;
for (var targetRowIndex in rowSpanIndices) {
final additionalSize =
(spanIndex < extraSize) ? commonSize + 1 : commonSize;
final currentHeight = rowHeights[targetRowIndex];
final newHeight = currentHeight + additionalSize;
rowHeights[targetRowIndex] = newHeight;
spanIndex++;
}
}
}
final tableLefts = List<int>.filled(columnWidths.length + 1, 0);
int tableWidth;
var left = 0;
for (final i in Iterable<int>.generate(columnWidths.length)) {
tableLefts[i] = left;
left += columnWidths[i] + columnBorderWidths[i];
}
tableLefts[columnWidths.length] = left;
tableWidth = left + columnBorderWidths[columnWidths.length];
final tableTops = List<int>.filled(rowHeights.length + 1, 0);
int tableHeight;
var top = 0;
for (final i in Iterable<int>.generate(rowHeights.length)) {
tableTops[i] = top;
top += rowHeights[i] + rowBorderHeights[i];
}
tableTops[rowHeights.length] = top;
tableHeight = top + rowBorderHeights[rowHeights.length];
final surface = TextSurface(tableWidth, tableHeight);
for (final rowIndex in Iterable<int>.generate(rowCount + 1)) {
final rowDrawStartIndex = tableTops[rowIndex];
for (final columnIndex in Iterable<int>.generate(columnCount! + 1)) {
final positionedCell = getOrNull(rowIndex, columnIndex);
final cell = positionedCell?.cell;
final cellCanonicalStyle = positionedCell?.canonicalStyle;
final previousRowPositionedCell = getOrNull(rowIndex, columnIndex - 1);
final previousRowCell = previousRowPositionedCell?.cell;
final previousRowCellCanonicalStyle =
previousRowPositionedCell?.canonicalStyle;
final previousColumnPositionedCell =
getOrNull(rowIndex - 1, columnIndex);
final previousColumnCell = previousColumnPositionedCell?.cell;
final previousColumnCellCanonicalStyle =
previousColumnPositionedCell?.canonicalStyle;
final columnDrawStartIndex = tableLefts[columnIndex];
final rowBorderHeight = rowBorderHeights[rowIndex];
final hasRowBorder = rowBorderHeight != 0;
final columnBorderWidth = columnBorderWidths[columnIndex];
final hasColumnBorder = columnBorderWidth != 0;
if (hasRowBorder && hasColumnBorder) {
final previousRowColumnPositionedCell =
getOrNull(rowIndex - 1, columnIndex - 1);
final previousRowColumnCell = previousRowColumnPositionedCell?.cell;
final previousRowColumnCellCanonicalStyle =
previousRowColumnPositionedCell?.canonicalStyle;
final cornerTopBorder =
!identical(previousRowColumnCell, previousColumnCell) &&
(previousRowColumnCellCanonicalStyle?.borderRight == true ||
previousColumnCellCanonicalStyle?.borderLeft == true ||
rowIndex > 0 &&
(columnIndex == 0 || columnIndex == columnCount) &&
tableStyle?.border == true);
final cornerLeftBorder =
!identical(previousRowColumnCell, previousRowCell) &&
(previousRowColumnCellCanonicalStyle?.borderBottom == true ||
previousRowCellCanonicalStyle?.borderTop == true ||
columnIndex > 0 &&
(rowIndex == 0 || rowIndex == rowCount) &&
tableStyle?.border == true);
final cornerBottomBorder = !identical(previousRowCell, cell) &&
(previousRowCellCanonicalStyle?.borderRight == true ||
cellCanonicalStyle?.borderLeft == true ||
rowIndex < rowCount &&
(columnIndex == 0 || columnIndex == columnCount) &&
tableStyle?.border == true);
final cornerRightBorder = !identical(previousColumnCell, cell) &&
(previousColumnCellCanonicalStyle?.borderBottom == true ||
cellCanonicalStyle?.borderTop == true ||
columnIndex < columnCount! &&
(rowIndex == 0 || rowIndex == rowCount) &&
tableStyle?.border == true);
if (cornerTopBorder ||
cornerLeftBorder ||
cornerBottomBorder ||
cornerRightBorder) {
final borderChar = border.get(
down: cornerBottomBorder,
up: cornerTopBorder,
left: cornerLeftBorder,
right: cornerRightBorder,
);
surface.set(rowDrawStartIndex, columnDrawStartIndex, borderChar);
}
}
if (hasColumnBorder &&
!identical(previousRowCell, cell) &&
(previousRowCellCanonicalStyle?.borderRight == true ||
cellCanonicalStyle?.borderLeft == true ||
(columnIndex == 0 || columnIndex == columnCount) &&
tableStyle?.border == true)) {
final rowDrawEndIndex =
tableTops[rowIndex + 1]; // Safe given cell != null.
final borderChar = border.vertical;
for (final rowDrawIndex in [
for (int i = rowDrawStartIndex + rowBorderHeight;
i < rowDrawEndIndex;
i++)
i
]) {
surface.set(rowDrawIndex, columnDrawStartIndex, borderChar);
}
}
if (hasRowBorder &&
!identical(previousColumnCell, cell) &&
(previousColumnCellCanonicalStyle?.borderBottom == true ||
cellCanonicalStyle?.borderTop == true ||
(rowIndex == 0 || rowIndex == rowCount) &&
tableStyle?.border == true)) {
final columnDrawEndIndex =
tableLefts[columnIndex + 1]; // Safe given cell != null
final borderChar = border.horizontal;
for (final columnDrawIndex in [
for (int i = columnDrawStartIndex + columnBorderWidth;
i < columnDrawEndIndex;
i++)
i
]) {
surface.set(rowDrawStartIndex, columnDrawIndex, borderChar);
}
}
}
}
for (var positionedCell in positionedCells) {
final rowIndex = positionedCell.rowIndex;
final columnIndex = positionedCell.columnIndex;
final cell = positionedCell.cell;
final cellLeft =
tableLefts[columnIndex] + columnBorderWidths[columnIndex];
final cellRight = tableLefts[columnIndex + cell.columnSpan];
final cellTop = tableTops[rowIndex] + rowBorderHeights[rowIndex];
final cellBottom = tableTops[rowIndex + cell.rowSpan];
final canvas = surface.clip(cellLeft, cellRight, cellTop, cellBottom);
final layout = layouts[cell]!;
layout.draw(canvas);
}
return surface.toString();
}