#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;
}
}