#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 System.Globalization;
using PdfSharp;
using PdfSharp.Drawing;
using MigraDoc.DocumentObjectModel;
using MigraDoc.DocumentObjectModel.publics;
namespace MigraDoc.Rendering
{
///
/// Represents a formatted document.
///
public class FormattedDocument : IAreaProvider
{
enum PagePosition
{
First,
Odd,
Even
}
private struct HeaderFooterPosition
{
public HeaderFooterPosition(int sectionNr, PagePosition pagePosition)
{
_sectionNr = sectionNr;
_pagePosition = pagePosition;
}
public override bool Equals(object obj)
{
if (obj is HeaderFooterPosition)
{
HeaderFooterPosition hfp = (HeaderFooterPosition)obj;
return _sectionNr == hfp._sectionNr && _pagePosition == hfp._pagePosition;
}
return false;
}
public override int GetHashCode()
{
return _sectionNr.GetHashCode() ^ _pagePosition.GetHashCode();
}
readonly int _sectionNr;
readonly PagePosition _pagePosition;
}
public FormattedDocument(Document document, DocumentRenderer documentRenderer)
{
_document = document;
_documentRenderer = documentRenderer;
}
///
/// Formats the document by performing line breaks and page breaks.
///
public void Format(XGraphics gfx)
{
_bookmarks = new Dictionary();
_pageRenderInfos = new Dictionary>();
_pageInfos = new Dictionary();
_pageFieldInfos = new Dictionary();
_formattedHeaders = new Dictionary();
_formattedFooters = new Dictionary();
_gfx = gfx;
_currentPage = 0;
_sectionNumber = 0;
_pageCount = 0;
_shownPageNumber = 0;
_documentRenderer.ProgressCompleted = 0;
_documentRenderer.ProgressMaximum = 0;
if (_documentRenderer.HasPrepareDocumentProgress)
{
foreach (Section section in _document.Sections)
_documentRenderer.ProgressMaximum += section.Elements.Count;
}
foreach (Section section in _document.Sections)
{
_isNewSection = true;
_currentSection = section;
++_sectionNumber;
if (NeedsEmptyPage())
InsertEmptyPage();
TopDownFormatter formatter = new TopDownFormatter(this, _documentRenderer, section.Elements);
formatter.FormatOnAreas(gfx, true);
FillSectionPagesInfo();
_documentRenderer.ProgressCompleted += section.Elements.Count;
}
_pageCount = _currentPage;
FillNumPagesInfo();
}
PagePosition CurrentPagePosition
{
get
{
if (_isNewSection)
return PagePosition.First;
// Choose header and footer based on the shown page number, not the physical page number.
if (_shownPageNumber % 2 == 0)
return PagePosition.Even;
return PagePosition.Odd;
}
}
void FormatHeadersFooters()
{
HeadersFooters headers = (HeadersFooters)_currentSection.GetValue("Headers", GV.ReadOnly);
if (headers != null)
{
PagePosition pagePos = CurrentPagePosition;
HeaderFooterPosition hfp = new HeaderFooterPosition(_sectionNumber, pagePos);
if (!_formattedHeaders.ContainsKey(hfp))
FormatHeader(hfp, ChooseHeaderFooter(headers, pagePos));
}
HeadersFooters footers = (HeadersFooters)_currentSection.GetValue("Footers", GV.ReadOnly);
if (footers != null)
{
PagePosition pagePos = CurrentPagePosition;
HeaderFooterPosition hfp = new HeaderFooterPosition(_sectionNumber, pagePos);
if (!_formattedFooters.ContainsKey(hfp))
FormatFooter(hfp, ChooseHeaderFooter(footers, pagePos));
}
}
void FormatHeader(HeaderFooterPosition hfp, HeaderFooter header)
{
if (header != null && !_formattedHeaders.ContainsKey(hfp))
{
FormattedHeaderFooter formattedHeaderFooter = new FormattedHeaderFooter(header, _documentRenderer, _currentFieldInfos);
formattedHeaderFooter.ContentRect = GetHeaderArea(_currentSection, _currentPage);
formattedHeaderFooter.Format(_gfx);
_formattedHeaders.Add(hfp, formattedHeaderFooter);
}
}
void FormatFooter(HeaderFooterPosition hfp, HeaderFooter footer)
{
if (footer != null && !_formattedFooters.ContainsKey(hfp))
{
FormattedHeaderFooter formattedHeaderFooter = new FormattedHeaderFooter(footer, _documentRenderer, _currentFieldInfos);
formattedHeaderFooter.ContentRect = GetFooterArea(_currentSection, _currentPage);
formattedHeaderFooter.Format(_gfx);
_formattedFooters.Add(hfp, formattedHeaderFooter);
}
}
///
/// Fills the number pages information after formatting the document.
///
void FillNumPagesInfo()
{
for (int page = 1; page <= _pageCount; ++page)
{
if (IsEmptyPage(page))
continue;
FieldInfos fieldInfos = _pageFieldInfos[page];
fieldInfos.NumPages = _pageCount;
}
}
///
/// Fills the section pages information after formatting a section.
///
void FillSectionPagesInfo()
{
for (int page = _currentPage; page > 0; --page)
{
if (IsEmptyPage(page))
continue;
FieldInfos fieldInfos = _pageFieldInfos[page];
if (fieldInfos.Section != _sectionNumber)
break;
fieldInfos.SectionPages = _sectionPages;
}
}
Rectangle CalcContentRect(int page)
{
PageSetup pageSetup = _currentSection.PageSetup;
XUnit width = pageSetup.EffectivePageWidth.Point;
width -= pageSetup.RightMargin.Point;
width -= pageSetup.LeftMargin.Point;
XUnit height = pageSetup.EffectivePageHeight.Point;
height -= pageSetup.TopMargin.Point;
height -= pageSetup.BottomMargin.Point;
XUnit x;
XUnit y = pageSetup.TopMargin.Point;
if (pageSetup.MirrorMargins)
x = page % 2 == 0 ? pageSetup.RightMargin.Point : pageSetup.LeftMargin.Point;
else
x = pageSetup.LeftMargin.Point;
return new Rectangle(x, y, width, height);
}
///
/// Gets the rendering informations for the page content.
///
/// The page to render.
/// Rendering information for the page content.
public RenderInfo[] GetRenderInfos(int page)
{
if (_pageRenderInfos.ContainsKey(page))
return (_pageRenderInfos[page]).ToArray();
return null;
}
private Dictionary> _pageRenderInfos;
///
/// Gets a formatted headerfooter object for header of the given page.
///
/// The physical page the header shall appear on.
/// The required header, null if none exists to render.
public FormattedHeaderFooter GetFormattedHeader(int page)
{
FieldInfos fieldInfos = _pageFieldInfos[page];
int logicalPage = fieldInfos.DisplayPageNr;
PagePosition pagePos = logicalPage % 2 == 0 ? PagePosition.Even : PagePosition.Odd;
if (page == 1)
pagePos = PagePosition.First;
else //page > 1
{
if (IsEmptyPage(page - 1)) // these empty pages only occur between sections.
pagePos = PagePosition.First;
else
{
FieldInfos prevFieldInfos = _pageFieldInfos[page - 1];
if (fieldInfos.Section != prevFieldInfos.Section)
pagePos = PagePosition.First;
}
}
HeaderFooterPosition hfp = new HeaderFooterPosition(fieldInfos.Section, pagePos);
if (_formattedHeaders.ContainsKey(hfp))
return _formattedHeaders[hfp];
return null;
}
///
/// Gets a formatted headerfooter object for footer of the given page.
///
/// The physical page the footer shall appear on.
/// The required footer, null if none exists to render.
public FormattedHeaderFooter GetFormattedFooter(int page)
{
FieldInfos fieldInfos = _pageFieldInfos[page];
int logicalPage = fieldInfos.DisplayPageNr;
PagePosition pagePos = logicalPage % 2 == 0 ? PagePosition.Even : PagePosition.Odd;
if (page == 1)
pagePos = PagePosition.First;
else //page > 1
{
if (IsEmptyPage(page - 1)) // these empty pages only occur between sections.
pagePos = PagePosition.First;
else
{
FieldInfos prevFieldInfos = _pageFieldInfos[page - 1];
if (fieldInfos.Section != prevFieldInfos.Section)
pagePos = PagePosition.First;
}
}
HeaderFooterPosition hfp = new HeaderFooterPosition(fieldInfos.Section, pagePos);
if (_formattedFooters.ContainsKey(hfp))
return _formattedFooters[hfp];
return null;
}
private Rectangle GetHeaderArea(Section section, int page)
{
PageSetup pageSetup = section.PageSetup;
XUnit xPos;
if (pageSetup.MirrorMargins && page % 2 == 0)
xPos = pageSetup.RightMargin.Point;
else
xPos = pageSetup.LeftMargin.Point;
XUnit width = pageSetup.EffectivePageWidth.Point;
width -= pageSetup.LeftMargin + pageSetup.RightMargin;
XUnit yPos = pageSetup.HeaderDistance.Point;
XUnit height = pageSetup.TopMargin - pageSetup.HeaderDistance;
return new Rectangle(xPos, yPos, width, height);
}
public Rectangle GetHeaderArea(int page)
{
FieldInfos fieldInfos = _pageFieldInfos[page];
Section section = _document.Sections[fieldInfos.Section - 1];
return GetHeaderArea(section, page);
}
public Rectangle GetFooterArea(int page)
{
FieldInfos fieldInfos = _pageFieldInfos[page];
Section section = _document.Sections[fieldInfos.Section - 1];
return GetFooterArea(section, page);
}
private Rectangle GetFooterArea(Section section, int page)
{
PageSetup pageSetup = section.PageSetup;
XUnit xPos;
if (pageSetup.MirrorMargins && page % 2 == 0)
xPos = pageSetup.RightMargin.Point;
else
xPos = pageSetup.LeftMargin.Point;
XUnit width = pageSetup.EffectivePageWidth.Point;
width -= pageSetup.LeftMargin + pageSetup.RightMargin;
XUnit yPos = pageSetup.EffectivePageHeight.Point;
yPos -= pageSetup.BottomMargin.Point;
XUnit height = pageSetup.BottomMargin - pageSetup.FooterDistance;
return new Rectangle(xPos, yPos, width, height);
}
private HeaderFooter ChooseHeaderFooter(HeadersFooters hfs, PagePosition pagePos)
{
if (hfs == null)
return null;
PageSetup pageSetup = _currentSection.PageSetup;
if (pagePos == PagePosition.First)
{
if (pageSetup.DifferentFirstPageHeaderFooter)
return (HeaderFooter)hfs.GetValue("FirstPage", GV.ReadOnly);
}
if (pagePos == PagePosition.Even || _shownPageNumber/*_currentPage*/ % 2 == 0)
{
if (pageSetup.OddAndEvenPagesHeaderFooter)
return (HeaderFooter)hfs.GetValue("EvenPage", GV.ReadOnly);
}
return (HeaderFooter)hfs.GetValue("Primary", GV.ReadOnly);
}
///
/// Gets the number of pages of the document.
///
public int PageCount
{
get { return _pageCount; }
}
int _pageCount;
///
/// Gets information about the specified page.
///
/// The page the information is asked for.
/// The page information.
public PageInfo GetPageInfo(int page)
{
if (page < 1 || page > _pageCount)
#if !SILVERLIGHT
throw new ArgumentOutOfRangeException("page", page, page.ToString(CultureInfo.InvariantCulture));
#else
throw new PdfSharp.ArgumentOutOfRangeException("page", page, page.ToString(CultureInfo.InvariantCulture));
#endif
return _pageInfos[page];
}
#region IAreaProvider Members
Area IAreaProvider.GetNextArea()
{
if (_isNewSection)
_sectionPages = 0;
++_currentPage;
++_shownPageNumber;
++_sectionPages;
InitFieldInfos();
FormatHeadersFooters();
_isNewSection = false;
return CalcContentRect(_currentPage);
}
int _currentPage;
Area IAreaProvider.ProbeNextArea()
{
return CalcContentRect(_currentPage + 1);
}
void InitFieldInfos()
{
_currentFieldInfos = new FieldInfos(_bookmarks);
_currentFieldInfos.PhysicalPageNr = _currentPage;
_currentFieldInfos.Section = _sectionNumber;
if (_isNewSection && !_currentSection.PageSetup._startingNumber.IsNull)
_shownPageNumber = _currentSection.PageSetup.StartingNumber;
_currentFieldInfos.DisplayPageNr = _shownPageNumber;
}
void IAreaProvider.StoreRenderInfos(List renderInfos)
{
_pageRenderInfos.Add(_currentPage, renderInfos);
XSize pageSize = CalcPageSize(_currentSection.PageSetup);
PageOrientation pageOrientation = CalcPageOrientation(_currentSection.PageSetup);
PageInfo pageInfo = new PageInfo(pageSize.Width, pageSize.Height, pageOrientation);
_pageInfos.Add(_currentPage, pageInfo);
_pageFieldInfos.Add(_currentPage, _currentFieldInfos);
}
PageOrientation CalcPageOrientation(PageSetup pageSetup)
{
PageOrientation pageOrientation = PageOrientation.Portrait;
if (_currentSection.PageSetup.Orientation == Orientation.Landscape)
pageOrientation = PageOrientation.Landscape;
return pageOrientation;
}
XSize CalcPageSize(PageSetup pageSetup)
{
return new XSize(pageSetup.PageWidth.Point, pageSetup.PageHeight.Point);
}
bool IAreaProvider.PositionHorizontally(LayoutInfo layoutInfo)
{
switch (layoutInfo.HorizontalReference)
{
case HorizontalReference.PageMargin:
case HorizontalReference.AreaBoundary:
return PositionHorizontallyToMargin(layoutInfo);
case HorizontalReference.Page:
return PositionHorizontallyToPage(layoutInfo);
}
return false;
}
///
/// Gets the alignment depending on the currentPage for the alignments "Outside" and "Inside".
///
/// The original alignment
/// the alignment depending on the currentPage for the alignments "Outside" and "Inside"
private ElementAlignment GetCurrentAlignment(ElementAlignment alignment)
{
ElementAlignment align = alignment;
if (align == ElementAlignment.Inside)
{
align = _currentPage % 2 == 0 ? ElementAlignment.Far : ElementAlignment.Near;
}
else if (align == ElementAlignment.Outside)
{
align = _currentPage % 2 == 0 ? ElementAlignment.Near : ElementAlignment.Far;
}
return align;
}
bool PositionHorizontallyToMargin(LayoutInfo layoutInfo)
{
Rectangle rect = CalcContentRect(_currentPage);
ElementAlignment align = GetCurrentAlignment(layoutInfo.HorizontalAlignment);
switch (align)
{
case ElementAlignment.Near:
if (layoutInfo.Left != 0)
{
layoutInfo.ContentArea.X += layoutInfo.Left;
return true;
}
if (layoutInfo.MarginLeft != 0)
{
layoutInfo.ContentArea.X += layoutInfo.MarginLeft;
return true;
}
return false;
case ElementAlignment.Far:
XUnit xPos = rect.X + rect.Width;
xPos -= layoutInfo.ContentArea.Width;
xPos -= layoutInfo.MarginRight;
layoutInfo.ContentArea.X = xPos;
return true;
case ElementAlignment.Center:
xPos = rect.Width;
xPos -= layoutInfo.ContentArea.Width;
xPos = rect.X + xPos / 2;
layoutInfo.ContentArea.X = xPos;
return true;
}
return false;
}
bool PositionHorizontallyToPage(LayoutInfo layoutInfo)
{
XUnit xPos;
ElementAlignment align = GetCurrentAlignment(layoutInfo.HorizontalAlignment);
switch (align)
{
case ElementAlignment.Near:
#if true
// Attempt to make it compatible with MigraDoc CPP.
// Ignore layoutInfo.Left if absolute position is specified in layoutInfo.MarginLeft.
// Use layoutInfo.Left if layoutInfo.MarginLeft is 0.
// TODO We would need HasValue for XUnit to determine whether a value was assigned.
if (layoutInfo.HorizontalReference == HorizontalReference.Page ||
layoutInfo.HorizontalReference == HorizontalReference.PageMargin)
xPos = layoutInfo.MarginLeft != 0 ? layoutInfo.MarginLeft : layoutInfo.Left;
else
xPos = Math.Max(layoutInfo.MarginLeft, layoutInfo.Left);
#else
if (layoutInfo.HorizontalReference == HorizontalReference.Page ||
layoutInfo.HorizontalReference == HorizontalReference.PageMargin)
xPos = layoutInfo.MarginLeft; // ignore layoutInfo.Left if absolute position is specified
else
xPos = Math.Max(layoutInfo.MarginLeft, layoutInfo.Left);
#endif
layoutInfo.ContentArea.X = xPos;
break;
case ElementAlignment.Far:
xPos = _currentSection.PageSetup.EffectivePageWidth.Point;
xPos -= layoutInfo.ContentArea.Width;
xPos -= layoutInfo.MarginRight;
layoutInfo.ContentArea.X = xPos;
break;
case ElementAlignment.Center:
xPos = _currentSection.PageSetup.EffectivePageWidth.Point;
xPos -= layoutInfo.ContentArea.Width;
xPos /= 2;
layoutInfo.ContentArea.X = xPos;
break;
}
return true;
}
bool PositionVerticallyToMargin(LayoutInfo layoutInfo)
{
Rectangle rect = CalcContentRect(_currentPage);
XUnit yPos;
switch (layoutInfo.VerticalAlignment)
{
case ElementAlignment.Near:
yPos = rect.Y;
if (layoutInfo.Top == 0)
yPos += layoutInfo.MarginTop;
else
yPos += layoutInfo.Top;
layoutInfo.ContentArea.Y = yPos;
break;
case ElementAlignment.Far:
yPos = rect.Y + rect.Height;
yPos -= layoutInfo.ContentArea.Height;
yPos -= layoutInfo.MarginBottom;
layoutInfo.ContentArea.Y = yPos;
break;
case ElementAlignment.Center:
yPos = rect.Height;
yPos -= layoutInfo.ContentArea.Height;
yPos = rect.Y + yPos / 2;
layoutInfo.ContentArea.Y = yPos;
break;
}
return true;
}
bool NeedsEmptyPage()
{
int nextPage = _currentPage + 1;
PageSetup pageSetup = _currentSection.PageSetup;
bool startOnEvenPage = pageSetup.SectionStart == BreakType.BreakEvenPage;
bool startOnOddPage = pageSetup.SectionStart == BreakType.BreakOddPage;
if (startOnOddPage)
return nextPage % 2 == 0;
if (startOnEvenPage)
return nextPage % 2 == 1;
return false;
}
void InsertEmptyPage()
{
++_currentPage;
++_shownPageNumber;
_emptyPages.Add(_currentPage, null);
XSize pageSize = CalcPageSize(_currentSection.PageSetup);
PageOrientation pageOrientation = CalcPageOrientation(_currentSection.PageSetup);
PageInfo pageInfo = new PageInfo(pageSize.Width, pageSize.Height, pageOrientation);
_pageInfos.Add(_currentPage, pageInfo);
}
bool PositionVerticallyToPage(LayoutInfo layoutInfo)
{
XUnit yPos;
switch (layoutInfo.VerticalAlignment)
{
case ElementAlignment.Near:
yPos = Math.Max(layoutInfo.MarginTop, layoutInfo.Top);
layoutInfo.ContentArea.Y = yPos;
break;
case ElementAlignment.Far:
yPos = _currentSection.PageSetup.EffectivePageHeight.Point;
yPos -= layoutInfo.ContentArea.Height;
yPos -= layoutInfo.MarginBottom;
layoutInfo.ContentArea.Y = yPos;
break;
case ElementAlignment.Center:
yPos = _currentSection.PageSetup.EffectivePageHeight.Point;
yPos -= layoutInfo.ContentArea.Height;
yPos /= 2;
layoutInfo.ContentArea.Y = yPos;
break;
}
return true;
}
bool IAreaProvider.PositionVertically(LayoutInfo layoutInfo)
{
switch (layoutInfo.VerticalReference)
{
case VerticalReference.PreviousElement:
return false;
case VerticalReference.AreaBoundary:
case VerticalReference.PageMargin:
return PositionVerticallyToMargin(layoutInfo);
case VerticalReference.Page:
return PositionVerticallyToPage(layoutInfo);
}
return false;
}
public FieldInfos GetFieldInfos(int page)
{
return _pageFieldInfos[page];
}
FieldInfos IAreaProvider.AreaFieldInfos
{
get { return _currentFieldInfos; }
}
bool IAreaProvider.IsAreaBreakBefore(LayoutInfo layoutInfo)
{
return layoutInfo.PageBreakBefore;
}
public bool IsEmptyPage(int page)
{
return _emptyPages.ContainsKey(page);
}
#endregion
Dictionary _bookmarks;
int _sectionPages;
int _shownPageNumber;
int _sectionNumber;
Section _currentSection;
bool _isNewSection;
FieldInfos _currentFieldInfos;
Dictionary _pageFieldInfos;
Dictionary _formattedHeaders;
Dictionary _formattedFooters;
readonly DocumentRenderer _documentRenderer;
XGraphics _gfx;
Dictionary _pageInfos;
readonly Dictionary _emptyPages = new Dictionary();
readonly Document _document;
}
}