ASCU_ALL/PrintPDF/MigraDoc.Rendering/Rendering/ParagraphRenderer.cs
2021-05-25 17:00:45 +05:00

2546 lines
91 KiB
C#
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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