/// <summary> /// OnChildDesiredSizeChanged /// Called from FlowDocumentPage for IContentHost implementation /// internal void OnChildDesiredSizeChanged(UIElement child) { if (_structuralCache != null && _structuralCache.IsFormattedOnce && !_structuralCache.ForceReformat) { // If executed during formatting process, delay invalidation. // This may happen during formatting when text host notifies its about // baseline changes. if (_structuralCache.IsFormattingInProgress) { Dispatcher.BeginInvoke(DispatcherPriority.Normal, new DispatcherOperationCallback(OnChildDesiredSizeChangedAsync), child); return; } // Get start and end positions int childStartIndex = TextContainerHelper.GetCPFromEmbeddedObject(child, ElementEdge.BeforeStart); if (childStartIndex < 0) { return; } TextPointer childStart = new TextPointer(_structuralCache.TextContainer.Start); childStart.MoveByOffset(childStartIndex); TextPointer childEnd = new TextPointer(childStart); childEnd.MoveByOffset(TextContainerHelper.EmbeddedObjectLength); // Create new DTR for changing UIElement and add it to DRTList. DirtyTextRange dtr = new DirtyTextRange(childStartIndex, TextContainerHelper.EmbeddedObjectLength, TextContainerHelper.EmbeddedObjectLength); _structuralCache.AddDirtyTextRange(dtr); // Notify formatter about content invalidation. if (_formatter != null) { _formatter.OnContentInvalidated(true, childStart, childEnd); } } }
/// <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); }
// ------------------------------------------------------------------ // IContentHost Helpers // ------------------------------------------------------------------ /// <summary> /// Searches for an element in the _complexContent.TextContainer. If the element is found, returns the /// position at which it is found. Otherwise returns null. /// </summary> /// <param name="e"> /// Element to be found. /// </param> /// <remarks> /// We assume that this function is called from within text if the caller knows that _complexContent exists /// and contains a TextContainer. Hence we assert for this condition within the function /// </remarks> private TextPointer FindElementPosition(IInputElement e) { // Parameter validation Debug.Assert(e != null); // Validate that this function is only called when a TextContainer exists as complex content Debug.Assert(_complexContent.TextContainer is TextContainer); TextPointer position; // If e is a TextElement we can optimize by checking its TextContainer if (e is TextElement) { if ((e as TextElement).TextContainer == _complexContent.TextContainer) { // Element found position = new TextPointer((e as TextElement).ElementStart); return position; } } // Else: search for e in the complex content position = new TextPointer((TextPointer)_complexContent.TextContainer.Start); while (position.CompareTo((TextPointer)_complexContent.TextContainer.End) < 0) { // Search each position in _complexContent.TextContainer for the element switch (position.GetPointerContext(LogicalDirection.Forward)) { case TextPointerContext.EmbeddedElement: DependencyObject embeddedObject = position.GetAdjacentElement(LogicalDirection.Forward); if (embeddedObject is ContentElement || embeddedObject is UIElement) { if (embeddedObject == e as ContentElement || embeddedObject == e as UIElement) { return position; } } break; default: break; } position.MoveByOffset(+1); } // Reached end of complex content without finding the element return null; }
/// <summary> /// Returns a TextPointer at a new position by a specified symbol /// count. /// </summary> /// <param name="offset"> /// Number of symbols to advance. offset may be negative, in which /// case the TextPointer is moved backwards. /// </param> /// <param name="direction"> /// LogicalDirection desired for a returned TextPointer. /// </param> /// <returns> /// TextPointer located at requested position in case if requested position /// does exist, otherwize returns null. LogicalDirection of the TextPointer /// returned is as specified by a <paramref name="direction"/>. /// </returns> /// <remarks> /// <para>This method, like all other TextPointer methods, defines a symbol /// as one of:</para> /// <para>- 16 bit Unicode character.</para> /// <para>- opening or closing tag of a <see cref="TextElement"/>.</para> /// <para>- the whole <see cref="UIElement"/> as atomic embedded object.</para> /// <para>See examples in <seealso cref="TextPointer.GetPositionAtOffset(int)"/> method with one parameter.</para> /// </remarks> public TextPointer GetPositionAtOffset(int offset, LogicalDirection direction) { TextPointer position = new TextPointer(this, direction); int actualCount = position.MoveByOffset(offset); if (actualCount == offset) { position.Freeze(); return position; } else { return null; } }