/// <summary> /// Ensures that the specified length is converted to pixels if necessary /// </summary> /// <param name="length"></param> private string NoEms(string length) { var len = new Length(length); if (len.Unit == Length.CssUnit.Ems) { length = len.ConvertEmToPixels(GetEmHeight()).ToString(); } return length; }
/// <summary> /// Gets the available width for the whole table. /// It also sets the value of <see cref="WidthSpecified" /> /// </summary> /// <returns></returns> /// <remarks> /// The table's width can be larger than the result of this method, because of the minimum /// size that individual boxes. /// </remarks> private float GetAvailableWidth() { var tblen = new Length(TableBox.Width); if (tblen.Number > 0) { WidthSpecified = true; if (tblen.IsPercentage) { return Value.ParseNumber(tblen.InternalLength, TableBox.ParentBox.AvailableWidth); } return tblen.Number; } return TableBox.ParentBox.AvailableWidth; }
/// <summary> /// Converts an HTML length into a Css length /// </summary> /// <param name="htmlLength"></param> /// <returns></returns> private string TranslateLength(string htmlLength) { var len = new Length(htmlLength); if (len.HasError) { return htmlLength + "px"; } return htmlLength; }
/// <summary> /// Analyzes the Table and assigns values to this CssTable object. /// To be called from the constructor /// </summary> private void Analyze(Graphics g) { var availSpace = GetAvailableWidth(); var availCellSpace = float.NaN; //Will be set later #region Assign box kinds foreach (var b in TableBox.Boxes) { b.RemoveAnonymousSpaces(); switch (b.Display) { case Constants.TableCaption: Caption = b; break; case Constants.TableColumn: for (var i = 0; i < GetSpan(b); i++) { Columns.Add(CreateColumn(b)); } break; case Constants.TableColumnGroup: if (b.Boxes.Count == 0) { var gspan = GetSpan(b); for (var i = 0; i < gspan; i++) { Columns.Add(CreateColumn(b)); } } else { foreach (var bb in b.Boxes) { var bbspan = GetSpan(bb); for (var i = 0; i < bbspan; i++) { Columns.Add(CreateColumn(bb)); } } } break; case Constants.TableFooterGroup: if (FooterBox != null) BodyRows.Add(b); else FooterBox = b; break; case Constants.TableHeaderGroup: if (HeaderBox != null) BodyRows.Add(b); else HeaderBox = b; break; case Constants.TableRow: BodyRows.Add(b); break; case Constants.TableRowGroup: foreach (var bb in b.Boxes) if (b.Display == Constants.TableRow) BodyRows.Add(b); break; default: 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) { var currow = 0; var curcol = 0; var rows = BodyRows; foreach (var row in rows) { row.RemoveAnonymousSpaces(); curcol = 0; for (var k = 0; k < row.Boxes.Count; k++) { var cell = row.Boxes[k]; var rowspan = GetRowSpan(cell); var realcol = GetCellRealColumnIndex(row, cell); //Real column of the cell for (var i = currow + 1; i < currow + rowspan; i++) { var colcount = 0; for (var j = 0; j <= rows[i].Boxes.Count; j++) { if (colcount == realcol) { rows[i].Boxes.Insert(colcount, new SpacingBox(TableBox, ref cell, currow)); break; } colcount++; realcol -= GetColSpan(rows[i].Boxes[j]) - 1; } } // End for (int i = currow + 1; i < currow + rowspan; i++) curcol++; } // End foreach (Box cell in row.Boxes) currow++; } // End foreach (Box row in rows) TableBox.TableFixed = true; } // End if (!TableBox.TableFixed) #endregion #region Determine Row and Column Count, and ColumnWidths //Rows RowCount = BodyRows.Count + (HeaderBox?.Boxes.Count ?? 0) + (FooterBox?.Boxes.Count ?? 0); //Columns if (Columns.Count > 0) ColumnCount = Columns.Count; else foreach (var 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 (var i = 0; i < ColumnWidths.Length; i++) ColumnWidths[i] = float.NaN; availCellSpace = GetAvailableCellWidth(); if (Columns.Count > 0) { #region Fill ColumnWidths array by scanning column widths for (var i = 0; i < Columns.Count; i++) { var len = new Length(Columns[i].Width); //Get specified width if (len.Number > 0) //If some width specified { if (len.IsPercentage) //Get width as a percentage { ColumnWidths[i] = Value.ParseNumber(Columns[i].Width, availCellSpace); } else if (len.Unit == Length.CssUnit.Pixels || len.Unit == Length.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 (var row in AllRows) { //Check for column width in table-cell definitions for (var 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 == Constants.TableCell) //And the box is a table-cell { var len = new Length(row.Boxes[i].Width); //Get specified width if (len.Number > 0) //If some width specified { var colspan = GetColSpan(row.Boxes[i]); var flen = 0f; if (len.IsPercentage) //Get width as a percentage { flen = Value.ParseNumber(row.Boxes[i].Width, availCellSpace); } else if (len.Unit == Length.CssUnit.Pixels || len.Unit == Length.CssUnit.None) { flen = len.Number; //Get width as an absolute-pixel value } flen /= Convert.ToSingle(colspan); for (var 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 var numberOfNans = 0; var occupedSpace = 0f; //Calculate number of NaNs and occuped space foreach (var t in ColumnWidths) if (float.IsNaN(t)) numberOfNans++; else occupedSpace += t; //Determine width that will be assigned to un asigned widths var nanWidth = (availCellSpace - occupedSpace)/Convert.ToSingle(numberOfNans); for (var i = 0; i < ColumnWidths.Length; i++) if (float.IsNaN(ColumnWidths[i])) ColumnWidths[i] = nanWidth; } else { //Assign NaNs using full width var maxFullWidths = new float[ColumnWidths.Length]; //Get the maximum full length of NaN boxes foreach (var row in AllRows) { for (var i = 0; i < row.Boxes.Count; i++) { var 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 (var i = 0; i < ColumnWidths.Length; i++) if (float.IsNaN(ColumnWidths[i])) ColumnWidths[i] = maxFullWidths[i]; } #endregion #region Reduce widths if necessary var curCol = 0; var reduceAmount = 1f; //While table width is larger than it should, and width is reductable while (GetWidthSum() > GetAvailableWidth() && CanReduceWidth()) { while (!CanReduceWidth(curCol)) curCol++; ColumnWidths[curCol] -= reduceAmount; curCol++; if (curCol >= ColumnWidths.Length) curCol = 0; } #endregion #region Check for minimum sizes (increment widths if necessary) foreach (var row in AllRows) { foreach (var cell in row.Boxes) { var colspan = GetColSpan(cell); var col = GetCellRealColumnIndex(row, cell); var affectcol = col + colspan - 1; if (ColumnWidths[col] < ColumnMinWidths[col]) { var diff = ColumnMinWidths[col] - ColumnWidths[col]; ColumnWidths[affectcol] = ColumnMinWidths[affectcol]; if (col < ColumnWidths.Length - 1) { ColumnWidths[col + 1] -= diff; } } } } #endregion #region Set table padding TableBox.Padding = "0"; //Ensure there's no padding #endregion #region Layout cells //Actually layout cells! var startx = TableBox.ClientLeft + HorizontalSpacing; var starty = TableBox.ClientTop + VerticalSpacing; var cury = starty; var maxRight = startx; var maxBottom = 0f; var currentrow = 0; foreach (var row in AllRows) { if (row is CssAnonymousSpaceBlockBox || row is CssAnonymousSpaceBox) continue; var curx = startx; curCol = 0; foreach (var cell in row.Boxes) { if (curCol >= ColumnWidths.Length) break; var rowspan = GetRowSpan(cell); var width = GetCellWidth(GetCellRealColumnIndex(row, cell), 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 var sb = cell as SpacingBox; 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 (var cell in row.Boxes) { var spacer = cell as SpacingBox; if (spacer == null && GetRowSpan(cell) == 1) { cell.ActualBottom = maxBottom; LayoutEngine.ApplyCellVerticalAlignment(g, cell); } else if (spacer != null && spacer.EndRow == currentrow) { spacer.ExtendedBox.ActualBottom = maxBottom; LayoutEngine.ApplyCellVerticalAlignment(g, spacer.ExtendedBox); } } cury = maxBottom + VerticalSpacing; currentrow++; } TableBox.ActualRight = maxRight + HorizontalSpacing + TableBox.ActualBorderRightWidth; TableBox.ActualBottom = maxBottom + VerticalSpacing + TableBox.ActualBorderBottomWidth; #endregion }