#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 description for ParagraphRenderer. /// public class ParagraphRenderer : Renderer { /// /// Process phases of the renderer. /// private enum Phase { Formatting, Rendering } /// /// Results that can occur when processing a paragraph element /// during formatting. /// private enum FormatResult { /// /// Ignore the current element during formatting. /// Ignore, /// /// Continue with the next element within the same line. /// Continue, /// /// Start a new line from the current object on. /// NewLine, /// /// Break formatting and continue in a new area (e.g. a new page). /// NewArea } Phase _phase; /// /// Initializes a ParagraphRenderer object for formatting. /// /// The XGraphics object to do measurements on. /// The paragraph to format. /// The field infos. 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; } /// /// Initializes a ParagraphRenderer object for rendering. /// /// The XGraphics object to render on. /// The render info object containing information necessary for rendering. /// The field infos. public ParagraphRenderer(XGraphics gfx, RenderInfo renderInfo, FieldInfos fieldInfos) : base(gfx, renderInfo, fieldInfos) { _paragraph = (Paragraph)renderInfo.DocumentObject; } /// /// Renders the paragraph. /// 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; } /// /// Gets a layout info with only margin and break information set. /// It can be taken before the paragraph is formatted. /// /// /// The following layout information is set properly:
/// MarginTop, MarginLeft, MarginRight, MarginBottom, KeepTogether, KeepWithNext, PagebreakBefore. ///
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; } } /// /// Adjusts the current x position to the given tab stop if possible. /// /// True, if the text doesn't fit the line any more and the tab causes a line break. 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; } /// /// Probes the paragraph elements after a left aligned tab stop and returns the vertical text position to start at. /// /// Position of the tab to probe. /// Out parameter determining whether the tab causes a line break. /// The new x-position to restart behind the tab. 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; } /// /// Probes the paragraph elements after a right aligned tab stop and returns the vertical text position to start at. /// /// Position of the tab to probe. /// Out parameter determining whether the tab causes a line break. /// The new x-position to restart behind the tab. 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; } /// /// Probes the paragraph elements after a right aligned tab stop and returns the vertical text position to start at. /// /// Position of the tab to probe. /// Out parameter determining whether the tab causes a line break. /// The new x-position to restart behind the tab. 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; } /// /// Probes the paragraph elements after a right aligned tab stop and returns the vertical text position to start at. /// /// Position of the tab to probe. /// Out parameter determining whether the tab causes a line break. /// The new x-position to restart behind the tab. 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; } /// /// Probes the paragraph after a tab. /// Caution: This Function resets the word count and line width before doing its work. /// /// True if the tab causes a linebreak. 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; } /// /// Gets the next tab stop following the current x position. /// /// The searched tab stop. 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; } /// /// Gets the horizontal position to start a new line. /// /// The position to start the line. 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; } } /// /// Renders a single line. /// /// 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(); _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; } /// /// Initializes this instance for formatting. /// /// The area for formatting /// A previous format info. /// False, if nothing of the paragraph will fit the area any more. private bool InitFormat(Area area, FormatInfo previousFormatInfo) { _phase = Phase.Formatting; _tabOffsets = new List(); 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; } /// /// Gets information necessary to render or measure the list symbol. /// /// The text to list symbol to render or measure /// The font to use for rendering or measuring. /// True, if a symbol needs to be rendered. 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; } } /// /// Formats the paragraph by performing line breaks etc. /// /// The area in which to render. /// The format info that was obtained on formatting the same paragraph on a previous area. 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(); } /// /// Finishes the layout info by calculating starting and trailing heights. /// 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; /// /// Processes the elements when formatting. /// /// /// 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); } /// /// Helper function for formatting word-like elements like text and fields. /// FormatResult FormatWord(string word) { XUnit width = MeasureString(word); return FormatAsWord(width); } XUnit _savedWordWidth = 0; /// /// When rendering a justified paragraph, only the part after the last tab stop needs remeasuring. /// 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)); } /// /// Processes (measures) a special character within text. /// /// The character to process. /// True if the character should start at a new line. 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); } } /// /// Processes (measures) a blank. /// /// True if the blank causes a line break. 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; } /// /// Processes a text element during formatting. /// /// The text element to measure. 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(); } } /// /// Starts a new line by resetting measuring values. /// Do not call before the first first line is formatted! /// /// True, if the new line may fit the formatting area. bool StartNewLine() { _tabOffsets = new List(); _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; } /// /// Stores all line information. /// 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); } /// /// Gets the top border offset for the first line, else 0. /// 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; } } /// /// Gets the bottom border offset for the last line, else 0. /// 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); } /// /// The font used for the current paragraph element. /// 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; } } /// /// Help function to receive a line height on empty paragraphs. /// /// The format. /// The GFX. /// The renderer. 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(); 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); } } /// /// The paragraph to format or render. /// 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 _imageRenderInfos; List _tabOffsets; DocumentObject _lastTab; bool _lastTabPassed; XUnit _lastTabPosition; } }