//--------------------------- // public static methods //--------------------------- /// <summary> /// resolves text effect on a text range to a list of text effect targets. /// The method will walk the text and perform the following task: /// 1) For each continous block of text, create a text effect targeting the scoping element /// 2) For the text effect created, calculate the starting cp index and cp count for the effect /// /// The method will create freezable copy of the TextEffect passed in and fill in /// CharacterIndex and Count for the range. /// </summary> /// <param name="startPosition">starting text pointer</param> /// <param name="endPosition">end text pointer</param> /// <param name="effect">effect that is apply on the text</param> public static TextEffectTarget[] Resolve( TextPointer startPosition, TextPointer endPosition, TextEffect effect ) { if (effect == null) throw new ArgumentNullException("effect"); ValidationHelper.VerifyPositionPair(startPosition, endPosition); TextPointer effectStart = new TextPointer(startPosition); // move to the first character symbol at or after Start position MoveToFirstCharacterSymbol(effectStart); TextEffect effectCopy; List<TextEffectTarget> list = new List<TextEffectTarget>(); // we will now traverse the TOM and resolve text effects to the immediate parent // of the characters. We are effectively applying the text effect onto // block of continous text. while (effectStart.CompareTo(endPosition) < 0) { // create a copy of the text effect // so that we can set the CharacterIndex and Count effectCopy = effect.Clone(); // create a position TextPointer continuousTextEnd = new TextPointer(effectStart); // move the position to the end of the continuous text block MoveToFirstNonCharacterSymbol(continuousTextEnd, endPosition); // make sure we are not out of the range continuousTextEnd = (TextPointer)TextPointerBase.Min(continuousTextEnd, endPosition); // set the character index to be the distance from the Start // of this text block to the Start of the text container effectCopy.PositionStart = effectStart.TextContainer.Start.GetOffsetToPosition(effectStart); // count is the distance from the text block start to end effectCopy.PositionCount = effectStart.GetOffsetToPosition(continuousTextEnd); list.Add( new TextEffectTarget( effectStart.Parent, effectCopy ) ); // move the effectStart to the beginning of the next text block. effectStart = continuousTextEnd; MoveToFirstCharacterSymbol(effectStart); } return list.ToArray(); }
//------------------------------------------------------ // // Public Methods // //------------------------------------------------------ //------------------------------------------------------ // // Public Properties // //------------------------------------------------------ //------------------------------------------------------ // // Protected Methods // //------------------------------------------------------ //------------------------------------------------------ // // Internal Methods // //------------------------------------------------------ #region Internal methods. /// <summary> /// Writes the container into the specified XmlWriter. /// </summary> internal void Write(TextPointer start, TextPointer end, XmlWriter writer) { System.Diagnostics.Debug.Assert(start != null); System.Diagnostics.Debug.Assert(end != null); System.Diagnostics.Debug.Assert(start.CompareTo(end) <= 0); System.Diagnostics.Debug.Assert(writer != null); WriteContainer(start, end, writer); }
// move to the first non-character symbol, but not pass beyond the limit private static void MoveToFirstNonCharacterSymbol( TextPointer navigator, // navigator to move TextPointer stopHint // don't move further if we already pass beyond this point ) { while (navigator.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text && navigator.CompareTo(stopHint) < 0 && navigator.MoveToNextContextPosition(LogicalDirection.Forward)) { ; } }
private static int GetCharOffsetToPosition(InlineCollection inlineCollection, TextPointer position, out TextPointer result) { int offset = 0; foreach (var inline in inlineCollection) { offset += GetCharOffsetToPosition(inline, position, out result); if (result == null || result.CompareTo(position) >= 0) return offset; } result = null; return offset; }
private static int GetCharOffsetToPosition(BlockCollection blockCollection, TextPointer position, out TextPointer result) { int offset = 0; foreach (var block in blockCollection) { offset += GetCharOffsetToPosition(block, position, out result); if (result == null || result.CompareTo(position) >= 0) return offset; } result = null; return offset; }
public static int GetCharOffsetToPosition(this FlowDocument document, TextPointer position) { if (document == null) throw new ArgumentNullException("document"); if (position == null) throw new ArgumentNullException("position"); if (position.CompareTo(document.ContentStart) == 0) return 0; TextPointer result; return GetCharOffsetToPosition(document.Blocks, position, out result); }
internal override Rect[] GetCharacterRects(TextPointer startPointer, TextPointer endPointer) { if (!invalidatedCache && cachedStartP != null && startPointer.CompareTo(cachedStartP) == 0) { return cachedRects; } else { cachedRects = base.GetCharacterRects(startPointer, endPointer); cachedStartP = startPointer; invalidatedCache = false; } return cachedRects; }
// Token: 0x06003E2C RID: 15916 RVA: 0x0011D638 File Offset: 0x0011B838 internal static TextTreeDeleteContentUndoUnit CreateDeleteContentUndoUnit(TextContainer tree, TextPointer start, TextPointer end) { if (start.CompareTo(end) == 0) { return(null); } UndoManager orClearUndoManager = TextTreeUndo.GetOrClearUndoManager(tree); if (orClearUndoManager == null) { return(null); } TextTreeDeleteContentUndoUnit textTreeDeleteContentUndoUnit = new TextTreeDeleteContentUndoUnit(tree, start, end); orClearUndoManager.Add(textTreeDeleteContentUndoUnit); return(textTreeDeleteContentUndoUnit); }
// Adds a DeleteContentUndoUnit to the open parent undo unit, if any. // Called by TextContainer.DeleteContent. internal static TextTreeDeleteContentUndoUnit CreateDeleteContentUndoUnit(TextContainer tree, TextPointer start, TextPointer end) { UndoManager undoManager; TextTreeDeleteContentUndoUnit undoUnit; if (start.CompareTo(end) == 0) return null; undoManager = GetOrClearUndoManager(tree); if (undoManager == null) return null; undoUnit = new TextTreeDeleteContentUndoUnit(tree, start, end); undoManager.Add(undoUnit); return undoUnit; }
/// <summary>Initializes a new instance of the <see cref="T:System.Windows.Documents.Hyperlink" /> class, taking two <see cref="T:System.Windows.Documents.TextPointer" /> objects that indicate the beginning and end of a selection of content to be contained by the new <see cref="T:System.Windows.Documents.Hyperlink" />.</summary> /// <param name="start">A <see cref="T:System.Windows.Documents.TextPointer" /> indicating the beginning of a selection of content to be contained by the new <see cref="T:System.Windows.Documents.Hyperlink" />.</param> /// <param name="end">A <see cref="T:System.Windows.Documents.TextPointer" /> indicating the end of a selection of content to be contained by the new <see cref="T:System.Windows.Documents.Hyperlink" />.</param> /// <exception cref="T:System.ArgumentNullException"> /// <paramref name="start" /> or <paramref name="end" /> is <see langword="null" />.</exception> /// <exception cref="T:System.ArgumentException"> /// <paramref name="start" /> and <paramref name="end" /> do not resolve to a range of content suitable for enclosure by a <see cref="T:System.Windows.Documents.Span" /> element; for example, if <paramref name="start" /> and <paramref name="end" /> indicate positions in different paragraphs.</exception> // Token: 0x0600301F RID: 12319 RVA: 0x000D8A18 File Offset: 0x000D6C18 public Hyperlink(TextPointer start, TextPointer end) : base(start, end) { TextPointer textPointer = base.ContentStart.CreatePointer(); TextPointer contentEnd = base.ContentEnd; while (textPointer.CompareTo(contentEnd) < 0) { Hyperlink hyperlink = textPointer.GetAdjacentElement(LogicalDirection.Forward) as Hyperlink; if (hyperlink != null) { hyperlink.Reposition(null, null); } else { textPointer.MoveToNextContextPosition(LogicalDirection.Forward); } } }
//------------------------------------------------------------------- // // Internal Methods // //------------------------------------------------------------------- #region Internal Methods /// <summary> /// Returns the integer "index" of a specified ListItem that is an immediate child of /// this List. This index is defined to be a sequential counter of ListElementItems only /// (skipping other elements) among this List's immediate children. /// /// The list item index of the first child of type ListItem is specified by /// this.StartListIndex, which has a default value of 1. /// /// The index returned by this method is used in the formation of some ListItem /// markers such as "(b)" and "viii." (as opposed to others, like disks and wedges, /// which are not sequential-position-dependent). /// </summary> /// <param name="item">The item whose index is to be returned.</param> /// <returns>Returns the index of a specified ListItem.</returns> internal int GetListItemIndex(ListItem item) { // Check for valid arg if (item == null) { throw new ArgumentNullException("item"); } if (item.Parent != this) { throw new InvalidOperationException(SR.Get(SRID.ListElementItemNotAChildOfList)); } // Count ListItem siblings (not other element types) back to first item. int itemIndex = StartIndex; TextPointer textNav = new TextPointer(this.ContentStart); while (textNav.CompareTo(this.ContentEnd) != 0) { // ListItem is a content element, so look for ElementStart runs only if (textNav.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementStart) { DependencyObject element = textNav.GetAdjacentElementFromOuterPosition(LogicalDirection.Forward); if (element is ListItem) { if (element == item) { break; } if (itemIndex < int.MaxValue) { ++itemIndex; } } // Skip entire content element content, because we are looking // only for immediate children. textNav.MoveToPosition(((TextElement)element).ElementEnd); } else { textNav.MoveToNextContextPosition(LogicalDirection.Forward); } } return(itemIndex); }
// Checks whether two paragraphs are meargeable. // To be meargeable they need to be separated by s sequence of closing, then opening // tags only. // And all tags must be Sections/Lists/ListItems only. internal static bool ParagraphsAreMergeable(Block firstParagraphOrBlockUIContainer, Block secondParagraphOrBlockUIContainer) { if (firstParagraphOrBlockUIContainer == null || secondParagraphOrBlockUIContainer == null || firstParagraphOrBlockUIContainer == secondParagraphOrBlockUIContainer) { return(false); // nothing to merge } TextPointer position = firstParagraphOrBlockUIContainer.ElementEnd; TextPointer startOfSecondParagraph = secondParagraphOrBlockUIContainer.ElementStart; // Skip and check all closing tags (if any) while (position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementEnd) { if (!TextSchema.AllowsParagraphMerging(position.Parent.GetType())) { return(false); // Crossing hard-structured element. Paragraphs are not meargeable. } position = position.GetNextContextPosition(LogicalDirection.Forward); } // Skip and check all opening tags (if any) while (position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementStart) { if (position.CompareTo(startOfSecondParagraph) == 0) { // Successfully skipped all tags, and reached the seconfParagraph. // The paragraphs are meargeable. return(true); } position = position.GetNextContextPosition(LogicalDirection.Forward); if (!TextSchema.AllowsParagraphMerging(position.Parent.GetType())) { return(false); // Crossing hardd-structured element. Paragraphs are not meargeable. } } // Non-tag run found. Paragraphs are not meargeable. return(false); }
/// <summary> /// Creates a new Hyperlink instance covering existing content. /// </summary> /// <param name="start"> /// Start position of the new Hyperlink. /// </param> /// <param name="end"> /// End position of the new Hyperlink. /// </param> /// <remarks> /// start and end must both be parented by the same Paragraph, otherwise /// the method will raise an ArgumentException. /// </remarks> public Hyperlink(TextPointer start, TextPointer end) : base(start, end) { // After inserting this Hyperlink, we need to extract any child Hyperlinks. TextPointer navigator = this.ContentStart.CreatePointer(); TextPointer stop = this.ContentEnd; while (navigator.CompareTo(stop) < 0) { Hyperlink hyperlink = navigator.GetAdjacentElement(LogicalDirection.Forward) as Hyperlink; if (hyperlink != null) { hyperlink.Reposition(null, null); } else { navigator.MoveToNextContextPosition(LogicalDirection.Forward); } } }
// Adds a DeleteContentUndoUnit to the open parent undo unit, if any. // Called by TextContainer.DeleteContent. internal static TextTreeDeleteContentUndoUnit CreateDeleteContentUndoUnit(TextContainer tree, TextPointer start, TextPointer end) { UndoManager undoManager; TextTreeDeleteContentUndoUnit undoUnit; if (start.CompareTo(end) == 0) { return(null); } undoManager = GetOrClearUndoManager(tree); if (undoManager == null) { return(null); } undoUnit = new TextTreeDeleteContentUndoUnit(tree, start, end); undoManager.Add(undoUnit); return(undoUnit); }
// Token: 0x060031B9 RID: 12729 RVA: 0x000DBB64 File Offset: 0x000D9D64 internal int GetListItemIndex(ListItem item) { if (item == null) { throw new ArgumentNullException("item"); } if (item.Parent != this) { throw new InvalidOperationException(SR.Get("ListElementItemNotAChildOfList")); } int num = this.StartIndex; TextPointer textPointer = new TextPointer(base.ContentStart); while (textPointer.CompareTo(base.ContentEnd) != 0) { if (textPointer.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementStart) { DependencyObject adjacentElementFromOuterPosition = textPointer.GetAdjacentElementFromOuterPosition(LogicalDirection.Forward); if (adjacentElementFromOuterPosition is ListItem) { if (adjacentElementFromOuterPosition == item) { break; } if (num < 2147483647) { num++; } } textPointer.MoveToPosition(((TextElement)adjacentElementFromOuterPosition).ElementEnd); } else { textPointer.MoveToNextContextPosition(LogicalDirection.Forward); } } return(num); }
/// <summary> /// Find the input string within the document, starting at the specified position. /// </summary> /// <param name="position">the current text position</param> /// <param name="input">input text</param> /// <returns>An <see cref="TextRange"/> representing the (next) matching string within the text container. Null if there are no matches.</returns> public TextRange FindNext(ref TextPointer position, String input) { FindText(input); foreach (var result in _searchHits) { if (position.CompareTo(result.End) < 0) { position = result.Start; double top = this.PointToScreen(position.GetLineStartPosition(0).GetCharacterRect(LogicalDirection.Forward).TopLeft).Y + this.PointFromScreen(new System.Windows.Point(0, 0)).Y; Trace.WriteLine(string.Format(" Top: {0}, CharOffset: {1}", top, position)); ScrollViewer.ScrollToVerticalOffset(ScrollViewer.VerticalOffset + top); position = result.End; return result; } } return null; }
// Token: 0x06003922 RID: 14626 RVA: 0x001031AC File Offset: 0x001013AC private static void MoveToFirstNonCharacterSymbol(TextPointer navigator, TextPointer stopHint) { while (navigator.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text && navigator.CompareTo(stopHint) < 0 && navigator.MoveToNextContextPosition(LogicalDirection.Forward)) { } }
// .................................................................... // // Row editing // // .................................................................... #region Row Editing /// <summary> /// Checks whether the given TextPointer is at row end position, where text insertion is impossible /// and returns a following position where text insertion or pasting is valid. /// New paragraphs is creeated at the end of TextContainer if necessary. /// </summary> internal static TextPointer EnsureInsertionPosition(TextPointer position) { Invariant.Assert(position != null, "null check: position"); // Normalize the pointer position = position.GetInsertionPosition(position.LogicalDirection); if (!TextPointerBase.IsAtInsertionPosition(position)) { // There is no insertion positions in the whole document at all. // Generate minimally necessary content to ensure at least one insertion position. position = CreateInsertionPositionInIncompleteContent(position); } else { // Check if position is at one of special structural boundary positions, where we can potentially have an // insertion position and create one. if (position.IsAtRowEnd) { // Find a next insertion position within the scope of the parent table. Table currentTable = TextRangeEditTables.GetTableFromPosition(position); position = GetAdjustedRowEndPosition(currentTable, position); if (position.CompareTo(currentTable.ElementEnd) == 0) { // The range is at the end of table which is the last block of text container OR // next insertion position crossed table boundary. // In both cases, we want to insert a paragraph after table end and move our insertion position there. position = CreateImplicitParagraph(currentTable.ElementEnd); } } Invariant.Assert(!position.IsAtRowEnd, "position is not expected to be at RowEnd anymore"); // Note that this is not an else if, because our next insertion position (if it is within the same table), // can fall into one of the following cases. We need to handle it. if (TextPointerBase.IsInBlockUIContainer(position)) { BlockUIContainer blockUIContainer = (BlockUIContainer)position.Parent; bool insertBefore = position.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart; position = insertBefore ? CreateImplicitParagraph(blockUIContainer.ElementStart) : CreateImplicitParagraph(blockUIContainer.ElementEnd); // Clean potentialy incomplete content if (blockUIContainer.IsEmpty) { blockUIContainer.RepositionWithContent(null); } } else if (TextPointerBase.IsBeforeFirstTable(position) || TextPointerBase.IsAtPotentialParagraphPosition(position)) { position = CreateImplicitParagraph(position); } else if (TextPointerBase.IsAtPotentialRunPosition(position)) { position = CreateImplicitRun(position); } } Invariant.Assert(TextSchema.IsInTextContent(position), "position must be in text content now"); return position; }
/// <summary> /// Deletes all equi-scoped segments of content from start TextPointer /// up to fragment root. Thus clears one half of a fragment. /// The other half remains untouched. /// All elements whose boundaries are crossed by this range /// remain in the tree (except for emptied formatting elements). /// </summary> /// <param name="start"> /// A position from which content clearinng starts. /// All content segments between this position and a fragment /// root will be deleted. /// </param> /// <param name="end"> /// A position indicating the other boundary of a fragment. /// This position is used for fragment root identification. /// </param> private static void DeleteEquiScopedContent(TextPointer start, TextPointer end) { // Validate parameters Invariant.Assert(start != null, "null check: start"); Invariant.Assert(end != null, "null check: end"); if (start.CompareTo(end) == 0) { return; } if (start.Parent == end.Parent) { DeleteContentBetweenPositions(start, end); return; } // Identify directional parameters LogicalDirection direction; LogicalDirection oppositeDirection; TextPointerContext enterScopeSymbol; TextPointerContext leaveScopeSymbol; ElementEdge edgeBeforeElement; ElementEdge edgeAfterElement; if (start.CompareTo(end) < 0) { direction = LogicalDirection.Forward; oppositeDirection = LogicalDirection.Backward; enterScopeSymbol = TextPointerContext.ElementStart; leaveScopeSymbol = TextPointerContext.ElementEnd; edgeBeforeElement = ElementEdge.BeforeStart; edgeAfterElement = ElementEdge.AfterEnd; } else { direction = LogicalDirection.Backward; oppositeDirection = LogicalDirection.Forward; enterScopeSymbol = TextPointerContext.ElementEnd; leaveScopeSymbol = TextPointerContext.ElementStart; edgeBeforeElement = ElementEdge.AfterEnd; edgeAfterElement = ElementEdge.BeforeStart; } // previousPosition will store a location where nondeleted content starts TextPointer previousPosition = new TextPointer(start); // nextPosition runs toward other end until level change - // so that we could delete all content from previousPosition // to nextPosition at once. TextPointer nextPosition = new TextPointer(start); // Run nextPosition forward until the very end of affected range while (nextPosition.CompareTo(end) != 0) { Invariant.Assert(direction == LogicalDirection.Forward && nextPosition.CompareTo(end) < 0 || direction == LogicalDirection.Backward && nextPosition.CompareTo(end) > 0, "Inappropriate position ordering"); Invariant.Assert(previousPosition.Parent == nextPosition.Parent, "inconsistent position Parents: previous and next"); TextPointerContext pointerContext = nextPosition.GetPointerContext(direction); if (pointerContext == TextPointerContext.Text || pointerContext == TextPointerContext.EmbeddedElement) { // Add this run to a collection of equi-scoped content nextPosition.MoveToNextContextPosition(direction); // Check if we went too far and return a little to end if necessary if (direction == LogicalDirection.Forward && nextPosition.CompareTo(end) > 0 || direction == LogicalDirection.Backward && nextPosition.CompareTo(end) < 0) { Invariant.Assert(nextPosition.Parent == end.Parent, "inconsistent poaition Parents: next and end"); nextPosition.MoveToPosition(end); break; } } else if (pointerContext == enterScopeSymbol) { // Jump over the element and continue collecting equi-scoped content nextPosition.MoveToNextContextPosition(direction); ((ITextPointer)nextPosition).MoveToElementEdge(edgeAfterElement); // If our range crosses the element then we stop before its opening tag if (direction == LogicalDirection.Forward && nextPosition.CompareTo(end) >= 0 || direction == LogicalDirection.Backward && nextPosition.CompareTo(end) <= 0) { nextPosition.MoveToNextContextPosition(oppositeDirection); ((ITextPointer)nextPosition).MoveToElementEdge(edgeBeforeElement); break; } } else if (pointerContext == leaveScopeSymbol) { // Delete preceding content and continue on outer level DeleteContentBetweenPositions(previousPosition, nextPosition); if (!ExtractEmptyFormattingElements(previousPosition)) { // Continue on outer level Invariant.Assert(nextPosition.GetPointerContext(direction) == leaveScopeSymbol, "Unexpected context of nextPosition"); nextPosition.MoveToNextContextPosition(direction); } previousPosition.MoveToPosition(nextPosition); } else { Invariant.Assert(false, "Not expecting None context here"); Invariant.Assert(pointerContext == TextPointerContext.None, "Unknown pointer context"); break; } } Invariant.Assert(previousPosition.Parent == nextPosition.Parent, "inconsistent Parents: previousPosition, nextPosition"); DeleteContentBetweenPositions(previousPosition, nextPosition); }
/// <summary> /// Creates a new Span instance covering existing content. /// </summary> /// <param name="start"> /// Start position of the new Span. /// </param> /// <param name="end"> /// End position of the new Span. /// </param> /// <remarks> /// start and end must both be parented by the same Paragraph, otherwise /// the method will raise an ArgumentException. /// </remarks> public Span(TextPointer start, TextPointer end) { if (start == null) { throw new ArgumentNullException("start"); } if (end == null) { throw new ArgumentNullException("start"); } if (start.TextContainer != end.TextContainer) { throw new ArgumentException(SR.Get(SRID.InDifferentTextContainers, "start", "end")); } if (start.CompareTo(end) > 0) { throw new ArgumentException(SR.Get(SRID.BadTextPositionOrder, "start", "end")); } start.TextContainer.BeginChange(); try { start = TextRangeEditTables.EnsureInsertionPosition(start); Invariant.Assert(start.Parent is Run); end = TextRangeEditTables.EnsureInsertionPosition(end); Invariant.Assert(end.Parent is Run); if (start.Paragraph != end.Paragraph) { throw new ArgumentException(SR.Get(SRID.InDifferentParagraphs, "start", "end")); } // If start or end positions have a Hyperlink ancestor, we cannot split them. Inline nonMergeableAncestor; if ((nonMergeableAncestor = start.GetNonMergeableInlineAncestor()) != null) { throw new InvalidOperationException(SR.Get(SRID.TextSchema_CannotSplitElement, nonMergeableAncestor.GetType().Name)); } if ((nonMergeableAncestor = end.GetNonMergeableInlineAncestor()) != null) { throw new InvalidOperationException(SR.Get(SRID.TextSchema_CannotSplitElement, nonMergeableAncestor.GetType().Name)); } TextElement commonAncestor = TextElement.GetCommonAncestor((TextElement)start.Parent, (TextElement)end.Parent); while (start.Parent != commonAncestor) { start = SplitElement(start); } while (end.Parent != commonAncestor) { end = SplitElement(end); } if (start.Parent is Run) { start = SplitElement(start); } if (end.Parent is Run) { end = SplitElement(end); } Invariant.Assert(start.Parent == end.Parent); Invariant.Assert(TextSchema.IsValidChild(/*position*/ start, /*childType*/ typeof(Span))); this.Reposition(start, end); } finally { start.TextContainer.EndChange(); } }
internal static bool UnindentListItems(TextRange range) { // If listitems in this range cross a list boundary, we cannot unindent them. if (!IsRangeWithinSingleList(range)) { return(false); } ListItem firstListItem = TextPointerBase.GetListItem(range.Start); ListItem lastListItem = TextPointerBase.GetListItem((TextPointer)TextRangeEdit.GetAdjustedRangeEnd(range.Start, range.End)); // At this point it is possible that lastListItem is a child of // firstListItem. // // This is due to a special case in IsRangeWithinSingleList // which allows the input TextRange to cross List boundaries only // in the case where the TextRange ends adjacent to an insertion // position at the same level as the start, e.g.: // // - parent item // - start item (range starts here) // - child item (range ends here) // <start item must not have any siblings> // // Here we check for that special case and ensure that // lastListItem is at the same level as firstListItem. TextElement parent = (TextElement)lastListItem.Parent; while (parent != firstListItem.Parent) { lastListItem = parent as ListItem; parent = (TextElement)parent.Parent; } if (lastListItem == null) { // This can happen if the input is a fragment, a collection // of ListItems not parented by an outer List. return(false); } // Cut wrapping list before startListItem if (firstListItem.PreviousListItem != null) { TextRangeEdit.SplitElement(firstListItem.ElementStart); } // Cut wrapping list after endListItem if (lastListItem.NextListItem != null) { TextRangeEdit.SplitElement(lastListItem.ElementEnd); } // Remove List wrapper from selected items List unindentedList = (List)firstListItem.Parent; // Check whether we have outer ListItem ListItem outerListItem = unindentedList.Parent as ListItem; if (outerListItem != null) { // Selected items belong to a nested list. // So we need to pull them to the level of enclosing ListItem, i.e. cut this ListItem. // In this case we also need to include trailing list into the last of selected items // Remove a wrapping List from selected items unindentedList.Reposition(null, null); // Remember end position of outerListItem to pull any trailing list or other blocks into the last of selected listitems TextPointer outerListItemEnd = outerListItem.ContentEnd; if (outerListItem.ContentStart.CompareTo(firstListItem.ElementStart) == 0) { // There is nothing before first list item; so outer list item would be empty - just delete it outerListItem.Reposition(null, null); } else { // Wrap all stuff preceding firstListItem in outerListItem outerListItem.Reposition(outerListItem.ContentStart, firstListItem.ElementStart); } if (outerListItemEnd.CompareTo(lastListItem.ElementEnd) == 0) { // There are no following siblings to pull into last selected item; do nothing. } else { // Pull trailing items (following siblings to the selected ones) into the last selected item // Remember a position to merge any trailing list after lastListItem TextPointer mergePosition = lastListItem.ContentEnd; // Reposition last selectd ListItem so that it includes trailing list (or other block) as its children lastListItem.Reposition(lastListItem.ContentStart, outerListItemEnd); // Merge any trailing list with a sublist outdented with our listitem MergeLists(mergePosition); } } else { // Selected items are not in nested list. // We need to simply unwrap them and convert to paragraphs TextPointer start = unindentedList.ElementStart; TextPointer end = unindentedList.ElementEnd; // Save the list's FlowDirection value, to apply later to its children. object listFlowDirectionValue = unindentedList.GetValue(Paragraph.FlowDirectionProperty); // Remove a wrapping List from selected items unindentedList.Reposition(null, null); // Remove ListItems from all selected items ListItem listItem = firstListItem; while (listItem != null) { ListItem nextListItem = listItem.ElementEnd.GetAdjacentElement(LogicalDirection.Forward) as ListItem; // If this is an empty <ListItem></ListItem>, insert an explicit paragraph in it before deleting the list item. if (listItem.ContentStart.CompareTo(listItem.ContentEnd) == 0) { TextRangeEditTables.EnsureInsertionPosition(listItem.ContentStart); } listItem.Reposition(null, null); listItem = listItem == lastListItem ? null : nextListItem; } // Apply FlowDirection of the list just deleted to all its children. TextRangeEdit.SetParagraphProperty(start, end, Paragraph.FlowDirectionProperty, listFlowDirectionValue); // Merge lists on boundaries MergeLists(start); MergeLists(end); } return(true); }
// Token: 0x06003B85 RID: 15237 RVA: 0x0010F26C File Offset: 0x0010D46C internal static bool UnindentListItems(TextRange range) { if (!TextRangeEditLists.IsRangeWithinSingleList(range)) { return(false); } ListItem listItem = TextPointerBase.GetListItem(range.Start); ListItem listItem2 = TextPointerBase.GetListItem((TextPointer)TextRangeEdit.GetAdjustedRangeEnd(range.Start, range.End)); for (TextElement textElement = (TextElement)listItem2.Parent; textElement != listItem.Parent; textElement = (TextElement)textElement.Parent) { listItem2 = (textElement as ListItem); } if (listItem2 == null) { return(false); } if (listItem.PreviousListItem != null) { TextRangeEdit.SplitElement(listItem.ElementStart); } if (listItem2.NextListItem != null) { TextRangeEdit.SplitElement(listItem2.ElementEnd); } List list = (List)listItem.Parent; ListItem listItem3 = list.Parent as ListItem; if (listItem3 != null) { list.Reposition(null, null); TextPointer contentEnd = listItem3.ContentEnd; if (listItem3.ContentStart.CompareTo(listItem.ElementStart) == 0) { listItem3.Reposition(null, null); } else { listItem3.Reposition(listItem3.ContentStart, listItem.ElementStart); } if (contentEnd.CompareTo(listItem2.ElementEnd) != 0) { TextPointer contentEnd2 = listItem2.ContentEnd; listItem2.Reposition(listItem2.ContentStart, contentEnd); TextRangeEditLists.MergeLists(contentEnd2); } } else { TextPointer elementStart = list.ElementStart; TextPointer elementEnd = list.ElementEnd; object value = list.GetValue(Block.FlowDirectionProperty); list.Reposition(null, null); ListItem listItem5; for (ListItem listItem4 = listItem; listItem4 != null; listItem4 = ((listItem4 == listItem2) ? null : listItem5)) { listItem5 = (listItem4.ElementEnd.GetAdjacentElement(LogicalDirection.Forward) as ListItem); if (listItem4.ContentStart.CompareTo(listItem4.ContentEnd) == 0) { TextRangeEditTables.EnsureInsertionPosition(listItem4.ContentStart); } listItem4.Reposition(null, null); } TextRangeEdit.SetParagraphProperty(elementStart, elementEnd, Block.FlowDirectionProperty, value); TextRangeEditLists.MergeLists(elementStart); TextRangeEditLists.MergeLists(elementEnd); } return(true); }
/// <summary> /// Applies a property to a range between start and end positions. /// </summary> /// <param name="start"> /// TextPointer identifying start of affected range. /// </param> /// <param name="end"> /// TextPointer identifying end of affected range. /// </param> /// <param name="formattingProperty"> /// A dependency property whose value is supposed to applied to a range. /// </param> /// <param name="value"> /// A value for a property to apply. /// </param> /// <param name="propertyValueAction"> /// Specifies how to use the value - as absolute, as increment or a decrement. /// </param> internal static void SetInlineProperty(TextPointer start, TextPointer end, DependencyProperty formattingProperty, object value, PropertyValueAction propertyValueAction) { // Check for corner case when we have siple text run with all properties set as requested. // This case is iportant optimization for Backspace-Type scenario, when Springload formatting applies for nothing for 50 properties if (start.CompareTo(end) >= 0 || propertyValueAction == PropertyValueAction.SetValue && start.Parent is Run && start.Parent == end.Parent && TextSchema.ValuesAreEqual(start.Parent.GetValue(formattingProperty), value)) { return; } // Remove unnecessary spans on range ends - to optimize resulting markup RemoveUnnecessarySpans(start); RemoveUnnecessarySpans(end); if (TextSchema.IsStructuralCharacterProperty(formattingProperty)) { SetStructuralInlineProperty(start, end, formattingProperty, value); } else { SetNonStructuralInlineProperty(start, end, formattingProperty, value, propertyValueAction); } }
// Helper that walks paragraphs between start and end positions, applying passed formattingProperty value on them. private static void ApplyStructuralInlinePropertyAcrossParagraphs(TextPointer start, TextPointer end, DependencyProperty formattingProperty, object value) { // We assume to call this method only for paragraph crossing case Invariant.Assert(start.Paragraph != null); Invariant.Assert(start.Paragraph.ContentEnd.CompareTo(end) < 0); // Apply to first Paragraph SetStructuralInlineProperty(start, start.Paragraph.ContentEnd, formattingProperty, value); start = start.Paragraph.ElementEnd; // Apply to last paragraph if (end.Paragraph != null) { SetStructuralInlineProperty(end.Paragraph.ContentStart, end, formattingProperty, value); end = end.Paragraph.ElementStart; } // Now, loop through paragraphs between start and end positions while (start != null && start.CompareTo(end) < 0) { if (start.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart && start.Parent is Paragraph) { Paragraph paragraph = (Paragraph)start.Parent; // Apply property to paragraph just found. SetStructuralInlineProperty(paragraph.ContentStart, paragraph.ContentEnd, formattingProperty, value); // Jump to Paragraph end to skip Inline formatting tags. start = paragraph.ElementEnd; } start = start.GetNextContextPosition(LogicalDirection.Forward); } }
private static void ApplyStructuralInlinePropertyAcrossRun(TextPointer start, TextPointer end, Run run, DependencyProperty formattingProperty, object value) { if (start.CompareTo(end) == 0) { // When the range is empty we should ignore the command, except // for the case of empty Run which can be encountered in empty paragraphs if (run.IsEmpty) { run.SetValue(formattingProperty, value); } } else { // Split elements at start and end boundaries. start = SplitFormattingElements(start, /*keepEmptyFormatting:*/false, /*limitingAncestor*/run.Parent as TextElement); end = SplitFormattingElements(end, /*keepEmptyFormatting:*/false, /*limitingAncestor*/run.Parent as TextElement); run = (Run)start.GetAdjacentElement(LogicalDirection.Forward); run.SetValue(formattingProperty, value); } // Clear property value from all ancestors of this Run. FixupStructuralPropertyEnvironment(run, formattingProperty); }
// Helper that walks Run and Span elements between start and end positions, // clearing value of passed formattingProperty on them. // private static void ClearPropertyValueFromSpansAndRuns(TextPointer start, TextPointer end, DependencyProperty formattingProperty) { // Normalize start position forward. start = start.GetPositionAtOffset(0, LogicalDirection.Forward); // Move to next context position before entering loop below, // since in the loop we look backward. start = start.GetNextContextPosition(LogicalDirection.Forward); while (start != null && start.CompareTo(end) < 0) { if (start.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart && TextSchema.IsFormattingType(start.Parent.GetType())) // look for Run/Span elements { start.Parent.ClearValue(formattingProperty); // Remove unnecessary Spans around this position, delete empty formatting elements (if any) // and merge with adjacent inlines if they have identical set of formatting properties. MergeFormattingInlines(start); } start = start.GetNextContextPosition(LogicalDirection.Forward); } }
// Finds a Run element with ElementStart at or after the given pointer // Creates Runs at potential run positions if encounters some. private static Run GetNextRun(TextPointer pointer, TextPointer limit) { Run run = null; while (pointer != null && pointer.CompareTo(limit) < 0) { if (pointer.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementStart && (run = pointer.GetAdjacentElement(LogicalDirection.Forward) as Run) != null) { break; } if (TextPointerBase.IsAtPotentialRunPosition(pointer)) { pointer = TextRangeEditTables.EnsureInsertionPosition(pointer); Invariant.Assert(pointer.Parent is Run); run = pointer.Parent as Run; break; } // Advance the scanning pointer pointer = pointer.GetNextContextPosition(LogicalDirection.Forward); } return run; }
/// <summary> /// Helper for TextContainer.DeleteContent allowing arbitrary /// order of positions and doinng nothing in case of empty range. /// Removes remaining empty formatting elements - if they not inside empty blocks. /// </summary> /// <param name="one"> /// One of content boundary positions. May precede or follow the TextPointer two. /// Must belong to the same scope as TextPointer two. /// </param> /// <param name="two"> /// Another content boundary position. May precede or follow the TextPointer one. /// Must belong to the same scope as TextPointer one. /// </param> /// <returns> /// true if surrounding formatting elements have beed deleted as a side effect. /// </returns> private static bool DeleteContentBetweenPositions(TextPointer one, TextPointer two) { Invariant.Assert(one.Parent == two.Parent, "inconsistent Parents: one and two"); if (one.CompareTo(two) < 0) { one.TextContainer.DeleteContentInternal(one, two); } else if (one.CompareTo(two) > 0) { two.TextContainer.DeleteContentInternal(two, one); } Invariant.Assert(one.CompareTo(two) == 0, "Positions one and two must be equal now"); return false; }
public static TextRange FindText(TextPointer findContainerStartPosition, TextPointer findContainerEndPosition, String input, FindFlags flags, CultureInfo cultureInfo) { TextRange textRange = null; if (findContainerStartPosition.CompareTo(findContainerEndPosition) < 0) { try { if (findMethod == null) { findMethod = typeof(FrameworkElement).Assembly.GetType("System.Windows.Documents.TextFindEngine"). GetMethod("Find", BindingFlags.Static | BindingFlags.Public); } textRange = findMethod.Invoke(null, new Object[] { findContainerStartPosition, findContainerEndPosition, input, flags, CultureInfo.CurrentCulture }) as TextRange; } catch (ApplicationException) { textRange = null; } } return textRange; }
/// <summary>Initializes a new instance of the <see cref="T:System.Windows.Documents.Span" /> class, taking two <see cref="T:System.Windows.Documents.TextPointer" /> objects that indicate the beginning and end of a selection of content that the new <see cref="T:System.Windows.Documents.Span" /> will contain.</summary> /// <param name="start">A <see cref="T:System.Windows.Documents.TextPointer" /> that indicates the beginning of a selection of content that the new <see cref="T:System.Windows.Documents.Span" /> will contain.</param> /// <param name="end">A <see cref="T:System.Windows.Documents.TextPointer" /> that indicates the end of a selection of content that the new <see cref="T:System.Windows.Documents.Span" /> will contain.</param> /// <exception cref="T:System.ArgumentNullException">Raised when <paramref name="start" /> or <paramref name="end" /> is null.</exception> /// <exception cref="T:System.ArgumentException">Raised when <paramref name="start" /> and <paramref name="end" /> do not resolve to a range of content suitable for enclosure by a <see cref="T:System.Windows.Documents.Span" /> element; for example, if <paramref name="start" /> and <paramref name="end" /> indicate positions in different paragraphs.</exception> // Token: 0x06003543 RID: 13635 RVA: 0x000F1218 File Offset: 0x000EF418 public Span(TextPointer start, TextPointer end) { if (start == null) { throw new ArgumentNullException("start"); } if (end == null) { throw new ArgumentNullException("start"); } if (start.TextContainer != end.TextContainer) { throw new ArgumentException(SR.Get("InDifferentTextContainers", new object[] { "start", "end" })); } if (start.CompareTo(end) > 0) { throw new ArgumentException(SR.Get("BadTextPositionOrder", new object[] { "start", "end" })); } start.TextContainer.BeginChange(); try { start = TextRangeEditTables.EnsureInsertionPosition(start); Invariant.Assert(start.Parent is Run); end = TextRangeEditTables.EnsureInsertionPosition(end); Invariant.Assert(end.Parent is Run); if (start.Paragraph != end.Paragraph) { throw new ArgumentException(SR.Get("InDifferentParagraphs", new object[] { "start", "end" })); } Inline nonMergeableInlineAncestor; if ((nonMergeableInlineAncestor = start.GetNonMergeableInlineAncestor()) != null) { throw new InvalidOperationException(SR.Get("TextSchema_CannotSplitElement", new object[] { nonMergeableInlineAncestor.GetType().Name })); } if ((nonMergeableInlineAncestor = end.GetNonMergeableInlineAncestor()) != null) { throw new InvalidOperationException(SR.Get("TextSchema_CannotSplitElement", new object[] { nonMergeableInlineAncestor.GetType().Name })); } TextElement commonAncestor = TextElement.GetCommonAncestor((TextElement)start.Parent, (TextElement)end.Parent); while (start.Parent != commonAncestor) { start = this.SplitElement(start); } while (end.Parent != commonAncestor) { end = this.SplitElement(end); } if (start.Parent is Run) { start = this.SplitElement(start); } if (end.Parent is Run) { end = this.SplitElement(end); } Invariant.Assert(start.Parent == end.Parent); Invariant.Assert(TextSchema.IsValidChild(start, typeof(Span))); base.Reposition(start, end); } finally { start.TextContainer.EndChange(); } }
/// <summary> /// Applies formatting properties for whole block elements. /// </summary> /// <param name="start"> /// a position within first block in sequence /// </param> /// <param name="end"> /// a positionn within last block in sequence /// </param> /// <param name="property"> /// property changed on blocks /// </param> /// <param name="value"> /// value for the property /// </param> /// <param name="propertyValueAction"> /// Specifies how to use the value - as absolute, as increment or a decrement. /// </param> internal static void SetParagraphProperty(TextPointer start, TextPointer end, DependencyProperty property, object value, PropertyValueAction propertyValueAction) { Invariant.Assert(start != null, "null check: start"); Invariant.Assert(end != null, "null check: end"); Invariant.Assert(start.CompareTo(end) <= 0, "expecting: start <= end"); Invariant.Assert(property != null, "null check: property"); // Exclude last opening tag to avoid affecting a paragraph following the selection end = (TextPointer)TextRangeEdit.GetAdjustedRangeEnd(start, end); // Expand start pointer to the beginning of the first paragraph/blockuicontainer Block startParagraphOrBlockUIContainer = start.ParagraphOrBlockUIContainer; if (startParagraphOrBlockUIContainer != null) { start = startParagraphOrBlockUIContainer.ContentStart; } // Applying FlowDirection requires splitting all containing lists on the range boundaries // because the property is applied to whole List element (to affect bullet appearence) if (property == Block.FlowDirectionProperty) { // Split any boundary lists if needed. // We want to maintain the invariant that all lists and paragraphs within a list, have the same FlowDirection value. // If paragraph FlowDirection command requests a different value of FlowDirection on parts of a list, // we split the list to maintain this invariant. if (!TextRangeEditLists.SplitListsForFlowDirectionChange(start, end, value)) { // If lists at start and end cannot be split successfully, we cannot apply FlowDirection property to the paragraph content. return; } // And expand range start to the beginning of the containing list ListItem listItem = start.GetListAncestor(); if (listItem != null && listItem.List != null) { start = listItem.List.ElementStart; } } // Walk all paragraphs in the affected segment. For FlowDirection property, also walk lists. SetParagraphPropertyWorker(start, end, property, value, propertyValueAction); }
/// <summary> /// Makes sure that the content spanned by the range is totally deleted /// including all structural elements and boundaries. /// We need this method to be able to make the range empty for any kind of content. /// This operation is straightforward for all kinds of ranges expect for /// TableCellRange. For cell range it means merging all selected cells /// and deleting the text content of resulting cell. /// </summary> internal static void DeleteContent(TextPointer start, TextPointer end) { // Order positions, as we do not to distinguish between anchor/moving ends in this operation; // but the following code depends on start<=end ordering if (start.CompareTo(end) > 0) { TextPointer whatWasEnd = end; end = start; start = whatWasEnd; } // Check whether we cross table structure boundaries TableCell startCell; TableCell endCell; TableRow startRow; TableRow endRow; TableRowGroup startRowGroup; TableRowGroup endRowGroup; Table startTable; Table endTable; // We need to run a loop here because after boundary tables deletions // we may encounter following tables, so that start/end range will be // again table-crossing. while ( start.CompareTo(end) < 0 && IdentifyTableElements( /*anchorPosition:*/start, /*movingPosition:*/end, /*includeCellAtMovingPosition:*/false, out startCell, out endCell, out startRow, out endRow, out startRowGroup, out endRowGroup, out startTable, out endTable)) { if (startTable == null && endTable == null || startTable == endTable) { bool isTableCellRange; List<TextSegment> textSegments = TextRangeEditTables.BuildTableRange( /*anchorPosition:*/start, /*movingPosition:*/end, /*includeCellAtMovingPosition*/false, out isTableCellRange); if (isTableCellRange && textSegments != null) { // It is cell selection. Create the content of all selected cells for (int i = 0; i < textSegments.Count; i++) { ClearTableCells(textSegments[i]); } // Collapse the range to bypass the folowing paragraph deletion end = start; } else { // Our range is within one table, so we need to delete // crossed row boundaries // Find start row if (startCell != null) { startRow = startCell.Row; } else if (startRow != null) { // do nothing here. we already have a start row for deletion. } else if (startRowGroup != null) { startRow = startRowGroup.Rows[0]; } // Find end row if (endCell != null) { endRow = startCell.Row; } else if (endRow != null) { // do nothing here. we already have an end row for deletion. } else if (endRowGroup != null) { endRow = endRowGroup.Rows[endRowGroup.Rows.Count - 1]; } Invariant.Assert(startRow != null && endRow != null, "startRow and endRow cannot be null, since our range is within one table"); TextRange rowsSegment = new TextRange(startRow.ContentStart, endRow.ContentEnd); TextRangeEditTables.DeleteRows(rowsSegment); // it will take care of rowspans } } else { // Table boundary is crossed on one or both of edges. // So we must delete half(s) of crossed table(s) if (startRow != null) { // Table boundary is crossed on start edge. // So we need to delete all rows from start to the end of this table. // Store position immediately after the first table start = startRow.Table.ElementEnd; // Delete all rows from startRow to the very end of the table TextRange rowsSegment = new TextRange(startRow.ContentStart, startRow.Table.ContentEnd); TextRangeEditTables.DeleteRows(rowsSegment); // it will take care of rowspans } if (endRow != null) { // Table boundary is crossed on end egde. // So we need to delete all rows from start of the table to this endRow. // Store position immediately before the second table end = endRow.Table.ElementStart; // Delete all rows from the beginning of the table to endRow TextRange rowsSegment = new TextRange(endRow.Table.ContentStart, endRow.ContentEnd); TextRangeEditTables.DeleteRows(rowsSegment); } } } // Now that we do not cross table structure, we can apply simple paragraph content deletion. // Delete remaining content between tables if (start.CompareTo(end) < 0) { // Note that both start and end are not normalized here, // which is important, say, when the block between deleted tables // was another Table or something - we want to delete the whole thing, whatever it is // and normalizting start/end would cross its boundary. TextRangeEdit.DeleteParagraphContent(start, end); } }
// Validates that the sibling element at this position belong to expected itemType (Inline, Block, ListItem) private static void ValidateMergingPositions(Type itemType, TextPointer start, TextPointer end) { if (start.CompareTo(end) < 0) { // Verify inner part TextPointerContext forwardFromStart = start.GetPointerContext(LogicalDirection.Forward); TextPointerContext backwardFromEnd = end.GetPointerContext(LogicalDirection.Backward); Invariant.Assert(forwardFromStart == TextPointerContext.ElementStart, "Expecting first opening tag of pasted fragment"); Invariant.Assert(backwardFromEnd == TextPointerContext.ElementEnd, "Expecting last closing tag of pasted fragment"); Invariant.Assert(itemType.IsAssignableFrom(start.GetAdjacentElement(LogicalDirection.Forward).GetType()), "The first pasted fragment item is expected to be a " + itemType.Name); Invariant.Assert(itemType.IsAssignableFrom(end.GetAdjacentElement(LogicalDirection.Backward).GetType()), "The last pasted fragment item is expected to be a " + itemType.Name); // Veryfy outer part TextPointerContext backwardFromStart = start.GetPointerContext(LogicalDirection.Backward); TextPointerContext forwardFromEnd = end.GetPointerContext(LogicalDirection.Forward); Invariant.Assert(backwardFromStart == TextPointerContext.ElementStart || backwardFromStart == TextPointerContext.ElementEnd || backwardFromStart == TextPointerContext.None, "Bad context preceding a pasted fragment"); Invariant.Assert(!(backwardFromStart == TextPointerContext.ElementEnd) || itemType.IsAssignableFrom(start.GetAdjacentElement(LogicalDirection.Backward).GetType()), "An element preceding a pasted fragment is expected to be a " + itemType.Name); Invariant.Assert(forwardFromEnd == TextPointerContext.ElementStart || forwardFromEnd == TextPointerContext.ElementEnd || forwardFromEnd == TextPointerContext.None, "Bad context following a pasted fragment"); Invariant.Assert(!(forwardFromEnd == TextPointerContext.ElementStart) || itemType.IsAssignableFrom(end.GetAdjacentElement(LogicalDirection.Forward).GetType()), "An element following a pasted fragment is expected to be a " + itemType.Name); } }
// ------------------------------------------------------------------ // 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; }
// Applies one property to a range from start to end to simulate inheritance of this property from source conntext private static void ApplyContextualProperty(Type targetType, TextPointer start, TextPointer end, DependencyProperty property, object value) { if (TextSchema.ValuesAreEqual(start.Parent.GetValue(property), value)) { return; // The property at insertion position is the same as it was in source context. Nothing to do. } // Advance start pointer to enter pasted fragment start = start.GetNextContextPosition(LogicalDirection.Forward); while (start != null && start.CompareTo(end) < 0) { TextPointerContext passedContext = start.GetPointerContext(LogicalDirection.Backward); if (passedContext == TextPointerContext.ElementStart) { TextElement element = (TextElement)start.Parent; // Check if this element affects the property in question if (element.ReadLocalValue(property) != DependencyProperty.UnsetValue || !TextSchema.ValuesAreEqual(element.GetValue(property), element.Parent.GetValue(property))) { // The element affects this property, so we can skip it start = element.ElementEnd; } else if (targetType.IsAssignableFrom(element.GetType())) { start = element.ElementEnd; if (targetType == typeof(Block) && start.CompareTo(end) > 0) { // Contextual properties should not apply to the last paragraph // when it is merged with the following content - // to avoid affecting the folowing visible content formatting. break; } // This is topmost-level inline element which inherits this property. // Set the value explicitly if (!TextSchema.ValuesAreEqual(value, element.GetValue(property))) { element.ClearValue(property); if (!TextSchema.ValuesAreEqual(value, element.GetValue(property))) { element.SetValue(property, value); } TextRangeEdit.MergeFormattingInlines(element.ElementStart); } } else { // Traverse down into a structured (non-innline) element start = start.GetNextContextPosition(LogicalDirection.Forward); } } else { // Traverse up from any element Invariant.Assert(passedContext != TextPointerContext.None, "TextPointerContext.None is not expected"); start = start.GetNextContextPosition(LogicalDirection.Forward); } } }
/// <summary> /// Find the corresponding <see cref="TextRange"/> instance /// representing the input string given a specified text pointer position. /// </summary> /// <param name="position">the current text position</param> /// <param name="textToFind">input text</param> /// <param name="findOptions">the search option</param> /// <returns>An <see cref="TextRange"/> instance represeneting the matching string withing the text container.</returns> public TextRange GetTextRangeFromPosition(ref TextPointer position, String input, FindOptions findOptions) { Boolean matchCase = findOptions.MatchCase; Boolean matchWholeWord = findOptions.MatchWholeWord; TextRange textRange = null; while (position != null) { if (position.CompareTo(inputDocument.ContentEnd) == 0) //到了文档结尾 { break; } //是文本元素 if (position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text) { String textRun = position.GetTextInRun(LogicalDirection.Forward); //读取文本 StringComparison stringComparison = matchCase ? StringComparison.CurrentCulture : StringComparison.CurrentCultureIgnoreCase; //进行查找 Int32 indexInRun = textRun.IndexOf(input, stringComparison); if (indexInRun >= 0) //找到了 { position = position.GetPositionAtOffset(indexInRun); //移动文字指针到开头 TextPointer nextPointer = position.GetPositionAtOffset(input.Length); //设定匹配项尾文字指针 textRange = new TextRange(position, nextPointer); if (matchWholeWord) //是全字匹配的话 { if (IsWholeWord(textRange)) // 测试匹配项是否是一个单词 { // 是一个完整的单词 break; } else { // 找到的不是一个完整的单词,继续在本Run元素中查找 position = position.GetPositionAtOffset(input.Length); return GetTextRangeFromPosition(ref position, input, findOptions); } } else { //不要求全字匹配 position = position.GetPositionAtOffset(input.Length); break; } } else { // 没找到匹配项,移到当前的Run元素之后 "textRun". position = position.GetPositionAtOffset(textRun.Length); } } else { //如果当前位置不是文本类型的元素,继续"前进",跳过这些非文本元素. position = position.GetNextContextPosition(LogicalDirection.Forward); } } return textRange; }
// Finds a Paragraph/BlockUIContainer/List element with ElementStart before or at the given pointer // Creates implicit paragraphs at potential paragraph positions if needed private static Block GetNextBlock(TextPointer pointer, TextPointer limit) { Block block = null; while (pointer != null && pointer.CompareTo(limit) <= 0) { if (pointer.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart) { block = pointer.Parent as Block; if (block is Paragraph || block is BlockUIContainer || block is List) { break; } } if (TextPointerBase.IsAtPotentialParagraphPosition(pointer)) { pointer = TextRangeEditTables.EnsureInsertionPosition(pointer); block = pointer.Paragraph; Invariant.Assert(block != null); break; } // Advance the scanning pointer pointer = pointer.GetNextContextPosition(LogicalDirection.Forward); } return block; }
// Applies a whole property bag to a range from start to end to simulate inheritance of this property from source conntext private static void ApplyContextualProperties(TextPointer start, TextPointer end, TextElement propertyBag) { Invariant.Assert(propertyBag.IsEmpty && propertyBag.Parent == null, "propertyBag is supposed to be an empty element outside any tree"); LocalValueEnumerator contextualProperties = propertyBag.GetLocalValueEnumerator(); while (start.CompareTo(end) < 0 && contextualProperties.MoveNext()) { // Note: we repeatedly check for IsEmpty because the selection // may become empty as a result of normalization after formatting // (thai character sequence). LocalValueEntry propertyEntry = contextualProperties.Current; DependencyProperty property = propertyEntry.Property; if (TextSchema.IsCharacterProperty(property) && TextSchema.IsParagraphProperty(property)) { // In case a property is both an Inline and Paragraph property, // propertyBag element type (section or span) decides how it should be applied. if (TextSchema.IsBlock(propertyBag.GetType())) { ApplyContextualProperty(typeof(Block), start, end, property, propertyEntry.Value); } else { ApplyContextualProperty(typeof(Inline), start, end, property, propertyEntry.Value); } } else if (TextSchema.IsCharacterProperty(property)) { ApplyContextualProperty(typeof(Inline), start, end, property, propertyEntry.Value); } else if (TextSchema.IsParagraphProperty(property)) { ApplyContextualProperty(typeof(Block), start, end, property, propertyEntry.Value); } } // Merge formatting elements at end position TextRangeEdit.MergeFormattingInlines(start); TextRangeEdit.MergeFormattingInlines(end); }
/// <summary> /// Creates a new Span instance covering existing content. /// </summary> /// <param name="start"> /// Start position of the new Span. /// </param> /// <param name="end"> /// End position of the new Span. /// </param> /// <remarks> /// start and end must both be parented by the same Paragraph, otherwise /// the method will raise an ArgumentException. /// </remarks> public Span(TextPointer start, TextPointer end) { if (start == null) { throw new ArgumentNullException("start"); } if (end == null) { throw new ArgumentNullException("start"); } if (start.TextContainer != end.TextContainer) { throw new ArgumentException(SR.Get(SRID.InDifferentTextContainers, "start", "end")); } if (start.CompareTo(end) > 0) { throw new ArgumentException(SR.Get(SRID.BadTextPositionOrder, "start", "end")); } start.TextContainer.BeginChange(); try { start = TextRangeEditTables.EnsureInsertionPosition(start); Invariant.Assert(start.Parent is Run); end = TextRangeEditTables.EnsureInsertionPosition(end); Invariant.Assert(end.Parent is Run); if (start.Paragraph != end.Paragraph) { throw new ArgumentException(SR.Get(SRID.InDifferentParagraphs, "start", "end")); } // If start or end positions have a Hyperlink ancestor, we cannot split them. Inline nonMergeableAncestor; if ((nonMergeableAncestor = start.GetNonMergeableInlineAncestor()) != null) { throw new InvalidOperationException(SR.Get(SRID.TextSchema_CannotSplitElement, nonMergeableAncestor.GetType().Name)); } if ((nonMergeableAncestor = end.GetNonMergeableInlineAncestor()) != null) { throw new InvalidOperationException(SR.Get(SRID.TextSchema_CannotSplitElement, nonMergeableAncestor.GetType().Name)); } TextElement commonAncestor = TextElement.GetCommonAncestor((TextElement)start.Parent, (TextElement)end.Parent); while (start.Parent != commonAncestor) { start = SplitElement(start); } while (end.Parent != commonAncestor) { end = SplitElement(end); } if (start.Parent is Run) { start = SplitElement(start); } if (end.Parent is Run) { end = SplitElement(end); } Invariant.Assert(start.Parent == end.Parent); Invariant.Assert(TextSchema.IsValidChild(/*position*/start, /*childType*/typeof(Span))); this.Reposition(start, end); } finally { start.TextContainer.EndChange(); } }
/// <summary> /// 根据搜索选项在文档中查找并替换全部匹配字串 /// </summary> /// <param name="input">要查找的字串</param> /// <param name="replacement">用于替换的字串</param> /// <param name="findOptions">搜索选项 </param> /// <returns>进行了替换的次数</returns> /// <remarks> /// 此方法将移动文字指针到最后位置 /// </remarks> public int ReplaceAll(String input, String replacement, FindOptions findOptions) { int count = 0; currentPosition = inputDocument.ContentStart; while (currentPosition.CompareTo(inputDocument.ContentEnd) < 0) { TextRange textRange = Replace(input, replacement, findOptions); if (textRange != null) { count++; } } return count; }
/// <summary> /// UpdateAccessKey - Scans forward in the tree looking for the access key marker, replacing it with access key element. We only support one find. /// </summary> private void UpdateAccessKey() { TextPointer navigator = new TextPointer(TextContainer.Start); while (!_accessKeyLocated && navigator.CompareTo(TextContainer.End) < 0 ) { TextPointerContext symbolType = navigator.GetPointerContext(LogicalDirection.Forward); switch (symbolType) { case TextPointerContext.Text: string text = navigator.GetTextInRun(LogicalDirection.Forward); int index = FindAccessKeyMarker(text); if(index != -1 && index < text.Length - 1) { string keyText = StringInfo.GetNextTextElement(text, index + 1); TextPointer keyEnd = navigator.GetPositionAtOffset(index + 1 + keyText.Length); _accessKey = new Run(keyText); _accessKey.Style = AccessKeyStyle; RegisterAccessKey(); HasCustomSerializationStorage.SetValue(_accessKey, true); _accessKeyLocated = true; UninitializeTextContainerListener(); TextContainer.BeginChange(); try { TextPointer underlineStart = new TextPointer(navigator, index); TextRangeEdit.DeleteInlineContent(underlineStart, keyEnd); _accessKey.RepositionWithContent(underlineStart); } finally { TextContainer.EndChange(); InitializeTextContainerListener(); } } break; } navigator.MoveToNextContextPosition(LogicalDirection.Forward); } // Convert double _ to single _ navigator = new TextPointer(TextContainer.Start); string accessKeyMarker = AccessKeyMarker.ToString(); string doubleAccessKeyMarker = accessKeyMarker + accessKeyMarker; while (navigator.CompareTo(TextContainer.End) < 0) { TextPointerContext symbolType = navigator.GetPointerContext(LogicalDirection.Forward); switch (symbolType) { case TextPointerContext.Text: string text = navigator.GetTextInRun(LogicalDirection.Forward); string nexText = text.Replace(doubleAccessKeyMarker, accessKeyMarker); if (text != nexText) { TextPointer keyStart = new TextPointer(navigator, 0); TextPointer keyEnd = new TextPointer(navigator, text.Length); UninitializeTextContainerListener(); TextContainer.BeginChange(); try { keyEnd.InsertTextInRun(nexText); TextRangeEdit.DeleteInlineContent(keyStart, keyEnd); } finally { TextContainer.EndChange(); InitializeTextContainerListener(); } } break; } navigator.MoveToNextContextPosition(LogicalDirection.Forward); } }
// Removes inline properties that affect formatting from the given range internal static void CharacterResetFormatting(TextPointer start, TextPointer end) { if (start.CompareTo(end) < 0) { // Split formatting elements at range boundaries start = SplitFormattingElements(start, /*keepEmptyFormatting:*/false, /*preserveStructuralFormatting*/true, /*limitingAncestor*/null); end = SplitFormattingElements(end, /*keepEmptyFormatting:*/false, /*preserveStructuralFormatting*/true, /*limitingAncestor*/null); while (start.CompareTo(end) < 0) { if (start.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart) { // When entering a next element check whether we should clear its inline properties. TextElement parent = (TextElement)start.Parent; // Note we do cleaning for Inline elements only - so properties set on Paragraphs // and other blocks will stay unchanged even if they set as local value. if (parent is Span && parent.ContentEnd.CompareTo(end) > 0) { // Preserve Hyperlink/Span properties when it is partially selected } // We can't assume that custom types derived from Span, once their formatting // properties are removed, can be transformed into a Span. So treat custom // types as inlines, even if they're derived from Span. else if (parent is Span && TextSchema.IsKnownType(parent.GetType())) { // Remember a position to merge inlines TextPointer mergePosition = parent.ElementStart; // Preserve only non-formatting properties of original span element. Span newSpan = TransferNonFormattingInlineProperties((Span)parent); if (newSpan != null) { newSpan.Reposition(parent.ElementStart, parent.ElementEnd); mergePosition = newSpan.ElementStart; } // Throw away original span parent.Reposition(null, null); // Now that content has changed, we must try to merge inlines at this position MergeFormattingInlines(mergePosition); } else if (parent is Inline) { ClearFormattingInlineProperties((Inline)parent); // Now that properties may be removed we must try to merge this element with a preceding one MergeFormattingInlines(parent.ElementStart); } } start = start.GetNextContextPosition(LogicalDirection.Forward); } // At the end try ro merge elements at end position MergeFormattingInlines(end); } }
private static List<TextSegment> BuildCrossTableSelection( TextPointer anchorPosition, TextPointer movingPosition, TableRow anchorRow, TableRow movingRow) { List<TextSegment> textSegments = new List<TextSegment>(1); if (anchorPosition.CompareTo(movingPosition) < 0) { textSegments.Add( NewNormalizedTextSegment( anchorRow != null ? anchorRow.ContentStart : anchorPosition, movingRow != null ? movingRow.ContentEnd : movingPosition)); } else { textSegments.Add( NewNormalizedTextSegment( movingRow != null ? movingRow.ContentStart : movingPosition, anchorRow != null ? anchorRow.ContentEnd : anchorPosition)); } return textSegments; }