//------------------------------------------------------------------- // // Constructors // //------------------------------------------------------------------- #region Constructors /// <summary> /// Constructor. /// </summary> /// <param name="host"> /// TextFormatter host /// </param> /// <param name="cpPara"> /// Character position of paragraph /// </param> /// <param name="durTrack"> /// Track width /// </param> /// <param name="paraClient"> /// Owner of the line /// </param> /// <param name="runCache"> /// Text run cache /// </param> internal OptimalTextSource(TextFormatterHost host, int cpPara, int durTrack, TextParaClient paraClient, TextRunCache runCache) : base(paraClient) { _host = host; _durTrack = durTrack; _runCache = runCache; _cpPara = cpPara; }
/// <summary> /// Client to format a text line that fills a paragraph in the document. /// </summary> /// <param name="textSource">an object representing text layout clients text source for TextFormatter.</param> /// <param name="firstCharIndex">character index to specify where in the source text the line starts</param> /// <param name="paragraphWidth">width of paragraph in which the line fills</param> /// <param name="paragraphProperties">properties that can change from one paragraph to the next, such as text flow direction, text alignment, or indentation.</param> /// <param name="previousLineBreak">text formatting state at the point where the previous line in the paragraph /// was broken by the text formatting process, as specified by the TextLine.LineBreak property for the previous /// line; this parameter can be null, and will always be null for the first line in a paragraph.</param> /// <param name="textRunCache">an object representing content cache of the client.</param> /// <returns>object representing a line of text that client interacts with. </returns> public abstract TextLine FormatLine( TextSource textSource, int firstCharIndex, double paragraphWidth, TextParagraphProperties paragraphProperties, TextLineBreak previousLineBreak, TextRunCache textRunCache );
[FriendAccessAllowed] // used by Framework internal abstract TextParagraphCache CreateParagraphCache( #endif TextSource textSource, int firstCharIndex, double paragraphWidth, TextParagraphProperties paragraphProperties, TextLineBreak previousLineBreak, TextRunCache textRunCache );
public abstract MinMaxParagraphWidth FormatMinMaxParagraphWidth(TextSource textSource, int firstCharIndex, TextParagraphProperties paragraphProperties, TextRunCache textRunCache);
//------------------------------------------------------------------- // // Internal Methods // //------------------------------------------------------------------- #region Internal Methods /// <summary> /// Create and format text line. /// </summary> /// <param name="dcp">First character position for the line.</param> /// <param name="formatWidth">Width to pass to LS formatter.</param> /// <param name="paragraphWidth">Line wrapping width.</param> /// <param name="lineProperties">Line's properties.</param> /// <param name="textRunCache">Run cache.</param> /// <param name="formatter">Text formatter.</param> /// <remarks> /// formatWidth/paragraphWidth is an attempt to work around bug 114719. /// Unfortunately, Line Services cannot guarantee that once a line /// has been measured, measuring the same content with the actual line /// width will produce the same line. /// /// For example, suppose we format dcp 0 with paragraphWidth = 100. /// Suppose this results in a line from dcp 0 - 10, with width = 95. /// /// We would expect that a call to FormatLine with dcp = 0, /// paragraphWidth = 95 would result in the same 10 char line. /// But in practice it might return a 9 char line. /// /// The workaround is to pass in an explicit formatting width across /// multiple calls, even if the paragraphWidth changes. /// </remarks> internal void Format(int dcp, double formatWidth, double paragraphWidth, LineProperties lineProperties, TextRunCache textRunCache, TextFormatter formatter) { _lineProperties = lineProperties; _dcp = dcp; _paragraphWidth = paragraphWidth; // We must ignore TextAlignment here since formatWidth does not // necessarilly equal paragraphWidth. We'll adjust on later calls. lineProperties.IgnoreTextAlignment = (lineProperties.TextAlignment != TextAlignment.Justify); try { _line = formatter.FormatLine(this, dcp, formatWidth, lineProperties, null, textRunCache); } finally { lineProperties.IgnoreTextAlignment = false; } }
/// <summary> /// Format and produce a text line either with or without previously known /// line break point. /// </summary> private TextLine FormatLineInternal( TextSource textSource, int firstCharIndex, int lineLength, double paragraphWidth, TextParagraphProperties paragraphProperties, TextLineBreak previousLineBreak, TextRunCache textRunCache ) { EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordText, EventTrace.Level.Verbose, EventTrace.Event.WClientStringBegin, "TextFormatterImp.FormatLineInternal Start"); // prepare formatting settings FormatSettings settings = PrepareFormatSettings( textSource, firstCharIndex, paragraphWidth, paragraphProperties, previousLineBreak, textRunCache, (lineLength != 0), // Do optimal break if break is given true, // isSingleLineFormatting _textFormattingMode ); TextLine textLine = null; if ( !settings.Pap.AlwaysCollapsible && previousLineBreak == null && lineLength <= 0 ) { // simple text line. textLine = SimpleTextLine.Create( settings, firstCharIndex, RealToIdealFloor(paragraphWidth) ) as TextLine; } if (textLine == null) { // content is complex, creating complex line textLine = new TextMetrics.FullTextLine( settings, firstCharIndex, lineLength, RealToIdealFloor(paragraphWidth), LineFlags.None ) as TextLine; } EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordText, EventTrace.Level.Verbose, EventTrace.Event.WClientStringEnd, "TextFormatterImp.FormatLineInternal End"); return textLine; }
/// <summary> /// Finds and returns the position after backspace at the edge of a caret unit in /// specified direction. /// </summary> /// <param name="position"> /// Initial text position of an object/character. /// </param> /// <param name="dcp"> /// Offset of the current position from start of TextContainer /// </param> /// <param name="lineIndex"> /// Index of line in which position is found /// </param> internal ITextPointer GetBackspaceCaretUnitPosition(ITextPointer position, int dcp, int lineIndex) { Invariant.Assert(IsLayoutDataValid); // Line props may be invalid, even if Measure/Arrange is valid - rendering only props are changing. LineProperties lineProperties = GetLineProperties(); EnsureComplexContent(); // Get character index for position int characterIndex = _complexContent.TextContainer.Start.GetOffsetToPosition(position); // Process special cases if (characterIndex == dcp) { if (lineIndex == 0) { // Cannot go back any further return position; } else { // Change lineIndex and dcp to previous line Debug.Assert(lineIndex > 0); --lineIndex; dcp -= GetLine(lineIndex).Length; Debug.Assert(dcp >= 0); } } double wrappingWidth = CalcWrappingWidth(RenderSize.Width); CharacterHit textSourceCharacterIndex = new CharacterHit(characterIndex, 0); CharacterHit backspaceCharacterHit; LineMetrics lineMetrics = GetLine(lineIndex); TextRunCache textRunCache = new TextRunCache(); // Create and Format line using(Line line = CreateLine(lineProperties)) { // Format line. Set showParagraphEllipsis flag to false since we are not using information about // ellipsis to change line offsets in this case. line.Format(dcp, wrappingWidth, GetLineProperties(lineIndex == 0, lineProperties), lineMetrics.TextLineBreak, textRunCache, false); // Check consistency of line formatting MS.Internal.Invariant.Assert(lineMetrics.Length == line.Length, "Line length is out of [....]"); backspaceCharacterHit = line.GetBackspaceCaretCharacterHit(textSourceCharacterIndex); } // Get CharacterHit and call line API // Determine logical direction for next caret index and create TextPointer from it LogicalDirection logicalDirection; if (backspaceCharacterHit.FirstCharacterIndex + backspaceCharacterHit.TrailingLength == dcp) { // Going forward brought us to the start of a line, context must be backward for previous line if (dcp == 0) { // First line, so we will stay forward logicalDirection = LogicalDirection.Forward; } else { logicalDirection = LogicalDirection.Backward; } } else { logicalDirection = (backspaceCharacterHit.TrailingLength > 0) ? LogicalDirection.Backward : LogicalDirection.Forward; } ITextPointer backspaceCaretPosition = _complexContent.TextContainer.Start.CreatePointer(backspaceCharacterHit.FirstCharacterIndex + backspaceCharacterHit.TrailingLength, logicalDirection); // Return backspaceCaretPosition return backspaceCaretPosition; }
/// <summary> /// Implementation of TextParagraphView.GetTightBoundingGeometryFromTextPositions. /// <seealso cref="TextParagraphView.GetTightBoundingGeometryFromTextPositions"/> /// </summary> internal Geometry GetTightBoundingGeometryFromTextPositions(ITextPointer startPosition, ITextPointer endPosition) { #if TEXTPANELLAYOUTDEBUG MS.Internal.PtsHost.TextPanelDebug.StartTimer("TextBlock.GetTightBoundingGeometryFromTextPositions", MS.Internal.PtsHost.TextPanelDebug.Category.TextView); #endif Invariant.Assert(IsLayoutDataValid); Invariant.Assert(startPosition != null); Invariant.Assert(endPosition != null); Invariant.Assert(startPosition.CompareTo(endPosition) <= 0); Geometry geometry = null; // Line props may be invalid, even if Measure/Arrange is valid - rendering only props are changing. LineProperties lineProperties = GetLineProperties(); EnsureComplexContent(); // TextOM access requires complex content. int dcpPositionStart = _complexContent.TextContainer.Start.GetOffsetToPosition(startPosition); int dcpPositionEnd = _complexContent.TextContainer.Start.GetOffsetToPosition(endPosition); double wrappingWidth = CalcWrappingWidth(RenderSize.Width); Vector contentOffset = CalcContentOffset(RenderSize, wrappingWidth); TextRunCache textRunCache = new TextRunCache(); Line line = CreateLine(lineProperties); int dcpLineStart = 0; ITextPointer endOfLineTextPointer = _complexContent.TextContainer.Start.CreatePointer(0); double lineOffset = 0; int lineCount = LineCount; for (int i = 0, count = lineCount; i < count; ++i) { LineMetrics lineMetrics = GetLine(i); if (dcpPositionEnd <= dcpLineStart) { // this line starts after the range's end. // safe to break from the loop. break; } int dcpLineEnd = dcpLineStart + lineMetrics.Length; endOfLineTextPointer.MoveByOffset(lineMetrics.Length); if (dcpPositionStart < dcpLineEnd) { using (line) { bool ellipsis = ParagraphEllipsisShownOnLine(i, lineOffset); line.Format(dcpLineStart, wrappingWidth, GetLineProperties(dcpLineStart == 0, lineProperties), lineMetrics.TextLineBreak, textRunCache, ellipsis); if (Invariant.Strict) { // Check consistency of line formatting MS.Internal.Invariant.Assert(GetLine(i).Length == line.Length, "Line length is out of [....]"); } int dcpStart = Math.Max(dcpLineStart, dcpPositionStart); int dcpEnd = Math.Min(dcpLineEnd, dcpPositionEnd); if (dcpStart != dcpEnd) { IList<Rect> aryTextBounds = line.GetRangeBounds(dcpStart, dcpEnd - dcpStart, contentOffset.X, contentOffset.Y + lineOffset); if (aryTextBounds.Count > 0) { int j = 0; int c = aryTextBounds.Count; do { Rect rect = aryTextBounds[j]; if (j == (c - 1) && dcpPositionEnd >= dcpLineEnd && TextPointerBase.IsNextToAnyBreak(endOfLineTextPointer, LogicalDirection.Backward)) { double endOfParaGlyphWidth = FontSize * CaretElement.c_endOfParaMagicMultiplier; rect.Width = rect.Width + endOfParaGlyphWidth; } RectangleGeometry rectGeometry = new RectangleGeometry(rect); CaretElement.AddGeometry(ref geometry, rectGeometry); } while (++j < c); } } } } dcpLineStart += lineMetrics.Length; lineOffset += lineMetrics.Height; } #if TEXTPANELLAYOUTDEBUG MS.Internal.PtsHost.TextPanelDebug.StopTimer("TextBlock.GetTightBoundingGeometryFromTextPositions", MS.Internal.PtsHost.TextPanelDebug.Category.TextView); #endif return (geometry); }
/// <summary> /// Retrieve text position from the distance (relative to the beginning /// of specified line). /// </summary> /// <param name="dcp">Index of the first character in the line.</param> /// <param name="distance">Distance relative to the beginning of the line.</param> /// <param name="lineVOffset"> /// Vertical offset of the line in which the position lies, /// </param> /// <param name="index"> /// Index of the line /// </param> /// <returns> /// A text position and its orientation matching or closest to the distance. /// </returns> internal ITextPointer GetTextPositionFromDistance(int dcp, double distance, double lineVOffset, int index) { #if TEXTPANELLAYOUTDEBUG MS.Internal.PtsHost.TextPanelDebug.StartTimer("TextBlock.GetTextPositionFromDistance", MS.Internal.PtsHost.TextPanelDebug.Category.TextView); #endif Invariant.Assert(IsLayoutDataValid); // Line props may be invalid, even if Measure/Arrange is valid - rendering only props are changing. LineProperties lineProperties = GetLineProperties(); EnsureComplexContent(); // TextOM access requires complex content. double wrappingWidth = CalcWrappingWidth(RenderSize.Width); Vector contentOffset = CalcContentOffset(RenderSize, wrappingWidth); distance -= contentOffset.X; lineVOffset -= contentOffset.Y; TextRunCache textRunCache = new TextRunCache(); ITextPointer pos; using(Line line = CreateLine(lineProperties)) { MS.Internal.Invariant.Assert(index >= 0 && index < LineCount); TextLineBreak textLineBreak = GetLine(index).TextLineBreak; bool ellipsis = ParagraphEllipsisShownOnLine(index, lineVOffset); line.Format(dcp, wrappingWidth, GetLineProperties(dcp == 0, lineProperties), textLineBreak, textRunCache, ellipsis); MS.Internal.Invariant.Assert(GetLine(index).Length == line.Length, "Line length is out of [....]"); CharacterHit charIndex = line.GetTextPositionFromDistance(distance); LogicalDirection logicalDirection; logicalDirection = (charIndex.TrailingLength > 0) ? LogicalDirection.Backward : LogicalDirection.Forward; pos = _complexContent.TextContainer.Start.CreatePointer(charIndex.FirstCharacterIndex + charIndex.TrailingLength, logicalDirection); } #if TEXTPANELLAYOUTDEBUG MS.Internal.PtsHost.TextPanelDebug.StopTimer("TextBlock.GetTextPositionFromDistance", MS.Internal.PtsHost.TextPanelDebug.Category.TextView); #endif return pos; }
/// <summary> /// Returns an ICollection of bounding rectangles for the given ContentElement /// </summary> /// <param name="child"> /// Content element for which rectangles are required /// </param> /// <remarks> /// Looks at the ContentElement e line by line and gets rectangle bounds for each line /// </remarks> protected virtual ReadOnlyCollection<Rect> GetRectanglesCore(ContentElement child) { if (child == null) { throw new ArgumentNullException("child"); } // If layout data is not updated we assume that we will not be able to find the element we need and throw excception if (!IsLayoutDataValid) { // return empty collection return new ReadOnlyCollection<Rect>(new List<Rect>(0)); } // Line props may be invalid, even if Measure/Arrange is valid - rendering only props are changing. LineProperties lineProperties = GetLineProperties(); // Check for complex content if (_complexContent == null || !(_complexContent.TextContainer is TextContainer)) { // return empty collection return new ReadOnlyCollection<Rect>(new List<Rect>(0)); } // First find the element start and end position TextPointer start = FindElementPosition((IInputElement)child); if (start == null) { return new ReadOnlyCollection<Rect>(new List<Rect>(0)); } TextPointer end = null; if (child is TextElement) { end = new TextPointer(((TextElement)child).ElementEnd); } else if (child is FrameworkContentElement) { end = new TextPointer(start); end.MoveByOffset(+1); } if (end == null) { return new ReadOnlyCollection<Rect>(new List<Rect>(0)); } int startOffset = _complexContent.TextContainer.Start.GetOffsetToPosition(start); int endOffset = _complexContent.TextContainer.Start.GetOffsetToPosition(end); int lineIndex = 0; int lineOffset = 0; double lineHeightOffset = 0; int lineCount = LineCount; while (startOffset >= (lineOffset + GetLine(lineIndex).Length) && lineIndex < lineCount) { Debug.Assert(lineCount == LineCount); lineOffset += GetLine(lineIndex).Length; lineIndex++; lineHeightOffset += GetLine(lineIndex).Height; } Debug.Assert(lineIndex < lineCount); int lineStart = lineOffset; List<Rect> rectangles = new List<Rect>(); double wrappingWidth = CalcWrappingWidth(RenderSize.Width); TextRunCache textRunCache = new TextRunCache(); Vector contentOffset = CalcContentOffset(RenderSize, wrappingWidth); do { Debug.Assert(lineCount == LineCount); // Check that line index never exceeds line count Debug.Assert(lineIndex < lineCount); // Create lines as long as they are spanned by the element LineMetrics lineMetrics = GetLine(lineIndex); Line line = CreateLine(lineProperties); using (line) { // Check if paragraph ellipsis are rendered bool ellipsis = ParagraphEllipsisShownOnLine(lineIndex, lineOffset); line.Format(lineStart, wrappingWidth, GetLineProperties(lineIndex == 0, lineProperties), lineMetrics.TextLineBreak, textRunCache, ellipsis); // Verify consistency of line formatting // if (lineMetrics.Length == line.Length) { //MS.Internal.Invariant.Assert(lineMetrics.Length == line.Length, "Line length is out of [....]"); //Debug.Assert(DoubleUtil.AreClose(CalcLineAdvance(line.Height, lineProperties), lineMetrics.Height), "Line height is out of [....]."); int boundStart = (startOffset >= lineStart) ? startOffset : lineStart; int boundEnd = (endOffset < lineStart + lineMetrics.Length) ? endOffset : lineStart + lineMetrics.Length; double xOffset = contentOffset.X; double yOffset = contentOffset.Y + lineHeightOffset; List<Rect> lineBounds = line.GetRangeBounds(boundStart, boundEnd - boundStart, xOffset, yOffset); Debug.Assert(lineBounds.Count > 0); rectangles.AddRange(lineBounds); } } lineStart += lineMetrics.Length; lineHeightOffset += lineMetrics.Height; lineIndex++; } while (endOffset > lineStart); // Rectangles collection must be non-null Invariant.Assert(rectangles != null); return new ReadOnlyCollection<Rect>(rectangles); }
internal FormattingContext(bool measureMode, bool clearOnLeft, bool clearOnRight, TextRunCache textRunCache) { MeasureMode = measureMode; ClearOnLeft = clearOnLeft; ClearOnRight = clearOnRight; TextRunCache = textRunCache; LineFormatLengthTarget = -1; }
/// <summary> /// Verify all text formatting arguments /// </summary> private void VerifyTextFormattingArguments( TextSource textSource, int firstCharIndex, double paragraphWidth, TextParagraphProperties paragraphProperties, TextRunCache textRunCache ) { if (textSource == null) throw new ArgumentNullException("textSource"); if (textRunCache == null) throw new ArgumentNullException("textRunCache"); if (paragraphProperties == null) throw new ArgumentNullException("paragraphProperties"); if (paragraphProperties.DefaultTextRunProperties == null) throw new ArgumentNullException("paragraphProperties.DefaultTextRunProperties"); if (paragraphProperties.DefaultTextRunProperties.Typeface == null) throw new ArgumentNullException("paragraphProperties.DefaultTextRunProperties.Typeface"); if (DoubleUtil.IsNaN(paragraphWidth)) throw new ArgumentOutOfRangeException("paragraphWidth", SR.Get(SRID.ParameterValueCannotBeNaN)); if (double.IsInfinity(paragraphWidth)) throw new ArgumentOutOfRangeException("paragraphWidth", SR.Get(SRID.ParameterValueCannotBeInfinity)); if ( paragraphWidth < 0 || paragraphWidth > Constants.RealInfiniteWidth) { throw new ArgumentOutOfRangeException("paragraphWidth", SR.Get(SRID.ParameterMustBeBetween, 0, Constants.RealInfiniteWidth)); } double realMaxFontRenderingEmSize = Constants.RealInfiniteWidth / Constants.GreatestMutiplierOfEm; if ( paragraphProperties.DefaultTextRunProperties.FontRenderingEmSize < 0 || paragraphProperties.DefaultTextRunProperties.FontRenderingEmSize > realMaxFontRenderingEmSize) { throw new ArgumentOutOfRangeException("paragraphProperties.DefaultTextRunProperties.FontRenderingEmSize", SR.Get(SRID.ParameterMustBeBetween, 0, realMaxFontRenderingEmSize)); } if (paragraphProperties.Indent > Constants.RealInfiniteWidth) throw new ArgumentOutOfRangeException("paragraphProperties.Indent", SR.Get(SRID.ParameterCannotBeGreaterThan, Constants.RealInfiniteWidth)); if (paragraphProperties.LineHeight > Constants.RealInfiniteWidth) throw new ArgumentOutOfRangeException("paragraphProperties.LineHeight", SR.Get(SRID.ParameterCannotBeGreaterThan, Constants.RealInfiniteWidth)); if ( paragraphProperties.DefaultIncrementalTab < 0 || paragraphProperties.DefaultIncrementalTab > Constants.RealInfiniteWidth) { throw new ArgumentOutOfRangeException("paragraphProperties.DefaultIncrementalTab", SR.Get(SRID.ParameterMustBeBetween, 0, Constants.RealInfiniteWidth)); } }
/// <summary> /// Validate all the relevant text formatting initial settings and package them /// </summary> private FormatSettings PrepareFormatSettings( TextSource textSource, int firstCharIndex, double paragraphWidth, TextParagraphProperties paragraphProperties, TextLineBreak previousLineBreak, TextRunCache textRunCache, bool useOptimalBreak, bool isSingleLineFormatting, TextFormattingMode textFormattingMode ) { VerifyTextFormattingArguments( textSource, firstCharIndex, paragraphWidth, paragraphProperties, textRunCache ); if (textRunCache.Imp == null) { // No run cache object available, create one textRunCache.Imp = new TextRunCacheImp(); } // initialize formatting settings return new FormatSettings( this, textSource, textRunCache.Imp, new ParaProp(this, paragraphProperties, useOptimalBreak), previousLineBreak, isSingleLineFormatting, textFormattingMode, false ); }
internal override TextParagraphCache CreateParagraphCache( #endif TextSource textSource, int firstCharIndex, double paragraphWidth, TextParagraphProperties paragraphProperties, TextLineBreak previousLineBreak, TextRunCache textRunCache ) { // prepare formatting settings FormatSettings settings = PrepareFormatSettings( textSource, firstCharIndex, paragraphWidth, paragraphProperties, previousLineBreak, textRunCache, true, // optimalBreak false, // !isSingleLineFormatting _textFormattingMode ); // // Optimal paragraph formatting session specific check // if (!settings.Pap.Wrap && settings.Pap.OptimalBreak) { // Optimal paragraph must wrap. throw new ArgumentException(SR.Get(SRID.OptimalParagraphMustWrap)); } // create paragraph content cache object return new TextParagraphCache( settings, firstCharIndex, RealToIdeal(paragraphWidth) ); }
/// <summary> /// Client to ask for the possible smallest and largest paragraph width that can fully contain the passing text content /// </summary> /// <param name="textSource">an object representing text layout clients text source for TextFormatter.</param> /// <param name="firstCharIndex">character index to specify where in the source text the line starts</param> /// <param name="paragraphProperties">properties that can change from one paragraph to the next, such as text flow direction, text alignment, or indentation.</param> /// <param name="textRunCache">an object representing content cache of the client.</param> /// <returns>min max paragraph width</returns> public override MinMaxParagraphWidth FormatMinMaxParagraphWidth( TextSource textSource, int firstCharIndex, TextParagraphProperties paragraphProperties, TextRunCache textRunCache ) { // prepare formatting settings FormatSettings settings = PrepareFormatSettings( textSource, firstCharIndex, 0, // infinite paragraphWidth paragraphProperties, null, // always format the whole paragraph - no previousLineBreak textRunCache, false, // optimalBreak true, // isSingleLineFormatting _textFormattingMode ); // create specialized line specifically for min/max calculation TextMetrics.FullTextLine line = new TextMetrics.FullTextLine( settings, firstCharIndex, 0, // lineLength 0, // paragraph width has no significant meaning in min/max calculation (LineFlags.KeepState | LineFlags.MinMax) ); // line width in this case is the width of a line when the entire paragraph is laid out // as a single long line. MinMaxParagraphWidth minMax = new MinMaxParagraphWidth(line.MinWidth, line.Width); line.Dispose(); return minMax; }
// ------------------------------------------------------------------ // Create and format text line. // // lineStartIndex - index of the first character in the line // width - wrapping width of the line // lineProperties - properties of the line // textRunCache - run cache used by text formatter // showParagraphEllipsis - true if paragraph ellipsis is shown // at the end of the line // ------------------------------------------------------------------ internal void Format(int dcp, double width, TextParagraphProperties lineProperties, TextLineBreak textLineBreak, TextRunCache textRunCache, bool showParagraphEllipsis) { #if TEXTPANELLAYOUTDEBUG TextPanelDebug.IncrementCounter("Line.Format", TextPanelDebug.Category.TextView); #endif _mirror = (lineProperties.FlowDirection == FlowDirection.RightToLeft); _dcp = dcp; _showParagraphEllipsis = showParagraphEllipsis; _wrappingWidth = width; _line = _owner.TextFormatter.FormatLine(this, dcp, width, lineProperties, textLineBreak, textRunCache); }
/// <summary> /// Hit tests to the correct ContentElement within the ContentHost /// that the mouse is over. /// </summary> /// <param name="point">Mouse coordinates relative to the ContentHost.</param> protected virtual IInputElement InputHitTestCore(Point point) { // If layout data is not updated return 'this'. if (!IsLayoutDataValid) { return this; } // Line props may be invalid, even if Measure/Arrange is valid - rendering only props are changing. LineProperties lineProperties = GetLineProperties(); // If there is only one line and it is already cached, use it to do hit-testing. // Otherwise, do following: // a) use cached line information to find which line has been hit, // b) re-create the line that has been hit, // c) hit-test the line. IInputElement ie = null; double wrappingWidth = CalcWrappingWidth(RenderSize.Width); Vector contentOffset = CalcContentOffset(RenderSize, wrappingWidth); point -= contentOffset; // // Take into account content offset. if (point.X < 0 || point.Y < 0) return this; ie = null; int dcp = 0; double lineOffset = 0; TextRunCache textRunCache = new TextRunCache(); int lineCount = LineCount; for (int i = 0; i < lineCount; i++) { Debug.Assert(lineCount == LineCount); LineMetrics lineMetrics = GetLine(i); if (lineOffset + lineMetrics.Height > point.Y) { // The current line has been hit. Format the line and // retrieve IInputElement from the hit position. Line line = CreateLine(lineProperties); using (line) { // Check if paragraph ellipsis are rendered bool ellipsis = ParagraphEllipsisShownOnLine(i, lineOffset); line.Format(dcp, wrappingWidth, GetLineProperties(dcp == 0, lineProperties), lineMetrics.TextLineBreak, textRunCache, ellipsis); // Verify consistency of line formatting // Check that lineMetrics.Length is in [....] with line.Length // // if ((line.Start <= point.X) && (line.Start + line.Width >= point.X)) { ie = line.InputHitTest(point.X); } } break; // Line covering the point has been found; no need to continue. } dcp += lineMetrics.Length; lineOffset += lineMetrics.Height; } // If nothing has been hit, assume that element itself has been hit. return (ie != null) ? ie : this; }
/// <summary> /// Retrieves detailed information about a line of text. /// </summary> /// <param name="dcp">Index of the first character in the line.</param> /// <param name="index"> Index of the line</param> /// <param name="lineVOffset"> Vertical offset of the line</param> /// <param name="cchContent">Number of content characters in the line.</param> /// <param name="cchEllipses">Number of content characters hidden by ellipses.</param> internal void GetLineDetails(int dcp, int index, double lineVOffset, out int cchContent, out int cchEllipses) { Invariant.Assert(IsLayoutDataValid); Invariant.Assert(index >= 0 && index < LineCount); // Line props may be invalid, even if Measure/Arrange is valid - rendering only props are changing. LineProperties lineProperties = GetLineProperties(); double wrappingWidth = CalcWrappingWidth(RenderSize.Width); TextRunCache textRunCache = new TextRunCache(); // Retrieve details from the line. using(Line line = CreateLine(lineProperties)) { // Format line. Set showParagraphEllipsis flag to false TextLineBreak textLineBreak = GetLine(index).TextLineBreak; bool ellipsis = ParagraphEllipsisShownOnLine(index, lineVOffset); line.Format(dcp, wrappingWidth, GetLineProperties(dcp == 0, lineProperties), textLineBreak, textRunCache, ellipsis); MS.Internal.Invariant.Assert(GetLine(index).Length == line.Length, "Line length is out of [....]"); cchContent = line.ContentLength; cchEllipses = line.GetEllipsesLength(); } }
/// <summary> /// Client to ask for the possible smallest and largest paragraph width that can fully contain the passing text content /// </summary> /// <param name="textSource">an object representing text layout clients text source for TextFormatter.</param> /// <param name="firstCharIndex">character index to specify where in the source text the line starts</param> /// <param name="paragraphProperties">properties that can change from one paragraph to the next, such as text flow direction, text alignment, or indentation.</param> /// <param name="textRunCache">an object representing content cache of the client.</param> /// <returns>min max paragraph width</returns> public abstract MinMaxParagraphWidth FormatMinMaxParagraphWidth( TextSource textSource, int firstCharIndex, TextParagraphProperties paragraphProperties, TextRunCache textRunCache );
/// <summary> /// Retrieves bounds of an object/character at the specified TextPointer. /// Throws IndexOutOfRangeException if position is out of range. /// </summary> /// <param name="orientedPosition">Position of an object/character.</param> /// <returns>Bounds of an object/character.</returns> internal Rect GetRectangleFromTextPosition(ITextPointer orientedPosition) { #if TEXTPANELLAYOUTDEBUG MS.Internal.PtsHost.TextPanelDebug.StartTimer("TextBlock.GetRectangleFromTextPosition", MS.Internal.PtsHost.TextPanelDebug.Category.TextView); #endif Invariant.Assert(IsLayoutDataValid); Invariant.Assert(orientedPosition != null); // Line props may be invalid, even if Measure/Arrange is valid - rendering only props are changing. LineProperties lineProperties = GetLineProperties(); EnsureComplexContent(); // From TextFormatter get rectangle of a single character. // If orientation is Backward, get the length of th previous character. int characterIndex = _complexContent.TextContainer.Start.GetOffsetToPosition(orientedPosition); int originalCharacterIndex = characterIndex; if (orientedPosition.LogicalDirection == LogicalDirection.Backward && characterIndex > 0) { --characterIndex; } double wrappingWidth = CalcWrappingWidth(RenderSize.Width); Vector contentOffset = CalcContentOffset(RenderSize, wrappingWidth); double lineOffset = 0; int dcp = 0; TextRunCache textRunCache = new TextRunCache(); Rect rect = Rect.Empty; FlowDirection flowDirection = FlowDirection.LeftToRight; int lineCount = LineCount; for (int i = 0; i < lineCount; i++) { Debug.Assert(lineCount == LineCount); LineMetrics lineMetrics = GetLine(i); // characterIndex needs to be within line range. If position points to // dcp + line.Length, it means that the next line starts from such position, // hence go to the next line. // But if this is the last line (EOP character), get rectangle form the last // character of the line. if (dcp + lineMetrics.Length > characterIndex || ((dcp + lineMetrics.Length == characterIndex) && (i == lineCount - 1))) { using(Line line = CreateLine(lineProperties)) { bool ellipsis = ParagraphEllipsisShownOnLine(i, lineOffset); line.Format(dcp, wrappingWidth, GetLineProperties(dcp == 0, lineProperties), lineMetrics.TextLineBreak, textRunCache, ellipsis); // Check consistency of line length MS.Internal.Invariant.Assert(lineMetrics.Length == line.Length, "Line length is out of [....]"); rect = line.GetBoundsFromTextPosition(characterIndex, out flowDirection); } break; } dcp += lineMetrics.Length; lineOffset += lineMetrics.Height; } if (!rect.IsEmpty) // Empty rects can't be modified { rect.X += contentOffset.X; rect.Y += contentOffset.Y + lineOffset; // Return only TopLeft and Height. // Adjust rect.Left by taking into account flow direction of the // content and orientation of input position. if (lineProperties.FlowDirection != flowDirection) { if (orientedPosition.LogicalDirection == LogicalDirection.Forward || originalCharacterIndex == 0) { rect.X = rect.Right; } } else { // NOTE: check for 'originalCharacterIndex > 0' is only required for position at the beginning // content with Backward orientation. This should not be a valid position. // Remove it later if (orientedPosition.LogicalDirection == LogicalDirection.Backward && originalCharacterIndex > 0) { rect.X = rect.Right; } } rect.Width = 0; } #if TEXTPANELLAYOUTDEBUG MS.Internal.PtsHost.TextPanelDebug.StopTimer("TextBlock.GetRectangleFromTextPosition", MS.Internal.PtsHost.TextPanelDebug.Category.TextView); #endif return rect; }
internal TextCache(TextBoxView owner) { _lineProperties = owner.GetLineProperties(); _textRunCache = new TextRunCache(); Control hostControl = (Control)owner.Host; TextFormattingMode textFormattingMode = TextOptions.GetTextFormattingMode(hostControl); _textFormatter = System.Windows.Media.TextFormatting.TextFormatter.FromCurrentDispatcher(textFormattingMode); }
/// <summary> /// Determines if the given position is at the edge of a caret unit /// in the specified direction, and returns true if it is and false otherwise. /// Used by the ITextView.IsCaretAtUnitBoundary(ITextPointer position) in /// TextParagraphView /// </summary> /// <param name="position"> /// Position to test. /// </param> /// <param name="dcp"> /// Offset of the current position from start of TextContainer /// </param> /// <param name="lineIndex"> /// Index of line in which position is found /// </param> internal bool IsAtCaretUnitBoundary(ITextPointer position, int dcp, int lineIndex) { Invariant.Assert(IsLayoutDataValid); // Line props may be invalid, even if Measure/Arrange is valid - rendering only props are changing. LineProperties lineProperties = GetLineProperties(); EnsureComplexContent(); TextRunCache textRunCache = new TextRunCache(); bool isAtCaretUnitBoundary = false; int characterIndex = _complexContent.TextContainer.Start.GetOffsetToPosition(position); CharacterHit charHit = new CharacterHit(); if (position.LogicalDirection == LogicalDirection.Backward) { if (characterIndex > dcp) { // Go to trailing edge of previous character charHit = new CharacterHit(characterIndex - 1, 1); } else { // We should not be at line's start dcp with backward context, except in case this is the first line. This is not // a unit boundary return false; } } else if (position.LogicalDirection == LogicalDirection.Forward) { // Get leading edge of this character index charHit = new CharacterHit(characterIndex, 0); } LineMetrics lineMetrics = GetLine(lineIndex); double wrappingWidth = CalcWrappingWidth(RenderSize.Width); using(Line line = CreateLine(lineProperties)) { // Format line. Set showParagraphEllipsis flag to false since we are not using information about // ellipsis to change line offsets in this case. line.Format(dcp, wrappingWidth, GetLineProperties(lineIndex == 0, lineProperties), lineMetrics.TextLineBreak, textRunCache, false); // Check consistency of line formatting MS.Internal.Invariant.Assert(lineMetrics.Length == line.Length, "Line length is out of [....]"); isAtCaretUnitBoundary = line.IsAtCaretCharacterHit(charHit); } return isAtCaretUnitBoundary; }
/// <summary> /// Client to format a text line that fills a paragraph in the document. /// </summary> /// <param name="textSource">an object representing text layout clients text source for TextFormatter.</param> /// <param name="firstCharIndex">character index to specify where in the source text the line starts</param> /// <param name="paragraphWidth">width of paragraph in which the line fills</param> /// <param name="paragraphProperties">properties that can change from one paragraph to the next, such as text flow direction, text alignment, or indentation.</param> /// <param name="previousLineBreak">LineBreak property of the previous text line, or null if this is the first line in the paragraph</param> /// <param name="textRunCache">an object representing content cache of the client.</param> /// <returns>object representing a line of text that client interacts with. </returns> public override TextLine FormatLine( TextSource textSource, int firstCharIndex, double paragraphWidth, TextParagraphProperties paragraphProperties, TextLineBreak previousLineBreak, TextRunCache textRunCache ) { return FormatLineInternal( textSource, firstCharIndex, 0, // lineLength paragraphWidth, paragraphProperties, previousLineBreak, textRunCache ); }
// ------------------------------------------------------------------ // Do content alignment. // ------------------------------------------------------------------ private void AlignContent() { Debug.Assert(IsLayoutDataValid); Debug.Assert(CheckFlags(Flags.RequiresAlignment)); // Line props may be invalid, even if Measure/Arrange is valid - rendering only props are changing. LineProperties lineProperties = GetLineProperties(); double wrappingWidth = CalcWrappingWidth(RenderSize.Width); Vector contentOffset = CalcContentOffset(RenderSize, wrappingWidth); // Create / format all lines. // Since we are disposing line object, it can be reused to format following lines. Line line = CreateLine(lineProperties); TextRunCache textRunCache = new TextRunCache(); int dcp = 0; double lineOffset = 0; int lineCount = LineCount; for (int i = 0; i < lineCount; i++) { Debug.Assert(lineCount == LineCount); LineMetrics lineMetrics = GetLine(i); using (line) { bool ellipsis = ParagraphEllipsisShownOnLine(i, lineOffset); line.Format(dcp, wrappingWidth, GetLineProperties(dcp == 0, lineProperties), lineMetrics.TextLineBreak, textRunCache, ellipsis); double lineHeight = CalcLineAdvance(line.Height, lineProperties); // Check consistency of line formatting MS.Internal.Invariant.Assert(lineMetrics.Length == line.Length, "Line length is out of [....]"); Debug.Assert(DoubleUtil.AreClose(lineHeight, lineMetrics.Height), "Line height is out of [....]."); // Calculated line width might be different from measure width in following cases: // a) dynamically sized children, when FinalSize != AvailableSize // b) non-default horizontal alignment, when FinalSize != AvailableSize // Hence do not assert about matching line width with cached line metrics. lineMetrics = UpdateLine(i, lineMetrics, line.Start, line.Width); dcp += lineMetrics.Length; lineOffset += lineHeight; } } SetFlags(false, Flags.RequiresAlignment); }
internal override TextLine RecreateLine( #endif TextSource textSource, int firstCharIndex, int lineLength, double paragraphWidth, TextParagraphProperties paragraphProperties, TextLineBreak previousLineBreak, TextRunCache textRunCache ) { return FormatLineInternal( textSource, firstCharIndex, lineLength, paragraphWidth, paragraphProperties, previousLineBreak, textRunCache ); }