ASCU_ALL/PrintPDF/MigraDoc.Rendering/Rendering/ParagraphRenderer.cs

2546 lines
91 KiB
C#
Raw Normal View History

2020-09-04 12:49:15 +05:00
#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.Diagnostics;
using System.Text;
using MigraDoc.DocumentObjectModel;
using PdfSharp.Pdf;
using PdfSharp.Drawing;
using MigraDoc.DocumentObjectModel.Fields;
using MigraDoc.DocumentObjectModel.Shapes;
using MigraDoc.Rendering.Resources;
namespace MigraDoc.Rendering
{
public struct TabOffset
{
public TabOffset(TabLeader leader, XUnit offset)
{
Leader = leader;
Offset = offset;
}
public TabLeader Leader;
public XUnit Offset;
}
/// <summary>
/// Summary description for ParagraphRenderer.
/// </summary>
public class ParagraphRenderer : Renderer
{
/// <summary>
/// Process phases of the renderer.
/// </summary>
private enum Phase
{
Formatting,
Rendering
}
/// <summary>
/// Results that can occur when processing a paragraph element
/// during formatting.
/// </summary>
private enum FormatResult
{
/// <summary>
/// Ignore the current element during formatting.
/// </summary>
Ignore,
/// <summary>
/// Continue with the next element within the same line.
/// </summary>
Continue,
/// <summary>
/// Start a new line from the current object on.
/// </summary>
NewLine,
/// <summary>
/// Break formatting and continue in a new area (e.g. a new page).
/// </summary>
NewArea
}
Phase _phase;
/// <summary>
/// Initializes a ParagraphRenderer object for formatting.
/// </summary>
/// <param name="gfx">The XGraphics object to do measurements on.</param>
/// <param name="paragraph">The paragraph to format.</param>
/// <param name="fieldInfos">The field infos.</param>
public ParagraphRenderer(XGraphics gfx, Paragraph paragraph, FieldInfos fieldInfos)
: base(gfx, paragraph, fieldInfos)
{
_paragraph = paragraph;
ParagraphRenderInfo parRenderInfo = new ParagraphRenderInfo();
parRenderInfo.DocumentObject = _paragraph;
((ParagraphFormatInfo)parRenderInfo.FormatInfo)._widowControl = _paragraph.Format.WidowControl;
_renderInfo = parRenderInfo;
}
/// <summary>
/// Initializes a ParagraphRenderer object for rendering.
/// </summary>
/// <param name="gfx">The XGraphics object to render on.</param>
/// <param name="renderInfo">The render info object containing information necessary for rendering.</param>
/// <param name="fieldInfos">The field infos.</param>
public ParagraphRenderer(XGraphics gfx, RenderInfo renderInfo, FieldInfos fieldInfos)
: base(gfx, renderInfo, fieldInfos)
{
_paragraph = (Paragraph)renderInfo.DocumentObject;
}
/// <summary>
/// Renders the paragraph.
/// </summary>
public override void Render()
{
InitRendering();
if ((int)_paragraph.Format.OutlineLevel >= 1 && _gfx.PdfPage != null) // Don't call GetOutlineTitle() in vain
_documentRenderer.AddOutline((int)_paragraph.Format.OutlineLevel, GetOutlineTitle(), _gfx.PdfPage);
RenderShading();
RenderBorders();
ParagraphFormatInfo parFormatInfo = (ParagraphFormatInfo)_renderInfo.FormatInfo;
for (int idx = 0; idx < parFormatInfo.LineCount; ++idx)
{
LineInfo lineInfo = parFormatInfo.GetLineInfo(idx);
_isLastLine = (idx == parFormatInfo.LineCount - 1);
_lastTabPosition = 0;
if (lineInfo.ReMeasureLine)
ReMeasureLine(ref lineInfo);
RenderLine(lineInfo);
}
}
bool IsRenderedField(DocumentObject docObj)
{
if (docObj is NumericFieldBase)
return true;
if (docObj is DocumentInfo)
return true;
if (docObj is DateField)
return true;
return false;
}
string GetFieldValue(DocumentObject field)
{
NumericFieldBase numericFieldBase = field as NumericFieldBase;
if (numericFieldBase != null)
{
int number = -1;
PageRefField refField = field as PageRefField;
if (refField != null)
{
PageRefField pageRefField = refField;
number = _fieldInfos.GetShownPageNumber(pageRefField.Name);
if (number <= 0)
{
if (_phase == Phase.Formatting)
return "XX";
return Messages2.BookmarkNotDefined(pageRefField.Name);
}
}
else if (field is SectionField)
{
number = _fieldInfos.Section;
if (number <= 0)
return "XX";
}
else if (field is PageField)
{
number = _fieldInfos.DisplayPageNr;
if (number <= 0)
return "XX";
}
else if (field is NumPagesField)
{
number = _fieldInfos.NumPages;
if (number <= 0)
return "XXX";
}
else if (field is SectionPagesField)
{
number = _fieldInfos.SectionPages;
if (number <= 0)
return "XX";
}
return NumberFormatter.Format(number, numericFieldBase.Format);
}
else
{
DateField dateField = field as DateField;
if (dateField != null)
{
DateTime dt = (_fieldInfos.Date);
if (dt == DateTime.MinValue)
dt = DateTime.Now;
return _fieldInfos.Date.ToString(dateField.Format);
}
InfoField infoField = field as InfoField;
if (infoField != null)
return GetDocumentInfo(infoField.Name);
Debug.Assert(false, "Given parameter must be a rendered Field");
}
return "";
}
string GetOutlineTitle()
{
ParagraphIterator iter = new ParagraphIterator(_paragraph.Elements);
iter = iter.GetFirstLeaf();
bool ignoreBlank = true;
string title = "";
while (iter != null)
{
DocumentObject current = iter.Current;
if (!ignoreBlank && (IsBlank(current) || IsTab(current) || IsLineBreak(current)))
{
title += " ";
ignoreBlank = true;
}
else if (current is Text)
{
title += ((Text)current).Content;
ignoreBlank = false;
}
else if (IsRenderedField(current))
{
title += GetFieldValue(current);
ignoreBlank = false;
}
else if (IsSymbol(current))
{
title += GetSymbol((Character)current);
ignoreBlank = false;
}
if (title.Length > 64)
break;
iter = iter.GetNextLeaf();
}
return title;
}
/// <summary>
/// Gets a layout info with only margin and break information set.
/// It can be taken before the paragraph is formatted.
/// </summary>
/// <remarks>
/// The following layout information is set properly:<br />
/// MarginTop, MarginLeft, MarginRight, MarginBottom, KeepTogether, KeepWithNext, PagebreakBefore.
/// </remarks>
public override LayoutInfo InitialLayoutInfo
{
get
{
LayoutInfo layoutInfo = new LayoutInfo();
layoutInfo.PageBreakBefore = _paragraph.Format.PageBreakBefore;
layoutInfo.MarginTop = _paragraph.Format.SpaceBefore.Point;
layoutInfo.MarginBottom = _paragraph.Format.SpaceAfter.Point;
//Don't confuse margins with left or right indent.
//Indents are invisible for the layouter.
layoutInfo.MarginRight = 0;
layoutInfo.MarginLeft = 0;
layoutInfo.KeepTogether = _paragraph.Format.KeepTogether;
layoutInfo.KeepWithNext = _paragraph.Format.KeepWithNext;
return layoutInfo;
}
}
/// <summary>
/// Adjusts the current x position to the given tab stop if possible.
/// </summary>
/// <returns>True, if the text doesn't fit the line any more and the tab causes a line break.</returns>
FormatResult FormatTab()
{
// For Tabs in Justified context
if (_paragraph.Format.Alignment == ParagraphAlignment.Justify)
_reMeasureLine = true;
TabStop nextTabStop = GetNextTabStop();
_savedWordWidth = 0;
if (nextTabStop == null)
return FormatResult.NewLine;
bool notFitting = false;
XUnit xPositionBeforeTab = _currentXPosition;
switch (nextTabStop.Alignment)
{
case TabAlignment.Left:
_currentXPosition = ProbeAfterLeftAlignedTab(nextTabStop.Position.Point, out notFitting);
break;
case TabAlignment.Right:
_currentXPosition = ProbeAfterRightAlignedTab(nextTabStop.Position.Point, out notFitting);
break;
case TabAlignment.Center:
_currentXPosition = ProbeAfterCenterAlignedTab(nextTabStop.Position.Point, out notFitting);
break;
case TabAlignment.Decimal:
_currentXPosition = ProbeAfterDecimalAlignedTab(nextTabStop.Position.Point, out notFitting);
break;
}
if (!notFitting)
{
// For correct right paragraph alignment with tabs
if (!IgnoreHorizontalGrowth)
_currentLineWidth += _currentXPosition - xPositionBeforeTab;
_tabOffsets.Add(new TabOffset(nextTabStop.Leader, _currentXPosition - xPositionBeforeTab));
if (_currentLeaf != null)
_lastTab = _currentLeaf.Current;
}
return notFitting ? FormatResult.NewLine : FormatResult.Continue;
}
bool IsLineBreak(DocumentObject docObj)
{
if (docObj is Character)
{
if (((Character)docObj).SymbolName == SymbolName.LineBreak)
return true;
}
return false;
}
bool IsBlank(DocumentObject docObj)
{
if (docObj is Text)
{
if (((Text)docObj).Content == " ")
return true;
}
return false;
}
bool IsTab(DocumentObject docObj)
{
if (docObj is Character)
{
if (((Character)docObj).SymbolName == SymbolName.Tab)
return true;
}
return false;
}
bool IsSoftHyphen(DocumentObject docObj)
{
Text text = docObj as Text;
if (text != null)
return text.Content == "­";
return false;
}
/// <summary>
/// Probes the paragraph elements after a left aligned tab stop and returns the vertical text position to start at.
/// </summary>
/// <param name="tabStopPosition">Position of the tab to probe.</param>
/// <param name="notFitting">Out parameter determining whether the tab causes a line break.</param>
/// <returns>The new x-position to restart behind the tab.</returns>
XUnit ProbeAfterLeftAlignedTab(XUnit tabStopPosition, out bool notFitting)
{
//--- Save ---------------------------------
ParagraphIterator iter;
int blankCount;
XUnit xPosition;
XUnit lineWidth;
XUnit wordsWidth;
XUnit blankWidth;
SaveBeforeProbing(out iter, out blankCount, out wordsWidth, out xPosition, out lineWidth, out blankWidth);
//------------------------------------------
XUnit xPositionAfterTab = xPosition;
_currentXPosition = _formattingArea.X + tabStopPosition.Point;
notFitting = ProbeAfterTab();
if (!notFitting)
xPositionAfterTab = _formattingArea.X + tabStopPosition;
//--- Restore ---------------------------------
RestoreAfterProbing(iter, blankCount, wordsWidth, xPosition, lineWidth, blankWidth);
//------------------------------------------
return xPositionAfterTab;
}
/// <summary>
/// Probes the paragraph elements after a right aligned tab stop and returns the vertical text position to start at.
/// </summary>
/// <param name="tabStopPosition">Position of the tab to probe.</param>
/// <param name="notFitting">Out parameter determining whether the tab causes a line break.</param>
/// <returns>The new x-position to restart behind the tab.</returns>
XUnit ProbeAfterRightAlignedTab(XUnit tabStopPosition, out bool notFitting)
{
//--- Save ---------------------------------
ParagraphIterator iter;
int blankCount;
XUnit xPosition;
XUnit lineWidth;
XUnit wordsWidth;
XUnit blankWidth;
SaveBeforeProbing(out iter, out blankCount, out wordsWidth, out xPosition, out lineWidth, out blankWidth);
//------------------------------------------
XUnit xPositionAfterTab = xPosition;
notFitting = ProbeAfterTab();
if (!notFitting && xPosition + _currentLineWidth <= _formattingArea.X + tabStopPosition)
xPositionAfterTab = _formattingArea.X + tabStopPosition - _currentLineWidth;
//--- Restore ------------------------------
RestoreAfterProbing(iter, blankCount, wordsWidth, xPosition, lineWidth, blankWidth);
//------------------------------------------
return xPositionAfterTab;
}
Hyperlink GetHyperlink()
{
DocumentObject elements = DocumentRelations.GetParent(_currentLeaf.Current);
DocumentObject parent = DocumentRelations.GetParent(elements);
while (!(parent is Paragraph))
{
Hyperlink hyperlink = parent as Hyperlink;
if (hyperlink != null)
return hyperlink;
elements = DocumentRelations.GetParent(parent);
parent = DocumentRelations.GetParent(elements);
}
return null;
}
/// <summary>
/// Probes the paragraph elements after a right aligned tab stop and returns the vertical text position to start at.
/// </summary>
/// <param name="tabStopPosition">Position of the tab to probe.</param>
/// <param name="notFitting">Out parameter determining whether the tab causes a line break.</param>
/// <returns>The new x-position to restart behind the tab.</returns>
XUnit ProbeAfterCenterAlignedTab(XUnit tabStopPosition, out bool notFitting)
{
//--- Save ---------------------------------
ParagraphIterator iter;
int blankCount;
XUnit xPosition;
XUnit lineWidth;
XUnit wordsWidth;
XUnit blankWidth;
SaveBeforeProbing(out iter, out blankCount, out wordsWidth, out xPosition, out lineWidth, out blankWidth);
//------------------------------------------
XUnit xPositionAfterTab = xPosition;
notFitting = ProbeAfterTab();
if (!notFitting)
{
if (xPosition + _currentLineWidth / 2.0 <= _formattingArea.X + tabStopPosition)
{
Rectangle rect = _formattingArea.GetFittingRect(_currentYPosition, _currentVerticalInfo.Height);
if (_formattingArea.X + tabStopPosition + _currentLineWidth / 2.0 > rect.X + rect.Width - RightIndent)
{
//the text is too long on the right hand side of the tabstop => align to right indent.
xPositionAfterTab = rect.X +
rect.Width -
RightIndent -
_currentLineWidth;
}
else
xPositionAfterTab = _formattingArea.X + tabStopPosition - _currentLineWidth / 2;
}
}
//--- Restore ------------------------------
RestoreAfterProbing(iter, blankCount, wordsWidth, xPosition, lineWidth, blankWidth);
//------------------------------------------
return xPositionAfterTab;
}
/// <summary>
/// Probes the paragraph elements after a right aligned tab stop and returns the vertical text position to start at.
/// </summary>
/// <param name="tabStopPosition">Position of the tab to probe.</param>
/// <param name="notFitting">Out parameter determining whether the tab causes a line break.</param>
/// <returns>The new x-position to restart behind the tab.</returns>
XUnit ProbeAfterDecimalAlignedTab(XUnit tabStopPosition, out bool notFitting)
{
notFitting = false;
ParagraphIterator savedLeaf = _currentLeaf;
//Extra for auto tab after list symbol
if (IsTab(_currentLeaf.Current))
_currentLeaf = _currentLeaf.GetNextLeaf();
if (_currentLeaf == null)
{
_currentLeaf = savedLeaf;
return _currentXPosition + tabStopPosition;
}
VerticalLineInfo newVerticalInfo = CalcCurrentVerticalInfo();
Rectangle fittingRect = _formattingArea.GetFittingRect(_currentYPosition, newVerticalInfo.Height);
if (fittingRect == null)
{
notFitting = true;
_currentLeaf = savedLeaf;
return _currentXPosition;
}
if (IsPlainText(_currentLeaf.Current))
{
Text text = (Text)_currentLeaf.Current;
string word = text.Content;
int lastIndex = text.Content.LastIndexOfAny(new char[] { ',', '.' });
if (lastIndex > 0)
word = word.Substring(0, lastIndex);
XUnit wordLength = MeasureString(word);
notFitting = _currentXPosition + wordLength >= _formattingArea.X + _formattingArea.Width + Tolerance;
if (!notFitting)
return _formattingArea.X + tabStopPosition - wordLength;
return _currentXPosition;
}
_currentLeaf = savedLeaf;
return ProbeAfterRightAlignedTab(tabStopPosition, out notFitting);
}
void SaveBeforeProbing(out ParagraphIterator paragraphIter, out int blankCount, out XUnit wordsWidth, out XUnit xPosition, out XUnit lineWidth, out XUnit blankWidth)
{
paragraphIter = _currentLeaf;
blankCount = _currentBlankCount;
xPosition = _currentXPosition;
lineWidth = _currentLineWidth;
wordsWidth = _currentWordsWidth;
blankWidth = _savedBlankWidth;
}
void RestoreAfterProbing(ParagraphIterator paragraphIter, int blankCount, XUnit wordsWidth, XUnit xPosition, XUnit lineWidth, XUnit blankWidth)
{
_currentLeaf = paragraphIter;
_currentBlankCount = blankCount;
_currentXPosition = xPosition;
_currentLineWidth = lineWidth;
_currentWordsWidth = wordsWidth;
_savedBlankWidth = blankWidth;
}
/// <summary>
/// Probes the paragraph after a tab.
/// Caution: This Function resets the word count and line width before doing its work.
/// </summary>
/// <returns>True if the tab causes a linebreak.</returns>
bool ProbeAfterTab()
{
_currentLineWidth = 0;
_currentBlankCount = 0;
//Extra for auto tab after list symbol
//TODO: KLPO4KLPO: Check if this conditional statement is still required
if (_currentLeaf != null && IsTab(_currentLeaf.Current))
_currentLeaf = _currentLeaf.GetNextLeaf();
bool wordAppeared = false;
while (_currentLeaf != null && !IsLineBreak(_currentLeaf.Current) && !IsTab(_currentLeaf.Current))
{
FormatResult result = FormatElement(_currentLeaf.Current);
if (result != FormatResult.Continue)
break;
wordAppeared = wordAppeared || IsWordLikeElement(_currentLeaf.Current);
_currentLeaf = _currentLeaf.GetNextLeaf();
}
return _currentLeaf != null && !IsLineBreak(_currentLeaf.Current) &&
!IsTab(_currentLeaf.Current) && !wordAppeared;
}
/// <summary>
/// Gets the next tab stop following the current x position.
/// </summary>
/// <returns>The searched tab stop.</returns>
private TabStop GetNextTabStop()
{
ParagraphFormat format = _paragraph.Format;
TabStops tabStops = format.TabStops;
XUnit lastPosition = 0;
foreach (TabStop tabStop in tabStops)
{
if (tabStop.Position.Point > _formattingArea.Width - RightIndent + Tolerance)
break;
// Compare with "Tolerance" to prevent rounding errors from taking us one tabstop too far.
if (tabStop.Position.Point + _formattingArea.X > _currentXPosition + Tolerance)
return tabStop;
lastPosition = tabStop.Position.Point;
}
//Automatic tab stop: FirstLineIndent < 0 => automatic tab stop at LeftIndent.
if (format.FirstLineIndent < 0 ||
(format._listInfo != null && !format._listInfo.IsNull() && format.ListInfo.NumberPosition < format.LeftIndent))
{
XUnit leftIndent = format.LeftIndent.Point;
if (_isFirstLine && _currentXPosition < leftIndent + _formattingArea.X)
return new TabStop(leftIndent.Point);
}
XUnit defaultTabStop = "1.25cm";
if (!_paragraph.Document._defaultTabStop.IsNull)
defaultTabStop = _paragraph.Document.DefaultTabStop.Point;
XUnit currTabPos = defaultTabStop;
while (currTabPos + _formattingArea.X <= _formattingArea.Width - RightIndent)
{
if (currTabPos > lastPosition && currTabPos + _formattingArea.X > _currentXPosition + Tolerance)
return new TabStop(currTabPos.Point);
currTabPos += defaultTabStop;
}
return null;
}
/// <summary>
/// Gets the horizontal position to start a new line.
/// </summary>
/// <returns>The position to start the line.</returns>
XUnit StartXPosition
{
get
{
XUnit xPos = 0;
if (_phase == Phase.Formatting)
{
xPos = _formattingArea.GetFittingRect(_currentYPosition, _currentVerticalInfo.Height).X;
xPos += LeftIndent;
}
else //if (phase == Phase.Rendering)
{
Area contentArea = _renderInfo.LayoutInfo.ContentArea;
//next lines for non fitting lines that produce an empty fitting rect:
XUnit rectX = contentArea.X;
XUnit rectWidth = contentArea.Width;
Rectangle fittingRect = contentArea.GetFittingRect(_currentYPosition, _currentVerticalInfo.Height);
if (fittingRect != null)
{
rectX = fittingRect.X;
rectWidth = fittingRect.Width;
}
switch (_paragraph.Format.Alignment)
{
case ParagraphAlignment.Left:
case ParagraphAlignment.Justify:
xPos = rectX;
xPos += LeftIndent;
break;
case ParagraphAlignment.Right:
xPos = rectX + rectWidth - RightIndent;
xPos -= _currentLineWidth;
break;
case ParagraphAlignment.Center:
xPos = rectX + (rectWidth + LeftIndent - RightIndent - _currentLineWidth) / 2.0;
break;
}
}
return xPos;
}
}
/// <summary>
/// Renders a single line.
/// </summary>
/// <param name="lineInfo"></param>
void RenderLine(LineInfo lineInfo)
{
_currentVerticalInfo = lineInfo.Vertical;
_currentLeaf = lineInfo.StartIter;
_startLeaf = lineInfo.StartIter;
_endLeaf = lineInfo.EndIter;
_currentBlankCount = lineInfo.BlankCount;
_currentLineWidth = lineInfo.LineWidth;
_currentWordsWidth = lineInfo.WordsWidth;
_currentXPosition = StartXPosition;
_tabOffsets = lineInfo.TabOffsets;
_lastTabPassed = lineInfo.LastTab == null;
_lastTab = lineInfo.LastTab;
_tabIdx = 0;
bool ready = _currentLeaf == null;
if (_isFirstLine)
RenderListSymbol();
while (!ready)
{
if (_currentLeaf.Current == lineInfo.EndIter.Current)
ready = true;
if (_currentLeaf.Current == lineInfo.LastTab)
_lastTabPassed = true;
RenderElement(_currentLeaf.Current);
_currentLeaf = _currentLeaf.GetNextLeaf();
}
_currentYPosition += lineInfo.Vertical.Height;
_isFirstLine = false;
}
void ReMeasureLine(ref LineInfo lineInfo)
{
//--- Save ---------------------------------
ParagraphIterator iter;
int blankCount;
XUnit xPosition;
XUnit lineWidth;
XUnit wordsWidth;
XUnit blankWidth;
SaveBeforeProbing(out iter, out blankCount, out wordsWidth, out xPosition, out lineWidth, out blankWidth);
bool origLastTabPassed = _lastTabPassed;
//------------------------------------------
_currentLeaf = lineInfo.StartIter;
_endLeaf = lineInfo.EndIter;
_formattingArea = _renderInfo.LayoutInfo.ContentArea;
_tabOffsets = new List<TabOffset>();
_currentLineWidth = 0;
_currentWordsWidth = 0;
Rectangle fittingRect = _formattingArea.GetFittingRect(_currentYPosition, _currentVerticalInfo.Height);
if (fittingRect != null)
{
_currentXPosition = fittingRect.X + LeftIndent;
FormatListSymbol();
bool goOn = true;
while (goOn && _currentLeaf != null)
{
if (_currentLeaf.Current == lineInfo.LastTab)
_lastTabPassed = true;
FormatElement(_currentLeaf.Current);
goOn = _currentLeaf != null && _currentLeaf.Current != _endLeaf.Current;
if (goOn)
_currentLeaf = _currentLeaf.GetNextLeaf();
}
lineInfo.LineWidth = _currentLineWidth;
lineInfo.WordsWidth = _currentWordsWidth;
lineInfo.BlankCount = _currentBlankCount;
lineInfo.TabOffsets = _tabOffsets;
lineInfo.ReMeasureLine = false;
_lastTabPassed = origLastTabPassed;
}
RestoreAfterProbing(iter, blankCount, wordsWidth, xPosition, lineWidth, blankWidth);
}
XUnit CurrentWordDistance
{
get
{
if (_phase == Phase.Rendering &&
_paragraph.Format.Alignment == ParagraphAlignment.Justify && _lastTabPassed)
{
if (_currentBlankCount >= 1 && !(_isLastLine && _renderInfo.FormatInfo.IsEnding))
{
Area contentArea = _renderInfo.LayoutInfo.ContentArea;
XUnit width = contentArea.GetFittingRect(_currentYPosition, _currentVerticalInfo.Height).Width;
if (_lastTabPosition > 0)
{
width -= (_lastTabPosition -
contentArea.X);
}
else
width -= LeftIndent;
width -= RightIndent;
return (width - _currentWordsWidth) / (_currentBlankCount);
}
}
return MeasureString(" ");
}
}
void RenderElement(DocumentObject docObj)
{
string typeName = docObj.GetType().Name;
switch (typeName)
{
case "Text":
if (IsBlank(docObj))
RenderBlank();
else if (IsSoftHyphen(docObj))
RenderSoftHyphen();
else
RenderText((Text)docObj);
break;
case "Character":
RenderCharacter((Character)docObj);
break;
case "DateField":
RenderDateField((DateField)docObj);
break;
case "InfoField":
RenderInfoField((InfoField)docObj);
break;
case "NumPagesField":
RenderNumPagesField((NumPagesField)docObj);
break;
case "PageField":
RenderPageField((PageField)docObj);
break;
case "SectionField":
RenderSectionField((SectionField)docObj);
break;
case "SectionPagesField":
RenderSectionPagesField((SectionPagesField)docObj);
break;
case "BookmarkField":
RenderBookmarkField();
break;
case "PageRefField":
RenderPageRefField((PageRefField)docObj);
break;
case "Image":
RenderImage((Image)docObj);
break;
// default:
// throw new NotImplementedException(typeName + " is coming soon...");
}
}
void RenderImage(Image image)
{
RenderInfo renderInfo = CurrentImageRenderInfo;
XUnit top = CurrentBaselinePosition;
Area contentArea = renderInfo.LayoutInfo.ContentArea;
top -= contentArea.Height;
RenderByInfos(_currentXPosition, top, new RenderInfo[] { renderInfo });
RenderUnderline(contentArea.Width, true);
RealizeHyperlink(contentArea.Width);
_currentXPosition += contentArea.Width;
}
void RenderDateField(DateField dateField)
{
RenderWord(_fieldInfos.Date.ToString(dateField.Format));
}
void RenderInfoField(InfoField infoField)
{
RenderWord(GetDocumentInfo(infoField.Name));
}
void RenderNumPagesField(NumPagesField numPagesField)
{
RenderWord(GetFieldValue(numPagesField));
}
void RenderPageField(PageField pageField)
{
RenderWord(GetFieldValue(pageField));
}
void RenderSectionField(SectionField sectionField)
{
RenderWord(GetFieldValue(sectionField));
}
void RenderSectionPagesField(SectionPagesField sectionPagesField)
{
RenderWord(GetFieldValue(sectionPagesField));
}
void RenderBookmarkField()
{
RenderUnderline(0, false);
}
void RenderPageRefField(PageRefField pageRefField)
{
RenderWord(GetFieldValue(pageRefField));
}
void RenderCharacter(Character character)
{
switch (character.SymbolName)
{
case SymbolName.Blank:
case SymbolName.Em:
case SymbolName.Em4:
case SymbolName.En:
RenderSpace(character);
break;
case SymbolName.LineBreak:
RenderLinebreak();
break;
case SymbolName.Tab:
RenderTab();
break;
default:
RenderSymbol(character);
break;
}
}
void RenderSpace(Character character)
{
XUnit width = GetSpaceWidth(character);
RenderUnderline(width, false);
RealizeHyperlink(width);
_currentXPosition += width;
}
void RenderLinebreak()
{
RenderUnderline(0, false);
RealizeHyperlink(0);
}
void RenderSymbol(Character character)
{
string sym = GetSymbol(character);
string completeWord = sym;
for (int idx = 1; idx < character.Count; ++idx)
completeWord += sym;
RenderWord(completeWord);
}
void RenderTab()
{
TabOffset tabOffset = NextTabOffset();
RenderUnderline(tabOffset.Offset, false);
RenderTabLeader(tabOffset);
RealizeHyperlink(tabOffset.Offset);
_currentXPosition += tabOffset.Offset;
if (_currentLeaf.Current == _lastTab)
_lastTabPosition = _currentXPosition;
}
void RenderTabLeader(TabOffset tabOffset)
{
string leaderString;
switch (tabOffset.Leader)
{
case TabLeader.Dashes:
leaderString = "-";
break;
case TabLeader.Dots:
leaderString = ".";
break;
case TabLeader.Heavy:
case TabLeader.Lines:
leaderString = "_";
break;
case TabLeader.MiddleDot:
leaderString = "·";
break;
default:
return;
}
XUnit leaderWidth = MeasureString(leaderString);
XUnit xPosition = _currentXPosition;
string drawString = "";
while (xPosition + leaderWidth <= _currentXPosition + tabOffset.Offset)
{
drawString += leaderString;
xPosition += leaderWidth;
}
Font font = CurrentDomFont;
XFont xFont = CurrentFont;
if (font.Subscript || font.Superscript)
xFont = FontHandler.ToSubSuperFont(xFont);
_gfx.DrawString(drawString, xFont, CurrentBrush, _currentXPosition, CurrentBaselinePosition);
}
TabOffset NextTabOffset()
{
TabOffset offset = _tabOffsets.Count > _tabIdx ? _tabOffsets[_tabIdx] :
new TabOffset(0, 0);
++_tabIdx;
return offset;
}
int _tabIdx;
bool IgnoreBlank()
{
if (_currentLeaf == _startLeaf)
return true;
if (_endLeaf != null && _currentLeaf.Current == _endLeaf.Current)
return true;
ParagraphIterator nextIter = _currentLeaf.GetNextLeaf();
while (nextIter != null && (IsBlank(nextIter.Current) || nextIter.Current is BookmarkField))
{
nextIter = nextIter.GetNextLeaf();
}
if (nextIter == null)
return true;
if (IsTab(nextIter.Current))
return true;
ParagraphIterator prevIter = _currentLeaf.GetPreviousLeaf();
// Can be null if currentLeaf is the first leaf
DocumentObject obj = prevIter != null ? prevIter.Current : null;
while (obj != null && obj is BookmarkField)
{
prevIter = prevIter.GetPreviousLeaf();
obj = prevIter != null ? prevIter.Current : null;
}
if (obj == null)
return true;
return IsBlank(obj) || IsTab(obj);
}
void RenderBlank()
{
if (!IgnoreBlank())
{
XUnit wordDistance = CurrentWordDistance;
RenderUnderline(wordDistance, false);
RealizeHyperlink(wordDistance);
_currentXPosition += wordDistance;
}
else
{
RenderUnderline(0, false);
RealizeHyperlink(0);
}
}
void RenderSoftHyphen()
{
if (_currentLeaf.Current == _endLeaf.Current)
RenderWord("-");
}
void RenderText(Text text)
{
RenderWord(text.Content);
}
void RenderWord(string word)
{
Font font = CurrentDomFont;
XFont xFont = CurrentFont;
if (font.Subscript || font.Superscript)
xFont = FontHandler.ToSubSuperFont(xFont);
_gfx.DrawString(word, xFont, CurrentBrush, _currentXPosition, CurrentBaselinePosition);
XUnit wordWidth = MeasureString(word);
RenderUnderline(wordWidth, true);
RealizeHyperlink(wordWidth);
_currentXPosition += wordWidth;
}
void StartHyperlink(XUnit left, XUnit top)
{
_hyperlinkRect = new XRect(left, top, 0, 0);
}
void EndHyperlink(Hyperlink hyperlink, XUnit right, XUnit bottom)
{
_hyperlinkRect.Width = right - _hyperlinkRect.X;
_hyperlinkRect.Height = bottom - _hyperlinkRect.Y;
PdfPage page = _gfx.PdfPage;
if (page != null)
{
XRect rect = _gfx.Transformer.WorldToDefaultPage(_hyperlinkRect);
switch (hyperlink.Type)
{
case HyperlinkType.Local:
int pageRef = _fieldInfos.GetPhysicalPageNumber(hyperlink.Name);
if (pageRef > 0)
page.AddDocumentLink(new PdfRectangle(rect), pageRef);
break;
case HyperlinkType.Web:
page.AddWebLink(new PdfRectangle(rect), hyperlink.Name);
break;
case HyperlinkType.File:
page.AddFileLink(new PdfRectangle(rect), hyperlink.Name);
break;
}
_hyperlinkRect = new XRect();
}
}
void RealizeHyperlink(XUnit width)
{
XUnit top = _currentYPosition;
XUnit left = _currentXPosition;
XUnit bottom = top + _currentVerticalInfo.Height;
XUnit right = left + width;
Hyperlink hyperlink = GetHyperlink();
bool hyperlinkChanged = _currentHyperlink != hyperlink;
if (hyperlinkChanged)
{
if (_currentHyperlink != null)
EndHyperlink(_currentHyperlink, left, bottom);
if (hyperlink != null)
StartHyperlink(left, top);
_currentHyperlink = hyperlink;
}
if (_currentLeaf.Current == _endLeaf.Current)
{
if (_currentHyperlink != null)
EndHyperlink(_currentHyperlink, right, bottom);
_currentHyperlink = null;
}
}
Hyperlink _currentHyperlink;
XRect _hyperlinkRect;
XUnit CurrentBaselinePosition
{
get
{
VerticalLineInfo verticalInfo = _currentVerticalInfo;
XUnit position = _currentYPosition;
Font font = CurrentDomFont;
XFont xFont = CurrentFont;
// $MaOs BUG: For LineSpacingRule.AtLeast the text should be positioned at the line bottom. Maybe verticalInfo.InherentlineSpace does not contain the lineSpacing value in this case.
double setLineSpace = verticalInfo.InherentlineSpace;
double standardFontLineSpace = xFont.GetHeight();
// Set position to bottom of text.
position += setLineSpace;
if (font.Subscript)
{
// Move sub-/superscaled descender up.
position -= FontHandler.GetSubSuperScaling(CurrentFont) * FontHandler.GetDescent(xFont);
}
else if (font.Superscript)
{
// Set position to top of text.
position -= standardFontLineSpace;
// Move sub-/superscaled LineSpace down and descender up.
position += FontHandler.GetSubSuperScaling(CurrentFont) * (standardFontLineSpace - FontHandler.GetDescent(xFont));
}
else
// Move descender up.
position -= verticalInfo.Descent;
return position;
}
}
XBrush CurrentBrush
{
get
{
if (_currentLeaf != null)
return FontHandler.FontColorToXBrush(CurrentDomFont);
return null;
}
}
private void InitRendering()
{
_phase = Phase.Rendering;
ParagraphFormatInfo parFormatInfo = (ParagraphFormatInfo)_renderInfo.FormatInfo;
if (parFormatInfo.LineCount == 0)
return;
_isFirstLine = parFormatInfo.IsStarting;
LineInfo lineInfo = parFormatInfo.GetFirstLineInfo();
Area contentArea = _renderInfo.LayoutInfo.ContentArea;
_currentYPosition = contentArea.Y + TopBorderOffset;
// StL: GetFittingRect liefert manchmal null
Rectangle rect = contentArea.GetFittingRect(_currentYPosition, lineInfo.Vertical.Height);
if (rect != null)
_currentXPosition = rect.X;
_currentLineWidth = 0;
}
/// <summary>
/// Initializes this instance for formatting.
/// </summary>
/// <param name="area">The area for formatting</param>
/// <param name="previousFormatInfo">A previous format info.</param>
/// <returns>False, if nothing of the paragraph will fit the area any more.</returns>
private bool InitFormat(Area area, FormatInfo previousFormatInfo)
{
_phase = Phase.Formatting;
_tabOffsets = new List<TabOffset>();
ParagraphFormatInfo prevParaFormatInfo = (ParagraphFormatInfo)previousFormatInfo;
if (previousFormatInfo == null || prevParaFormatInfo.LineCount == 0)
{
((ParagraphFormatInfo)_renderInfo.FormatInfo)._isStarting = true;
ParagraphIterator parIt = new ParagraphIterator(_paragraph.Elements);
_currentLeaf = parIt.GetFirstLeaf();
_isFirstLine = true;
}
else
{
_currentLeaf = prevParaFormatInfo.GetLastLineInfo().EndIter.GetNextLeaf();
_isFirstLine = false;
((ParagraphFormatInfo)_renderInfo.FormatInfo)._isStarting = false;
}
_startLeaf = _currentLeaf;
_currentVerticalInfo = CalcCurrentVerticalInfo();
_currentYPosition = area.Y + TopBorderOffset;
_formattingArea = area;
Rectangle rect = _formattingArea.GetFittingRect(_currentYPosition, _currentVerticalInfo.Height);
if (rect == null)
return false;
_currentXPosition = rect.X + LeftIndent;
if (_isFirstLine)
FormatListSymbol();
return true;
}
/// <summary>
/// Gets information necessary to render or measure the list symbol.
/// </summary>
/// <param name="symbol">The text to list symbol to render or measure</param>
/// <param name="font">The font to use for rendering or measuring.</param>
/// <returns>True, if a symbol needs to be rendered.</returns>
bool GetListSymbol(out string symbol, out XFont font)
{
font = null;
symbol = null;
ParagraphFormatInfo formatInfo = (ParagraphFormatInfo)_renderInfo.FormatInfo;
if (_phase == Phase.Formatting)
{
ParagraphFormat format = _paragraph.Format;
if (format._listInfo != null && !format._listInfo.IsNull())
{
ListInfo listInfo = format.ListInfo;
double size = format.Font.Size;
XFontStyle style = FontHandler.GetXStyle(format.Font);
switch (listInfo.ListType)
{
case ListType.BulletList1:
symbol = "·";
font = new XFont("Symbol", size, style);
break;
case ListType.BulletList2:
symbol = "o";
font = new XFont("Courier New", size, style);
break;
case ListType.BulletList3:
symbol = "§";
font = new XFont("Wingdings", size, style);
break;
case ListType.NumberList1:
symbol = _documentRenderer.NextListNumber(listInfo) + ".";
font = FontHandler.FontToXFont(format.Font, _gfx.MUH);
break;
case ListType.NumberList2:
symbol = _documentRenderer.NextListNumber(listInfo) + ")";
font = FontHandler.FontToXFont(format.Font, _gfx.MUH);
break;
case ListType.NumberList3:
symbol = NumberFormatter.Format(_documentRenderer.NextListNumber(listInfo), "alphabetic") + ")";
font = FontHandler.FontToXFont(format.Font, _gfx.MUH);
break;
}
formatInfo.ListFont = font;
formatInfo.ListSymbol = symbol;
return true;
}
}
else
{
if (formatInfo.ListFont != null && formatInfo.ListSymbol != null)
{
font = formatInfo.ListFont;
symbol = formatInfo.ListSymbol;
return true;
}
}
return false;
}
XUnit LeftIndent
{
get
{
ParagraphFormat format = _paragraph.Format;
XUnit leftIndent = format.LeftIndent.Point;
if (_isFirstLine)
{
if (format._listInfo != null && !format._listInfo.IsNull())
{
if (!format.ListInfo._numberPosition.IsNull)
return format.ListInfo.NumberPosition.Point;
if (format._firstLineIndent.IsNull)
return 0;
}
return leftIndent + _paragraph.Format.FirstLineIndent.Point;
}
return leftIndent;
}
}
XUnit RightIndent
{
get
{
return _paragraph.Format.RightIndent.Point;
}
}
/// <summary>
/// Formats the paragraph by performing line breaks etc.
/// </summary>
/// <param name="area">The area in which to render.</param>
/// <param name="previousFormatInfo">The format info that was obtained on formatting the same paragraph on a previous area.</param>
public override void Format(Area area, FormatInfo previousFormatInfo)
{
ParagraphFormatInfo formatInfo = ((ParagraphFormatInfo)_renderInfo.FormatInfo);
if (!InitFormat(area, previousFormatInfo))
{
formatInfo._isStarting = false;
return;
}
formatInfo._isEnding = true;
FormatResult lastResult = FormatResult.Continue;
while (_currentLeaf != null)
{
FormatResult result = FormatElement(_currentLeaf.Current);
switch (result)
{
case FormatResult.Ignore:
_currentLeaf = _currentLeaf.GetNextLeaf();
break;
case FormatResult.Continue:
lastResult = result;
_currentLeaf = _currentLeaf.GetNextLeaf();
break;
case FormatResult.NewLine:
lastResult = result;
StoreLineInformation();
if (!StartNewLine())
{
result = FormatResult.NewArea;
formatInfo._isEnding = false;
}
break;
}
if (result == FormatResult.NewArea)
{
lastResult = result;
formatInfo._isEnding = false;
break;
}
}
if (formatInfo.IsEnding && lastResult != FormatResult.NewLine)
StoreLineInformation();
formatInfo.ImageRenderInfos = _imageRenderInfos;
FinishLayoutInfo();
}
/// <summary>
/// Finishes the layout info by calculating starting and trailing heights.
/// </summary>
private void FinishLayoutInfo()
{
LayoutInfo layoutInfo = _renderInfo.LayoutInfo;
ParagraphFormat format = _paragraph.Format;
ParagraphFormatInfo parInfo = (ParagraphFormatInfo)_renderInfo.FormatInfo;
layoutInfo.MinWidth = _minWidth;
layoutInfo.KeepTogether = format.KeepTogether;
if (parInfo.IsComplete)
{
int limitOfLines = 1;
if (parInfo._widowControl)
limitOfLines = 3;
if (parInfo.LineCount <= limitOfLines)
layoutInfo.KeepTogether = true;
}
if (parInfo.IsStarting)
{
layoutInfo.MarginTop = format.SpaceBefore.Point;
layoutInfo.PageBreakBefore = format.PageBreakBefore;
}
else
{
layoutInfo.MarginTop = 0;
layoutInfo.PageBreakBefore = false;
}
if (parInfo.IsEnding)
{
layoutInfo.MarginBottom = _paragraph.Format.SpaceAfter.Point;
layoutInfo.KeepWithNext = _paragraph.Format.KeepWithNext;
}
else
{
layoutInfo.MarginBottom = 0;
layoutInfo.KeepWithNext = false;
}
if (parInfo.LineCount > 0)
{
XUnit startingHeight = parInfo.GetFirstLineInfo().Vertical.Height;
if (parInfo._isStarting && _paragraph.Format.WidowControl && parInfo.LineCount >= 2)
startingHeight += parInfo.GetLineInfo(1).Vertical.Height;
layoutInfo.StartingHeight = startingHeight;
XUnit trailingHeight = parInfo.GetLastLineInfo().Vertical.Height;
if (parInfo.IsEnding && _paragraph.Format.WidowControl && parInfo.LineCount >= 2)
trailingHeight += parInfo.GetLineInfo(parInfo.LineCount - 2).Vertical.Height;
layoutInfo.TrailingHeight = trailingHeight;
}
}
private XUnit PopSavedBlankWidth()
{
XUnit width = _savedBlankWidth;
_savedBlankWidth = 0;
return width;
}
private void SaveBlankWidth(XUnit blankWidth)
{
_savedBlankWidth = blankWidth;
}
XUnit _savedBlankWidth = 0;
/// <summary>
/// Processes the elements when formatting.
/// </summary>
/// <param name="docObj"></param>
/// <returns></returns>
FormatResult FormatElement(DocumentObject docObj)
{
switch (docObj.GetType().Name)
{
case "Text":
if (IsBlank(docObj))
return FormatBlank();
if (IsSoftHyphen(docObj))
return FormatSoftHyphen();
return FormatText((Text)docObj);
case "Character":
return FormatCharacter((Character)docObj);
case "DateField":
return FormatDateField((DateField)docObj);
case "InfoField":
return FormatInfoField((InfoField)docObj);
case "NumPagesField":
return FormatNumPagesField((NumPagesField)docObj);
case "PageField":
return FormatPageField((PageField)docObj);
case "SectionField":
return FormatSectionField((SectionField)docObj);
case "SectionPagesField":
return FormatSectionPagesField((SectionPagesField)docObj);
case "BookmarkField":
return FormatBookmarkField((BookmarkField)docObj);
case "PageRefField":
return FormatPageRefField((PageRefField)docObj);
case "Image":
return FormatImage((Image)docObj);
default:
return FormatResult.Continue;
}
}
FormatResult FormatImage(Image image)
{
XUnit width = CurrentImageRenderInfo.LayoutInfo.ContentArea.Width;
return FormatAsWord(width);
}
RenderInfo CalcImageRenderInfo(Image image)
{
Renderer renderer = Create(_gfx, _documentRenderer, image, _fieldInfos);
renderer.Format(new Rectangle(0, 0, double.MaxValue, double.MaxValue), null);
return renderer.RenderInfo;
}
bool IsPlainText(DocumentObject docObj)
{
if (docObj is Text)
return !IsSoftHyphen(docObj) && !IsBlank(docObj);
return false;
}
bool IsSymbol(DocumentObject docObj)
{
if (docObj is Character)
{
return !IsSpaceCharacter(docObj) && !IsTab(docObj) && !IsLineBreak(docObj);
}
return false;
}
bool IsSpaceCharacter(DocumentObject docObj)
{
Character character = docObj as Character;
if (character != null)
{
switch ((character).SymbolName)
{
case SymbolName.Blank:
case SymbolName.Em:
case SymbolName.Em4:
case SymbolName.En:
return true;
}
}
return false;
}
bool IsWordLikeElement(DocumentObject docObj)
{
if (IsPlainText(docObj))
return true;
if (IsRenderedField(docObj))
return true;
if (IsSymbol(docObj))
return true;
return false;
}
FormatResult FormatBookmarkField(BookmarkField bookmarkField)
{
_fieldInfos.AddBookmark(bookmarkField.Name);
return FormatResult.Ignore;
}
FormatResult FormatPageRefField(PageRefField pageRefField)
{
_reMeasureLine = true;
string fieldValue = GetFieldValue(pageRefField);
return FormatWord(fieldValue);
}
FormatResult FormatNumPagesField(NumPagesField numPagesField)
{
_reMeasureLine = true;
string fieldValue = GetFieldValue(numPagesField);
return FormatWord(fieldValue);
}
FormatResult FormatPageField(PageField pageField)
{
_reMeasureLine = true;
string fieldValue = GetFieldValue(pageField);
return FormatWord(fieldValue);
}
FormatResult FormatSectionField(SectionField sectionField)
{
_reMeasureLine = true;
string fieldValue = GetFieldValue(sectionField);
return FormatWord(fieldValue);
}
FormatResult FormatSectionPagesField(SectionPagesField sectionPagesField)
{
_reMeasureLine = true;
string fieldValue = GetFieldValue(sectionPagesField);
return FormatWord(fieldValue);
}
/// <summary>
/// Helper function for formatting word-like elements like text and fields.
/// </summary>
FormatResult FormatWord(string word)
{
XUnit width = MeasureString(word);
return FormatAsWord(width);
}
XUnit _savedWordWidth = 0;
/// <summary>
/// When rendering a justified paragraph, only the part after the last tab stop needs remeasuring.
/// </summary>
private bool IgnoreHorizontalGrowth
{
get
{
return _phase == Phase.Rendering && _paragraph.Format.Alignment == ParagraphAlignment.Justify &&
!_lastTabPassed;
}
}
FormatResult FormatAsWord(XUnit width)
{
VerticalLineInfo newVertInfo = CalcCurrentVerticalInfo();
Rectangle rect = _formattingArea.GetFittingRect(_currentYPosition, newVertInfo.Height + BottomBorderOffset);
if (rect == null)
return FormatResult.NewArea;
if (_currentXPosition + width <= rect.X + rect.Width - RightIndent + Tolerance)
{
_savedWordWidth = width;
_currentXPosition += width;
// For Tabs in justified context
if (!IgnoreHorizontalGrowth)
_currentWordsWidth += width;
if (_savedBlankWidth > 0)
{
// For Tabs in justified context
if (!IgnoreHorizontalGrowth)
++_currentBlankCount;
}
// For Tabs in justified context
if (!IgnoreHorizontalGrowth)
_currentLineWidth += width + PopSavedBlankWidth();
_currentVerticalInfo = newVertInfo;
_minWidth = Math.Max(_minWidth, width);
return FormatResult.Continue;
}
else
{
_savedWordWidth = width;
return FormatResult.NewLine;
}
}
FormatResult FormatDateField(DateField dateField)
{
_reMeasureLine = true;
string estimatedFieldValue = DateTime.Now.ToString(dateField.Format);
return FormatWord(estimatedFieldValue);
}
FormatResult FormatInfoField(InfoField infoField)
{
string fieldValue = GetDocumentInfo(infoField.Name);
if (fieldValue != "")
return FormatWord(fieldValue);
return FormatResult.Continue;
}
string GetDocumentInfo(string name)
{
string valueName;
switch (name.ToLower())
{
case "title":
valueName = "Title";
break;
case "author":
valueName = "Author";
break;
case "keywords":
valueName = "Keywords";
break;
case "subject":
valueName = "Subject";
break;
default:
return String.Empty;
}
return _paragraph.Document.Info.GetValue(valueName).ToString();
//string docInfoValue = "";
//string[] enumNames = Enum.GetNames(typeof(InfoFieldType));
//foreach (string enumName in enumNames)
//{
// if (String.Compare(name, enumName, true) == 0)
// {
// docInfoValue = paragraph.Document.Info.GetValue(enumName).ToString();
// break;
// }
//}
//return docInfoValue;
}
Area GetShadingArea()
{
Area contentArea = _renderInfo.LayoutInfo.ContentArea;
ParagraphFormat format = _paragraph.Format;
XUnit left = contentArea.X;
left += format.LeftIndent;
if (format.FirstLineIndent < 0)
left += format.FirstLineIndent;
XUnit top = contentArea.Y;
XUnit bottom = contentArea.Y + contentArea.Height;
XUnit right = contentArea.X + contentArea.Width;
right -= format.RightIndent;
if (_paragraph.Format._borders != null && !_paragraph.Format._borders.IsNull())
{
Borders borders = format.Borders;
BordersRenderer bordersRenderer = new BordersRenderer(borders, _gfx);
if (_renderInfo.FormatInfo.IsStarting)
top += bordersRenderer.GetWidth(BorderType.Top);
if (_renderInfo.FormatInfo.IsEnding)
bottom -= bordersRenderer.GetWidth(BorderType.Bottom);
left -= borders.DistanceFromLeft;
right += borders.DistanceFromRight;
}
return new Rectangle(left, top, right - left, bottom - top);
}
void RenderShading()
{
if (_paragraph.Format._shading == null || _paragraph.Format._shading.IsNull())
return;
ShadingRenderer shadingRenderer = new ShadingRenderer(_gfx, _paragraph.Format.Shading);
Area area = GetShadingArea();
shadingRenderer.Render(area.X, area.Y, area.Width, area.Height);
}
void RenderBorders()
{
if (_paragraph.Format._borders == null || _paragraph.Format._borders.IsNull())
return;
Area shadingArea = GetShadingArea();
XUnit left = shadingArea.X;
XUnit top = shadingArea.Y;
XUnit bottom = shadingArea.Y + shadingArea.Height;
XUnit right = shadingArea.X + shadingArea.Width;
Borders borders = _paragraph.Format.Borders;
BordersRenderer bordersRenderer = new BordersRenderer(borders, _gfx);
XUnit borderWidth = bordersRenderer.GetWidth(BorderType.Left);
if (borderWidth > 0)
{
left -= borderWidth;
bordersRenderer.RenderVertically(BorderType.Left, left, top, bottom - top);
}
borderWidth = bordersRenderer.GetWidth(BorderType.Right);
if (borderWidth > 0)
{
bordersRenderer.RenderVertically(BorderType.Right, right, top, bottom - top);
right += borderWidth;
}
borderWidth = bordersRenderer.GetWidth(BorderType.Top);
if (_renderInfo.FormatInfo.IsStarting && borderWidth > 0)
{
top -= borderWidth;
bordersRenderer.RenderHorizontally(BorderType.Top, left, top, right - left);
}
borderWidth = bordersRenderer.GetWidth(BorderType.Bottom);
if (_renderInfo.FormatInfo.IsEnding && borderWidth > 0)
{
bordersRenderer.RenderHorizontally(BorderType.Bottom, left, bottom, right - left);
}
}
XUnit MeasureString(string word)
{
XFont xFont = CurrentFont;
XUnit width = _gfx.MeasureString(word, xFont, StringFormat).Width;
Font font = CurrentDomFont;
if (font.Subscript || font.Superscript)
width *= FontHandler.GetSubSuperScaling(xFont);
return width;
}
XUnit GetSpaceWidth(Character character)
{
XUnit width = 0;
switch (character.SymbolName)
{
case SymbolName.Blank:
width = MeasureString(" ");
break;
case SymbolName.Em:
width = MeasureString("m");
break;
case SymbolName.Em4:
width = 0.25 * MeasureString("m");
break;
case SymbolName.En:
width = MeasureString("n");
break;
}
return width * character.Count;
}
void RenderListSymbol()
{
string symbol;
XFont font;
if (GetListSymbol(out symbol, out font))
{
XBrush brush = FontHandler.FontColorToXBrush(_paragraph.Format.Font);
_gfx.DrawString(symbol, font, brush, _currentXPosition, CurrentBaselinePosition);
_currentXPosition += _gfx.MeasureString(symbol, font, StringFormat).Width;
TabOffset tabOffset = NextTabOffset();
_currentXPosition += tabOffset.Offset;
_lastTabPosition = _currentXPosition;
}
}
void FormatListSymbol()
{
string symbol;
XFont font;
if (GetListSymbol(out symbol, out font))
{
_currentVerticalInfo = CalcVerticalInfo(font);
_currentXPosition += _gfx.MeasureString(symbol, font, StringFormat).Width;
FormatTab();
}
}
FormatResult FormatSpace(Character character)
{
XUnit width = GetSpaceWidth(character);
return FormatAsWord(width);
}
static string GetSymbol(Character character)
{
char ch;
switch (character.SymbolName)
{
case SymbolName.Euro:
ch = '€';
break;
case SymbolName.Copyright:
ch = '©';
break;
case SymbolName.Trademark:
ch = '™';
break;
case SymbolName.RegisteredTrademark:
ch = '®';
break;
case SymbolName.Bullet:
ch = '•';
break;
case SymbolName.Not:
ch = '¬';
break;
//REM: Non-breakable blanks are still ignored.
// case SymbolName.SymbolNonBreakableBlank:
// return "\xA0";
// break;
case SymbolName.EmDash:
ch = '—';
break;
case SymbolName.EnDash:
ch = '';
break;
default:
char c = character.Char;
char[] chars = Encoding.UTF8.GetChars(new byte[] { (byte)c });
//#if !SILVERLIGHT
// char[] chars = System.Text.Encoding.Default.GetChars(new byte[] { (byte)c });
//#else
// char[] chars = System.Text.Encoding.UTF8.GetChars(new byte[] { (byte)c });
//#endif
ch = chars[0];
break;
}
string returnString = "";
returnString += ch;
int count = character.Count;
while (--count > 0)
returnString += ch;
return returnString;
}
FormatResult FormatSymbol(Character character)
{
return FormatWord(GetSymbol(character));
}
/// <summary>
/// Processes (measures) a special character within text.
/// </summary>
/// <param name="character">The character to process.</param>
/// <returns>True if the character should start at a new line.</returns>
FormatResult FormatCharacter(Character character)
{
switch (character.SymbolName)
{
case SymbolName.Blank:
case SymbolName.Em:
case SymbolName.Em4:
case SymbolName.En:
return FormatSpace(character);
case SymbolName.LineBreak:
return FormatLineBreak();
case SymbolName.Tab:
return FormatTab();
default:
return FormatSymbol(character);
}
}
/// <summary>
/// Processes (measures) a blank.
/// </summary>
/// <returns>True if the blank causes a line break.</returns>
FormatResult FormatBlank()
{
if (IgnoreBlank())
return FormatResult.Ignore;
_savedWordWidth = 0;
XUnit width = MeasureString(" ");
VerticalLineInfo newVertInfo = CalcCurrentVerticalInfo();
Rectangle rect = _formattingArea.GetFittingRect(_currentYPosition, newVertInfo.Height + BottomBorderOffset);
if (rect == null)
return FormatResult.NewArea;
if (width + _currentXPosition <= rect.X + rect.Width + Tolerance)
{
_currentXPosition += width;
_currentVerticalInfo = newVertInfo;
SaveBlankWidth(width);
return FormatResult.Continue;
}
return FormatResult.NewLine;
}
FormatResult FormatLineBreak()
{
if (_phase != Phase.Rendering)
_currentLeaf = _currentLeaf.GetNextLeaf();
_savedWordWidth = 0;
return FormatResult.NewLine;
}
/// <summary>
/// Processes a text element during formatting.
/// </summary>
/// <param name="text">The text element to measure.</param>
FormatResult FormatText(Text text)
{
return FormatWord(text.Content);
}
FormatResult FormatSoftHyphen()
{
if (_currentLeaf.Current == _startLeaf.Current)
return FormatResult.Continue;
ParagraphIterator nextIter = _currentLeaf.GetNextLeaf();
ParagraphIterator prevIter = _currentLeaf.GetPreviousLeaf();
// nextIter can be null if the soft hyphen is at the end of a paragraph. To prevent a crash, we jump out if nextIter is null.
if (!IsWordLikeElement(prevIter.Current) || nextIter == null || !IsWordLikeElement(nextIter.Current))
return FormatResult.Continue;
//--- Save ---------------------------------
ParagraphIterator iter;
int blankCount;
XUnit xPosition;
XUnit lineWidth;
XUnit wordsWidth;
XUnit blankWidth;
SaveBeforeProbing(out iter, out blankCount, out wordsWidth, out xPosition, out lineWidth, out blankWidth);
//------------------------------------------
_currentLeaf = nextIter;
FormatResult result = FormatElement(nextIter.Current);
//--- Restore ------------------------------
RestoreAfterProbing(iter, blankCount, wordsWidth, xPosition, lineWidth, blankWidth);
//------------------------------------------
if (result == FormatResult.Continue)
return FormatResult.Continue;
RestoreAfterProbing(iter, blankCount, wordsWidth, xPosition, lineWidth, blankWidth);
Rectangle fittingRect = _formattingArea.GetFittingRect(_currentYPosition, _currentVerticalInfo.Height);
XUnit hyphenWidth = MeasureString("-");
if (xPosition + hyphenWidth <= fittingRect.X + fittingRect.Width + Tolerance
// If one word fits, but not the hyphen, the formatting must continue with the next leaf
|| prevIter.Current == _startLeaf.Current)
{
// For Tabs in justified context
if (!IgnoreHorizontalGrowth)
{
_currentWordsWidth += hyphenWidth;
_currentLineWidth += hyphenWidth;
}
_currentLeaf = nextIter;
return FormatResult.NewLine;
}
else
{
_currentWordsWidth -= _savedWordWidth;
_currentLineWidth -= _savedWordWidth;
_currentLineWidth -= GetPreviousBlankWidth(prevIter);
_currentLeaf = prevIter;
return FormatResult.NewLine;
}
}
XUnit GetPreviousBlankWidth(ParagraphIterator beforeIter)
{
XUnit width = 0;
ParagraphIterator savedIter = _currentLeaf;
_currentLeaf = beforeIter.GetPreviousLeaf();
while (_currentLeaf != null)
{
if (_currentLeaf.Current is BookmarkField)
_currentLeaf = _currentLeaf.GetPreviousLeaf();
else if (IsBlank(_currentLeaf.Current))
{
if (!IgnoreBlank())
width = CurrentWordDistance;
break;
}
else
break;
}
_currentLeaf = savedIter;
return width;
}
void HandleNonFittingLine()
{
if (_currentLeaf != null)
{
if (_savedWordWidth > 0)
{
_currentWordsWidth = _savedWordWidth;
_currentLineWidth = _savedWordWidth;
}
_currentLeaf = _currentLeaf.GetNextLeaf();
_currentYPosition += _currentVerticalInfo.Height;
_currentVerticalInfo = new VerticalLineInfo();
}
}
/// <summary>
/// Starts a new line by resetting measuring values.
/// Do not call before the first first line is formatted!
/// </summary>
/// <returns>True, if the new line may fit the formatting area.</returns>
bool StartNewLine()
{
_tabOffsets = new List<TabOffset>();
_lastTab = null;
_lastTabPosition = 0;
_currentYPosition += _currentVerticalInfo.Height;
Rectangle rect = _formattingArea.GetFittingRect(_currentYPosition, _currentVerticalInfo.Height + BottomBorderOffset);
if (rect == null)
return false;
_isFirstLine = false;
_currentXPosition = StartXPosition; // depends on "currentVerticalInfo"
_currentVerticalInfo = new VerticalLineInfo();
_currentVerticalInfo = CalcCurrentVerticalInfo();
_startLeaf = _currentLeaf;
_currentBlankCount = 0;
_currentWordsWidth = 0;
_currentLineWidth = 0;
return true;
}
/// <summary>
/// Stores all line information.
/// </summary>
void StoreLineInformation()
{
PopSavedBlankWidth();
XUnit topBorderOffset = TopBorderOffset;
Area contentArea = _renderInfo.LayoutInfo.ContentArea;
if (topBorderOffset > 0)//May only occure for the first line.
contentArea = _formattingArea.GetFittingRect(_formattingArea.Y, topBorderOffset);
if (contentArea == null)
contentArea = _formattingArea.GetFittingRect(_currentYPosition, _currentVerticalInfo.Height);
else
contentArea = contentArea.Unite(_formattingArea.GetFittingRect(_currentYPosition, _currentVerticalInfo.Height));
XUnit bottomBorderOffset = BottomBorderOffset;
if (bottomBorderOffset > 0)
contentArea = contentArea.Unite(_formattingArea.GetFittingRect(_currentYPosition + _currentVerticalInfo.Height, bottomBorderOffset));
LineInfo lineInfo = new LineInfo();
lineInfo.Vertical = _currentVerticalInfo;
if (_startLeaf != null && _startLeaf == _currentLeaf)
HandleNonFittingLine();
lineInfo.LastTab = _lastTab;
_renderInfo.LayoutInfo.ContentArea = contentArea;
lineInfo.StartIter = _startLeaf;
if (_currentLeaf == null)
lineInfo.EndIter = new ParagraphIterator(_paragraph.Elements).GetLastLeaf();
else
lineInfo.EndIter = _currentLeaf.GetPreviousLeaf();
lineInfo.BlankCount = _currentBlankCount;
lineInfo.WordsWidth = _currentWordsWidth;
lineInfo.LineWidth = _currentLineWidth;
lineInfo.TabOffsets = _tabOffsets;
lineInfo.ReMeasureLine = _reMeasureLine;
_savedWordWidth = 0;
_reMeasureLine = false;
((ParagraphFormatInfo)_renderInfo.FormatInfo).AddLineInfo(lineInfo);
}
/// <summary>
/// Gets the top border offset for the first line, else 0.
/// </summary>
XUnit TopBorderOffset
{
get
{
XUnit offset = 0;
if (_isFirstLine && _paragraph.Format._borders != null && !_paragraph.Format._borders.IsNull())
{
offset += _paragraph.Format.Borders.DistanceFromTop;
if (_paragraph.Format._borders != null && !_paragraph.Format._borders.IsNull())
{
BordersRenderer bordersRenderer = new BordersRenderer(_paragraph.Format.Borders, _gfx);
offset += bordersRenderer.GetWidth(BorderType.Top);
}
}
return offset;
}
}
bool IsLastVisibleLeaf
{
get
{
// REM: Code is missing here for blanks, bookmarks etc. which might be invisible.
if (_currentLeaf.IsLastLeaf)
return true;
return false;
}
}
/// <summary>
/// Gets the bottom border offset for the last line, else 0.
/// </summary>
XUnit BottomBorderOffset
{
get
{
XUnit offset = 0;
//while formatting, it is impossible to determine whether we are in the last line until the last visible leaf is reached.
if ((_phase == Phase.Formatting && (_currentLeaf == null || IsLastVisibleLeaf))
|| (_phase == Phase.Rendering && (_isLastLine)))
{
if (_paragraph.Format._borders != null && !_paragraph.Format._borders.IsNull())
{
offset += _paragraph.Format.Borders.DistanceFromBottom;
BordersRenderer bordersRenderer = new BordersRenderer(_paragraph.Format.Borders, _gfx);
offset += bordersRenderer.GetWidth(BorderType.Bottom);
}
}
return offset;
}
}
VerticalLineInfo CalcCurrentVerticalInfo()
{
return CalcVerticalInfo(CurrentFont);
}
VerticalLineInfo CalcVerticalInfo(XFont font)
{
ParagraphFormat paragraphFormat = _paragraph.Format;
LineSpacingRule spacingRule = paragraphFormat.LineSpacingRule;
XUnit lineHeight = 0;
XUnit descent = FontHandler.GetDescent(font);
descent = Math.Max(_currentVerticalInfo.Descent, descent);
XUnit singleLineSpace = font.GetHeight();
RenderInfo imageRenderInfo = CurrentImageRenderInfo;
if (imageRenderInfo != null)
singleLineSpace = singleLineSpace - FontHandler.GetAscent(font) + imageRenderInfo.LayoutInfo.ContentArea.Height;
XUnit inherentLineSpace = Math.Max(_currentVerticalInfo.InherentlineSpace, singleLineSpace);
switch (spacingRule)
{
case LineSpacingRule.Single:
lineHeight = singleLineSpace;
break;
case LineSpacingRule.OnePtFive:
lineHeight = 1.5 * singleLineSpace;
break;
case LineSpacingRule.Double:
lineHeight = 2.0 * singleLineSpace;
break;
case LineSpacingRule.Multiple:
lineHeight = _paragraph.Format.LineSpacing * singleLineSpace;
break;
case LineSpacingRule.AtLeast:
lineHeight = Math.Max(singleLineSpace, _paragraph.Format.LineSpacing);
break;
case LineSpacingRule.Exactly:
lineHeight = new XUnit(_paragraph.Format.LineSpacing);
inherentLineSpace = _paragraph.Format.LineSpacing.Point;
break;
}
lineHeight = Math.Max(_currentVerticalInfo.Height, lineHeight);
if (MaxElementHeight > 0)
lineHeight = Math.Min(MaxElementHeight - Tolerance, lineHeight);
return new VerticalLineInfo(lineHeight, descent, inherentLineSpace);
}
/// <summary>
/// The font used for the current paragraph element.
/// </summary>
private XFont CurrentFont
{
get { return FontHandler.FontToXFont(CurrentDomFont, /*_documentRenderer.PrivateFonts,*/ _gfx.MUH); }
}
private Font CurrentDomFont
{
get
{
if (_currentLeaf != null)
{
DocumentObject parent = DocumentRelations.GetParent(_currentLeaf.Current);
parent = DocumentRelations.GetParent(parent);
FormattedText formattedText = parent as FormattedText;
if (formattedText != null)
return formattedText.Font;
Hyperlink hyperlink = parent as Hyperlink;
if (hyperlink != null)
return hyperlink.Font;
}
return _paragraph.Format.Font;
}
}
/// <summary>
/// Help function to receive a line height on empty paragraphs.
/// </summary>
/// <param name="format">The format.</param>
/// <param name="gfx">The GFX.</param>
/// <param name="renderer">The renderer.</param>
public static XUnit GetLineHeight(ParagraphFormat format, XGraphics gfx, DocumentRenderer renderer)
{
XFont font = FontHandler.FontToXFont(format.Font, /*renderer.PrivateFonts,*/ gfx.MUH);
XUnit singleLineSpace = font.GetHeight();
switch (format.LineSpacingRule)
{
case LineSpacingRule.Exactly:
return format.LineSpacing.Point;
case LineSpacingRule.AtLeast:
return Math.Max(format.LineSpacing.Point, font.GetHeight()); // old: GetHeight(gfx));
case LineSpacingRule.Multiple:
return format.LineSpacing * format.Font.Size;
case LineSpacingRule.OnePtFive:
return 1.5 * singleLineSpace;
case LineSpacingRule.Double:
return 2.0 * singleLineSpace;
case LineSpacingRule.Single:
default:
return singleLineSpace;
}
}
void RenderUnderline(XUnit width, bool isWord)
{
XPen pen = GetUnderlinePen(isWord);
bool penChanged = UnderlinePenChanged(pen);
if (penChanged)
{
if (_currentUnderlinePen != null)
EndUnderline(_currentUnderlinePen, _currentXPosition);
if (pen != null)
StartUnderline(_currentXPosition);
_currentUnderlinePen = pen;
}
if (_currentLeaf.Current == _endLeaf.Current)
{
if (_currentUnderlinePen != null)
EndUnderline(_currentUnderlinePen, _currentXPosition + width);
_currentUnderlinePen = null;
}
}
void StartUnderline(XUnit xPosition)
{
_underlineStartPos = xPosition;
}
void EndUnderline(XPen pen, XUnit xPosition)
{
XUnit yPosition = CurrentBaselinePosition;
yPosition += 0.33 * _currentVerticalInfo.Descent;
_gfx.DrawLine(pen, _underlineStartPos, yPosition, xPosition, yPosition);
}
XPen _currentUnderlinePen;
XUnit _underlineStartPos;
bool UnderlinePenChanged(XPen pen)
{
if (pen == null && _currentUnderlinePen == null)
return false;
if (pen == null && _currentUnderlinePen != null)
return true;
if (pen != null && _currentUnderlinePen == null)
return true;
if (pen != null && pen.Color != _currentUnderlinePen.Color)
return true;
return pen.Width != _currentUnderlinePen.Width;
}
RenderInfo CurrentImageRenderInfo
{
get
{
if (_currentLeaf != null && _currentLeaf.Current is Image)
{
Image image = (Image)_currentLeaf.Current;
if (_imageRenderInfos != null && _imageRenderInfos.ContainsKey(image))
return _imageRenderInfos[image];
else
{
if (_imageRenderInfos == null)
_imageRenderInfos = new Dictionary<Image, RenderInfo>();
RenderInfo renderInfo = CalcImageRenderInfo(image);
_imageRenderInfos.Add(image, renderInfo);
return renderInfo;
}
}
return null;
}
}
XPen GetUnderlinePen(bool isWord)
{
Font font = CurrentDomFont;
Underline underlineType = font.Underline;
if (underlineType == Underline.None)
return null;
if (underlineType == Underline.Words && !isWord)
return null;
#if noCMYK
XPen pen = new XPen(XColor.FromArgb(font.Color.Argb), font.Size / 16);
#else
XPen pen = new XPen(ColorHelper.ToXColor(font.Color, _paragraph.Document.UseCmykColor), font.Size / 16);
#endif
switch (font.Underline)
{
case Underline.DotDash:
pen.DashStyle = XDashStyle.DashDot;
break;
case Underline.DotDotDash:
pen.DashStyle = XDashStyle.DashDotDot;
break;
case Underline.Dash:
pen.DashStyle = XDashStyle.Dash;
break;
case Underline.Dotted:
pen.DashStyle = XDashStyle.Dot;
break;
case Underline.Single:
default:
pen.DashStyle = XDashStyle.Solid;
break;
}
return pen;
}
private static XStringFormat StringFormat
{
get { return _stringFormat ?? (_stringFormat = XStringFormats.Default); }
}
/// <summary>
/// The paragraph to format or render.
/// </summary>
readonly Paragraph _paragraph;
XUnit _currentWordsWidth;
int _currentBlankCount;
XUnit _currentLineWidth;
bool _isFirstLine;
bool _isLastLine;
VerticalLineInfo _currentVerticalInfo;
Area _formattingArea;
XUnit _currentYPosition;
XUnit _currentXPosition;
ParagraphIterator _currentLeaf;
ParagraphIterator _startLeaf;
ParagraphIterator _endLeaf;
static XStringFormat _stringFormat;
bool _reMeasureLine;
XUnit _minWidth = 0;
Dictionary<Image, RenderInfo> _imageRenderInfos;
List<TabOffset> _tabOffsets;
DocumentObject _lastTab;
bool _lastTabPassed;
XUnit _lastTabPosition;
}
}