package boxrenderer;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.util.List;
import com.google.common.collect.Lists;
public class TableBox extends AbstractBox implements Box {
private List<TableRowBox> rows = Lists.newArrayList();
// if set to true then the border and padding is not applied on tablebox render and
// and the margin of the nested cells is not applied too.
// Also the cell borders are modified so that they to not overlap
private boolean borderCollapse = false;
private static class RenderInfo {
public TableCellBox content;
// public Character debug;
public boolean rendered = false;
public boolean collapsed = false;
public boolean processed = false;
}
public TableBox() {}
public TableBox(TableRowBox... rows) {
for(TableRowBox row : rows) {
addRow(row);
}
}
@Override
public Dimension getContentDimension(Graphics2D g2) throws Exception {
if(borderCollapse) {
// printDebug();
collapseBorder(g2);
}
int[] columnWidths = getColumnWidths(g2);
int[] rowHeights = getRowHeight(g2);
int width = getSum(columnWidths, 0, columnWidths.length);
int height = getSum(rowHeights, 0, rowHeights.length);
Dimension result = new Dimension(width, height);
return result;
}
@Override
public void renderContent(Graphics2D g2) throws Exception {
if(borderCollapse) {
collapseBorder(g2);
}
int[] columnWidths = getColumnWidths(g2);
int[] rowHeights = getRowHeight(g2);
RenderInfo[][] renderInfo = getRenderInfo();
int columnCount = columnWidths.length;
int rowCount = rowHeights.length;
// render the cells
for(int row = 0;row<renderInfo.length;row++) {
for(int column = 0;column<renderInfo[row].length;column++) {
RenderInfo ri = renderInfo[row][column];
if((ri != null) && !ri.rendered) {
ri.rendered=true;
int width = getSum(columnWidths, column, getColspan(ri.content, column, columnCount));
int height = getSum(rowHeights, row, getRowspan(ri.content, row, rowCount));
int x = getSum(columnWidths, 0, column);
int y = getSum(rowHeights, 0, row);
Graphics2D g0 = (Graphics2D)g2.create(x, y, width, height);
try {
ri.content.render(g0);
} finally {
g0.dispose();
}
}
}
}
}
public void addRow(TableRowBox row) {
rows.add(row);
}
private RenderInfo[][] getRenderInfo() {
int columnCount = getColumnCount();
int rowCount = getRowCount();
RenderInfo[][] renderInfo = new RenderInfo[rowCount][columnCount];
int rowIndex = 0;
for(TableRowBox row : rows) {
int columnIndex = 0;
for(TableCellBox cell : row.getCells()) {
while(renderInfo[rowIndex][columnIndex] != null) {
columnIndex++;
}
RenderInfo ri = new RenderInfo();
ri.content = cell;
for(int i = 0;i<getRowspan(cell, rowIndex, rowCount);i++) {
for(int j = 0;j<getColspan(cell, columnIndex, columnCount);j++) {
renderInfo[rowIndex+i][columnIndex+j] = ri;
}
}
}
rowIndex++;
}
return renderInfo;
}
/**
* returns the colspan of the current cell. if the colspan is set to 0 this mean span to the end
* of the table
* @param cell
* @param currentColumn - count from 0
* @param maxColumns
*/
private int getColspan(TableCellBox cell, int currentColumn, int maxColumns) {
int colspan = cell.getColspan();
if(colspan == 0) {
return maxColumns - currentColumn;
} else {
return colspan;
}
}
private int getRowspan(TableCellBox cell, int currentRow, int maxRows) {
int rowspan = cell.getRowspan();
if(rowspan == 0) {
return maxRows - currentRow;
} else {
return rowspan;
}
}
private int getColumnCount() {
int columns = 0;
int rowIndex = 0;
int rowCount = getRowCount();
int[] rowspans = new int[rowCount];
for(TableRowBox row : rows) {
int i = 0;
for(TableCellBox cell : row.getCells()) {
int cols = cell.getColspan();
if(cols == 0) {
cols = 1;
}
i+=cols;
for(int rs = 1; rs<getRowspan(cell, rowIndex, rowCount); rs++) {
rowspans[rowIndex+rs]+=cols;
}
}
columns = Math.max(columns, i+rowspans[rowIndex]);
rowIndex++;
}
return columns;
}
private int getRowCount() {
int rowIndex = 0;
int rowsTotal = rows.size();
for(TableRowBox row : rows) {
for(TableCellBox cell : row.getCells()) {
rowsTotal = Math.max(rowsTotal, cell.getRowspan()+rowIndex);
}
rowIndex++;
}
return rowsTotal;
}
private void collapseBorder(Graphics2D g2) throws Exception {
setBorder(new Border());
setPadding(new Padding());
RenderInfo[][] renderInfo = getRenderInfo();
for(int row = 0;row<renderInfo.length;row++) {
for(int column = 0;column<renderInfo[row].length;column++) {
RenderInfo ri = renderInfo[row][column];
if((ri != null) && !ri.collapsed) {
ri.collapsed=true;
ri.content.setMargin(new Margin());
if(row > 0) {
ri.content.getBorder().setTop(0);
}
if(column > 0) {
ri.content.getBorder().setLeft(0);
}
}
}
}
}
private int[] getColumnWidths(Graphics2D g2) throws Exception {
int columnCount = getColumnCount();
int[] result = new int[columnCount];
RenderInfo[][] renderInfo = getRenderInfo();
// set the largest column width for each columm, ignoring colspan columns
for(int r = 0;r<renderInfo.length;r++) {
for(int c=0;c<renderInfo[r].length;c++) {
RenderInfo ri = renderInfo[r][c];
if(ri!= null) {
TableCellBox cell = ri.content;
if(cell.getColspan()==1) {
result[c] = Math.max(cell.getDimension(g2).width, result[c]);
}
}
}
}
// make sure that the colspan cells fit into the space determined by the previous loop
// if not then the needed extra space it distributed over the spaned columns
for(int r = 0;r<renderInfo.length;r++) {
for(int c=0;c<renderInfo[r].length;c++) {
RenderInfo ri = renderInfo[r][c];
if(ri == null) {
continue;
}
TableCellBox cell = ri.content;
int span = getColspan(cell, c, columnCount);
if(span>1 && !ri.processed) {
ri.processed = true;
int width = getSum(result, c, span);
int cellWidth = cell.getDimension(g2).width;
if(cellWidth > width) {
// this code widens just the last column of the span:
// int width2 = getSum(result, c, span-1);
// int width3 = cellWidth-width2;
// result[c+span-1] = width3;
widen(result, c, span, cellWidth);
}
}
}
}
return result;
}
private int[] getRowHeight(Graphics2D g2) throws Exception {
int rowCount = getRowCount();
int[] result = new int[rowCount];
RenderInfo[][] renderInfo = getRenderInfo();
// set the largest row heigth for each row, ignoring rowspan cells
for(int r = 0;r<renderInfo.length;r++) {
for(int c=0;c<renderInfo[r].length;c++) {
RenderInfo ri = renderInfo[r][c];
if(ri != null) {
TableCellBox cell = ri.content;
if(cell.getRowspan()==1) {
result[r] = Math.max(cell.getDimension(g2).height, result[r]);
}
}
}
}
// make sure that the rowspan cells fit into the space determined by previous loop
// if not then the needed space is distributed over the spaned rows
for(int r = 0;r<renderInfo.length;r++) {
for(int c=0;c<renderInfo[r].length;c++) {
RenderInfo ri = renderInfo[r][c];
if(ri == null) {
continue;
}
TableCellBox cell = ri.content;
int span = getRowspan(cell, r, rowCount);
if((span>1) && (!ri.processed)) {
ri.processed=true;
int height = getSum(result, r, span);
int cellHeight = cell.getDimension(g2).height;
if(cellHeight > height) {
// int height2 = getSum(result, r, span-1);
// int height3 = cellHeight-height2;
// result[r+span-1] = height3;
widen(result, r, span, cellHeight);
}
}
}
}
return result;
}
private void widen(int[] result, int fromIndex, int count, int spaceNeeded) {
int measure = getSum(result, fromIndex, count);
int spaceNeededRemaining = spaceNeeded - measure;
for(int i=fromIndex;i<fromIndex+count;i++) {
if(spaceNeededRemaining <= 0) {
break;
}
int remaining = count-(i-fromIndex);
int thisWidened = spaceNeededRemaining / remaining;
result[i] += thisWidened;
spaceNeededRemaining -= thisWidened;
}
}
private int getSum(int[] iarray, int index, int span) {
int result = 0;
for(int i = index; i < index+span;i++) {
result+=iarray[i];
}
return result;
}
public boolean isBorderCollapse() {
return borderCollapse;
}
public void setBorderCollapse(boolean borderCollapse) {
this.borderCollapse = borderCollapse;
}
// private void printDebug() {
// String chars = "0123456789abcdefghijklmnopqrstuvwxyz";
// int i = 0;
// StringBuilder builder = new StringBuilder();
// RenderInfo[][] renderInfo = getRenderInfo();
// for(int row = 0;row<renderInfo.length;row++) {
// for(int column = 0;column<renderInfo[row].length;column++) {
// RenderInfo ri = renderInfo[row][column];
// if(ri == null) {
// builder.append('-');
// } else if(ri.debug != null){
// builder.append(ri.debug);
// } else {
// if(i >= chars.length()) {
// i = 0;
// }
// char next = chars.charAt(i++);
// ri.debug = next;
// builder.append(ri.debug);
// }
// }
// builder.append('\n');
// }
// System.out.println(builder.toString());
// }
}