363 lines
15 KiB
C#
363 lines
15 KiB
C#
#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
|
|
{
|
|
/// <summary>
|
|
/// Formats a series of document elements from top to bottom.
|
|
/// </summary>
|
|
public class TopDownFormatter
|
|
{
|
|
/// <summary>
|
|
/// Returns the max of the given Margins, if both are positive or 0, the sum otherwise.
|
|
/// </summary>
|
|
/// <param name="prevBottomMargin">The bottom margin of the previous element.</param>
|
|
/// <param name="nextTopMargin">The top margin of the next element.</param>
|
|
/// <returns></returns>
|
|
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;
|
|
|
|
/// <summary>
|
|
/// Formats the elements on the areas provided by the area provider.
|
|
/// </summary>
|
|
/// <param name="gfx">The graphics object to render on.</param>
|
|
/// <param name="topLevel">if set to <c>true</c> formats the object is on top level.</param>
|
|
public void FormatOnAreas(XGraphics gfx, bool topLevel)
|
|
{
|
|
_gfx = gfx;
|
|
XUnit prevBottomMargin = 0;
|
|
XUnit yPos = prevBottomMargin;
|
|
RenderInfo prevRenderInfo = null;
|
|
FormatInfo prevFormatInfo = null;
|
|
List<RenderInfo> renderInfos = new List<RenderInfo>();
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finishes rendering for the page.
|
|
/// </summary>
|
|
/// <param name="lastRenderInfo">The last render info.</param>
|
|
/// <param name="pagebreakBefore">set to <c>true</c> if there is a pagebreak before this page.</param>
|
|
/// <param name="renderInfos">The render infos.</param>
|
|
/// <returns>
|
|
/// The RenderInfo to set as previous RenderInfo.
|
|
/// </returns>
|
|
RenderInfo FinishPage(RenderInfo lastRenderInfo, bool pagebreakBefore, ref List<RenderInfo> 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<RenderInfo>();
|
|
return prevRenderInfo;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indicates that a break between areas has to be performed before the element with the given idx.
|
|
/// </summary>
|
|
/// <param name="idx">Index of the document element.</param>
|
|
/// <param name="renderer">A formatted renderer for the document element.</param>
|
|
/// <param name="remainingArea">The remaining area.</param>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indicates that the Ending of the element has to be removed.
|
|
/// </summary>
|
|
/// <param name="prevRenderInfo">The prev render info.</param>
|
|
/// <param name="succedingRenderInfo">The succeding render info.</param>
|
|
/// <param name="remainingArea">The remaining area.</param>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The maximum number of elements that can be combined via keepwithnext and keeptogether
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|