RaUI/Source/ryControls/HtmlRenderer/Core/Dom/CssLayoutEngineTable.cs
zilinsoft 3262955f2f ### 2023-11-07更新
------
#### RaUIV4    V4.0.2311.0701
- *.[全新]整合了MyDb、ryControls、MyDb_MySQL等dll文件到RaUI一个项目。
- *.[新增]新增ApkOp类,可以轻松获取APK信息。
- *.[新增]新增JsonExt扩展类,让Json操作更简单。
- *.[新增]新增WebP类,可以支持webp格式的图片。
- *.[改进]ryQuickSQL中的AddField方法改为自动替换已存在的同名值。
- *.[修复]ryQuickSQL中的AddFieldCalc方法无法正常计算的BUG。
2023-11-07 16:37:53 +08:00

1004 lines
37 KiB
C#

// "Therefore those skilled at the unorthodox
// are infinite as heaven and earth,
// inexhaustible as the great rivers.
// When they come to an end,
// they begin again,
// like the days and months;
// they die and are reborn,
// like the four seasons."
//
// - Sun Tsu,
// "The Art of War"
using System;
using System.Collections.Generic;
using TheArtOfDev.HtmlRenderer.Adapters;
using TheArtOfDev.HtmlRenderer.Adapters.Entities;
using TheArtOfDev.HtmlRenderer.Core.Entities;
using TheArtOfDev.HtmlRenderer.Core.Parse;
using TheArtOfDev.HtmlRenderer.Core.Utils;
namespace TheArtOfDev.HtmlRenderer.Core.Dom
{
/// <summary>
/// Layout engine for tables executing the complex layout of tables with rows/columns/headers/etc.
/// </summary>
internal sealed class CssLayoutEngineTable
{
#region Fields and Consts
/// <summary>
/// the main box of the table
/// </summary>
private readonly CssBox _tableBox;
/// <summary>
///
/// </summary>
private CssBox _caption;
private CssBox _headerBox;
private CssBox _footerBox;
/// <summary>
/// collection of all rows boxes
/// </summary>
private readonly List<CssBox> _bodyrows = new List<CssBox>();
/// <summary>
/// collection of all columns boxes
/// </summary>
private readonly List<CssBox> _columns = new List<CssBox>();
/// <summary>
///
/// </summary>
private readonly List<CssBox> _allRows = new List<CssBox>();
private int _columnCount;
private bool _widthSpecified;
private double[] _columnWidths;
private double[] _columnMinWidths;
#endregion
/// <summary>
/// Init.
/// </summary>
/// <param name="tableBox"></param>
private CssLayoutEngineTable(CssBox tableBox)
{
_tableBox = tableBox;
}
/// <summary>
/// Get the table cells spacing for all the cells in the table.<br/>
/// Used to calculate the spacing the table has in addition to regular padding and borders.
/// </summary>
/// <param name="tableBox">the table box to calculate the spacing for</param>
/// <returns>the calculated spacing</returns>
public static double GetTableSpacing(CssBox tableBox)
{
int count = 0;
int columns = 0;
foreach (var box in tableBox.Boxes)
{
if (box.Display == CssConstants.TableColumn)
{
columns += GetSpan(box);
}
else if (box.Display == CssConstants.TableRowGroup)
{
foreach (CssBox cr in tableBox.Boxes)
{
count++;
if (cr.Display == CssConstants.TableRow)
columns = Math.Max(columns, cr.Boxes.Count);
}
}
else if (box.Display == CssConstants.TableRow)
{
count++;
columns = Math.Max(columns, box.Boxes.Count);
}
// limit the amount of rows to process for performance
if (count > 30)
break;
}
// +1 columns because padding is between the cell and table borders
return (columns + 1) * GetHorizontalSpacing(tableBox);
}
/// <summary>
///
/// </summary>
/// <param name="g"></param>
/// <param name="tableBox"> </param>
public static void PerformLayout(RGraphics g, CssBox tableBox)
{
ArgChecker.AssertArgNotNull(g, "g");
ArgChecker.AssertArgNotNull(tableBox, "tableBox");
try
{
var table = new CssLayoutEngineTable(tableBox);
table.Layout(g);
}
catch (Exception ex)
{
tableBox.HtmlContainer.ReportError(HtmlRenderErrorType.Layout, "Failed table layout", ex);
}
}
#region Private Methods
/// <summary>
/// Analyzes the Table and assigns values to this CssTable object.
/// To be called from the constructor
/// </summary>
private void Layout(RGraphics g)
{
MeasureWords(_tableBox, g);
// get the table boxes into the proper fields
AssignBoxKinds();
// Insert EmptyBoxes for vertical cell spanning.
InsertEmptyBoxes();
// Determine Row and Column Count, and ColumnWidths
var availCellSpace = CalculateCountAndWidth();
DetermineMissingColumnWidths(availCellSpace);
// Check for minimum sizes (increment widths if necessary)
EnforceMinimumSize();
// While table width is larger than it should, and width is reducible
EnforceMaximumSize();
// Ensure there's no padding
_tableBox.PaddingLeft = _tableBox.PaddingTop = _tableBox.PaddingRight = _tableBox.PaddingBottom = "0";
//Actually layout cells!
LayoutCells(g);
}
/// <summary>
/// Get the table boxes into the proper fields.
/// </summary>
private void AssignBoxKinds()
{
foreach (var box in _tableBox.Boxes)
{
switch (box.Display)
{
case CssConstants.TableCaption:
_caption = 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;
case CssConstants.TableHeaderGroup:
if (_headerBox != null)
_bodyrows.Add(box);
else
_headerBox = box;
break;
case CssConstants.TableFooterGroup:
if (_footerBox != null)
_bodyrows.Add(box);
else
_footerBox = box;
break;
case CssConstants.TableColumn:
for (int i = 0; i < GetSpan(box); i++)
_columns.Add(box);
break;
case CssConstants.TableColumnGroup:
if (box.Boxes.Count == 0)
{
int gspan = GetSpan(box);
for (int i = 0; i < gspan; i++)
{
_columns.Add(box);
}
}
else
{
foreach (CssBox bb in box.Boxes)
{
int bbspan = GetSpan(bb);
for (int i = 0; i < bbspan; i++)
{
_columns.Add(bb);
}
}
}
break;
}
}
if (_headerBox != null)
_allRows.AddRange(_headerBox.Boxes);
_allRows.AddRange(_bodyrows);
if (_footerBox != null)
_allRows.AddRange(_footerBox.Boxes);
}
/// <summary>
/// Insert EmptyBoxes for vertical cell spanning.
/// </summary>
private void InsertEmptyBoxes()
{
if (!_tableBox._tableFixed)
{
int currow = 0;
List<CssBox> rows = _bodyrows;
foreach (CssBox row in rows)
{
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++)
{
if (rows.Count > 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;
}
}
}
}
currow++;
}
_tableBox._tableFixed = true;
}
}
/// <summary>
/// Determine Row and Column Count, and ColumnWidths
/// </summary>
/// <returns></returns>
private double CalculateCountAndWidth()
{
//Columns
if (_columns.Count > 0)
{
_columnCount = _columns.Count;
}
else
{
foreach (CssBox b in _allRows)
_columnCount = Math.Max(_columnCount, b.Boxes.Count);
}
//Initialize column widths array with NaNs
_columnWidths = new double[_columnCount];
for (int i = 0; i < _columnWidths.Length; i++)
_columnWidths[i] = double.NaN;
double availCellSpace = GetAvailableCellWidth();
if (_columns.Count > 0)
{
// 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
}
}
}
}
else
{
// 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 (i < 20 || double.IsNaN(_columnWidths[i])) // limit column width check
{
if (i < row.Boxes.Count && row.Boxes[i].Display == CssConstants.TableCell)
{
double len = CssValueParser.ParseLength(row.Boxes[i].Width, availCellSpace, row.Boxes[i]);
if (len > 0) //If some width specified
{
int colspan = GetColSpan(row.Boxes[i]);
len /= Convert.ToSingle(colspan);
for (int j = i; j < i + colspan; j++)
{
_columnWidths[j] = double.IsNaN(_columnWidths[j]) ? len : Math.Max(_columnWidths[j], len);
}
}
}
}
}
}
}
return availCellSpace;
}
/// <summary>
///
/// </summary>
/// <param name="availCellSpace"></param>
private void DetermineMissingColumnWidths(double availCellSpace)
{
double occupedSpace = 0f;
if (_widthSpecified) //If a width was specified,
{
//Assign NaNs equally with space left after gathering not-NaNs
int numOfNans = 0;
//Calculate number of NaNs and occupied space
foreach (double colWidth in _columnWidths)
{
if (double.IsNaN(colWidth))
numOfNans++;
else
occupedSpace += colWidth;
}
var orgNumOfNans = numOfNans;
double[] orgColWidths = null;
if (numOfNans < _columnWidths.Length)
{
orgColWidths = new double[_columnWidths.Length];
for (int i = 0; i < _columnWidths.Length; i++)
orgColWidths[i] = _columnWidths[i];
}
if (numOfNans > 0)
{
// Determine the max width for each column
double[] minFullWidths, maxFullWidths;
GetColumnsMinMaxWidthByContent(true, out minFullWidths, out maxFullWidths);
// set the columns that can fulfill by the max width in a loop because it changes the nanWidth
int oldNumOfNans;
do
{
oldNumOfNans = numOfNans;
for (int i = 0; i < _columnWidths.Length; i++)
{
var nanWidth = (availCellSpace - occupedSpace) / numOfNans;
if (double.IsNaN(_columnWidths[i]) && nanWidth > maxFullWidths[i])
{
_columnWidths[i] = maxFullWidths[i];
numOfNans--;
occupedSpace += maxFullWidths[i];
}
}
} while (oldNumOfNans != numOfNans);
if (numOfNans > 0)
{
// Determine width that will be assigned to un assigned widths
double nanWidth = (availCellSpace - occupedSpace) / numOfNans;
for (int i = 0; i < _columnWidths.Length; i++)
{
if (double.IsNaN(_columnWidths[i]))
_columnWidths[i] = nanWidth;
}
}
}
if (numOfNans == 0 && occupedSpace < availCellSpace)
{
if (orgNumOfNans > 0)
{
// spread extra width between all non width specified columns
double extWidth = (availCellSpace - occupedSpace) / orgNumOfNans;
for (int i = 0; i < _columnWidths.Length; i++)
if (orgColWidths == null || double.IsNaN(orgColWidths[i]))
_columnWidths[i] += extWidth;
}
else
{
// spread extra width between all columns with respect to relative sizes
for (int i = 0; i < _columnWidths.Length; i++)
_columnWidths[i] += (availCellSpace - occupedSpace) * (_columnWidths[i] / occupedSpace);
}
}
}
else
{
//Get the minimum and maximum full length of NaN boxes
double[] minFullWidths, maxFullWidths;
GetColumnsMinMaxWidthByContent(true, out minFullWidths, out maxFullWidths);
for (int i = 0; i < _columnWidths.Length; i++)
{
if (double.IsNaN(_columnWidths[i]))
_columnWidths[i] = minFullWidths[i];
occupedSpace += _columnWidths[i];
}
// spread extra width between all columns
for (int i = 0; i < _columnWidths.Length; i++)
{
if (maxFullWidths[i] > _columnWidths[i])
{
var temp = _columnWidths[i];
_columnWidths[i] = Math.Min(_columnWidths[i] + (availCellSpace - occupedSpace) / Convert.ToSingle(_columnWidths.Length - i), maxFullWidths[i]);
occupedSpace = occupedSpace + _columnWidths[i] - temp;
}
}
}
}
/// <summary>
/// While table width is larger than it should, and width is reductable.<br/>
/// If table max width is limited by we need to lower the columns width even if it will result in clipping<br/>
/// </summary>
private void EnforceMaximumSize()
{
int curCol = 0;
var widthSum = GetWidthSum();
while (widthSum > GetAvailableTableWidth() && CanReduceWidth())
{
while (!CanReduceWidth(curCol))
curCol++;
_columnWidths[curCol] -= 1f;
curCol++;
if (curCol >= _columnWidths.Length)
curCol = 0;
}
// if table max width is limited by we need to lower the columns width even if it will result in clipping
var maxWidth = GetMaxTableWidth();
if (maxWidth < 90999)
{
widthSum = GetWidthSum();
if (maxWidth < widthSum)
{
//Get the minimum and maximum full length of NaN boxes
double[] minFullWidths, maxFullWidths;
GetColumnsMinMaxWidthByContent(false, out minFullWidths, out maxFullWidths);
// lower all the columns to the minimum
for (int i = 0; i < _columnWidths.Length; i++)
_columnWidths[i] = minFullWidths[i];
// either min for all column is not enought and we need to lower it more resulting in clipping
// or we now have extra space so we can give it to columns than need it
widthSum = GetWidthSum();
if (maxWidth < widthSum)
{
// lower the width of columns starting from the largest one until the max width is satisfied
for (int a = 0; a < 15 && maxWidth < widthSum - 0.1; a++) // limit iteration so bug won't create infinite loop
{
int nonMaxedColumns = 0;
double largeWidth = 0f, secLargeWidth = 0f;
for (int i = 0; i < _columnWidths.Length; i++)
{
if (_columnWidths[i] > largeWidth + 0.1)
{
secLargeWidth = largeWidth;
largeWidth = _columnWidths[i];
nonMaxedColumns = 1;
}
else if (_columnWidths[i] > largeWidth - 0.1)
{
nonMaxedColumns++;
}
}
double decrease = secLargeWidth > 0 ? largeWidth - secLargeWidth : (widthSum - maxWidth) / _columnWidths.Length;
if (decrease * nonMaxedColumns > widthSum - maxWidth)
decrease = (widthSum - maxWidth) / nonMaxedColumns;
for (int i = 0; i < _columnWidths.Length; i++)
if (_columnWidths[i] > largeWidth - 0.1)
_columnWidths[i] -= decrease;
widthSum = GetWidthSum();
}
}
else
{
// spread extra width to columns that didn't reached max width where trying to spread it between all columns
for (int a = 0; a < 15 && maxWidth > widthSum + 0.1; a++) // limit iteration so bug won't create infinite loop
{
int nonMaxedColumns = 0;
for (int i = 0; i < _columnWidths.Length; i++)
if (_columnWidths[i] + 1 < maxFullWidths[i])
nonMaxedColumns++;
if (nonMaxedColumns == 0)
nonMaxedColumns = _columnWidths.Length;
bool hit = false;
double minIncrement = (maxWidth - widthSum) / nonMaxedColumns;
for (int i = 0; i < _columnWidths.Length; i++)
{
if (_columnWidths[i] + 0.1 < maxFullWidths[i])
{
minIncrement = Math.Min(minIncrement, maxFullWidths[i] - _columnWidths[i]);
hit = true;
}
}
for (int i = 0; i < _columnWidths.Length; i++)
if (!hit || _columnWidths[i] + 1 < maxFullWidths[i])
_columnWidths[i] += minIncrement;
widthSum = GetWidthSum();
}
}
}
}
}
/// <summary>
/// Check for minimum sizes (increment widths if necessary)
/// </summary>
private void EnforceMinimumSize()
{
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.Length > col && _columnWidths[col] < GetColumnMinWidths()[col])
{
double diff = GetColumnMinWidths()[col] - _columnWidths[col];
_columnWidths[affectcol] = GetColumnMinWidths()[affectcol];
if (col < _columnWidths.Length - 1)
{
_columnWidths[col + 1] -= diff;
}
}
}
}
}
/// <summary>
/// Layout the cells by the calculated table layout
/// </summary>
/// <param name="g"></param>
private void LayoutCells(RGraphics g)
{
double startx = Math.Max(_tableBox.ClientLeft + GetHorizontalSpacing(), 0);
double starty = Math.Max(_tableBox.ClientTop + GetVerticalSpacing(), 0);
double cury = starty;
double maxRight = startx;
double maxBottom = 0f;
int currentrow = 0;
for (int i = 0; i < _allRows.Count; i++)
{
var row = _allRows[i];
double 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);
double width = GetCellWidth(columnIndex, cell);
cell.Location = new RPoint(curx, cury);
cell.Size = new RSize(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++;
}
maxRight = Math.Max(maxRight, _tableBox.Location.X + _tableBox.ActualWidth);
_tableBox.ActualRight = maxRight + GetHorizontalSpacing() + _tableBox.ActualBorderRightWidth;
_tableBox.ActualBottom = Math.Max(maxBottom, starty) + GetVerticalSpacing() + _tableBox.ActualBorderBottomWidth;
}
/// <summary>
/// Gets the spanned width of a cell (With of all columns it spans minus one).
/// </summary>
private double GetSpannedMinWidth(CssBox row, CssBox cell, int realcolindex, int colspan)
{
double w = 0f;
for (int i = realcolindex; i < row.Boxes.Count || i < realcolindex + colspan - 1; i++)
{
if (i < GetColumnMinWidths().Length)
w += GetColumnMinWidths()[i];
}
return w;
}
/// <summary>
/// Gets the cell column index checking its position and other cells colspans
/// </summary>
/// <param name="row"></param>
/// <param name="cell"></param>
/// <returns></returns>
private static int GetCellRealColumnIndex(CssBox row, CssBox cell)
{
int i = 0;
foreach (CssBox b in row.Boxes)
{
if (b.Equals(cell))
break;
i += GetColSpan(b);
}
return i;
}
/// <summary>
/// Gets the cells width, taking colspan and being in the specified column
/// </summary>
/// <param name="column"></param>
/// <param name="b"></param>
/// <returns></returns>
private double GetCellWidth(int column, CssBox b)
{
double colspan = Convert.ToSingle(GetColSpan(b));
double sum = 0f;
for (int i = column; i < column + colspan; i++)
{
if (column >= _columnWidths.Length)
break;
if (_columnWidths.Length <= i)
break;
sum += _columnWidths[i];
}
sum += (colspan - 1) * GetHorizontalSpacing();
return sum; // -b.ActualBorderLeftWidth - b.ActualBorderRightWidth - b.ActualPaddingRight - b.ActualPaddingLeft;
}
/// <summary>
/// Gets the colspan of the specified box
/// </summary>
/// <param name="b"></param>
private static int GetColSpan(CssBox b)
{
string att = b.GetAttribute("colspan", "1");
int colspan;
if (!int.TryParse(att, out colspan))
{
return 1;
}
return colspan;
}
/// <summary>
/// Gets the rowspan of the specified box
/// </summary>
/// <param name="b"></param>
private static int GetRowSpan(CssBox b)
{
string att = b.GetAttribute("rowspan", "1");
int rowspan;
if (!int.TryParse(att, out rowspan))
{
return 1;
}
return rowspan;
}
/// <summary>
/// Recursively measures words inside the box
/// </summary>
/// <param name="box">the box to measure</param>
/// <param name="g">Device to use</param>
private static void MeasureWords(CssBox box, RGraphics g)
{
if (box != null)
{
foreach (var childBox in box.Boxes)
{
childBox.MeasureWordsSize(g);
MeasureWords(childBox, g);
}
}
}
/// <summary>
/// Tells if the columns widths can be reduced,
/// by checking the minimum widths of all cells
/// </summary>
/// <returns></returns>
private bool CanReduceWidth()
{
for (int i = 0; i < _columnWidths.Length; i++)
{
if (CanReduceWidth(i))
{
return true;
}
}
return false;
}
/// <summary>
/// Tells if the specified column can be reduced,
/// by checking its minimum width
/// </summary>
/// <param name="columnIndex"></param>
/// <returns></returns>
private bool CanReduceWidth(int columnIndex)
{
if (_columnWidths.Length >= columnIndex || GetColumnMinWidths().Length >= columnIndex)
return false;
return _columnWidths[columnIndex] > GetColumnMinWidths()[columnIndex];
}
/// <summary>
/// Gets the available width for the whole table.
/// It also sets the value of 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 double GetAvailableTableWidth()
{
CssLength tblen = new CssLength(_tableBox.Width);
if (tblen.Number > 0)
{
_widthSpecified = true;
return CssValueParser.ParseLength(_tableBox.Width, _tableBox.ParentBox.AvailableWidth, _tableBox);
}
else
{
return _tableBox.ParentBox.AvailableWidth;
}
}
/// <summary>
/// Gets the available width for the whole table.
/// It also sets the value of 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 double GetMaxTableWidth()
{
var tblen = new CssLength(_tableBox.MaxWidth);
if (tblen.Number > 0)
{
_widthSpecified = true;
return CssValueParser.ParseLength(_tableBox.MaxWidth, _tableBox.ParentBox.AvailableWidth, _tableBox);
}
else
{
return 9999f;
}
}
/// <summary>
/// Calculate the min and max width for each column of the table by the content in all rows.<br/>
/// the min width possible without clipping content<br/>
/// the max width the cell content can take without wrapping<br/>
/// </summary>
/// <param name="onlyNans">if to measure only columns that have no calculated width</param>
/// <param name="minFullWidths">return the min width for each column - the min width possible without clipping content</param>
/// <param name="maxFullWidths">return the max width for each column - the max width the cell content can take without wrapping</param>
private void GetColumnsMinMaxWidthByContent(bool onlyNans, out double[] minFullWidths, out double[] maxFullWidths)
{
maxFullWidths = new double[_columnWidths.Length];
minFullWidths = new double[_columnWidths.Length];
foreach (CssBox row in _allRows)
{
for (int i = 0; i < row.Boxes.Count; i++)
{
int col = GetCellRealColumnIndex(row, row.Boxes[i]);
col = _columnWidths.Length > col ? col : _columnWidths.Length - 1;
if ((!onlyNans || double.IsNaN(_columnWidths[col])) && i < row.Boxes.Count)
{
double minWidth, maxWidth;
row.Boxes[i].GetMinMaxWidth(out minWidth, out maxWidth);
var colSpan = GetColSpan(row.Boxes[i]);
minWidth = minWidth / colSpan;
maxWidth = maxWidth / colSpan;
for (int j = 0; j < colSpan; j++)
{
minFullWidths[col + j] = Math.Max(minFullWidths[col + j], minWidth);
maxFullWidths[col + j] = Math.Max(maxFullWidths[col + j], maxWidth);
}
}
}
}
}
/// <summary>
/// Gets the width available for cells
/// </summary>
/// <returns></returns>
/// <remarks>
/// It takes away the cell-spacing from <see cref="GetAvailableTableWidth"/>
/// </remarks>
private double GetAvailableCellWidth()
{
return GetAvailableTableWidth() - GetHorizontalSpacing() * (_columnCount + 1) - _tableBox.ActualBorderLeftWidth - _tableBox.ActualBorderRightWidth;
}
/// <summary>
/// Gets the current sum of column widths
/// </summary>
/// <returns></returns>
private double GetWidthSum()
{
double f = 0f;
foreach (double t in _columnWidths)
{
if (double.IsNaN(t))
throw new Exception("CssTable Algorithm error: There's a NaN in column widths");
else
f += t;
}
//Take cell-spacing
f += GetHorizontalSpacing() * (_columnWidths.Length + 1);
//Take table borders
f += _tableBox.ActualBorderLeftWidth + _tableBox.ActualBorderRightWidth;
return f;
}
/// <summary>
/// Gets the span attribute of the tag of the specified box
/// </summary>
/// <param name="b"></param>
private static int GetSpan(CssBox b)
{
double f = CssValueParser.ParseNumber(b.GetAttribute("span"), 1);
return Math.Max(1, Convert.ToInt32(f));
}
/// <summary>
/// Gets the minimum width of each column
/// </summary>
private double[] GetColumnMinWidths()
{
if (_columnMinWidths == null)
{
_columnMinWidths = new double[_columnWidths.Length];
foreach (CssBox row in _allRows)
{
foreach (CssBox cell in row.Boxes)
{
int colspan = GetColSpan(cell);
int col = GetCellRealColumnIndex(row, cell);
int affectcol = Math.Min(col + colspan, _columnMinWidths.Length) - 1;
double spannedwidth = GetSpannedMinWidth(row, cell, col, colspan) + (colspan - 1) * GetHorizontalSpacing();
_columnMinWidths[affectcol] = Math.Max(_columnMinWidths[affectcol], cell.GetMinimumWidth() - spannedwidth);
}
}
}
return _columnMinWidths;
}
/// <summary>
/// Gets the actual horizontal spacing of the table
/// </summary>
private double GetHorizontalSpacing()
{
return _tableBox.BorderCollapse == CssConstants.Collapse ? -1f : _tableBox.ActualBorderSpacingHorizontal;
}
/// <summary>
/// Gets the actual horizontal spacing of the table
/// </summary>
private static double GetHorizontalSpacing(CssBox box)
{
return box.BorderCollapse == CssConstants.Collapse ? -1f : box.ActualBorderSpacingHorizontal;
}
/// <summary>
/// Gets the actual vertical spacing of the table
/// </summary>
private double GetVerticalSpacing()
{
return _tableBox.BorderCollapse == CssConstants.Collapse ? -1f : _tableBox.ActualBorderSpacingVertical;
}
#endregion
}
}