using System; using System.Collections.Generic; using System.Text; using FastReport.Engine; using FastReport.Preview; using System.Drawing; using FastReport.Utils; namespace FastReport.Table { /// /// Represents a result table. /// /// /// Do not use this class directly. It is used by the and /// objects to render a result. /// public class TableResult : TableBase { private bool skip; private List rowsToSerialize; private List columnsToSerialize; private bool isFirstRow; /// /// Occurs after calculation of table bounds. /// /// /// You may use this event to change automatically calculated rows/column sizes. It may be useful /// if you need to fit dynamically printed table on a page. /// public event EventHandler AfterCalcBounds; internal bool Skip { get { return skip; } set { skip = value; } } internal List RowsToSerialize { get { return rowsToSerialize; } } internal List ColumnsToSerialize { get { return columnsToSerialize; } } private float GetRowsHeight(int startRow, int count) { float height = 0; // include row header if (startRow != 0 && (RepeatHeaders || RepeatColumnHeaders)) { for (int i = 0; i < FixedRows; i++) { height += Rows[i].Height; } } for (int i = 0; i < count; i++) { height += Rows[startRow + i].Height; } return height; } private float GetColumnsWidth(int startColumn, int count) { float width = 0; // include column header if (startColumn != 0 && (RepeatHeaders || RepeatRowHeaders)) { for (int i = 0; i < FixedColumns; i++) { width += Columns[i].Width; } } for (int i = 0; i < count; i++) { if (i == count - 1) width += Math.Max(Columns[startColumn + i].Width, Columns[startColumn + i].MinimumBreakWidth); else width += Columns[startColumn + i].Width; } return width; } private int GetRowsFit(int startRow, float freeSpace) { int rowsFit = 0; int rowsToKeep = 0; int rowsKept = 0; int saveRowsFit = 0; bool keeping = false; while (startRow + rowsFit < Rows.Count && (rowsFit == 0 || !Rows[startRow + rowsFit].PageBreak) && (!this.CanBreak | GetRowsHeight(startRow, rowsFit + 1) <= freeSpace + 0.1f)) { if (keeping) { rowsKept++; if (rowsKept >= rowsToKeep) keeping = false; } else if (Rows[startRow + rowsFit].KeepRows > 1) { rowsToKeep = Rows[startRow + rowsFit].KeepRows; rowsKept = 1; saveRowsFit = rowsFit; keeping = true; } rowsFit++; } if (keeping) rowsFit = saveRowsFit; // case if the row header does not fit on a page (at the start of table) if (startRow == 0 && rowsFit < FixedRows) rowsFit = 0; return rowsFit; } private int GetColumnsFit(int startColumn, float freeSpace) { int columnsFit = 0; int columnsToKeep = 0; int columnsKept = 0; int saveColumnsFit = 0; bool keeping = false; while (startColumn + columnsFit < Columns.Count && (columnsFit == 0 || !Columns[startColumn + columnsFit].PageBreak) && GetColumnsWidth(startColumn, columnsFit + 1) <= freeSpace + 0.1f) { if (keeping) { columnsKept++; if (columnsKept >= columnsToKeep) keeping = false; } else if (Columns[startColumn + columnsFit].KeepColumns > 1) { columnsToKeep = Columns[startColumn + columnsFit].KeepColumns; columnsKept = 1; saveColumnsFit = columnsFit; keeping = true; } columnsFit++; } if (keeping) columnsFit = saveColumnsFit; return columnsFit; } private void ProcessDuplicates(TableCell cell, int startX, int startY, List list) { string cellAlias = cell.Alias; TableCellData cellData = cell.CellData; string cellText = cell.Text; CellDuplicates cellDuplicates = cell.CellDuplicates; Func func = (row) => { int span = 0; for (int x = startX; x < ColumnCount; x++) { TableCell c = this[x, row]; if (IsInsideSpan(c, list)) break; if (c.Alias == cellAlias) { if (c.Text == cellText) span++; else break; } else break; } return span; }; int colSpan = func(startY); int rowSpan = 1; for (int y = startY + 1; y < RowCount; y++) { int span = func(y); if (span < cellData.ColSpan) break; rowSpan++; } if (cellDuplicates == CellDuplicates.Clear) { for (int x = 0; x < colSpan; x++) for (int y = 0; y < rowSpan; y++) if (!(x == 0 && y == 0)) GetCellData(x + startX, y + startY).Text = ""; } else if (cellDuplicates == CellDuplicates.Merge || (cellDuplicates == CellDuplicates.MergeNonEmpty && !String.IsNullOrEmpty(cellText))) { cellData.ColSpan = colSpan; cellData.RowSpan = rowSpan; } list.Add(new Rectangle(startX, startY, colSpan, rowSpan)); } private bool IsInsideSpan(TableCell cell, List list) { Point address = cell.Address; foreach (Rectangle span in list) { if (span.Contains(address)) return true; } return false; } private void ProcessDuplicates() { List list = new List(); for (int x = 0; x < ColumnCount; x++) { for (int y = 0; y < RowCount; y++) { TableCell cell = this[x, y]; if (cell.CellDuplicates != CellDuplicates.Show && !IsInsideSpan(cell, list)) { ProcessDuplicates(cell, x, y, list); } } } } internal void GeneratePages(object sender, EventArgs e) { isFirstRow = false; if (Skip) { Skip = false; return; } // check if band contains several tables if (sender is BandBase) { BandBase senderBand = sender as BandBase; isFirstRow = senderBand.IsFirstRow; SortedList tables = new SortedList(); foreach (Base obj in senderBand.Objects) { TableBase table = obj as TableBase; if (table != null && table.ResultTable != null) try { tables.Add(table.Left, table); } catch (ArgumentException) { throw new ArgumentException(Res.Get("Messages,MatrixLayoutError")); } } // render tables side-by-side if (tables.Count > 1) { ReportEngine engine = Report.Engine; TableLayoutInfo info = new TableLayoutInfo(); info.startPage = engine.CurPage; info.tableSize = new Size(1, 1); info.startX = tables.Values[0].Left; int startPage = info.startPage; float saveCurY = engine.CurY; int maxPage = 0; float maxCurY = 0; for (int i = 0; i < tables.Count; i++) { TableBase table = tables.Values[i]; // do not allow table to render itself in the band.AfterPrint event table.ResultTable.Skip = true; // render using the down-then-across mode table.Layout = TableLayout.DownThenAcross; engine.CurPage = info.startPage + (info.tableSize.Width - 1) * info.tableSize.Height; engine.CurY = saveCurY; float addLeft = 0; if (i > 0) addLeft = table.Left - tables.Values[i - 1].Right; table.ResultTable.Left = info.startX + addLeft; // calculate cells' bounds table.ResultTable.CalcBounds(); // generate pages Report.PreparedPages.AddPageAction = AddPageAction.WriteOver; info = table.ResultTable.GeneratePagesDownThenAcross(); if (engine.CurPage > maxPage) { maxPage = engine.CurPage; maxCurY = engine.CurY; } else if (engine.CurPage == maxPage && engine.CurY > maxCurY) { maxCurY = engine.CurY; } } engine.CurPage = maxPage; engine.CurY = maxCurY; Skip = false; return; } } // calculate cells' bounds CalcBounds(); // manage duplicates ProcessDuplicates(); if (Report.Engine.UnlimitedHeight || Report.Engine.UnlimitedWidth) { if (!Report.Engine.UnlimitedWidth) GeneratePagesWrapped(); else if (!Report.Engine.UnlimitedHeight) GeneratePagesDownThenAcross(); else GeneratePagesAcrossThenDown(); } else if (Layout == TableLayout.AcrossThenDown) GeneratePagesAcrossThenDown(); else if (Layout == TableLayout.DownThenAcross) GeneratePagesDownThenAcross(); else GeneratePagesWrapped(); } internal void AddToParent(Base parent) { // calculate cells' bounds CalcBounds(); // manage duplicates ProcessDuplicates(); // copy everything to regular table because TableResult is not suitable for this TableBase cloneTable = new TableBase(); cloneTable.Assign(this); foreach (TableColumn c in Columns) { TableColumn cloneColumn = new TableColumn(); cloneColumn.Assign(c); cloneTable.Columns.Add(cloneColumn); } foreach (TableRow r in Rows) { TableRow cloneRow = new TableRow(); cloneRow.Assign(r); cloneTable.Rows.Add(cloneRow); for (int columnIndex = 0; columnIndex < ColumnCount; columnIndex++) { TableCell cloneCell = new TableCell(); // this is the point why we have to do the cloning manually. r[columnIndex] may return shared instance of TableCell. cloneCell.AssignAll(r[columnIndex]); cloneCell.Parent = cloneRow; } } cloneTable.Parent = parent; } internal void CalcBounds() { // allow row/column manipulation from a script LockCorrectSpans = false; // fire AfterData event OnAfterData(); // calculate cells' bounds Height = CalcHeight(); // fire AfterCalcBounds event OnAfterCalcBounds(); } private void OnAfterCalcBounds() { if (AfterCalcBounds != null) AfterCalcBounds(this, EventArgs.Empty); } private void GeneratePagesAcrossThenDown() { ReportEngine engine = Report.Engine; PreparedPages preparedPages = Report.PreparedPages; preparedPages.CanUploadToCache = false; preparedPages.AddPageAction = AddPageAction.WriteOver; List spans = GetSpanList(); int startRow = 0; bool addNewPage = false; float freeSpace = engine.FreeSpace; Top = 0; while (startRow < Rows.Count) { if (addNewPage) { engine.StartNewPage(); freeSpace = engine.FreeSpace; } int startColumn = 0; int rowsFit = GetRowsFit(startRow, freeSpace); if (startRow == 0 && engine.IsKeeping && rowsFit < RowCount && isFirstRow && engine.KeepCurY > 0) { engine.EndColumn(); freeSpace = engine.FreeSpace; rowsFit = GetRowsFit(startRow, freeSpace); } // avoid the infinite loop if there is not enough space for one row if (rowsFit == 0) rowsFit = 1; int saveCurPage = engine.CurPage; float saveLeft = Left; float saveCurY = engine.CurY; float curY = engine.CurY; if (rowsFit != 0) { while (startColumn < Columns.Count) { int columnsFit = GetColumnsFit(startColumn, engine.PageWidth - Left); // avoid the infinite loop if there is not enough space for one column if (startColumn > 0 && columnsFit == 0) columnsFit = 1; engine.CurY = saveCurY; curY = GeneratePage(startColumn, startRow, columnsFit, rowsFit, new RectangleF(0, 0, engine.PageWidth, CanBreak ? freeSpace : Height), spans) + saveCurY; Left = 0; startColumn += columnsFit; if (startColumn < Columns.Count) { // if we have something to print, start a new page engine.StartNewPage(); } else if (engine.CurPage > saveCurPage) { // finish the last printed page in case it is not the start page engine.EndPage(false); } if (Report.Aborted) break; } } startRow += rowsFit; Left = saveLeft; engine.CurPage = saveCurPage; engine.CurY = curY; preparedPages.AddPageAction = AddPageAction.Add; addNewPage = true; if (Report.Aborted) break; } } private TableLayoutInfo GeneratePagesDownThenAcross() { ReportEngine engine = Report.Engine; PreparedPages preparedPages = Report.PreparedPages; preparedPages.CanUploadToCache = false; TableLayoutInfo info = new TableLayoutInfo(); info.startPage = engine.CurPage; List spans = GetSpanList(); int startColumn = 0; bool addNewPage = false; float saveCurY = engine.CurY; float lastCurY = 0; int lastPage = 0; Top = 0; while (startColumn < Columns.Count) { if (addNewPage) engine.StartNewPage(); int startRow = 0; int columnsFit = GetColumnsFit(startColumn, engine.PageWidth - Left); // avoid the infinite loop if there is not enough space for one column if (startColumn > 0 && columnsFit == 0) columnsFit = 1; engine.CurY = saveCurY; info.tableSize.Width++; info.tableSize.Height = 0; if (columnsFit > 0) { while (startRow < Rows.Count) { int rowsFit = GetRowsFit(startRow, engine.FreeSpace); if (startRow == 0 && engine.IsKeeping && rowsFit < RowCount && isFirstRow && engine.KeepCurY > 0) { engine.EndColumn(); rowsFit = GetRowsFit(startRow, engine.FreeSpace); } // avoid the infinite loop if there is not enough space for one row if (startRow > 0 && rowsFit == 0) rowsFit = 1; engine.CurY += GeneratePage(startColumn, startRow, columnsFit, rowsFit, new RectangleF(0, 0, engine.PageWidth, engine.FreeSpace), spans); info.tableSize.Height++; startRow += rowsFit; if (startRow < Rows.Count) { // if we have something to print, start a new page engine.StartNewPage(); } else if (startColumn > 0) { // finish the last printed page in case it is not a start page engine.EndPage(false); } if (Report.Aborted) break; } } info.startX = Left + GetColumnsWidth(startColumn, columnsFit); startColumn += columnsFit; Left = 0; preparedPages.AddPageAction = AddPageAction.Add; addNewPage = true; if (lastPage == 0) { lastPage = engine.CurPage; lastCurY = engine.CurY; } if (Report.Aborted) break; } engine.CurPage = lastPage; engine.CurY = lastCurY; return info; } private void GeneratePagesWrapped() { ReportEngine engine = Report.Engine; PreparedPages preparedPages = Report.PreparedPages; preparedPages.CanUploadToCache = false; List spans = GetSpanList(); int startColumn = 0; Top = 0; while (startColumn < Columns.Count) { int startRow = 0; int columnsFit = GetColumnsFit(startColumn, engine.PageWidth - Left); // avoid the infinite loop if there is not enough space for one column if (startColumn > 0 && columnsFit == 0) columnsFit = 1; while (startRow < Rows.Count) { int rowsFit = GetRowsFit(startRow, engine.FreeSpace); if (startRow == 0 && engine.IsKeeping && rowsFit < RowCount && isFirstRow && engine.KeepCurY > 0) { engine.EndColumn(); rowsFit = GetRowsFit(startRow, engine.FreeSpace); } if (rowsFit == 0) { engine.StartNewPage(); rowsFit = GetRowsFit(startRow, engine.FreeSpace); } engine.CurY += GeneratePage(startColumn, startRow, columnsFit, rowsFit, new RectangleF(0, 0, engine.PageWidth, engine.FreeSpace), spans); startRow += rowsFit; if (Report.Aborted) break; } startColumn += columnsFit; if (startColumn < Columns.Count) engine.CurY += WrappedGap; if (Report.Aborted) break; } } private void AdjustSpannedCellWidth(TableCellData cell) { if (!AdjustSpannedCellsWidth) return; // check that spanned cell has enough width float columnsWidth = 0; for (int i = 0; i < cell.ColSpan; i++) { columnsWidth += Columns[cell.Address.X + i].Width; } // if cell is bigger than sum of column width, increase the last column width float cellWidth = cell.CalcWidth(); if (columnsWidth < cellWidth) Columns[cell.Address.X + cell.ColSpan - 1].Width += cellWidth - columnsWidth; } private float GeneratePage(int startColumn, int startRow, int columnsFit, int rowsFit, RectangleF bounds, List spans) { // break spans foreach (Rectangle span in spans) { TableCellData spannedCell = GetCellData(span.Left, span.Top); TableCellData newSpannedCell = null; if (span.Left < startColumn && span.Right > startColumn) { if ((RepeatHeaders || RepeatRowHeaders) && span.Left < FixedColumns) { spannedCell.ColSpan = Math.Min(span.Right, startColumn + columnsFit) - startColumn + FixedColumns; } else { newSpannedCell = GetCellData(startColumn, span.Top); newSpannedCell.RunTimeAssign(spannedCell.Cell, true); newSpannedCell.ColSpan = Math.Min(span.Right, startColumn + columnsFit) - startColumn; newSpannedCell.RowSpan = spannedCell.RowSpan; AdjustSpannedCellWidth(newSpannedCell); } } if (span.Left < startColumn + columnsFit && span.Right > startColumn + columnsFit) { spannedCell.ColSpan = startColumn + columnsFit - span.Left; AdjustSpannedCellWidth(spannedCell); } if (span.Top < startRow && span.Bottom > startRow) { if ((RepeatHeaders || RepeatColumnHeaders) && span.Top < FixedRows) spannedCell.RowSpan = Math.Min(span.Bottom, startRow + rowsFit) - startRow + FixedRows; } if (span.Top < startRow + rowsFit && span.Bottom > startRow + rowsFit) { spannedCell.RowSpan = startRow + rowsFit - span.Top; newSpannedCell = GetCellData(span.Left, startRow + rowsFit); newSpannedCell.RunTimeAssign(spannedCell.Cell, true); newSpannedCell.ColSpan = spannedCell.ColSpan; newSpannedCell.RowSpan = span.Bottom - (startRow + rowsFit); // break the cell text TableCell cell = spannedCell.Cell; using (TextObject tempObject = new TextObject()) { if (!cell.Break(tempObject)) cell.Text = ""; if (cell.CanBreak) newSpannedCell.Text = tempObject.Text; } // fix the row height float textHeight = newSpannedCell.Cell.CalcHeight(); float rowsHeight = 0; for (int i = 0; i < newSpannedCell.RowSpan; i++) { rowsHeight += Rows[i + startRow + rowsFit].Height; } if (rowsHeight < textHeight) { // fix the last row's height Rows[startRow + rowsFit + newSpannedCell.RowSpan - 1].Height += textHeight - rowsHeight; } } } // set visible columns ColumnsToSerialize.Clear(); if (RepeatHeaders || RepeatRowHeaders) { for (int i = 0; i < FixedColumns; i++) { // Apply visible expression if needed. if (!String.IsNullOrEmpty(Columns[i].VisibleExpression)) Columns[i].Visible = CalcVisibleExpression(Columns[i].VisibleExpression); if (Columns[i].Visible) ColumnsToSerialize.Add(Columns[i]); } if (startColumn < FixedColumns) { columnsFit -= FixedColumns - startColumn; startColumn = FixedColumns; } } // calc visible columns and last X coordinate of table for unlimited page width float tableEndX = Columns[0].Width; for (int i = startColumn; i < startColumn + columnsFit; i++) { // Apply visible expression if needed. if (!String.IsNullOrEmpty(Columns[i].VisibleExpression)) Columns[i].Visible = CalcVisibleExpression(Columns[i].VisibleExpression); if (Columns[i].Visible) { ColumnsToSerialize.Add(Columns[i]); tableEndX += Columns[i].Width; } } // set visible rows RowsToSerialize.Clear(); if (RepeatHeaders || RepeatColumnHeaders) { for (int i = 0; i < FixedRows; i++) { // Apply visible expression if needed. if (!String.IsNullOrEmpty(Rows[i].VisibleExpression)) Rows[i].Visible = CalcVisibleExpression(Rows[i].VisibleExpression); if (Rows[i].Visible) RowsToSerialize.Add(Rows[i]); } if (startRow < FixedRows) { rowsFit -= FixedRows - startRow; startRow = FixedRows; } } // calc visible rows and last Y coordinate of table for unlimited page height float tableEndY = Rows[0].Top; for (int i = startRow; i < startRow + rowsFit; i++) { // Apply visible expression if needed. if (!String.IsNullOrEmpty(Rows[i].VisibleExpression)) Rows[i].Visible = CalcVisibleExpression(Rows[i].VisibleExpression); if (Rows[i].Visible) { RowsToSerialize.Add(Rows[i]); tableEndY += Rows[i].Height; } } // include row header if (startRow != 0 && (RepeatHeaders || RepeatColumnHeaders)) { for (int i = 0; i < FixedRows; i++) { tableEndY += Rows[i].Height; } } // generate unlimited page if (Report.Engine.UnlimitedHeight || Report.Engine.UnlimitedWidth) { if (Report.Engine.UnlimitedHeight) { bounds.Height = tableEndY; } if (Report.Engine.UnlimitedWidth) { bounds.Width = tableEndX; } } DataBand band = new DataBand(); band.Bounds = bounds; band.Objects.Add(this); Report.Engine.AddToPreparedPages(band); return GetRowsHeight(startRow, rowsFit); } /// protected override void Dispose(bool disposing) { LockCorrectSpans = true; base.Dispose(disposing); } /// public override void GetChildObjects(ObjectCollection list) { foreach (TableColumn column in ColumnsToSerialize) { list.Add(column); } foreach (TableRow row in RowsToSerialize) { list.Add(row); } } /// /// Creates a new instance of the class. /// public TableResult() { LockCorrectSpans = true; rowsToSerialize = new List(); columnsToSerialize = new List(); } private class TableLayoutInfo { public int startPage; public Size tableSize; public float startX; } } }