using FastReport.Data; using FastReport.Preview; using FastReport.Utils; using System; using System.Collections.Generic; namespace FastReport.Engine { /// /// Represents the report engine. /// public partial class ReportEngine { #region Fields private Report report; private float curX; private float originX; private float curY; private int curColumn; private BandBase curBand; private DateTime date; private int hierarchyLevel; private float hierarchyIndent; private string hierarchyRowNo; private bool finalPass; private int firstReportPage; private bool isFirstReportPage; private int pagesLimit = 0; #endregion Fields #region Properties private Report Report { get { return report; } } private PreparedPages PreparedPages { get { return report.PreparedPages; } } /// /// Gets or sets the current X offset. /// /// /// This property specifies the X offset where the current band will be printed. /// public float CurX { get { return curX; } set { curX = Converter.DecreasePrecision(value, 2); } } /// /// Gets or sets the current Y offset. /// /// /// This property specifies the Y offset where the current band will be printed. /// After the band is printed, this value is incremented by the band's height. /// public float CurY { get { return curY; } set { curY = Converter.DecreasePrecision(value, 2); } } /// /// Gets the index of currently printing column in the multi-column report. /// /// /// This value is 0-based. /// public int CurColumn { get { return curColumn; } } /// /// Gets or sets index of current prepared page the current band will print on. /// /// /// Note: the page with specified index must exists. This property is used to print side-by-side /// subreports and Table object. Usually you don't need to use it. /// public int CurPage { get { return PreparedPages.CurPage; } set { PreparedPages.CurPage = value; } } /// /// Gets the current page width, in pixels. /// /// /// This property returns a paper width minus left and right margins. /// public float PageWidth { get { return page.WidthInPixels - (page.LeftMargin + page.RightMargin) * Units.Millimeters; } } /// /// Gets the current page height, in pixels. /// /// /// This property returns a paper height minus top and bottom margins. /// public float PageHeight { get { return page.HeightInPixels - (page.TopMargin + page.BottomMargin) * Units.Millimeters; } } /// /// Gets the value indicating whether the page has unlimited height. /// public bool UnlimitedHeight { get { return page.UnlimitedHeight; } } /// /// Gets the value indicating whether the page has unlimited width. /// public bool UnlimitedWidth { get { return page.UnlimitedWidth; } } /// /// Gets or sets the current height of unlimited page. /// public float UnlimitedHeightValue { get { return page.UnlimitedHeightValue; } set { page.UnlimitedHeightValue = value; } } /// /// Gets or sets the current width of unlimited page. /// public float UnlimitedWidthValue { get { return page.UnlimitedWidthValue; } set { page.UnlimitedWidthValue = value; } } /// /// Gets the height of page footer (including all its child bands), in pixels. /// public float PageFooterHeight { get { return GetBandHeightWithChildren(page.PageFooter); } } /// /// Gets the height of column footer (including all its child bands), in pixels. /// public float ColumnFooterHeight { get { return GetBandHeightWithChildren(page.ColumnFooter); } } /// /// Gets the free space on the current page, in pixels. /// /// /// This property returns the page height minus footers height minus CurY value. /// public float FreeSpace { get { float pageHeight = PageHeight; pageHeight -= PageFooterHeight; pageHeight -= ColumnFooterHeight; pageHeight -= GetFootersHeight(); return Converter.DecreasePrecision(pageHeight - CurY, 2); } } /// /// Gets the current prepared page number. /// /// /// This value is 1-based. The initial value (usually 1) is set in the Report.InitialPageNumber property. /// public int PageNo { get { return GetLogicalPageNumber(); } } /// /// Gets the number of total pages in a prepared report. /// /// /// To use this property, your report must be two-pass. Set the /// property to true. /// public int TotalPages { get { return GetLogicalTotalPages(); } } /// /// Gets the string that represents the current page number. /// /// /// This property returns a locale-based value, for example: "Page 1". /// public string PageN { get { return String.Format(Res.Get("Misc,PageN"), PageNo); } } /// /// Gets the string that represents the "Page N of M" number. /// /// /// This property returns a locale-based value, for example: "Page 1 of 10". /// public string PageNofM { get { return String.Format(Res.Get("Misc,PageNofM"), PageNo, TotalPages); } } /// /// Gets the current row number of currently printing band. /// /// /// This value is 1-based. It resets to 1 on each new group. /// public int RowNo { get { return curBand == null ? 0 : curBand.RowNo; } } /// /// Gets the running current row number of currently printing band. /// /// /// This value is 1-based. /// public int AbsRowNo { get { return curBand == null ? 0 : curBand.AbsRowNo; } } /// /// Gets the date of report start. /// public DateTime Date { get { return date; } } /// /// Gets a value indicating whether the report is executing the final pass. /// /// /// This property is true if report is one-pass, or if report is two-pass and /// the second pass is executing. /// public bool FinalPass { get { return finalPass; } } /// /// Gets a value indicating whether the report is executing the first pass. /// /// /// This property is true if report is one-pass, or if report is two-pass and /// the first pass is executing. /// public bool FirstPass { get { return !(Report.DoublePass && FinalPass); } } /// /// Gets a level of hierarchy when printing hierarchical bands. /// /// /// The first level of hierarchy has 0 index. /// public int HierarchyLevel { get { return hierarchyLevel; } } /// /// Gets the row number like "1.2.1" when printing hierarchical bands. /// public string HierarchyRowNo { get { return hierarchyRowNo; } } #endregion Properties #region Constructors internal ReportEngine(Report report) { this.report = report; objectsToProcess = new List(); } #endregion Constructors #region Private Methods private void ResetDesigningFlag() { ObjectCollection allObjects = Report.AllObjects; foreach (Base c in allObjects) { c.SetDesigning(false); } } private void InitializeData() { foreach (Base c in Report.Dictionary.AllObjects) { if (c is DataComponentBase) (c as DataComponentBase).InitializeComponent(); } foreach (Base c in Report.AllObjects) { if (c is ReportComponentBase obj) obj.ResetData(); } } private void InitializeSecondPassData() { foreach (Base c in Report.Dictionary.AllObjects) { if (c is DataSourceBase) { DataSourceBase data = c as DataSourceBase; if (data.RowCount > 0) data.First(); } } } private void PrepareToFirstPass(bool append) { finalPass = !Report.DoublePass; if (!append) PreparedPages.Clear(); else PreparedPages.CurPage = PreparedPages.Count > 0 ? PreparedPages.Count - 1 : 0; isFirstReportPage = true; hierarchyLevel = 1; PreparedPages.PrepareToFirstPass(); Report.Dictionary.Totals.ClearValues(); objectsToProcess.Clear(); InitializePages(); InitPageNumbers(); } private void PrepareToSecondPass() { Report.Dictionary.Totals.ClearValues(); objectsToProcess.Clear(); PreparedPages.ClearFirstPass(); InitializeSecondPassData(); } private float GetBandHeightWithChildren(BandBase band) { float result = 0; while (band != null) { if (CanPrint(band)) result += (band.CanGrow || band.CanShrink) ? CalcHeight(band) : band.Height; else if (FinalPass && !String.IsNullOrEmpty(band.VisibleExpression) && band.VisibleExpression.Contains("TotalPages")) result += (band.CanGrow || band.CanShrink) ? CalcHeight(band) : band.Height; band = band.Child; if (band != null && ((band as ChildBand).FillUnusedSpace || (band as ChildBand).CompleteToNRows != 0)) break; } return result; } private void RunReportPages(ReportPage page) { OnStateChanged(Report, EngineState.ReportStarted); if (page == null) RunReportPages(); else RunReportPage(page); OnStateChanged(Report, EngineState.ReportFinished); } private void LimitPreparedPages() { // limit the prepared pages if (Report.MaxPages > 0) { while (PreparedPages.Count > Report.MaxPages) { PreparedPages.RemovePage(PreparedPages.Count - 1); } } // Limit the prepared pages again because pagesLimit has better priority than Report.MaxPages. if (pagesLimit > 0) { while (PreparedPages.Count > pagesLimit) { PreparedPages.RemovePage(PreparedPages.Count - 1); } } } #endregion Private Methods #region Internal Methods internal bool Run(bool runDialogs, bool append, bool resetDataState) { return Run(runDialogs, append, resetDataState, null); } internal bool Run(bool runDialogs, bool append, bool resetDataState, int pagesLimit) { this.pagesLimit = pagesLimit; return Run(runDialogs, append, resetDataState, null); } internal bool Run(bool runDialogs, bool append, bool resetDataState, ReportPage page) { RunPhase1(resetDataState); try { if (runDialogs && !RunDialogs()) return false; RunPhase2(append, page); } finally { RunFinished(); } return true; } internal void RunPhase1(bool resetDataState = true, bool webDialog = false) { date = SystemFake.DateTime.Now; Report.SetOperation(ReportOperation.Running); ResetDesigningFlag(); // don't reset the data state if we run the hyperlink's detail page or refresh a report. // This is necessary to keep data filtering settings alive if (resetDataState) InitializeData(); // don't call OnStartReport event again, if it's web dialog re-render if (!webDialog) Report.OnStartReport(EventArgs.Empty); } internal void RunPhase2(bool append, ReportPage page) { Config.ReportSettings.OnStartProgress(Report); PrepareToFirstPass(append); RunReportPages(page); ResetLogicalPageNumber(); if (Report.DoublePass && !Report.Aborted) { finalPass = true; PrepareToSecondPass(); RunReportPages(page); } } internal void RunPhase2(int? pagesLimit = null) { if (pagesLimit != null) this.pagesLimit = pagesLimit.Value; try { RunPhase2(false, null); } finally { RunFinished(); } } internal void RunFinished() { Report.OnFinishReport(EventArgs.Empty); Config.ReportSettings.OnFinishProgress(Report); Report.SetOperation(ReportOperation.None); LimitPreparedPages(); } #endregion Internal Methods } }