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