Code:
/ 4.0 / 4.0 / DEVDIV_TFS / Dev10 / Releases / RTMRel / wpf / src / Framework / MS / Internal / Annotations / TextAnchor.cs / 1305600 / TextAnchor.cs
//------------------------------------------------------------------------------ // //// Copyright (C) Microsoft Corporation. All rights reserved. // // // Description: // TextAnchor represents a set of TextSegments that are part of an annotation's // attached anchor. The TextSegments do not overlap and are ordered. // // We cannot use TextRange for this purpose because we need to represent sets of // TextSegments that are not valid TextRanges (such as non-rectangular regions of // a table). // // History: // 11/16/2005: rruiz: creates the TextAnchor class // //----------------------------------------------------------------------------- using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Text; using System.Windows; using System.Windows.Documents; using MS.Internal; namespace System.Windows.Annotations { ////// public sealed class TextAnchor { //----------------------------------------------------- // // Constructors // //----------------------------------------------------- #region Constructors ////// Creates an empty TextAnchor. If left empty it will be invalid for most operations. /// internal TextAnchor() { } ////// Creates a clone of the passed in TextAnchor. /// /// internal TextAnchor(TextAnchor anchor) { Invariant.Assert(anchor != null, "Anchor to clone is null."); foreach (TextSegment segment in anchor.TextSegments) { _segments.Add(new TextSegment(segment.Start, segment.End)); } } /* * Code used to trim text segments for alternative display of sticky note anchors * ////// ctor that initializes the TextSegments array by cloning and trimming the input segment Array /// /// input segment ///This is used to convert a TextRange into TextAnchor. /// Input segments must be ordered and non overlapping internal TextAnchor(IListsegments) { if (segments == null) return; ITextPointer lastPointer = null; for (int i = 0; i < segments.Count; i++) { Invariant.Assert((lastPointer == null) || (lastPointer.CompareTo(segments[i].Start) <= 0), "overlapped segments found"); TextSegment newSegment = TextAnchor.Trim(segments[i]); if (newSegment.IsNull) continue; _segments.Add(newSegment); lastPointer = newSegment.End; } } */ #endregion Constructors //------------------------------------------------------ // // Public Methods // //----------------------------------------------------- #region Public Methods /// /// Determines if the text pointer is contained by one of the /// anchor's TextSegment.s /// /// text pointer to test internal bool Contains(ITextPointer textPointer) { if (textPointer == null) { throw new ArgumentNullException("textPointer"); } if (textPointer.TextContainer != this.Start.TextContainer) { throw new ArgumentException(SR.Get(SRID.NotInAssociatedTree, "textPointer")); } // Correct position normalization on range boundary so that // our test would not depend on what side of formatting tags // pointer is located. if (textPointer.CompareTo(this.Start) < 0) { textPointer = textPointer.GetInsertionPosition(LogicalDirection.Forward); } else if (textPointer.CompareTo(this.End) > 0) { textPointer = textPointer.GetInsertionPosition(LogicalDirection.Backward); } // Check if at least one segment contains this position. for (int i = 0; i < _segments.Count; i++) { if (_segments[i].Contains(textPointer)) { return true; } } return false; } ////// Add a text segment with the specified text pointers. /// /// start pointer for the new text segment /// end pointer for the new text segment internal void AddTextSegment(ITextPointer start, ITextPointer end) { Invariant.Assert(start != null, "Non-null start required to create segment."); Invariant.Assert(end != null, "Non-null end required to create segment."); TextSegment newSegment = CreateNormalizedSegment(start, end); InsertSegment(newSegment); } ////// Returns the hash code for this anchor. Implementation is required /// because Equals was overriden. /// public override int GetHashCode() { return base.GetHashCode(); } ////// Determines if two TextAnchors are equal - they contain /// the same number of segments and the segments all have the /// same start and ends. /// /// the other TextAnchor to compare to public override bool Equals(object obj) { TextAnchor other = obj as TextAnchor; if (other == null) return false; if (other._segments.Count != this._segments.Count) return false; for (int i = 0; i < _segments.Count; i++) { if ((_segments[i].Start.CompareTo(other._segments[i].Start) != 0) || (_segments[i].End.CompareTo(other._segments[i].End) != 0)) return false; } return true; } ////// Determines if there is any overlap between this anchor and the passed /// in set of TextSegments. /// /// set of segments to test against internal bool IsOverlapping(ICollectiontextSegments) { Invariant.Assert(textSegments != null, "TextSegments must not be null."); textSegments = SortTextSegments(textSegments, false); TextSegment ourSegment, theirSegment; IEnumerator ourEnumerator = _segments.GetEnumerator(); IEnumerator theirEnumerator = textSegments.GetEnumerator(); bool moreOurs = ourEnumerator.MoveNext(); bool moreTheirs = theirEnumerator.MoveNext(); while (moreOurs && moreTheirs) { ourSegment = ourEnumerator.Current; theirSegment = theirEnumerator.Current; //special case for 0 length segments if (theirSegment.Start.CompareTo(theirSegment.End) == 0) { // Check boundaries. If theirSegment is at the beginning/end of ourSegment // we check the LogicalDirection. Thus we can handle end of lines, end of pages, // bidiractional texts (arabic etc) // If their segment is at the start of ourSegment // We have overlapping if the direction of theirSegment.Start is toward ourSegment if ((ourSegment.Start.CompareTo(theirSegment.Start) == 0) && (theirSegment.Start.LogicalDirection == LogicalDirection.Forward)) return true; // If their segment is at the end of ourSegment // We have overlapping if the direction of theirSegment.End is toward ourSegment if ((ourSegment.End.CompareTo(theirSegment.End) == 0) && (theirSegment.End.LogicalDirection == LogicalDirection.Backward)) return true; } // our segment is after their segment, so try the next of their segments if (ourSegment.Start.CompareTo(theirSegment.End) >= 0) { moreTheirs = theirEnumerator.MoveNext(); // point to the next of their segments continue; } // our segment is before their segment so try next of our segments if (ourSegment.End.CompareTo(theirSegment.Start) <= 0) { moreOurs = ourEnumerator.MoveNext(); // point to the next of our segments continue; } // at this point we know for sure that there is some overlap return true; } // no overlaps found return false; } /// /// Calculate the 'exclusive' union of the two anchors. Exclusive means none of the segments /// contributed by either anchor are allowed to overlap. The method will throw an exception if /// they do. This method modifies the first anchor passed in. Callers should assign the /// result of this method to the anchor they passed in. /// internal static TextAnchor ExclusiveUnion(TextAnchor anchor, TextAnchor otherAnchor) { Invariant.Assert(anchor != null, "anchor must not be null."); Invariant.Assert(otherAnchor != null, "otherAnchor must not be null."); foreach (TextSegment segment in otherAnchor.TextSegments) { anchor.InsertSegment(segment); } return anchor; } ////// Modifies the passed in TextAnchor to contain its relative /// complement to the set of text segments passed in. The resulting /// TextAnchor contains those segments or portions of segments that do /// not overlap with the passed in segments in anyway. If after trimming /// the anchor has no more segments, null is returned instead. Callers /// should assign the result of this method to the anchor they passed in. /// /// the anchor to trim /// the text segments to calculate relative complement with ///Note: textSegments is expected to be ordered and contain no overlapping segments internal static TextAnchor TrimToRelativeComplement(TextAnchor anchor, ICollectiontextSegments) { Invariant.Assert(anchor != null, "Anchor must not be null."); Invariant.Assert(textSegments != null, "TextSegments must not be null."); textSegments = SortTextSegments(textSegments, true); IEnumerator enumerator = textSegments.GetEnumerator(); bool hasMore = enumerator.MoveNext(); int currentIndex = 0; TextSegment current; TextSegment otherSegment = TextSegment.Null; while (currentIndex < anchor._segments.Count && hasMore) { Invariant.Assert(otherSegment.Equals(TextSegment.Null) || otherSegment.Equals(enumerator.Current) || otherSegment.End.CompareTo(enumerator.Current.Start) <= 0, "TextSegments are overlapping or not ordered."); current = anchor._segments[currentIndex]; otherSegment = enumerator.Current; // Current segment is after other segment, no overlap // Also, done with the other segment, move to the next one if (current.Start.CompareTo(otherSegment.End) >= 0) { hasMore = enumerator.MoveNext(); continue; // No increment, still processing the current segment } // Current segment starts after other segment starts and ... if (current.Start.CompareTo(otherSegment.Start) >= 0) { // ends before other segment ends, complete overlap, remove the segment if (current.End.CompareTo(otherSegment.End) <= 0) { anchor._segments.RemoveAt(currentIndex); continue; // No increment, happens implicitly because of the removal } else { // ends after other segment, first portion of current overlaps, // create new segment from end of other segment to end of current anchor._segments[currentIndex] = CreateNormalizedSegment(otherSegment.End, current.End); // Done with the other segment, move to the next one hasMore = enumerator.MoveNext(); continue; // No increment, need to process just created segment } } // Current segment starts before other segment starts and ... else { // ends after it starts, first portion of current does not overlap, // create new segment for that portion if (current.End.CompareTo(otherSegment.Start) > 0) { anchor._segments[currentIndex] = CreateNormalizedSegment(current.Start, otherSegment.Start); // If there's any portion of current after other segment, create a new segment for that which // will be the next one processed if (current.End.CompareTo(otherSegment.End) > 0) { // Overlap ends before current segment's end, we create a new segment with the remainder of current segment anchor._segments.Insert(currentIndex + 1, CreateNormalizedSegment(otherSegment.End, current.End)); // Done with the other segment, move to the next one hasMore = enumerator.MoveNext(); } } // ends before it starts, current is completely before other, no overlap, do nothing } currentIndex++; } if (anchor._segments.Count > 0) return anchor; else return null; } /// /// Modifies the text anchor's TextSegments so all of them /// overlap with the passed in text segments. This is used /// for instance to clamp a TextAnchor to a set of visible /// text segments. If after trimming the anchor has no more /// segments, null is returned instead. Callers should /// assign the result of this method to the anchor they /// passed in. /// ////// Note: This method assumes textSegments is ordered and do not overlap amongs themselves /// /// The target of the method is to trim this anchor's segments to overlap with the passed in segments. /// The loop handles the following three cases - /// 1. Current segment is after other segment, the other segment doesn't contribute at all, we move to the next other segment /// 2. Current segment is before other segment, no overlap, remove current segment /// 3. Current segment starts before other segment, and ends after other segment begins, /// therefore the portion from current's start to other's start should be trimmed /// 4. Current segment starts in the middle of other segment, two possibilities /// a. current segment is completely within other segment, the whole segment overlaps /// so we move on to the next current segment /// b. current segment ends after other segment ends, we split current into the /// overlapped portion and the remainder which will be looked at separately /// /// the anchor to trim /// collection of text segments to intersect with internal static TextAnchor TrimToIntersectionWith(TextAnchor anchor, ICollectiontextSegments) { Invariant.Assert(anchor != null, "Anchor must not be null."); Invariant.Assert(textSegments != null, "TextSegments must not be null."); textSegments = SortTextSegments(textSegments, true); TextSegment currentSegment, otherSegment = TextSegment.Null; int current = 0; IEnumerator enumerator = textSegments.GetEnumerator(); bool hasMore = enumerator.MoveNext(); while (current < anchor._segments.Count && hasMore) { Invariant.Assert(otherSegment.Equals(TextSegment.Null) || otherSegment.Equals(enumerator.Current) || otherSegment.End.CompareTo(enumerator.Current.Start) <= 0, "TextSegments are overlapping or not ordered."); currentSegment = anchor._segments[current]; otherSegment = enumerator.Current; // Current segment is after other segment, so try the next other segment if (currentSegment.Start.CompareTo(otherSegment.End) >= 0) { hasMore = enumerator.MoveNext(); // point to the next other continue; // Do not increment, we are still on the same current } // Current segment is before other segment, no overlap so remove it and continue if (currentSegment.End.CompareTo(otherSegment.Start) <= 0) { anchor._segments.RemoveAt(current); continue; // Do not increment, it happens implicitly because of the remove } // // We know from here down that there is some overlap. // // Current starts before the other segment and ends after other segment begins, the first portion of current segment doesn't overlap so we remove it if (currentSegment.Start.CompareTo(otherSegment.Start) < 0) { anchor._segments[current] = CreateNormalizedSegment(otherSegment.Start, currentSegment.End); continue; // Do not increment, we need to look at this just created segment } // Current segment begins in the middle of other segment... else { // and ends after other segment does, we split current into the portion that is overlapping and the remainder if (currentSegment.End.CompareTo(otherSegment.End) > 0) { anchor._segments[current] = CreateNormalizedSegment(currentSegment.Start, otherSegment.End); // This segment will be the first one looked at next anchor._segments.Insert(current + 1, CreateNormalizedSegment(otherSegment.End, currentSegment.End)); hasMore = enumerator.MoveNext(); } // and ends at the same place as other segment, its completely overlapping, we move on to the next other else if (currentSegment.End.CompareTo(otherSegment.End) == 0) { hasMore = enumerator.MoveNext(); } // and ends within other segment, its completely overlapping, but we aren't done with other so we just continue } current++; } // If we finished and there are no more other segments, then any remaining segments // in our list must not overlap, so we remove them. if (!hasMore && current < anchor._segments.Count) { anchor._segments.RemoveRange(current, anchor._segments.Count - current); } if (anchor._segments.Count == 0) return null; else return anchor; } #endregion Public Methods //------------------------------------------------------ // // Public Properties // //------------------------------------------------------ #region Public Properties /// /// The start of the bounding range of this TextAnchor. /// public ContentPosition BoundingStart { get { return Start as ContentPosition; } } ////// The end of the bounding range of this TextAnchor. /// public ContentPosition BoundingEnd { get { return End as ContentPosition; } } #endregion Public Properties //----------------------------------------------------- // // Internal Properties // //------------------------------------------------------ #region Internal Properties ////// The start pointer of the first segment in the TextAnchor /// internal ITextPointer Start { get { return _segments.Count > 0 ? _segments[0].Start : null; } } ////// The end pointer of the last segment in the TextAnchor /// internal ITextPointer End { get { return _segments.Count > 0 ? _segments[_segments.Count - 1].End : null; } } ////// Returns whether or not this text anchor is empty - meaning /// it has one text segment whose start and end are the same. /// internal bool IsEmpty { get { return (_segments.Count == 1 && (object)_segments[0].Start == (object)_segments[0].End); } } ////// Returns a concatenation of the text for each of this anchor's /// TextSegments. /// internal string Text { get { // Buffer for building a resulting plain text StringBuilder textBuffer = new StringBuilder(); for (int i = 0; i < _segments.Count; i++) { textBuffer.Append(TextRangeBase.GetTextInternal(_segments[i].Start, _segments[i].End)); } return textBuffer.ToString(); } } ////// Returns a read only collection of this anchor's TextSegments. /// internal ReadOnlyCollectionTextSegments { get { return _segments.AsReadOnly(); } } #endregion Internal Properties //----------------------------------------------------- // // Private Methods // //----------------------------------------------------- #region Private Methods /// /// Sorts a list of text segments by their Start pointer first then End pointer. /// Used because list of TextSegments from a TextView are not guaranteed to be sorted /// but in most cases they are. /// Note: In most cases the set of segments is of count 1 and this method is a no-op. /// In the majority of other cases the number of segments is less than 5. /// In extreme cases (such as a table with many, many columns and each cell /// in a row being split across pages) you may have more than 5 segments /// but this is very rare. /// /// segments to be sorted /// We've seen 0 length segments in the TextView that overlap other segments /// this will break our algorithm, so we remove them (excludeZeroLength = true). When we calculate /// IsOverlapping 0-length segments are OK - then excludeZeroLength is false private static ICollectionSortTextSegments(ICollection textSegments, bool excludeZeroLength) { Invariant.Assert(textSegments != null, "TextSegments must not be null."); List orderedList = new List (textSegments.Count); orderedList.AddRange(textSegments); if (excludeZeroLength) { //remove 0 length segments - work around for a bug in MultiPageTextView for (int i = orderedList.Count - 1; i >= 0; i--) { TextSegment segment = orderedList[i]; if (segment.Start.CompareTo(segment.End) >= 0) { //remove that one orderedList.Remove(segment); } } } // If there are 0 or 1 segments, no need to sort, just return the original collection if (orderedList.Count > 1) { orderedList.Sort(new TextSegmentComparer()); } return orderedList; } /// /// Inserts a segment into this anchor in the right order. If the new segment /// overlaps with existing anchors it throws an exception. /// private void InsertSegment(TextSegment newSegment) { int i = 0; for (; i < _segments.Count; i++) { if (newSegment.Start.CompareTo(_segments[i].Start) < 0) break; } // Make sure it starts after the one its being put behind if (i > 0 && newSegment.Start.CompareTo(_segments[i - 1].End) < 0) throw new InvalidOperationException(SR.Get(SRID.TextSegmentsMustNotOverlap)); // Make sure it ends before the one its being put ahead of if (i < _segments.Count && newSegment.End.CompareTo(_segments[i].Start) > 0) throw new InvalidOperationException(SR.Get(SRID.TextSegmentsMustNotOverlap)); _segments.Insert(i, newSegment); } ////// Creates a new segment with the specified pointers, but first /// normalizes them to make sure they are on insertion positions. /// /// start of the new segment /// end of the new segment private static TextSegment CreateNormalizedSegment(ITextPointer start, ITextPointer end) { // Normalize the segment if (start.CompareTo(end) == 0) { // When the range is empty we must keep it that way during normalization if (!TextPointerBase.IsAtInsertionPosition(start, start.LogicalDirection)) { start = start.GetInsertionPosition(start.LogicalDirection); end = start; } } else { if (!TextPointerBase.IsAtInsertionPosition(start, start.LogicalDirection)) { start = start.GetInsertionPosition(LogicalDirection.Forward); } if (!TextPointerBase.IsAtInsertionPosition(end, start.LogicalDirection)) { end = end.GetInsertionPosition(LogicalDirection.Backward); } // Collapse range in case of overlapped normalization result if (start.CompareTo(end) >= 0) { // The range is effectuvely empty, so collapse it to single pointer instance if (start.LogicalDirection == LogicalDirection.Backward) { // Choose a position normalized backward, start = end.GetFrozenPointer(LogicalDirection.Backward); // NOTE that otherwise we will use start position, // which is oriented and normalizd Forward } end = start; } } return new TextSegment(start, end); } // // Code used to trim text segments for alternative display of sticky note anchors. // ////////// Trims certain whitespace off ends of segments if they fit certain ///// conditions - such as being inside of an embedded element. ///// Returns a whole new TextSegment that's been trimmed or TextSegment.Null ///// if the trimming results in a non-existent TextSegment. ///// //private static TextSegment Trim(TextSegment segment) //{ // ITextPointer cursor = segment.Start.CreatePointer(); // ITextPointer segmentStart = null; // TextPointerContext nextContext = cursor.GetPointerContext(LogicalDirection.Forward); // while ((cursor.CompareTo(segment.End) < 0) && // (nextContext != TextPointerContext.Text) && // (nextContext != TextPointerContext.EmbeddedElement)) // { // // Simply skip all other opening tags // cursor.MoveToNextContextPosition(LogicalDirection.Forward); // nextContext = cursor.GetPointerContext(LogicalDirection.Forward); // } // while (cursor.CompareTo(segment.End) >= 0) // return TextSegment.Null; // segmentStart = cursor; // cursor = segment.End.CreatePointer(); // nextContext = cursor.GetPointerContext(LogicalDirection.Backward); // while ((cursor.CompareTo(segmentStart) > 0) && // (nextContext != TextPointerContext.Text) && // (nextContext != TextPointerContext.EmbeddedElement)) // { // cursor.MoveToNextContextPosition(LogicalDirection.Backward); // nextContext = cursor.GetPointerContext(LogicalDirection.Backward); // } // return segmentStart.CompareTo(cursor) < 0 ? new TextSegment(segmentStart, cursor) : TextSegment.Null; //} // #endregion Private Methods //----------------------------------------------------- // // Private Fields // //------------------------------------------------------ #region Private Fields // List of text segments for this anchor private List_segments = new List (1); #endregion Private Fields //----------------------------------------------------- // // Private Classes // //------------------------------------------------------ #region Private Classes /// /// Simple comparer class that sorts TextSegments by their Start pointers. /// If Start pointers are the same, then they are sorted by their End pointers. /// Null is sorted as less than a non-null result. /// private class TextSegmentComparer : IComparer{ /// /// All comparisons are done a segments Start pointer. If /// those are the same, then the End pointers are compared. /// Returns 0 if x is == to y; -1 if x is less than y; 1 if x is greater than y. /// If x is null and y is not, returns -1; if y is null and x is not, returns 1. /// public int Compare(TextSegment x, TextSegment y) { if (x.Equals(TextSegment.Null)) { // Both are null if (y.Equals(TextSegment.Null)) return 0; // x is null but y is not else return -1; } else { // x is not null but y is if (y.Equals(TextSegment.Null)) return 1; else { int retVal = x.Start.CompareTo(y.Start); // If starts are different, return their comparison if (retVal != 0) return retVal; // Otherwise return the comparison of the ends else return x.End.CompareTo(y.End); } } } } #endregion Private Classes } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------------------------ // //// Copyright (C) Microsoft Corporation. All rights reserved. // // // Description: // TextAnchor represents a set of TextSegments that are part of an annotation's // attached anchor. The TextSegments do not overlap and are ordered. // // We cannot use TextRange for this purpose because we need to represent sets of // TextSegments that are not valid TextRanges (such as non-rectangular regions of // a table). // // History: // 11/16/2005: rruiz: creates the TextAnchor class // //----------------------------------------------------------------------------- using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Text; using System.Windows; using System.Windows.Documents; using MS.Internal; namespace System.Windows.Annotations { ////// public sealed class TextAnchor { //----------------------------------------------------- // // Constructors // //----------------------------------------------------- #region Constructors ////// Creates an empty TextAnchor. If left empty it will be invalid for most operations. /// internal TextAnchor() { } ////// Creates a clone of the passed in TextAnchor. /// /// internal TextAnchor(TextAnchor anchor) { Invariant.Assert(anchor != null, "Anchor to clone is null."); foreach (TextSegment segment in anchor.TextSegments) { _segments.Add(new TextSegment(segment.Start, segment.End)); } } /* * Code used to trim text segments for alternative display of sticky note anchors * ////// ctor that initializes the TextSegments array by cloning and trimming the input segment Array /// /// input segment ///This is used to convert a TextRange into TextAnchor. /// Input segments must be ordered and non overlapping internal TextAnchor(IListsegments) { if (segments == null) return; ITextPointer lastPointer = null; for (int i = 0; i < segments.Count; i++) { Invariant.Assert((lastPointer == null) || (lastPointer.CompareTo(segments[i].Start) <= 0), "overlapped segments found"); TextSegment newSegment = TextAnchor.Trim(segments[i]); if (newSegment.IsNull) continue; _segments.Add(newSegment); lastPointer = newSegment.End; } } */ #endregion Constructors //------------------------------------------------------ // // Public Methods // //----------------------------------------------------- #region Public Methods /// /// Determines if the text pointer is contained by one of the /// anchor's TextSegment.s /// /// text pointer to test internal bool Contains(ITextPointer textPointer) { if (textPointer == null) { throw new ArgumentNullException("textPointer"); } if (textPointer.TextContainer != this.Start.TextContainer) { throw new ArgumentException(SR.Get(SRID.NotInAssociatedTree, "textPointer")); } // Correct position normalization on range boundary so that // our test would not depend on what side of formatting tags // pointer is located. if (textPointer.CompareTo(this.Start) < 0) { textPointer = textPointer.GetInsertionPosition(LogicalDirection.Forward); } else if (textPointer.CompareTo(this.End) > 0) { textPointer = textPointer.GetInsertionPosition(LogicalDirection.Backward); } // Check if at least one segment contains this position. for (int i = 0; i < _segments.Count; i++) { if (_segments[i].Contains(textPointer)) { return true; } } return false; } ////// Add a text segment with the specified text pointers. /// /// start pointer for the new text segment /// end pointer for the new text segment internal void AddTextSegment(ITextPointer start, ITextPointer end) { Invariant.Assert(start != null, "Non-null start required to create segment."); Invariant.Assert(end != null, "Non-null end required to create segment."); TextSegment newSegment = CreateNormalizedSegment(start, end); InsertSegment(newSegment); } ////// Returns the hash code for this anchor. Implementation is required /// because Equals was overriden. /// public override int GetHashCode() { return base.GetHashCode(); } ////// Determines if two TextAnchors are equal - they contain /// the same number of segments and the segments all have the /// same start and ends. /// /// the other TextAnchor to compare to public override bool Equals(object obj) { TextAnchor other = obj as TextAnchor; if (other == null) return false; if (other._segments.Count != this._segments.Count) return false; for (int i = 0; i < _segments.Count; i++) { if ((_segments[i].Start.CompareTo(other._segments[i].Start) != 0) || (_segments[i].End.CompareTo(other._segments[i].End) != 0)) return false; } return true; } ////// Determines if there is any overlap between this anchor and the passed /// in set of TextSegments. /// /// set of segments to test against internal bool IsOverlapping(ICollectiontextSegments) { Invariant.Assert(textSegments != null, "TextSegments must not be null."); textSegments = SortTextSegments(textSegments, false); TextSegment ourSegment, theirSegment; IEnumerator ourEnumerator = _segments.GetEnumerator(); IEnumerator theirEnumerator = textSegments.GetEnumerator(); bool moreOurs = ourEnumerator.MoveNext(); bool moreTheirs = theirEnumerator.MoveNext(); while (moreOurs && moreTheirs) { ourSegment = ourEnumerator.Current; theirSegment = theirEnumerator.Current; //special case for 0 length segments if (theirSegment.Start.CompareTo(theirSegment.End) == 0) { // Check boundaries. If theirSegment is at the beginning/end of ourSegment // we check the LogicalDirection. Thus we can handle end of lines, end of pages, // bidiractional texts (arabic etc) // If their segment is at the start of ourSegment // We have overlapping if the direction of theirSegment.Start is toward ourSegment if ((ourSegment.Start.CompareTo(theirSegment.Start) == 0) && (theirSegment.Start.LogicalDirection == LogicalDirection.Forward)) return true; // If their segment is at the end of ourSegment // We have overlapping if the direction of theirSegment.End is toward ourSegment if ((ourSegment.End.CompareTo(theirSegment.End) == 0) && (theirSegment.End.LogicalDirection == LogicalDirection.Backward)) return true; } // our segment is after their segment, so try the next of their segments if (ourSegment.Start.CompareTo(theirSegment.End) >= 0) { moreTheirs = theirEnumerator.MoveNext(); // point to the next of their segments continue; } // our segment is before their segment so try next of our segments if (ourSegment.End.CompareTo(theirSegment.Start) <= 0) { moreOurs = ourEnumerator.MoveNext(); // point to the next of our segments continue; } // at this point we know for sure that there is some overlap return true; } // no overlaps found return false; } /// /// Calculate the 'exclusive' union of the two anchors. Exclusive means none of the segments /// contributed by either anchor are allowed to overlap. The method will throw an exception if /// they do. This method modifies the first anchor passed in. Callers should assign the /// result of this method to the anchor they passed in. /// internal static TextAnchor ExclusiveUnion(TextAnchor anchor, TextAnchor otherAnchor) { Invariant.Assert(anchor != null, "anchor must not be null."); Invariant.Assert(otherAnchor != null, "otherAnchor must not be null."); foreach (TextSegment segment in otherAnchor.TextSegments) { anchor.InsertSegment(segment); } return anchor; } ////// Modifies the passed in TextAnchor to contain its relative /// complement to the set of text segments passed in. The resulting /// TextAnchor contains those segments or portions of segments that do /// not overlap with the passed in segments in anyway. If after trimming /// the anchor has no more segments, null is returned instead. Callers /// should assign the result of this method to the anchor they passed in. /// /// the anchor to trim /// the text segments to calculate relative complement with ///Note: textSegments is expected to be ordered and contain no overlapping segments internal static TextAnchor TrimToRelativeComplement(TextAnchor anchor, ICollectiontextSegments) { Invariant.Assert(anchor != null, "Anchor must not be null."); Invariant.Assert(textSegments != null, "TextSegments must not be null."); textSegments = SortTextSegments(textSegments, true); IEnumerator enumerator = textSegments.GetEnumerator(); bool hasMore = enumerator.MoveNext(); int currentIndex = 0; TextSegment current; TextSegment otherSegment = TextSegment.Null; while (currentIndex < anchor._segments.Count && hasMore) { Invariant.Assert(otherSegment.Equals(TextSegment.Null) || otherSegment.Equals(enumerator.Current) || otherSegment.End.CompareTo(enumerator.Current.Start) <= 0, "TextSegments are overlapping or not ordered."); current = anchor._segments[currentIndex]; otherSegment = enumerator.Current; // Current segment is after other segment, no overlap // Also, done with the other segment, move to the next one if (current.Start.CompareTo(otherSegment.End) >= 0) { hasMore = enumerator.MoveNext(); continue; // No increment, still processing the current segment } // Current segment starts after other segment starts and ... if (current.Start.CompareTo(otherSegment.Start) >= 0) { // ends before other segment ends, complete overlap, remove the segment if (current.End.CompareTo(otherSegment.End) <= 0) { anchor._segments.RemoveAt(currentIndex); continue; // No increment, happens implicitly because of the removal } else { // ends after other segment, first portion of current overlaps, // create new segment from end of other segment to end of current anchor._segments[currentIndex] = CreateNormalizedSegment(otherSegment.End, current.End); // Done with the other segment, move to the next one hasMore = enumerator.MoveNext(); continue; // No increment, need to process just created segment } } // Current segment starts before other segment starts and ... else { // ends after it starts, first portion of current does not overlap, // create new segment for that portion if (current.End.CompareTo(otherSegment.Start) > 0) { anchor._segments[currentIndex] = CreateNormalizedSegment(current.Start, otherSegment.Start); // If there's any portion of current after other segment, create a new segment for that which // will be the next one processed if (current.End.CompareTo(otherSegment.End) > 0) { // Overlap ends before current segment's end, we create a new segment with the remainder of current segment anchor._segments.Insert(currentIndex + 1, CreateNormalizedSegment(otherSegment.End, current.End)); // Done with the other segment, move to the next one hasMore = enumerator.MoveNext(); } } // ends before it starts, current is completely before other, no overlap, do nothing } currentIndex++; } if (anchor._segments.Count > 0) return anchor; else return null; } /// /// Modifies the text anchor's TextSegments so all of them /// overlap with the passed in text segments. This is used /// for instance to clamp a TextAnchor to a set of visible /// text segments. If after trimming the anchor has no more /// segments, null is returned instead. Callers should /// assign the result of this method to the anchor they /// passed in. /// ////// Note: This method assumes textSegments is ordered and do not overlap amongs themselves /// /// The target of the method is to trim this anchor's segments to overlap with the passed in segments. /// The loop handles the following three cases - /// 1. Current segment is after other segment, the other segment doesn't contribute at all, we move to the next other segment /// 2. Current segment is before other segment, no overlap, remove current segment /// 3. Current segment starts before other segment, and ends after other segment begins, /// therefore the portion from current's start to other's start should be trimmed /// 4. Current segment starts in the middle of other segment, two possibilities /// a. current segment is completely within other segment, the whole segment overlaps /// so we move on to the next current segment /// b. current segment ends after other segment ends, we split current into the /// overlapped portion and the remainder which will be looked at separately /// /// the anchor to trim /// collection of text segments to intersect with internal static TextAnchor TrimToIntersectionWith(TextAnchor anchor, ICollectiontextSegments) { Invariant.Assert(anchor != null, "Anchor must not be null."); Invariant.Assert(textSegments != null, "TextSegments must not be null."); textSegments = SortTextSegments(textSegments, true); TextSegment currentSegment, otherSegment = TextSegment.Null; int current = 0; IEnumerator enumerator = textSegments.GetEnumerator(); bool hasMore = enumerator.MoveNext(); while (current < anchor._segments.Count && hasMore) { Invariant.Assert(otherSegment.Equals(TextSegment.Null) || otherSegment.Equals(enumerator.Current) || otherSegment.End.CompareTo(enumerator.Current.Start) <= 0, "TextSegments are overlapping or not ordered."); currentSegment = anchor._segments[current]; otherSegment = enumerator.Current; // Current segment is after other segment, so try the next other segment if (currentSegment.Start.CompareTo(otherSegment.End) >= 0) { hasMore = enumerator.MoveNext(); // point to the next other continue; // Do not increment, we are still on the same current } // Current segment is before other segment, no overlap so remove it and continue if (currentSegment.End.CompareTo(otherSegment.Start) <= 0) { anchor._segments.RemoveAt(current); continue; // Do not increment, it happens implicitly because of the remove } // // We know from here down that there is some overlap. // // Current starts before the other segment and ends after other segment begins, the first portion of current segment doesn't overlap so we remove it if (currentSegment.Start.CompareTo(otherSegment.Start) < 0) { anchor._segments[current] = CreateNormalizedSegment(otherSegment.Start, currentSegment.End); continue; // Do not increment, we need to look at this just created segment } // Current segment begins in the middle of other segment... else { // and ends after other segment does, we split current into the portion that is overlapping and the remainder if (currentSegment.End.CompareTo(otherSegment.End) > 0) { anchor._segments[current] = CreateNormalizedSegment(currentSegment.Start, otherSegment.End); // This segment will be the first one looked at next anchor._segments.Insert(current + 1, CreateNormalizedSegment(otherSegment.End, currentSegment.End)); hasMore = enumerator.MoveNext(); } // and ends at the same place as other segment, its completely overlapping, we move on to the next other else if (currentSegment.End.CompareTo(otherSegment.End) == 0) { hasMore = enumerator.MoveNext(); } // and ends within other segment, its completely overlapping, but we aren't done with other so we just continue } current++; } // If we finished and there are no more other segments, then any remaining segments // in our list must not overlap, so we remove them. if (!hasMore && current < anchor._segments.Count) { anchor._segments.RemoveRange(current, anchor._segments.Count - current); } if (anchor._segments.Count == 0) return null; else return anchor; } #endregion Public Methods //------------------------------------------------------ // // Public Properties // //------------------------------------------------------ #region Public Properties /// /// The start of the bounding range of this TextAnchor. /// public ContentPosition BoundingStart { get { return Start as ContentPosition; } } ////// The end of the bounding range of this TextAnchor. /// public ContentPosition BoundingEnd { get { return End as ContentPosition; } } #endregion Public Properties //----------------------------------------------------- // // Internal Properties // //------------------------------------------------------ #region Internal Properties ////// The start pointer of the first segment in the TextAnchor /// internal ITextPointer Start { get { return _segments.Count > 0 ? _segments[0].Start : null; } } ////// The end pointer of the last segment in the TextAnchor /// internal ITextPointer End { get { return _segments.Count > 0 ? _segments[_segments.Count - 1].End : null; } } ////// Returns whether or not this text anchor is empty - meaning /// it has one text segment whose start and end are the same. /// internal bool IsEmpty { get { return (_segments.Count == 1 && (object)_segments[0].Start == (object)_segments[0].End); } } ////// Returns a concatenation of the text for each of this anchor's /// TextSegments. /// internal string Text { get { // Buffer for building a resulting plain text StringBuilder textBuffer = new StringBuilder(); for (int i = 0; i < _segments.Count; i++) { textBuffer.Append(TextRangeBase.GetTextInternal(_segments[i].Start, _segments[i].End)); } return textBuffer.ToString(); } } ////// Returns a read only collection of this anchor's TextSegments. /// internal ReadOnlyCollectionTextSegments { get { return _segments.AsReadOnly(); } } #endregion Internal Properties //----------------------------------------------------- // // Private Methods // //----------------------------------------------------- #region Private Methods /// /// Sorts a list of text segments by their Start pointer first then End pointer. /// Used because list of TextSegments from a TextView are not guaranteed to be sorted /// but in most cases they are. /// Note: In most cases the set of segments is of count 1 and this method is a no-op. /// In the majority of other cases the number of segments is less than 5. /// In extreme cases (such as a table with many, many columns and each cell /// in a row being split across pages) you may have more than 5 segments /// but this is very rare. /// /// segments to be sorted /// We've seen 0 length segments in the TextView that overlap other segments /// this will break our algorithm, so we remove them (excludeZeroLength = true). When we calculate /// IsOverlapping 0-length segments are OK - then excludeZeroLength is false private static ICollectionSortTextSegments(ICollection textSegments, bool excludeZeroLength) { Invariant.Assert(textSegments != null, "TextSegments must not be null."); List orderedList = new List (textSegments.Count); orderedList.AddRange(textSegments); if (excludeZeroLength) { //remove 0 length segments - work around for a bug in MultiPageTextView for (int i = orderedList.Count - 1; i >= 0; i--) { TextSegment segment = orderedList[i]; if (segment.Start.CompareTo(segment.End) >= 0) { //remove that one orderedList.Remove(segment); } } } // If there are 0 or 1 segments, no need to sort, just return the original collection if (orderedList.Count > 1) { orderedList.Sort(new TextSegmentComparer()); } return orderedList; } /// /// Inserts a segment into this anchor in the right order. If the new segment /// overlaps with existing anchors it throws an exception. /// private void InsertSegment(TextSegment newSegment) { int i = 0; for (; i < _segments.Count; i++) { if (newSegment.Start.CompareTo(_segments[i].Start) < 0) break; } // Make sure it starts after the one its being put behind if (i > 0 && newSegment.Start.CompareTo(_segments[i - 1].End) < 0) throw new InvalidOperationException(SR.Get(SRID.TextSegmentsMustNotOverlap)); // Make sure it ends before the one its being put ahead of if (i < _segments.Count && newSegment.End.CompareTo(_segments[i].Start) > 0) throw new InvalidOperationException(SR.Get(SRID.TextSegmentsMustNotOverlap)); _segments.Insert(i, newSegment); } ////// Creates a new segment with the specified pointers, but first /// normalizes them to make sure they are on insertion positions. /// /// start of the new segment /// end of the new segment private static TextSegment CreateNormalizedSegment(ITextPointer start, ITextPointer end) { // Normalize the segment if (start.CompareTo(end) == 0) { // When the range is empty we must keep it that way during normalization if (!TextPointerBase.IsAtInsertionPosition(start, start.LogicalDirection)) { start = start.GetInsertionPosition(start.LogicalDirection); end = start; } } else { if (!TextPointerBase.IsAtInsertionPosition(start, start.LogicalDirection)) { start = start.GetInsertionPosition(LogicalDirection.Forward); } if (!TextPointerBase.IsAtInsertionPosition(end, start.LogicalDirection)) { end = end.GetInsertionPosition(LogicalDirection.Backward); } // Collapse range in case of overlapped normalization result if (start.CompareTo(end) >= 0) { // The range is effectuvely empty, so collapse it to single pointer instance if (start.LogicalDirection == LogicalDirection.Backward) { // Choose a position normalized backward, start = end.GetFrozenPointer(LogicalDirection.Backward); // NOTE that otherwise we will use start position, // which is oriented and normalizd Forward } end = start; } } return new TextSegment(start, end); } // // Code used to trim text segments for alternative display of sticky note anchors. // ////////// Trims certain whitespace off ends of segments if they fit certain ///// conditions - such as being inside of an embedded element. ///// Returns a whole new TextSegment that's been trimmed or TextSegment.Null ///// if the trimming results in a non-existent TextSegment. ///// //private static TextSegment Trim(TextSegment segment) //{ // ITextPointer cursor = segment.Start.CreatePointer(); // ITextPointer segmentStart = null; // TextPointerContext nextContext = cursor.GetPointerContext(LogicalDirection.Forward); // while ((cursor.CompareTo(segment.End) < 0) && // (nextContext != TextPointerContext.Text) && // (nextContext != TextPointerContext.EmbeddedElement)) // { // // Simply skip all other opening tags // cursor.MoveToNextContextPosition(LogicalDirection.Forward); // nextContext = cursor.GetPointerContext(LogicalDirection.Forward); // } // while (cursor.CompareTo(segment.End) >= 0) // return TextSegment.Null; // segmentStart = cursor; // cursor = segment.End.CreatePointer(); // nextContext = cursor.GetPointerContext(LogicalDirection.Backward); // while ((cursor.CompareTo(segmentStart) > 0) && // (nextContext != TextPointerContext.Text) && // (nextContext != TextPointerContext.EmbeddedElement)) // { // cursor.MoveToNextContextPosition(LogicalDirection.Backward); // nextContext = cursor.GetPointerContext(LogicalDirection.Backward); // } // return segmentStart.CompareTo(cursor) < 0 ? new TextSegment(segmentStart, cursor) : TextSegment.Null; //} // #endregion Private Methods //----------------------------------------------------- // // Private Fields // //------------------------------------------------------ #region Private Fields // List of text segments for this anchor private List_segments = new List (1); #endregion Private Fields //----------------------------------------------------- // // Private Classes // //------------------------------------------------------ #region Private Classes /// /// Simple comparer class that sorts TextSegments by their Start pointers. /// If Start pointers are the same, then they are sorted by their End pointers. /// Null is sorted as less than a non-null result. /// private class TextSegmentComparer : IComparer{ /// /// All comparisons are done a segments Start pointer. If /// those are the same, then the End pointers are compared. /// Returns 0 if x is == to y; -1 if x is less than y; 1 if x is greater than y. /// If x is null and y is not, returns -1; if y is null and x is not, returns 1. /// public int Compare(TextSegment x, TextSegment y) { if (x.Equals(TextSegment.Null)) { // Both are null if (y.Equals(TextSegment.Null)) return 0; // x is null but y is not else return -1; } else { // x is not null but y is if (y.Equals(TextSegment.Null)) return 1; else { int retVal = x.Start.CompareTo(y.Start); // If starts are different, return their comparison if (retVal != 0) return retVal; // Otherwise return the comparison of the ends else return x.End.CompareTo(y.End); } } } } #endregion Private Classes } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- AccessDataSourceView.cs
- ImageMapEventArgs.cs
- ReachFixedPageSerializerAsync.cs
- ArgumentOutOfRangeException.cs
- DragCompletedEventArgs.cs
- SharedPerformanceCounter.cs
- ToolStripTemplateNode.cs
- Rect3DConverter.cs
- SoapFault.cs
- SessionViewState.cs
- ArraySortHelper.cs
- XamlClipboardData.cs
- TraceUtility.cs
- UITypeEditors.cs
- UiaCoreApi.cs
- TextBoxLine.cs
- DataSourceCacheDurationConverter.cs
- CharEntityEncoderFallback.cs
- UrlMapping.cs
- WsdlServiceChannelBuilder.cs
- HwndMouseInputProvider.cs
- Metafile.cs
- NativeRightsManagementAPIsStructures.cs
- EndPoint.cs
- SHA384Managed.cs
- Speller.cs
- StyleSheet.cs
- OleDbConnectionFactory.cs
- PointConverter.cs
- MethodBuilder.cs
- HttpStaticObjectsCollectionBase.cs
- MDIClient.cs
- DataStorage.cs
- AnnotationStore.cs
- IdentityVerifier.cs
- AutomationPropertyInfo.cs
- HighlightComponent.cs
- FormViewPageEventArgs.cs
- SQLDateTime.cs
- SafeLocalMemHandle.cs
- versioninfo.cs
- ItemDragEvent.cs
- WindowAutomationPeer.cs
- MetafileHeaderWmf.cs
- TabItemWrapperAutomationPeer.cs
- MenuScrollingVisibilityConverter.cs
- ConsoleCancelEventArgs.cs
- BaseTemplateBuildProvider.cs
- HttpServerVarsCollection.cs
- IUnknownConstantAttribute.cs
- GridViewColumn.cs
- DrawingDrawingContext.cs
- SystemColorTracker.cs
- ZipArchive.cs
- AuthenticationModuleElementCollection.cs
- Gdiplus.cs
- ExpressionLink.cs
- ActivityWithResult.cs
- AccessorTable.cs
- _FtpDataStream.cs
- NameSpaceEvent.cs
- Msmq3PoisonHandler.cs
- SQLMoney.cs
- UnsafePeerToPeerMethods.cs
- CounterSample.cs
- SqlCommandBuilder.cs
- CharacterMetrics.cs
- SapiRecognizer.cs
- SendActivityEventArgs.cs
- PolyQuadraticBezierSegment.cs
- DbLambda.cs
- Pair.cs
- CodeTypeMemberCollection.cs
- xmlfixedPageInfo.cs
- MatrixTransform.cs
- SrgsOneOf.cs
- EncoderParameters.cs
- ByteStreamMessageUtility.cs
- XmlEncodedRawTextWriter.cs
- ResourceReferenceKeyNotFoundException.cs
- ExternalCalls.cs
- DataTemplate.cs
- AppDomainFactory.cs
- RSAProtectedConfigurationProvider.cs
- UdpChannelListener.cs
- FileNameEditor.cs
- SourceLineInfo.cs
- SerializationFieldInfo.cs
- HttpDictionary.cs
- SqlConnectionPoolGroupProviderInfo.cs
- JoinTreeSlot.cs
- DbException.cs
- WindowsFormsEditorServiceHelper.cs
- ProjectionCamera.cs
- LabelLiteral.cs
- HostingPreferredMapPath.cs
- ConnectAlgorithms.cs
- SignerInfo.cs
- JsonDeserializer.cs
- StringValidator.cs