#region MigraDoc - Creating Documents on the Fly // // Authors: // Klaus Potzesny // // Copyright (c) 2001-2017 empira Software GmbH, Cologne Area (Germany) // // http://www.pdfsharp.com // http://www.migradoc.com // http://sourceforge.net/projects/pdfsharp // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. #endregion using System; using System.Collections.Generic; using MigraDoc.DocumentObjectModel; using PdfSharp.Drawing; namespace MigraDoc.Rendering { /// /// Formats a series of document elements from top to bottom. /// public class TopDownFormatter { /// /// Returns the max of the given Margins, if both are positive or 0, the sum otherwise. /// /// The bottom margin of the previous element. /// The top margin of the next element. /// private XUnit MarginMax(XUnit prevBottomMargin, XUnit nextTopMargin) { if (prevBottomMargin >= 0 && nextTopMargin >= 0) return Math.Max(prevBottomMargin, nextTopMargin); return prevBottomMargin + nextTopMargin; } public TopDownFormatter(IAreaProvider areaProvider, DocumentRenderer documentRenderer, DocumentElements elements) { _documentRenderer = documentRenderer; _areaProvider = areaProvider; _elements = elements; } readonly IAreaProvider _areaProvider; readonly DocumentElements _elements; /// /// Formats the elements on the areas provided by the area provider. /// /// The graphics object to render on. /// if set to true formats the object is on top level. public void FormatOnAreas(XGraphics gfx, bool topLevel) { _gfx = gfx; XUnit prevBottomMargin = 0; XUnit yPos = prevBottomMargin; RenderInfo prevRenderInfo = null; FormatInfo prevFormatInfo = null; List renderInfos = new List(); bool ready = _elements.Count == 0; bool isFirstOnPage = true; Area area = _areaProvider.GetNextArea(); XUnit maxHeight = area.Height; if (ready) { _areaProvider.StoreRenderInfos(renderInfos); return; } int idx = 0; while (!ready && area != null) { DocumentObject docObj = _elements[idx]; Renderer renderer = Renderer.Create(gfx, _documentRenderer, docObj, _areaProvider.AreaFieldInfos); if (renderer != null) // "Slightly hacked" for legends: see below renderer.MaxElementHeight = maxHeight; if (topLevel && _documentRenderer.HasPrepareDocumentProgress) { _documentRenderer.OnPrepareDocumentProgress(_documentRenderer.ProgressCompleted + idx + 1, _documentRenderer.ProgressMaximum); } // "Slightly hacked" for legends: they are rendered as part of the chart. // So they are skipped here. if (renderer == null) { ready = idx == _elements.Count - 1; if (ready) _areaProvider.StoreRenderInfos(renderInfos); ++idx; continue; } /////////////////////////////////////////// if (prevFormatInfo == null) { LayoutInfo initialLayoutInfo = renderer.InitialLayoutInfo; XUnit distance = prevBottomMargin; if (initialLayoutInfo.VerticalReference == VerticalReference.PreviousElement && initialLayoutInfo.Floating != Floating.None) distance = MarginMax(initialLayoutInfo.MarginTop, distance); area = area.Lower(distance); } renderer.Format(area, prevFormatInfo); _areaProvider.PositionHorizontally(renderer.RenderInfo.LayoutInfo); bool pagebreakBefore = _areaProvider.IsAreaBreakBefore(renderer.RenderInfo.LayoutInfo) && !isFirstOnPage; pagebreakBefore = pagebreakBefore || !isFirstOnPage && IsForcedAreaBreak(idx, renderer, area); if (!pagebreakBefore && renderer.RenderInfo.FormatInfo.IsEnding) { if (PreviousRendererNeedsRemoveEnding(prevRenderInfo, renderer.RenderInfo, area)) { prevRenderInfo.RemoveEnding(); renderer = Renderer.Create(gfx, _documentRenderer, docObj, _areaProvider.AreaFieldInfos); renderer.MaxElementHeight = maxHeight; renderer.Format(area, prevRenderInfo.FormatInfo); } else if (NeedsEndingOnNextArea(idx, renderer, area, isFirstOnPage)) { renderer.RenderInfo.RemoveEnding(); prevRenderInfo = FinishPage(renderer.RenderInfo, pagebreakBefore, ref renderInfos); if (prevRenderInfo != null) prevFormatInfo = prevRenderInfo.FormatInfo; else { prevFormatInfo = null; isFirstOnPage = true; } prevBottomMargin = 0; area = _areaProvider.GetNextArea(); maxHeight = area.Height; } else { renderInfos.Add(renderer.RenderInfo); isFirstOnPage = false; _areaProvider.PositionVertically(renderer.RenderInfo.LayoutInfo); if (renderer.RenderInfo.LayoutInfo.VerticalReference == VerticalReference.PreviousElement && renderer.RenderInfo.LayoutInfo.Floating != Floating.None) { prevBottomMargin = renderer.RenderInfo.LayoutInfo.MarginBottom; if (renderer.RenderInfo.LayoutInfo.Floating != Floating.None) area = area.Lower(renderer.RenderInfo.LayoutInfo.ContentArea.Height); } else prevBottomMargin = 0; prevFormatInfo = null; prevRenderInfo = null; ++idx; } } else { if (renderer.RenderInfo.FormatInfo.IsEmpty && isFirstOnPage) { area = area.Unite(new Rectangle(area.X, area.Y, area.Width, double.MaxValue)); renderer = Renderer.Create(gfx, _documentRenderer, docObj, _areaProvider.AreaFieldInfos); renderer.MaxElementHeight = maxHeight; renderer.Format(area, prevFormatInfo); prevFormatInfo = null; _areaProvider.PositionHorizontally(renderer.RenderInfo.LayoutInfo); _areaProvider.PositionVertically(renderer.RenderInfo.LayoutInfo); ready = idx == _elements.Count - 1; ++idx; } prevRenderInfo = FinishPage(renderer.RenderInfo, pagebreakBefore, ref renderInfos); if (prevRenderInfo != null) prevFormatInfo = prevRenderInfo.FormatInfo; else { prevFormatInfo = null; } isFirstOnPage = true; prevBottomMargin = 0; if (!ready) { area = _areaProvider.GetNextArea(); maxHeight = area.Height; } } if (idx == _elements.Count && !ready) { _areaProvider.StoreRenderInfos(renderInfos); ready = true; } } } /// /// Finishes rendering for the page. /// /// The last render info. /// set to true if there is a pagebreak before this page. /// The render infos. /// /// The RenderInfo to set as previous RenderInfo. /// RenderInfo FinishPage(RenderInfo lastRenderInfo, bool pagebreakBefore, ref List renderInfos) { RenderInfo prevRenderInfo; if (lastRenderInfo.FormatInfo.IsEmpty || pagebreakBefore) { prevRenderInfo = null; } else { prevRenderInfo = lastRenderInfo; renderInfos.Add(lastRenderInfo); if (lastRenderInfo.FormatInfo.IsEnding) prevRenderInfo = null; } _areaProvider.StoreRenderInfos(renderInfos); renderInfos = new List(); return prevRenderInfo; } /// /// Indicates that a break between areas has to be performed before the element with the given idx. /// /// Index of the document element. /// A formatted renderer for the document element. /// The remaining area. bool IsForcedAreaBreak(int idx, Renderer renderer, Area remainingArea) { FormatInfo formatInfo = renderer.RenderInfo.FormatInfo; LayoutInfo layoutInfo = renderer.RenderInfo.LayoutInfo; if (formatInfo.IsStarting && !formatInfo.StartingIsComplete) return true; if (layoutInfo.KeepTogether && !formatInfo.IsComplete) return true; if (layoutInfo.KeepTogether && layoutInfo.KeepWithNext) { Area area = remainingArea.Lower(layoutInfo.ContentArea.Height); return NextElementsDontFit(idx, area, layoutInfo.MarginBottom); } return false; } /// /// Indicates that the Ending of the element has to be removed. /// /// The prev render info. /// The succeding render info. /// The remaining area. bool PreviousRendererNeedsRemoveEnding(RenderInfo prevRenderInfo, RenderInfo succedingRenderInfo, Area remainingArea) { if (prevRenderInfo == null) return false; LayoutInfo layoutInfo = succedingRenderInfo.LayoutInfo; FormatInfo formatInfo = succedingRenderInfo.FormatInfo; LayoutInfo prevLayoutInfo = prevRenderInfo.LayoutInfo; if (formatInfo.IsEnding && !formatInfo.EndingIsComplete) { Area area = _areaProvider.ProbeNextArea(); if (area.Height > prevLayoutInfo.TrailingHeight + layoutInfo.TrailingHeight + Renderer.Tolerance) return true; } return false; } /// /// The maximum number of elements that can be combined via keepwithnext and keeptogether /// public static readonly int MaxCombineElements = 10; bool NextElementsDontFit(int idx, Area remainingArea, XUnit previousMarginBottom) { XUnit elementDistance = previousMarginBottom; Area area = remainingArea; for (int index = idx + 1; index < _elements.Count; ++index) { // Never combine more than MaxCombineElements elements if (index - idx > MaxCombineElements) return false; DocumentObject obj = _elements[index]; Renderer currRenderer = Renderer.Create(_gfx, _documentRenderer, obj, _areaProvider.AreaFieldInfos); elementDistance = MarginMax(elementDistance, currRenderer.InitialLayoutInfo.MarginTop); area = area.Lower(elementDistance); if (area.Height <= 0) return true; currRenderer.Format(area, null); FormatInfo currFormatInfo = currRenderer.RenderInfo.FormatInfo; LayoutInfo currLayoutInfo = currRenderer.RenderInfo.LayoutInfo; if (currLayoutInfo.VerticalReference != VerticalReference.PreviousElement) return false; if (!currFormatInfo.StartingIsComplete) return true; if (currLayoutInfo.KeepTogether && !currFormatInfo.IsComplete) return true; if (!(currLayoutInfo.KeepTogether && currLayoutInfo.KeepWithNext)) return false; area = area.Lower(currLayoutInfo.ContentArea.Height); if (area.Height <= 0) return true; elementDistance = currLayoutInfo.MarginBottom; } return false; } bool NeedsEndingOnNextArea(int idx, Renderer renderer, Area remainingArea, bool isFirstOnPage) { LayoutInfo layoutInfo = renderer.RenderInfo.LayoutInfo; if (isFirstOnPage && layoutInfo.KeepTogether) return false; FormatInfo formatInfo = renderer.RenderInfo.FormatInfo; if (!formatInfo.EndingIsComplete) return false; if (layoutInfo.KeepWithNext) { remainingArea = remainingArea.Lower(layoutInfo.ContentArea.Height); return NextElementsDontFit(idx, remainingArea, layoutInfo.MarginBottom); } return false; } readonly DocumentRenderer _documentRenderer; XGraphics _gfx; } }