/// <summary> /// Analyzes the Table and assigns values to this CssTable object. /// To be called from the constructor /// </summary> private void Analyze(Graphics g) { #region Assign box kinds foreach (CssBox box in TableBox.Boxes) { switch (box.Display) { case CssConstants.TableCaption: _caption = box; break; case CssConstants.TableColumn: for (int i = 0; i < GetSpan(box); i++) { Columns.Add(CreateColumn(box)); } break; case CssConstants.TableColumnGroup: if (box.Boxes.Count == 0) { int gspan = GetSpan(box); for (int i = 0; i < gspan; i++) { Columns.Add(CreateColumn(box)); } } else { foreach (CssBox bb in box.Boxes) { int bbspan = GetSpan(bb); for (int i = 0; i < bbspan; i++) { Columns.Add(CreateColumn(bb)); } } } break; case CssConstants.TableFooterGroup: if (FooterBox != null) { BodyRows.Add(box); } else { _footerBox = box; } break; case CssConstants.TableHeaderGroup: if (HeaderBox != null) { BodyRows.Add(box); } else { _headerBox = box; } break; case CssConstants.TableRow: BodyRows.Add(box); break; case CssConstants.TableRowGroup: foreach (CssBox childBox in box.Boxes) { if (childBox.Display == CssConstants.TableRow) { BodyRows.Add(childBox); } } break; } } #endregion #region Gather AllRows if (HeaderBox != null) { _allRows.AddRange(HeaderBox.Boxes); } _allRows.AddRange(BodyRows); if (FooterBox != null) { _allRows.AddRange(FooterBox.Boxes); } #endregion #region Insert EmptyBoxes for vertical cell spanning if (!TableBox._tableFixed) { int currow = 0; int curcol = 0; List <CssBox> rows = BodyRows; foreach (CssBox row in rows) { curcol = 0; for (int k = 0; k < row.Boxes.Count; k++) { CssBox cell = row.Boxes[k]; int rowspan = GetRowSpan(cell); int realcol = GetCellRealColumnIndex(row, cell); //Real column of the cell for (int i = currow + 1; i < currow + rowspan; i++) { int colcount = 0; for (int j = 0; j <= rows[i].Boxes.Count; j++) { if (colcount == realcol) { rows[i].Boxes.Insert(colcount, new CssSpacingBox(TableBox, ref cell, currow)); break; } colcount++; realcol -= GetColSpan(rows[i].Boxes[j]) - 1; } } curcol++; } currow++; } TableBox._tableFixed = true; } #endregion #region Determine Row and Column Count, and ColumnWidths //Rows _rowCount = BodyRows.Count + (HeaderBox != null ? HeaderBox.Boxes.Count : 0) + (FooterBox != null ? FooterBox.Boxes.Count : 0); //Columns if (Columns.Count > 0) { _columnCount = Columns.Count; } else { foreach (CssBox b in AllRows) //Check trhough rows { _columnCount = Math.Max(_columnCount, b.Boxes.Count); } } //Initialize column widths array _columnWidths = new float[_columnCount]; //Fill them with NaNs for (int i = 0; i < _columnWidths.Length; i++) { _columnWidths[i] = float.NaN; } float availCellSpace = GetAvailableCellWidth(); if (Columns.Count > 0) { #region Fill ColumnWidths array by scanning column widths for (int i = 0; i < Columns.Count; i++) { CssLength len = new CssLength(Columns[i].Width); //Get specified width if (len.Number > 0) //If some width specified { if (len.IsPercentage) //Get width as a percentage { ColumnWidths[i] = CssValueParser.ParseNumber(Columns[i].Width, availCellSpace); } else if (len.Unit == CssUnit.Pixels || len.Unit == CssUnit.None) { ColumnWidths[i] = len.Number; //Get width as an absolute-pixel value } } } #endregion } else { #region Fill ColumnWidths array by scanning width in table-cell definitions foreach (CssBox row in AllRows) { //Check for column width in table-cell definitions for (int i = 0; i < _columnCount; i++) { if (float.IsNaN(ColumnWidths[i]) && //Check if no width specified for column i < row.Boxes.Count && //And there's a box to check row.Boxes[i].Display == CssConstants.TableCell) //And the box is a table-cell { CssLength len = new CssLength(row.Boxes[i].Width); //Get specified width if (len.Number > 0) //If some width specified { int colspan = GetColSpan(row.Boxes[i]); float flen = 0f; if (len.IsPercentage)//Get width as a percentage { flen = CssValueParser.ParseNumber(row.Boxes[i].Width, availCellSpace); } else if (len.Unit == CssUnit.Pixels || len.Unit == CssUnit.None) { flen = len.Number; //Get width as an absolute-pixel value } flen /= Convert.ToSingle(colspan); for (int j = i; j < i + colspan; j++) { ColumnWidths[j] = flen; } } } } } #endregion } #endregion #region Determine missing Column widths if (WidthSpecified) //If a width was specified, { //Assign NaNs equally with space left after gathering not-NaNs int numberOfNans = 0; float occupedSpace = 0f; //Calculate number of NaNs and occuped space foreach (float t in ColumnWidths) { if (float.IsNaN(t)) { numberOfNans++; } else { occupedSpace += t; } } //Determine width that will be assigned to un asigned widths float nanWidth = (availCellSpace - occupedSpace) / Convert.ToSingle(numberOfNans); for (int i = 0; i < ColumnWidths.Length; i++) { if (float.IsNaN(ColumnWidths[i])) { ColumnWidths[i] = nanWidth; } } } else { //Assign NaNs using full width float[] maxFullWidths = new float[ColumnWidths.Length]; //Get the maximum full length of NaN boxes foreach (CssBox row in AllRows) { for (int i = 0; i < row.Boxes.Count; i++) { int col = GetCellRealColumnIndex(row, row.Boxes[i]); if (float.IsNaN(ColumnWidths[col]) && i < row.Boxes.Count && GetColSpan(row.Boxes[i]) == 1) { maxFullWidths[col] = Math.Max(maxFullWidths[col], row.Boxes[i].GetFullWidth(g)); } } } for (int i = 0; i < ColumnWidths.Length; i++) { if (float.IsNaN(ColumnWidths[i])) { ColumnWidths[i] = maxFullWidths[i]; } } } #endregion #region Reduce widths if necessary int curCol = 0; //While table width is larger than it should, and width is reductable while (GetWidthSum() > GetAvailableWidth() && CanReduceWidth()) { while (!CanReduceWidth(curCol)) { curCol++; } ColumnWidths[curCol] -= 1f; curCol++; if (curCol >= ColumnWidths.Length) { curCol = 0; } } #endregion #region Check for minimum sizes (increment widths if necessary) foreach (CssBox row in AllRows) { foreach (CssBox cell in row.Boxes) { int colspan = GetColSpan(cell); int col = GetCellRealColumnIndex(row, cell); int affectcol = col + colspan - 1; if (ColumnWidths[col] < ColumnMinWidths[col]) { float diff = ColumnMinWidths[col] - ColumnWidths[col]; ColumnWidths[affectcol] = ColumnMinWidths[affectcol]; if (col < ColumnWidths.Length - 1) { ColumnWidths[col + 1] -= diff; } } } } #endregion #region Set table padding //Ensure there's no padding TableBox.PaddingLeft = TableBox.PaddingTop = TableBox.PaddingRight = TableBox.PaddingBottom = "0"; #endregion #region Layout cells //Actually layout cells! float startx = TableBox.ClientLeft + HorizontalSpacing; float starty = TableBox.ClientTop + VerticalSpacing; float curx = startx; float cury = starty; float maxRight = startx; float maxBottom = 0f; int currentrow = 0; for (int i = 0; i < AllRows.Count; i++) { var row = AllRows[i]; curx = startx; curCol = 0; for (int j = 0; j < row.Boxes.Count; j++) { CssBox cell = row.Boxes[j]; if (curCol >= ColumnWidths.Length) { break; } int rowspan = GetRowSpan(cell); var columnIndex = GetCellRealColumnIndex(row, cell); float width = GetCellWidth(columnIndex, cell); cell.Location = new PointF(curx, cury); cell.Size = new SizeF(width, 0f); cell.MeasureBounds(g); //That will automatically set the bottom of the cell //Alter max bottom only if row is cell's row + cell's rowspan - 1 CssSpacingBox sb = cell as CssSpacingBox; if (sb != null) { if (sb.EndRow == currentrow) { maxBottom = Math.Max(maxBottom, sb.ExtendedBox.ActualBottom); } } else if (rowspan == 1) { maxBottom = Math.Max(maxBottom, cell.ActualBottom); } maxRight = Math.Max(maxRight, cell.ActualRight); curCol++; curx = cell.ActualRight + HorizontalSpacing; } foreach (CssBox cell in row.Boxes) { CssSpacingBox spacer = cell as CssSpacingBox; if (spacer == null && GetRowSpan(cell) == 1) { cell.ActualBottom = maxBottom; CssLayoutEngine.ApplyCellVerticalAlignment(g, cell); } else if (spacer != null && spacer.EndRow == currentrow) { spacer.ExtendedBox.ActualBottom = maxBottom; CssLayoutEngine.ApplyCellVerticalAlignment(g, spacer.ExtendedBox); } } cury = maxBottom + VerticalSpacing; currentrow++; } TableBox.ActualRight = maxRight + HorizontalSpacing + TableBox.ActualBorderRightWidth; TableBox.ActualBottom = maxBottom + VerticalSpacing + TableBox.ActualBorderBottomWidth; #endregion }
/// <summary> /// Layout the cells by the calculated table layout /// </summary> /// <param name="g"></param> private void LayoutCells(Graphics g) { float startx = Math.Max(_tableBox.ClientLeft + GetHorizontalSpacing(), 0); float starty = Math.Max(_tableBox.ClientTop + GetVerticalSpacing(), 0); float cury = starty; float maxRight = startx; float maxBottom = 0f; int currentrow = 0; for (int i = 0; i < _allRows.Count; i++) { var row = _allRows[i]; float curx = startx; int curCol = 0; for (int j = 0; j < row.Boxes.Count; j++) { CssBox cell = row.Boxes[j]; if (curCol >= _columnWidths.Length) { break; } int rowspan = GetRowSpan(cell); var columnIndex = GetCellRealColumnIndex(row, cell); float width = GetCellWidth(columnIndex, cell); cell.Location = new PointF(curx, cury); cell.Size = new SizeF(width, 0f); cell.PerformLayout(g); //That will automatically set the bottom of the cell //Alter max bottom only if row is cell's row + cell's rowspan - 1 CssSpacingBox sb = cell as CssSpacingBox; if (sb != null) { if (sb.EndRow == currentrow) { maxBottom = Math.Max(maxBottom, sb.ExtendedBox.ActualBottom); } } else if (rowspan == 1) { maxBottom = Math.Max(maxBottom, cell.ActualBottom); } maxRight = Math.Max(maxRight, cell.ActualRight); curCol++; curx = cell.ActualRight + GetHorizontalSpacing(); } foreach (CssBox cell in row.Boxes) { CssSpacingBox spacer = cell as CssSpacingBox; if (spacer == null && GetRowSpan(cell) == 1) { cell.ActualBottom = maxBottom; CssLayoutEngine.ApplyCellVerticalAlignment(g, cell); } else if (spacer != null && spacer.EndRow == currentrow) { spacer.ExtendedBox.ActualBottom = maxBottom; CssLayoutEngine.ApplyCellVerticalAlignment(g, spacer.ExtendedBox); } } cury = maxBottom + GetVerticalSpacing(); currentrow++; } _tableBox.ActualRight = maxRight + GetHorizontalSpacing() + _tableBox.ActualBorderRightWidth; _tableBox.ActualBottom = Math.Max(maxBottom, starty) + GetVerticalSpacing() + _tableBox.ActualBorderBottomWidth; }