using Sarand.BusinessMgr.Net.Standard.Base.App; using Sarand.BusinessMgr.Net.Standard.Base.Mgr; using Sarand.BusinessMgr.Net.Standard.Base.Procs; using Sarand.Net.Standard.Base.Core; using Sarand.Net.Standard.Base.CodeTypes; using Sarand.Net.Standard.Base.Dbas; using Sarand.Net.Standard.Base.Procs; using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Printing; using System.Drawing.Text; using System.Linq; using System.Text; using System.Threading.Tasks; using Sarand.Net.Standard.Base.App; using Sarand.Net.Standard.Base.Utils; using Sarand.BusinessMgr.Net.Standard.Base.Core; namespace Sarand.BusinessMgr.Net.Core.Procs.Reports { //======================================================================= public abstract class SbmReport : SbmReport_Base { //------------------------------------------------------------------- // Definitions. public enum PrintPageResult { HasMorePages, NoMorePages, } protected class PrintPageConfig { public PrintPageConfig(Graphics graphics, PageSettings pageSettings, Rectangle pageBounds, Rectangle marginBounds, int pageNumber, int pageCount) { // Graphics for printing to physical device (printer or print preview control). Graphics = graphics; // Printing configuration. PageSettings = pageSettings; PageBounds = pageBounds; Bounds = marginBounds; PixelsPerInch = new SizeF(Graphics.DpiX, Graphics.DpiY); TabX = (int)Math.Ceiling(Bounds.Width * 0.025f); PageNumber = pageNumber; } /// Current graphics for printing on page. public Graphics Graphics { get; private set; } /// Current page number public int PageNumber { get; } /// Virtual page number public int PageNumberVirtual { get; set; } = 1; /// Total number of pages public int PageCount { get; } /// Page settings from PrintPage event - eg. to modify page length for continuous feed. public PageSettings PageSettings { get; } /// Page bounds from PrintPage event - eg. to modify page length for continuous feed. public Rectangle PageBounds { get; } // Page bounds. public Rectangle Bounds { get; set; } // Pixel sizes. public SizeF PixelsPerInch { get; } // Horizontal printing positions. public int PosXLeft { get { return Bounds.Left; } } public int PosXCenter { get { return (int)(Bounds.Left + (Bounds.Width * 0.5f)); } } public int PosXRight { get { return Bounds.Right; } } public int TabX { get; set; } public int[] ColsX { get; set; } // Current vertical print position. public int PosY { get; set; } public int PosYBodyTop { get; set; } public int PosYBodyBottom { get; set; } public int PosYFooterTop { get; set; } // Line spacing - proportional to font size. public float HeadingTextLineSpacing { get; set; } = 1.25f; public float IntroTextLineSpacing { get; set; } = 1.20f; public float DetailTextLineSpacing { get; set; } = 1.15f; } //------------------------------------------------------------------- // Configuration. public new class Config : SbmReport_Base.Config { protected Config(ISbmUserContext_Core userContext) : base(userContext) { } } //------------------------------------------------------------------- // Data members. protected SbmSystem system = SbmGlobals.System; private Config config; /// Print mode controls whether physical printing to printer or preview control or to dummy device to determine page size. private enum PrintMode { Physical, Virtual } private PrintMode printMode = PrintMode.Physical; // Printing tools. private StringFormat stringFormat = StringFormat.GenericTypographic; private static ColorConverter colorConverter = new ColorConverter(); private SbmPrinter_Footer footerPrinter = new SbmPrinter_Footer(); protected Brush brushBlack = new SolidBrush(Color.Black); //------------------------------------------------------------------- // Construction. protected SbmReport(Config config) : base(config) { this.config = config; } protected override void Dispose(bool disposing) { if (disposing) { stringFormat.Dispose(); footerPrinter.Dispose(); brushBlack.Dispose(); } base.Dispose(disposing); } //------------------------------------------------------------------- // Properties. public static String FontFamilyName { get; } = "Arial"; public static Color PrintColor { get; } = Color.Black; protected StringFormat StringFormat { get { return stringFormat; } } protected static ColorConverter ColorConverter { get { return colorConverter; } } // Report printing progress. /// Check if currently printing to the preview device. protected bool PreviewPrinting { get; private set; } /// Check if the current print mode is 'Physical', ie. actually printing to the page. protected bool PhysicalPrinting { get { return printMode == PrintMode.Physical; } } /// Check if currently printing to file. protected bool FilePrinting { get; private set; } /// Total number of items to be printed public virtual int ItemCount { get; protected set; } /// Position of item currently being published in the list. public virtual int ItemNumber { get; protected set; } /// Progress based on the item being printed against the total number to be printed. public override decimal ProgressPercent { get { return (ItemCount > 0 ? (decimal)ItemNumber / ItemCount : 0); } } //------------------------------------------------------------------- // Text utilities. protected SizeF DrawString(Graphics graphics, String text, Font font, Brush brush, PointF position, StringFormat format) { if (!VirtualPrint) graphics.DrawString(text, font, brush, position, format); // Calculate size of text. SizeF sizeText = graphics.MeasureString(text, font); return sizeText; } protected SizeF DrawString(Graphics graphics, String text, Font font, Brush brush, RectangleF position, StringFormat format) { if (!VirtualPrint) graphics.DrawString(text, font, brush, position, format); // For single lines of text, the height can be different for Physical (printing) and // Virtual (dummy) graphics even though they appear to be all but identical. // Closest issue description: https://social.msdn.microsoft.com/Forums/windows/en-US/7a431b8c-3ad5-49d8-b012-f56a21c5cec5/measurestring-returns-different-height-after-text-width-exceeds-layout-width?forum=winforms SizeF sizeText = graphics.MeasureString(text, font, (int)position.Width, format); if (sizeText.Height < font.GetHeight(graphics)) // Aug-2018 Temporary fix. sizeText.Height = font.GetHeight(graphics); return sizeText; } //------------------------------------------------------------------- // Printer configuration. public abstract void ConfigurePrinter(PageSettings pageSettings); public abstract void ConfigurePrintPreview(PageSettings pageSettings); public abstract void ConfigurePageMargins(PageSettings pageSettings); protected abstract void InitDrawingTools(Graphics graphics, Rectangle bounds); //------------------------------------------------------------------- // P r i n t P a g e S e c t i o n //------------------------------------------------------------------- // Solution to how to determine the number of pages in a report and // print the report across multiple pages even though all printing is // done via PrintPage() which prints a single page at a time. /// /// Print page section - portion of the report that is printed as a contiguous section, ie. all on one page. /// NB Page sections should be kept short for reports that may run over one page to ensure that the section doesn't overflow a page. /// eg. A single row in a printed table with optional headings. /// Method can be anonymous to allow other parameters to be passed in the context of the report's page printing, rathe rthan just relying on the Page Config. /// protected delegate void PrintPageSectionFn(); /// Print mode is either physical - printing to printer or preview control - or virtual - to test whether section fits on page. protected bool VirtualPrint { get { return printMode == PrintMode.Virtual; } } private void SwitchPrintMode(PrintMode mode, PrintPageConfig cfg) { printMode = mode; } /// /// Test print the page section to verify that it fits on the page, before printing to the physical device. /// Returns false if section doesn't fit on the page. /// protected bool PrintPageSectionIfSpace(PrintPageConfig cfg, PrintPageSectionFn printPageSectionFn) { // Make a note of the current print position and test print the section to the virtual page. int posYInitial = cfg.PosY; SwitchPrintMode(PrintMode.Virtual, cfg); printPageSectionFn(); // Check if the section fits on the page. if (cfg.PosY >= cfg.PosYBodyBottom) { // If the virtual page is the same or greater (shouldn't happen) // than the current page, a new page is required. if (cfg.PageNumberVirtual >= cfg.PageNumber) { SwitchPrintMode(PrintMode.Physical, cfg); return false; } // Otherwise, we can increment the virtual page and reset printing to the top before reprinting the section. cfg.PageNumberVirtual++; cfg.PosY = cfg.PosYBodyTop; if (cfg.PageNumberVirtual == cfg.PageNumber) SwitchPrintMode(PrintMode.Physical, cfg); } else { // Section fits on virtual page. if (cfg.PageNumberVirtual != cfg.PageNumber) // Not yet on current page so no need to reprint section. return true; // On current page and section needs to be printed at original position to the physical device. cfg.PosY = posYInitial; SwitchPrintMode(PrintMode.Physical, cfg); } printPageSectionFn(); SwitchPrintMode(PrintMode.Physical, cfg); return true; } //------------------------------------------------------------------- // P r i n t i n g //------------------------------------------------------------------- public virtual void BeginPrint(PrintAction printAction, int pageNumberFrom = 0, int pageNumberTo = 0) { if (DebugMode) SaLogger.Debug($"{SysUtil.Fn} PrintAction: " + printAction + ". Page range: " + pageNumberFrom + "-" + pageNumberTo + ". Page count: " + PageCount); PageNumberFrom = PageNumberFrom; PageNumberTo = pageNumberTo; PageNumber = Math.Max(1, pageNumberFrom) - 1; ItemCount = 0; ItemNumber = 0; PreviewPrinting = printAction == PrintAction.PrintToPreview; FilePrinting = printAction == PrintAction.PrintToFile; } public virtual void QueryPageSettings(PageSettings pageSettings, PrintAction printAction) { } public PrintPageResult PrintPage(Graphics graphics, PageSettings pageSettings, Rectangle pageBounds, Rectangle marginBounds) { PageNumber++; // Configure graphics for printing document. // NB Graphics properties set here muust also be copied to virtual device graphics in PrintPageConfig above. graphics.SmoothingMode = SmoothingMode.AntiAlias; graphics.TextRenderingHint = TextRenderingHint.AntiAlias; InitDrawingTools(graphics, marginBounds); // Configure default String Format. StringFormat.Alignment = StringAlignment.Near; StringFormat.LineAlignment = StringAlignment.Near; StringFormat.FormatFlags = StringFormatFlags.FitBlackBox | StringFormatFlags.LineLimit | StringFormatFlags.NoClip /*| StringFormatFlags.NoWrap */; StringFormat.Trimming = StringTrimming.Character; // Page configuration. PrintPageConfig cfg = new PrintPageConfig(graphics, pageSettings, pageBounds, marginBounds, PageNumber, PageCount); cfg.Bounds = marginBounds; cfg.PosY = marginBounds.Top; cfg.PosYBodyTop = marginBounds.Top; cfg.PosYBodyBottom = marginBounds.Bottom; if (DebugMode) SaLogger.Debug($"{SysUtil.Fn} Page: " + PageNumber); SwitchPrintMode(PrintMode.Physical, cfg); // Log print configuration. if (SbmGlobals.System.IsExtendedLogging(SbmSystem.ExtendedLogginOption.Printing)) LogPageSettings(graphics, pageSettings, pageBounds, marginBounds); // Print page and interpret result. PrintPageResult printPageResult = PrintPage(cfg); // Update page range information depending on whether this is the last page in the report. if (printPageResult == PrintPageResult.HasMorePages) { PageNumberMaxKnown = Math.Max(PageNumberMaxKnown, PageNumber + 1); } else if (printPageResult == PrintPageResult.NoMorePages) { PageNumberMaxKnown = PageNumber; PageCount = PageNumber; } // Always return 'No more pages' if finished printing page range. if (PageNumberTo > 0 && PageNumber + 1 > PageNumberTo) return PrintPageResult.NoMorePages; return printPageResult; } protected abstract PrintPageResult PrintPage(PrintPageConfig cfg); public virtual void EndPrint(PrintAction printAction) { } //------------------------------------------------------------------- protected void LogPageSettings(Graphics graphics, PageSettings pageSettings, Rectangle pageBounds, Rectangle marginBounds) { // Log print configuration. List info = new List(); // Color=True, Landscape=False, Margins=[Margins Left=36 Right=36 Top=36 Bottom=36], PaperSize=[PaperSize Custom Kind=Custom Height=3600 Width=1800], PaperSource=[PaperSource unknown Kind=FormSource], PrinterResolution=[PrinterResolution X=600 Y=600]] info.Add("Bounds", pageSettings.Bounds.ToString()); info.Add("Color", pageSettings.Color.ToString()); info.Add("Landscape", pageSettings.Landscape.ToString()); info.Add("Margins", pageSettings.Margins.ToString()); info.Add("PaperSize", pageSettings.PaperSize.ToString()); info.Add("PaperSource", pageSettings.PaperSource.ToString()); info.Add("PrintableArea", pageSettings.PrintableArea.ToString()); info.Add("PrinterResolution", pageSettings.PrinterResolution.ToString()); SaLogger.Info(GetType().Name + "Report: " + Caption + ". Page settings" + (PreviewPrinting ? " [preview]:" : "\n\tPrinter: " + pageSettings.PrinterSettings.PrinterName) + "\n\tPage bounds: " + pageBounds + "\n\tMargin bounds: " + marginBounds + "\n\tPage settings:" + info.Concat(": ").Concat("", "\n\t\t"), true); } //------------------------------------------------------------------- // H e a d e r a n d F o o t e r //------------------------------------------------------------------- protected virtual void PrintHeader(PrintPageConfig cfg) { } //------------------------------------------------------------------- // Printing - Footer. protected virtual void PrintFooter(PrintPageConfig cfg) { // Print footer - Format. SbmPrinter_Footer.Config footerConfig = new SbmPrinter_Footer.Config(); footerConfig.Graphics = cfg.Graphics; footerConfig.PrintArea = cfg.Bounds; footerConfig.PosY = cfg.PosYFooterTop; footerConfig.PageNumber = PageNumber; footerConfig.PageCount = PageCount; footerPrinter.PrintFooter(footerConfig); } //------------------------------------------------------------------- } //======================================================================= }