namespace System.Workflow.ComponentModel.Design { using System; using System.Drawing; using System.Diagnostics; using System.Collections; using System.Windows.Forms; using System.Drawing.Imaging; using System.Drawing.Printing; using System.Drawing.Drawing2D; using System.ComponentModel.Design; #region Class WorkflowLayout //All the coordinates and sizes are in logical coordinate system internal abstract class WorkflowLayout : IDisposable { #region Members and Constructor/Destruction [Obsolete("The System.Workflow.* types are deprecated. Instead, please use the new types from System.Activities.*")] public enum LayoutUpdateReason { LayoutChanged, ZoomChanged } protected IServiceProvider serviceProvider; protected WorkflowView parentView; public WorkflowLayout(IServiceProvider serviceProvider) { Debug.Assert(serviceProvider != null); if (serviceProvider == null) throw new ArgumentNullException("serviceProvider"); this.serviceProvider = serviceProvider; this.parentView = this.serviceProvider.GetService(typeof(WorkflowView)) as WorkflowView; Debug.Assert(this.parentView != null); if (this.parentView == null) throw new InvalidOperationException(SR.GetString(SR.General_MissingService, typeof(WorkflowView).FullName)); } public virtual void Dispose() { } #endregion #region Public Interface public abstract float Scaling { get; } public abstract Size Extent { get; } public abstract Point RootDesignerAlignment { get; } public abstract bool IsCoOrdInLayout(Point logicalCoOrd); public abstract Rectangle MapInRectangleToLayout(Rectangle logicalRectangle); public abstract Rectangle MapOutRectangleFromLayout(Rectangle logicalRectangle); public abstract Point MapInCoOrdToLayout(Point logicalPoint); public abstract Point MapOutCoOrdFromLayout(Point logicalPoint); public abstract void OnPaint(PaintEventArgs e, ViewPortData viewPortData); public abstract void OnPaintWorkflow(PaintEventArgs e, ViewPortData viewPortData); public abstract void Update(Graphics graphics, LayoutUpdateReason reason); #endregion } #endregion #region Class DefaultWorkflowLayout: For rendering Root without any customization internal abstract class DefaultWorkflowLayout : WorkflowLayout { #region Members and Constructor public static Size Separator = new Size(30, 30); public DefaultWorkflowLayout(IServiceProvider serviceProvider) : base(serviceProvider) { } #endregion #region WorkflowLayout Overrides public override float Scaling { get { return 1.0f; } } public override Size Extent { get { Size rootDesignerSize = (this.parentView.RootDesigner != null) ? this.parentView.RootDesigner.Size : Size.Empty; Size totalSize = new Size(rootDesignerSize.Width + DefaultWorkflowLayout.Separator.Width * 2, rootDesignerSize.Height + DefaultWorkflowLayout.Separator.Height * 2); Size clientSize = this.parentView.ViewPortSize; return new Size(Math.Max(totalSize.Width, clientSize.Width), Math.Max(totalSize.Height, clientSize.Height)); } } public override Point RootDesignerAlignment { get { return new Point(DefaultWorkflowLayout.Separator); } } public override bool IsCoOrdInLayout(Point logicalCoOrd) { return true; } public override Rectangle MapInRectangleToLayout(Rectangle logicalRectangle) { return logicalRectangle; } public override Rectangle MapOutRectangleFromLayout(Rectangle logicalRectangle) { return logicalRectangle; } public override Point MapInCoOrdToLayout(Point logicalPoint) { return logicalPoint; } public override Point MapOutCoOrdFromLayout(Point logicalPoint) { return logicalPoint; } public override void Update(Graphics graphics, LayoutUpdateReason reason) { //We dont do anything as our layout is simple } // public override void OnPaint(PaintEventArgs e, ViewPortData viewPortData) { Graphics graphics = e.Graphics; Debug.Assert(graphics != null); //Get the drawing canvas Bitmap memoryBitmap = viewPortData.MemoryBitmap; Debug.Assert(memoryBitmap != null); //Fill the background using the workspace color so that we communicate the paging concept Rectangle workspaceRectangle = new Rectangle(Point.Empty, memoryBitmap.Size); graphics.FillRectangle(AmbientTheme.WorkspaceBackgroundBrush, workspaceRectangle); if (this.parentView.RootDesigner != null && this.parentView.RootDesigner.Bounds.Width >= 0 && this.parentView.RootDesigner.Bounds.Height >= 0) { GraphicsContainer graphicsState = graphics.BeginContainer(); //Create the scaling matrix Matrix transformationMatrix = new Matrix(); transformationMatrix.Scale(viewPortData.Scaling.Width, viewPortData.Scaling.Height, MatrixOrder.Prepend); //When we draw on the viewport we draw in scaled and translated. //So that we minimize the calls to DrawImage //Make sure that we scale down the logical view port origin in order to take care of scaling factor //Before we select the transform factor we make sure that logicalviewport origin is scaled down Point[] logicalViewPortOrigin = new Point[] { viewPortData.LogicalViewPort.Location }; transformationMatrix.TransformPoints(logicalViewPortOrigin); //For performance improvement and to eliminate one extra DrawImage...we draw the designers on the viewport //bitmap with visual depth consideration transformationMatrix.Translate(-logicalViewPortOrigin[0].X + viewPortData.ShadowDepth.Width, -logicalViewPortOrigin[0].Y + viewPortData.ShadowDepth.Height, MatrixOrder.Append); //Select the transform into viewport graphics. //Viewport bitmap has the scaled and translated designers which we then map to //the actual graphics based on page layout graphics.Transform = transformationMatrix; using (Region clipRegion = new Region(ActivityDesignerPaint.GetDesignerPath(this.parentView.RootDesigner, false))) { Region oldRegion = graphics.Clip; graphics.Clip = clipRegion; AmbientTheme ambientTheme = WorkflowTheme.CurrentTheme.AmbientTheme; graphics.FillRectangle(Brushes.White, this.parentView.RootDesigner.Bounds); if (ambientTheme.WorkflowWatermarkImage != null) ActivityDesignerPaint.DrawImage(graphics, ambientTheme.WorkflowWatermarkImage, this.parentView.RootDesigner.Bounds, new Rectangle(Point.Empty, ambientTheme.WorkflowWatermarkImage.Size), ambientTheme.WatermarkAlignment, AmbientTheme.WatermarkTransparency, false); graphics.Clip = oldRegion; } graphics.EndContainer(graphicsState); } } public override void OnPaintWorkflow(PaintEventArgs e, ViewPortData viewPortData) { Graphics graphics = e.Graphics; Debug.Assert(graphics != null); //Get the drawing canvas Bitmap memoryBitmap = viewPortData.MemoryBitmap; Debug.Assert(memoryBitmap != null); Rectangle bitmapArea = new Rectangle(Point.Empty, memoryBitmap.Size); ActivityDesignerPaint.DrawImage(graphics, memoryBitmap, bitmapArea, bitmapArea, DesignerContentAlignment.Fill, 1.0f, WorkflowTheme.CurrentTheme.AmbientTheme.DrawGrayscale); } #endregion } #endregion #region Class ActivityRootLayout: For rendering when Activity is Root internal sealed class ActivityRootLayout : DefaultWorkflowLayout { #region Members and Constructor internal ActivityRootLayout(IServiceProvider serviceProvider) : base(serviceProvider) { } #endregion public override Size Extent { get { Size rootDesignerSize = (this.parentView.RootDesigner != null) ? this.parentView.RootDesigner.Size : Size.Empty; Size totalSize = new Size(rootDesignerSize.Width + DefaultWorkflowLayout.Separator.Width * 2, rootDesignerSize.Height + DefaultWorkflowLayout.Separator.Height * 2); Size clientSize = this.parentView.ViewPortSize; //since the activity designer doesnt take the full viewport area, we need to scale available viewport back by the zoom factor clientSize.Width = (int)(clientSize.Width / ((float)this.parentView.Zoom / 100.0f)); clientSize.Height = (int)(clientSize.Height / ((float)this.parentView.Zoom / 100.0f)); return new Size(Math.Max(totalSize.Width, clientSize.Width), Math.Max(totalSize.Height, clientSize.Height)); } } public override void OnPaint(PaintEventArgs e, ViewPortData viewPortData) { base.OnPaint(e, viewPortData); Graphics graphics = e.Graphics; if (this.parentView.RootDesigner != null && this.parentView.RootDesigner.Bounds.Width >= 0 && this.parentView.RootDesigner.Bounds.Height >= 0) { GraphicsContainer graphicsState = graphics.BeginContainer(); //Create the scaling matrix Matrix transformationMatrix = new Matrix(); transformationMatrix.Scale(viewPortData.Scaling.Width, viewPortData.Scaling.Height, MatrixOrder.Prepend); //When we draw on the viewport we draw in scaled and translated. //So that we minimize the calls to DrawImage //Make sure that we scale down the logical view port origin in order to take care of scaling factor //Before we select the transform factor we make sure that logicalviewport origin is scaled down Point[] logicalViewPortOrigin = new Point[] { viewPortData.LogicalViewPort.Location }; transformationMatrix.TransformPoints(logicalViewPortOrigin); //For performance improvement and to eliminate one extra DrawImage...we draw the designers on the viewport //bitmap with visual depth consideration transformationMatrix.Translate(-logicalViewPortOrigin[0].X + viewPortData.ShadowDepth.Width, -logicalViewPortOrigin[0].Y + viewPortData.ShadowDepth.Height, MatrixOrder.Append); //Select the transform into viewport graphics. //Viewport bitmap has the scaled and translated designers which we then map to //the actual graphics based on page layout graphics.Transform = transformationMatrix; Rectangle rootBounds = this.parentView.RootDesigner.Bounds; graphics.ExcludeClip(rootBounds); rootBounds.Inflate(ActivityRootLayout.Separator.Width / 2, ActivityRootLayout.Separator.Height / 2); ActivityDesignerPaint.DrawDropShadow(graphics, rootBounds, AmbientTheme.WorkflowBorderPen.Color, AmbientTheme.DropShadowWidth, LightSourcePosition.Left | LightSourcePosition.Top, 0.2f, false); graphics.FillRectangle(WorkflowTheme.CurrentTheme.AmbientTheme.BackgroundBrush, rootBounds); graphics.DrawRectangle(AmbientTheme.WorkflowBorderPen, rootBounds); graphics.EndContainer(graphicsState); } } } #endregion #region Class WorkflowRootLayout: For rendering when Sequential Workflow is Root, by always centering it internal sealed class WorkflowRootLayout : DefaultWorkflowLayout { #region Members and Constructor public WorkflowRootLayout(IServiceProvider serviceProvider) : base(serviceProvider) { } #endregion #region WorkflowLayout Overrides public override Rectangle MapInRectangleToLayout(Rectangle logicalRectangle) { Size offSet = Offset; logicalRectangle.X -= offSet.Width; logicalRectangle.Y -= offSet.Height; return logicalRectangle; } public override Rectangle MapOutRectangleFromLayout(Rectangle logicalRectangle) { Size offSet = Offset; logicalRectangle.X += offSet.Width; logicalRectangle.Y += offSet.Height; return logicalRectangle; } public override Point MapInCoOrdToLayout(Point logicalPoint) { Size offSet = Offset; logicalPoint.Offset(-offSet.Width, -offSet.Height); return logicalPoint; } public override Point MapOutCoOrdFromLayout(Point logicalPoint) { Size offSet = Offset; logicalPoint.Offset(offSet.Width, offSet.Height); return logicalPoint; } #endregion #region Helpers private Size Offset { get { //This logic is needed in order to keep the service root designer centered Size layoutExtent = Extent; Size totalSize = this.parentView.ClientSizeToLogical(this.parentView.ViewPortSize); totalSize.Width = Math.Max(totalSize.Width, layoutExtent.Width); totalSize.Height = Math.Max(totalSize.Height, layoutExtent.Height); return new Size(Math.Max(0, (totalSize.Width - layoutExtent.Width) / 2), Math.Max(0, (totalSize.Height - layoutExtent.Height) / 2)); } } #endregion } #endregion #region Class PrintPreviewLayout: For rendering print preview layout internal sealed class PrintPreviewLayout : WorkflowLayout { #region Members and Constructor private static Size DefaultPageSeparator = new Size(30, 30); private static Margins DefaultPageMargins = new Margins(20, 20, 20, 20); private WorkflowPrintDocument printDocument = null; private ArrayList pageLayoutInfo = new ArrayList(); //We calculate the following variables when we perform layout, we store these variables //so that drawing will be faster private Margins headerFooterMargins = new Margins(0, 0, 0, 0); private Size pageSeparator = PrintPreviewLayout.DefaultPageSeparator; private Margins pageMargins = PrintPreviewLayout.DefaultPageMargins; private Size rowColumns = new Size(1, 1); //Width = Columns, Height = Rows private float scaling = 1.0f; private Size pageSize = Size.Empty; private DateTime previewTime = DateTime.Now; internal PrintPreviewLayout(IServiceProvider serviceProvider, WorkflowPrintDocument printDoc) : base(serviceProvider) { this.printDocument = printDoc; } #endregion #region WorkflowLayout Overrides public override float Scaling { get { return this.scaling; } } public override Size Extent { get { //RowColumns Width = Columns, Height = Rows Size maxSize = Size.Empty; maxSize.Width = (this.rowColumns.Width * this.pageSize.Width) + ((this.rowColumns.Width + 1) * (PageSeparator.Width)); maxSize.Height = (this.rowColumns.Height * this.pageSize.Height) + ((this.rowColumns.Height + 1) * (PageSeparator.Height)); return maxSize; } } public override Point RootDesignerAlignment { get { Point alignment = Point.Empty; Size printableAreaPerPage = new Size(this.pageSize.Width - (PageMargins.Left + PageMargins.Right), this.pageSize.Height - (PageMargins.Top + PageMargins.Bottom)); Size totalPrintableArea = new Size(this.rowColumns.Width * printableAreaPerPage.Width, this.rowColumns.Height * printableAreaPerPage.Height); Size rootDesignerSize = (this.parentView.RootDesigner != null) ? this.parentView.RootDesigner.Size : Size.Empty; Size selectionSize = WorkflowTheme.CurrentTheme.AmbientTheme.SelectionSize; if (this.printDocument.PageSetupData.CenterHorizontally) alignment.X = (totalPrintableArea.Width - rootDesignerSize.Width) / 2; alignment.X = Math.Max(alignment.X, selectionSize.Width + selectionSize.Width / 2); if (this.printDocument.PageSetupData.CenterVertically) alignment.Y = (totalPrintableArea.Height - rootDesignerSize.Height) / 2; alignment.Y = Math.Max(alignment.Y, selectionSize.Height + selectionSize.Height / 2); return alignment; } } public override bool IsCoOrdInLayout(Point logicalCoOrd) { foreach (PageLayoutData pageLayoutData in this.pageLayoutInfo) { if (pageLayoutData.ViewablePageBounds.Contains(logicalCoOrd)) return true; } return false; } public override Rectangle MapInRectangleToLayout(Rectangle logicalRectangle) { Rectangle transformedViewPort = Rectangle.Empty; //Now we start mapping the rectangle based on page layout foreach (PageLayoutData pageLayoutData in this.pageLayoutInfo) { Rectangle intersectedPhysicalViewPort = logicalRectangle; intersectedPhysicalViewPort.Intersect(pageLayoutData.ViewablePageBounds); if (!intersectedPhysicalViewPort.IsEmpty) { Point deltaLocation = new Point(intersectedPhysicalViewPort.X - pageLayoutData.ViewablePageBounds.X, intersectedPhysicalViewPort.Y - pageLayoutData.ViewablePageBounds.Y); Size deltaSize = new Size(pageLayoutData.ViewablePageBounds.Width - intersectedPhysicalViewPort.Width, pageLayoutData.ViewablePageBounds.Height - intersectedPhysicalViewPort.Height); deltaSize.Width -= deltaLocation.X; deltaSize.Height -= deltaLocation.Y; //Get the intersecting rectangle Rectangle insersectedLogicalViewPort = Rectangle.Empty; insersectedLogicalViewPort.X = pageLayoutData.LogicalPageBounds.X + deltaLocation.X; insersectedLogicalViewPort.Y = pageLayoutData.LogicalPageBounds.Y + deltaLocation.Y; insersectedLogicalViewPort.Width = pageLayoutData.LogicalPageBounds.Width - deltaLocation.X; insersectedLogicalViewPort.Width -= deltaSize.Width; insersectedLogicalViewPort.Height = pageLayoutData.LogicalPageBounds.Height - deltaLocation.Y; insersectedLogicalViewPort.Height -= deltaSize.Height; transformedViewPort = (transformedViewPort.IsEmpty) ? insersectedLogicalViewPort : Rectangle.Union(transformedViewPort, insersectedLogicalViewPort); } } return transformedViewPort; } public override Rectangle MapOutRectangleFromLayout(Rectangle logicalRectangle) { Rectangle transformedViewPort = Rectangle.Empty; //Now we start mapping the rectangle based on page layout foreach (PageLayoutData pageLayoutData in this.pageLayoutInfo) { Rectangle intersectedLogicalViewPort = logicalRectangle; intersectedLogicalViewPort.Intersect(pageLayoutData.LogicalPageBounds); if (!intersectedLogicalViewPort.IsEmpty) { Point deltaLocation = new Point(intersectedLogicalViewPort.X - pageLayoutData.LogicalPageBounds.X, intersectedLogicalViewPort.Y - pageLayoutData.LogicalPageBounds.Y); Size deltaSize = new Size(pageLayoutData.LogicalPageBounds.Width - intersectedLogicalViewPort.Width, pageLayoutData.LogicalPageBounds.Height - intersectedLogicalViewPort.Height); deltaSize.Width -= deltaLocation.X; deltaSize.Height -= deltaLocation.Y; //Get the intersecting rectangle Rectangle insersectedPhysicalViewPort = Rectangle.Empty; insersectedPhysicalViewPort.X = pageLayoutData.ViewablePageBounds.X + deltaLocation.X; insersectedPhysicalViewPort.Y = pageLayoutData.ViewablePageBounds.Y + deltaLocation.Y; insersectedPhysicalViewPort.Width = pageLayoutData.ViewablePageBounds.Width - deltaLocation.X; insersectedPhysicalViewPort.Width -= deltaSize.Width; insersectedPhysicalViewPort.Height = pageLayoutData.ViewablePageBounds.Height - deltaLocation.Y; insersectedPhysicalViewPort.Height -= deltaSize.Height; transformedViewPort = (transformedViewPort.IsEmpty) ? insersectedPhysicalViewPort : Rectangle.Union(transformedViewPort, insersectedPhysicalViewPort); } } return transformedViewPort; } public override Point MapInCoOrdToLayout(Point logicalPoint) { //Only for default layout we scale the coordinates outside the pageboundry for all other cases scaling fails foreach (PageLayoutData pageLayoutData in this.pageLayoutInfo) { if (pageLayoutData.PageBounds.Contains(logicalPoint)) { Point delta = new Point(logicalPoint.X - pageLayoutData.ViewablePageBounds.Left, logicalPoint.Y - pageLayoutData.ViewablePageBounds.Top); logicalPoint = new Point(pageLayoutData.LogicalPageBounds.Left + delta.X, pageLayoutData.LogicalPageBounds.Top + delta.Y); break; } } return logicalPoint; } public override Point MapOutCoOrdFromLayout(Point logicalPoint) { foreach (PageLayoutData pageLayoutData in this.pageLayoutInfo) { if (pageLayoutData.LogicalPageBounds.Contains(logicalPoint)) { Point delta = new Point(logicalPoint.X - pageLayoutData.LogicalPageBounds.Left, logicalPoint.Y - pageLayoutData.LogicalPageBounds.Top); logicalPoint = new Point(pageLayoutData.ViewablePageBounds.Left + delta.X, pageLayoutData.ViewablePageBounds.Top + delta.Y); break; } } return logicalPoint; } public override void OnPaint(PaintEventArgs e, ViewPortData viewPortData) { Graphics graphics = e.Graphics; Debug.Assert(graphics != null); AmbientTheme ambientTheme = WorkflowTheme.CurrentTheme.AmbientTheme; //Get the drawing canvas Bitmap memoryBitmap = viewPortData.MemoryBitmap; Debug.Assert(memoryBitmap != null); //Fill the background using the workspace color so that we communicate the paging concept graphics.FillRectangle(Brushes.White, new Rectangle(Point.Empty, memoryBitmap.Size)); //Fill the background using the workspace color so that we communicate the paging concept //if there is no workflow watermark, just return if (ambientTheme.WorkflowWatermarkImage == null) return; //Create the transformation matrix and calculate the physical viewport without translation and scaling //We need to get the physical view port due to the fact that there can be circustances when zoom percentage //is very high, logical view port can be empty in such cases GraphicsContainer graphicsState = graphics.BeginContainer(); Matrix coOrdTxMatrix = new Matrix(); coOrdTxMatrix.Scale(viewPortData.Scaling.Width, viewPortData.Scaling.Height, MatrixOrder.Prepend); coOrdTxMatrix.Invert(); Point[] points = new Point[] { viewPortData.Translation, new Point(viewPortData.ViewPortSize) }; coOrdTxMatrix.TransformPoints(points); Rectangle physicalViewPort = new Rectangle(points[0], new Size(points[1])); //because the watermark image needs to be scaled according to the zoom level, we //a) scale the graphics of the bitmap up by the zoom factor //a) scale the coordinates transform matrix down by the zoom factor coOrdTxMatrix = new Matrix(); coOrdTxMatrix.Scale(viewPortData.Scaling.Width / (float)this.parentView.Zoom * 100.0f, viewPortData.Scaling.Height / (float)this.parentView.Zoom * 100.0f); //Make sure that we now clear the translation factor Matrix graphicsMatrics = new Matrix(); graphicsMatrics.Scale((float)this.parentView.Zoom / 100.0f, (float)this.parentView.Zoom / 100.0f); graphics.Transform = graphicsMatrics; foreach (PageLayoutData pageLayoutData in this.pageLayoutInfo) { //We do not draw the non intersecting pages, get the intersected viewport //We purposely use the physical viewport here because, there are cases in which the viewport //will not contain any logical bitmap areas in which case we atleast need to draw the pages properly if (!pageLayoutData.PageBounds.IntersectsWith(physicalViewPort)) continue; //Draw the watermark into the in-memory bitmap //This is the area of the viewport bitmap we need to copy on the page Rectangle viewPortBitmapArea = Rectangle.Empty; viewPortBitmapArea.X = pageLayoutData.LogicalPageBounds.X - viewPortData.LogicalViewPort.X; viewPortBitmapArea.Y = pageLayoutData.LogicalPageBounds.Y - viewPortData.LogicalViewPort.Y; viewPortBitmapArea.Width = pageLayoutData.LogicalPageBounds.Width; viewPortBitmapArea.Height = pageLayoutData.LogicalPageBounds.Height; //This rectangle is in translated logical units, we need to scale it down points = new Point[] { viewPortBitmapArea.Location, new Point(viewPortBitmapArea.Size) }; coOrdTxMatrix.TransformPoints(points); viewPortBitmapArea.Location = points[0]; viewPortBitmapArea.Size = new Size(points[1]); ActivityDesignerPaint.DrawImage(graphics, ambientTheme.WorkflowWatermarkImage, viewPortBitmapArea, new Rectangle(Point.Empty, ambientTheme.WorkflowWatermarkImage.Size), ambientTheme.WatermarkAlignment, AmbientTheme.WatermarkTransparency, false); } //Now clear the matrix graphics.EndContainer(graphicsState); } public override void OnPaintWorkflow(PaintEventArgs e, ViewPortData viewPortData) { Graphics graphics = e.Graphics; Debug.Assert(graphics != null); Bitmap memoryBitmap = viewPortData.MemoryBitmap; Debug.Assert(memoryBitmap != null); //Get the drawing canvas AmbientTheme ambientTheme = WorkflowTheme.CurrentTheme.AmbientTheme; //We set the highest quality interpolation so that we do not loose the image quality GraphicsContainer graphicsState = graphics.BeginContainer(); //Fill the background using the workspace color so that we communicate the paging concept Rectangle workspaceRectangle = new Rectangle(Point.Empty, memoryBitmap.Size); graphics.FillRectangle(AmbientTheme.WorkspaceBackgroundBrush, workspaceRectangle); using (Font headerFooterFont = new Font(ambientTheme.Font.FontFamily, ambientTheme.Font.Size / this.scaling, ambientTheme.Font.Style)) { int currentPage = 0; Matrix emptyMatrix = new Matrix(); //Create the transformation matrix and calculate the physical viewport without translation and scaling //We need to get the physical view port due to the fact that there can be circustances when zoom percentage //is very high, logical view port can be empty in such cases Matrix coOrdTxMatrix = new Matrix(); coOrdTxMatrix.Scale(viewPortData.Scaling.Width, viewPortData.Scaling.Height, MatrixOrder.Prepend); coOrdTxMatrix.Invert(); Point[] points = new Point[] { viewPortData.Translation, new Point(viewPortData.ViewPortSize) }; coOrdTxMatrix.TransformPoints(points); coOrdTxMatrix.Invert(); Rectangle physicalViewPort = new Rectangle(points[0], new Size(points[1])); //Create the data for rendering header/footer WorkflowPrintDocument.HeaderFooterData headerFooterData = new WorkflowPrintDocument.HeaderFooterData(); headerFooterData.HeaderFooterMargins = this.headerFooterMargins; headerFooterData.PrintTime = this.previewTime; headerFooterData.TotalPages = this.pageLayoutInfo.Count; headerFooterData.Scaling = this.scaling; headerFooterData.Font = headerFooterFont; WorkflowDesignerLoader serviceDesignerLoader = this.serviceProvider.GetService(typeof(WorkflowDesignerLoader)) as WorkflowDesignerLoader; headerFooterData.FileName = (serviceDesignerLoader != null) ? serviceDesignerLoader.FileName : String.Empty; //Create the viewport transformation matrix Matrix viewPortMatrix = new Matrix(); viewPortMatrix.Scale(viewPortData.Scaling.Width, viewPortData.Scaling.Height, MatrixOrder.Prepend); viewPortMatrix.Translate(-viewPortData.Translation.X, -viewPortData.Translation.Y, MatrixOrder.Append); //We now have the viewport properly drawn, now we need to draw it on the actual graphics object //Now that we have the designer bitmap we start splicing it based on the pages //Note that this is quite expensive operation and hence one should try to use //The memory bitmap we have got is scaled appropriately foreach (PageLayoutData pageLayoutData in this.pageLayoutInfo) { currentPage += 1; //We do not draw the non intersecting pages, get the intersected viewport //We purposely use the physical viewport here because, there are cases in which the viewport //will not contain any logical bitmap areas in which case we atleast need to draw the pages properly if (!pageLayoutData.PageBounds.IntersectsWith(physicalViewPort) || pageLayoutData.PageBounds.Width <= 0 || pageLayoutData.PageBounds.Height <= 0) continue; //******START PAGE DRAWING, FIRST DRAW THE OUTLINE //Scale and translate so that we can draw the pages graphics.Transform = viewPortMatrix; graphics.FillRectangle(Brushes.White, pageLayoutData.PageBounds); ActivityDesignerPaint.DrawDropShadow(graphics, pageLayoutData.PageBounds, Color.Black, AmbientTheme.DropShadowWidth, LightSourcePosition.Left | LightSourcePosition.Top, 0.2f, false); //***START BITMAP SPLICING //Draw spliced bitmap for the page if we have any displayable area Rectangle intersectedViewPort = pageLayoutData.LogicalPageBounds; intersectedViewPort.Intersect(viewPortData.LogicalViewPort); if (!intersectedViewPort.IsEmpty) { //Make sure that we now clear the translation factor graphics.Transform = emptyMatrix; //Paint bitmap on the pages //Now that the page rectangle is actually drawn, we will scale down the Location of page rectangle //so that we can draw the viewport bitmap part on it Point bitmapDrawingPoint = Point.Empty; bitmapDrawingPoint.X = pageLayoutData.ViewablePageBounds.X + Math.Abs(pageLayoutData.LogicalPageBounds.X - intersectedViewPort.X); bitmapDrawingPoint.Y = pageLayoutData.ViewablePageBounds.Y + Math.Abs(pageLayoutData.LogicalPageBounds.Y - intersectedViewPort.Y); points = new Point[] { bitmapDrawingPoint }; coOrdTxMatrix.TransformPoints(points); bitmapDrawingPoint = new Point(points[0].X - viewPortData.Translation.X, points[0].Y - viewPortData.Translation.Y); //This is the area of the viewport bitmap we need to copy on the page Rectangle viewPortBitmapArea = Rectangle.Empty; viewPortBitmapArea.X = intersectedViewPort.X - viewPortData.LogicalViewPort.X; viewPortBitmapArea.Y = intersectedViewPort.Y - viewPortData.LogicalViewPort.Y; viewPortBitmapArea.Width = intersectedViewPort.Width; viewPortBitmapArea.Height = intersectedViewPort.Height; //This rectangle is in translated logical units, we need to scale it down points = new Point[] { viewPortBitmapArea.Location, new Point(viewPortBitmapArea.Size) }; coOrdTxMatrix.TransformPoints(points); viewPortBitmapArea.Location = points[0]; viewPortBitmapArea.Size = new Size(points[1]); ActivityDesignerPaint.DrawImage(graphics, memoryBitmap, new Rectangle(bitmapDrawingPoint, viewPortBitmapArea.Size), viewPortBitmapArea, DesignerContentAlignment.Fill, 1.0f, WorkflowTheme.CurrentTheme.AmbientTheme.DrawGrayscale); } //***END BITMAP SPLICING //Draw the page outline graphics.Transform = viewPortMatrix; graphics.DrawRectangle(Pens.Black, pageLayoutData.PageBounds); //Draw the printable page outline graphics.DrawRectangle(ambientTheme.ForegroundPen, pageLayoutData.ViewablePageBounds.Left - 3, pageLayoutData.ViewablePageBounds.Top - 3, pageLayoutData.ViewablePageBounds.Width + 6, pageLayoutData.ViewablePageBounds.Height + 6); //Draw the header and footer after we draw the actual page headerFooterData.PageBounds = pageLayoutData.PageBounds; headerFooterData.PageBoundsWithoutMargin = pageLayoutData.ViewablePageBounds; headerFooterData.CurrentPage = currentPage; //Draw the header if (this.printDocument.PageSetupData.HeaderTemplate.Length > 0) this.printDocument.PrintHeaderFooter(graphics, true, headerFooterData); //Draw footer if (this.printDocument.PageSetupData.FooterTemplate.Length > 0) this.printDocument.PrintHeaderFooter(graphics, false, headerFooterData); //***END DRAWING HEADER FOOTER } graphics.EndContainer(graphicsState); } } public override void Update(Graphics graphics, LayoutUpdateReason reason) { //do not recalculate pages when it's just a zoom change if (reason == LayoutUpdateReason.ZoomChanged) return; if (graphics == null) throw new ArgumentException("graphics"); //Set the scaling, pageSize, margins, pageseparator by reserse scaling; so that when we actually scale //at the time of drawing things will be correctly calculated Size margin = WorkflowTheme.CurrentTheme.AmbientTheme.Margin; Size paperSize = GetPaperSize(graphics); Margins margins = GetAdjustedMargins(graphics); Size rootDesignerSize = (this.parentView.RootDesigner != null) ? this.parentView.RootDesigner.Size : Size.Empty; if (!rootDesignerSize.IsEmpty) { Size selectionSize = WorkflowTheme.CurrentTheme.AmbientTheme.SelectionSize; rootDesignerSize.Width += 3 * selectionSize.Width; rootDesignerSize.Height += 3 * selectionSize.Height; } //STEP1 : Calculate the scaling factor if (this.printDocument.PageSetupData.AdjustToScaleFactor) { this.scaling = ((float)this.printDocument.PageSetupData.ScaleFactor / 100.0f); } else { Size printableArea = new Size(paperSize.Width - (margins.Left + margins.Right), paperSize.Height - (margins.Top + margins.Bottom)); printableArea.Width = Math.Max(printableArea.Width, 1); printableArea.Height = Math.Max(printableArea.Height, 1); PointF scaleFactor = new PointF( ((float)this.printDocument.PageSetupData.PagesWide * (float)printableArea.Width / (float)rootDesignerSize.Width), ((float)this.printDocument.PageSetupData.PagesTall * (float)printableArea.Height / (float)rootDesignerSize.Height)); //Take the minimum scaling as we do not want to unevenly scale the bitmap this.scaling = Math.Min(scaleFactor.X, scaleFactor.Y); //leave just 3 digital points (also, that will remove potential problems with ceiling e.g. when the number of pages would be 3.00000000001 we'll get 4) this.scaling = (float)(Math.Floor((double)this.scaling * 1000.0d) / 1000.0d); } //STEP2 : Calculate the pagesize this.pageSize = paperSize; this.pageSize.Width = Convert.ToInt32(Math.Ceiling(((float)this.pageSize.Width) / this.scaling)); this.pageSize.Height = Convert.ToInt32(Math.Ceiling(((float)this.pageSize.Height) / this.scaling)); //STEP3 : Calculate the page separator IDesignerOptionService designerOptionService = this.serviceProvider.GetService(typeof(IDesignerOptionService)) as IDesignerOptionService; if (designerOptionService != null) { object separator = designerOptionService.GetOptionValue("WinOEDesigner", "PageSeparator"); PageSeparator = (separator != null) ? (Size)separator : PrintPreviewLayout.DefaultPageSeparator; } PageSeparator = new Size(Convert.ToInt32(Math.Ceiling(((float)PageSeparator.Width) / this.scaling)), Convert.ToInt32(Math.Ceiling(((float)PageSeparator.Height) / this.scaling))); //STEP4: Calculate the margins after reverse scaling the margins, so that when we set the normal scalezoom we have correct margins PageMargins = margins; PageMargins.Left = Convert.ToInt32((float)PageMargins.Left / this.scaling); PageMargins.Right = Convert.ToInt32((float)PageMargins.Right / this.scaling); PageMargins.Top = Convert.ToInt32((float)PageMargins.Top / this.scaling); PageMargins.Bottom = Convert.ToInt32((float)PageMargins.Bottom / this.scaling); //STEP5: Calculate the header and footer margins this.headerFooterMargins.Top = Convert.ToInt32((float)this.printDocument.PageSetupData.HeaderMargin / this.scaling); this.headerFooterMargins.Bottom = Convert.ToInt32((float)this.printDocument.PageSetupData.FooterMargin / this.scaling); this.previewTime = DateTime.Now; //STEP6: Calculate the the row columns Size viewablePageSize = new Size(this.pageSize.Width - (PageMargins.Left + PageMargins.Right), this.pageSize.Height - (PageMargins.Top + PageMargins.Bottom)); viewablePageSize.Width = Math.Max(viewablePageSize.Width, 1); viewablePageSize.Height = Math.Max(viewablePageSize.Height, 1); //We check for greater than 1 here as the division might introduce rounding factor //Columns this.rowColumns.Width = rootDesignerSize.Width / viewablePageSize.Width; this.rowColumns.Width += ((rootDesignerSize.Width % viewablePageSize.Width) > 1) ? 1 : 0; this.rowColumns.Width = Math.Max(1, this.rowColumns.Width); //Rows this.rowColumns.Height = rootDesignerSize.Height / viewablePageSize.Height; this.rowColumns.Height += ((rootDesignerSize.Height % viewablePageSize.Height) > 1) ? 1 : 0; this.rowColumns.Height = Math.Max(1, this.rowColumns.Height); //STEP7: Calculate the pagelayoutdata this.pageLayoutInfo.Clear(); //Create the layout data for (int row = 0; row < this.rowColumns.Height; row++) { for (int column = 0; column < this.rowColumns.Width; column++) { Point pageLocation = Point.Empty; pageLocation.X = (column * this.pageSize.Width) + ((column + 1) * PageSeparator.Width); pageLocation.Y = (row * this.pageSize.Height) + ((row + 1) * PageSeparator.Height); Point viewablePageLocation = Point.Empty; viewablePageLocation.X = pageLocation.X + PageMargins.Left; viewablePageLocation.Y = pageLocation.Y + PageMargins.Top; Rectangle logicalBounds = new Rectangle(column * viewablePageSize.Width, row * viewablePageSize.Height, viewablePageSize.Width, viewablePageSize.Height); Rectangle pageBounds = new Rectangle(pageLocation, this.pageSize); Rectangle viewablePageBounds = new Rectangle(viewablePageLocation, viewablePageSize); this.pageLayoutInfo.Add(new PageLayoutData(logicalBounds, pageBounds, viewablePageBounds, new Point(column, row))); } } } #endregion #region Helpers private Size GetPaperSize(Graphics graphics) { Size size = Size.Empty; PaperSize paperSize = this.printDocument.DefaultPageSettings.PaperSize; this.printDocument.DefaultPageSettings.PaperSize = paperSize; if (this.printDocument.PageSetupData.Landscape) { size.Width = Math.Max(paperSize.Height, 1); size.Height = Math.Max(paperSize.Width, 1); } else { size.Width = Math.Max(paperSize.Width, 1); size.Height = Math.Max(paperSize.Height, 1); } return size; } private Margins GetAdjustedMargins(Graphics graphics) { Margins margins = this.printDocument.PageSetupData.Margins; if (this.printDocument.PageSetupData.Landscape) { int temp = margins.Left; margins.Left = margins.Right; margins.Right = temp; temp = margins.Bottom; margins.Bottom = margins.Top; margins.Top = temp; } //Read the unprintable margins Margins hardMargins = new Margins(); using (Graphics printerGraphics = this.printDocument.PrinterSettings.CreateMeasurementGraphics()) hardMargins = this.printDocument.GetHardMargins(printerGraphics); Margins adjustedMargins = new Margins(Math.Max(margins.Left, hardMargins.Left), Math.Max(margins.Right, hardMargins.Right), Math.Max(margins.Top, hardMargins.Top), Math.Max(margins.Bottom, hardMargins.Bottom)); return adjustedMargins; } private Size PageSeparator { get { return this.pageSeparator; } set { this.pageSeparator = value; } } private Margins PageMargins { get { return this.pageMargins; } set { this.pageMargins = value; } } #endregion #region Struct PageLayoutData // Please note that all the page bounds in here are in scaled coordinates // PageBounds are the bounds of the page in scaled coordinates with margins // ViewablePageBounds are the bounds in scaled coordinates without margins // LogicalBounds are the page bounds when mapped to our logical coordinate system. // |-----------------------| // |PageBounds | // | |---------------|...........Mapped to logical page bounds // | |Viewable | | // | |PageBounds | | // | | | | // | | | | // | | | | // | | | | // | | | | // | | | | // | | | | // | |---------------|........... // | | // ------------------------- private struct PageLayoutData { //logical page bounds start from 0,0 and go to the size of the designer public Rectangle LogicalPageBounds; //Page bounds are used to draw the complete page in the layout with margin public Rectangle PageBounds; //screen viewable page bounds (Excludes the margin area) public Rectangle ViewablePageBounds; //row column position contains the row column position of the page public Point Position; public PageLayoutData(Rectangle logicalPageBounds, Rectangle pageBounds, Rectangle viewablePageBounds, Point rowColumnPos) { this.LogicalPageBounds = logicalPageBounds; this.PageBounds = pageBounds; //Exclude the margins themselves this.ViewablePageBounds = viewablePageBounds; this.Position = rowColumnPos; } } #endregion } #endregion }