// Returns true when one or both ends of the error lies at the inner edge of non-mergeable inline // such as Hyperlink. In this case, a TextRange will normalize its ends outside // the scope of the inline, and the corrected text will not be covered by it. // // We work around the common case, when the error is contained within a single // Run. In more complex cases we'll fail and fall back to using a TextRange. private static bool IsErrorAtNonMergeableInlineEdge(SpellingError spellingError, out ITextPointer textStart, out ITextPointer textEnd) { bool result = false; textStart = spellingError.Start.CreatePointer(LogicalDirection.Backward); while (textStart.CompareTo(spellingError.End) < 0 && textStart.GetPointerContext(LogicalDirection.Forward) != TextPointerContext.Text) { textStart.MoveToNextContextPosition(LogicalDirection.Forward); } textEnd = spellingError.End.CreatePointer(); while (textEnd.CompareTo(spellingError.Start) > 0 && textEnd.GetPointerContext(LogicalDirection.Backward) != TextPointerContext.Text) { textEnd.MoveToNextContextPosition(LogicalDirection.Backward); } if (textStart.GetPointerContext(LogicalDirection.Forward) != TextPointerContext.Text || textStart.CompareTo(spellingError.End) == 0) { return(false); } Invariant.Assert(textEnd.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.Text && textEnd.CompareTo(spellingError.Start) != 0); if (TextPointerBase.IsAtNonMergeableInlineStart(textStart) || TextPointerBase.IsAtNonMergeableInlineEnd(textEnd)) { if (typeof(Run).IsAssignableFrom(textStart.ParentType) && textStart.HasEqualScope(textEnd)) { result = true; } } return(result); }
/// <summary> /// Walks the tree up from current position and writes all scoping tags /// in their natural order - from root to leafs. /// </summary> /// <param name="range"> /// Range identifying the whole selection. /// Needed for /// - table cell range case: proper column processing: to output only columns related to the selection /// - text segement case: hyperlink serialization heuristics /// </param> /// <param name="thisElement"> /// ITextPointer identifying an element. /// </param> /// <param name="scope"> /// A position identifying the scope which should be used for serialization. /// All tags outside of this scope will be ignored. /// </param> /// <param name="xmlWriter"> /// XmlWriter to write element tags. /// </param> /// <param name="xamlTypeMapper"></param> /// <param name="reduceElement"> /// <see cref="WriteStartXamlElement"/> /// </param> /// <param name="ignoreWriteHyperlinkEnd"></param> /// <param name="ignoreList"></param> /// <param name="preserveTextElements"></param> /// /// <returns> /// Number of opening tags written into XmlWriter. /// This number should be used afterwards to close all opened tags. /// </returns> private static int WriteOpeningTags(ITextRange range, ITextPointer thisElement, ITextPointer scope, XmlWriter xmlWriter, XamlTypeMapper xamlTypeMapper, bool reduceElement, out bool ignoreWriteHyperlinkEnd, ref List<int> ignoreList, bool preserveTextElements) { ignoreWriteHyperlinkEnd = false; // Recursion ends when we reach the scope level. We will write tags on returing path from the recursion if (thisElement.HasEqualScope(scope)) { return 0; // no elements have opened at this level. Return elementCount==0. } Invariant.Assert(typeof(TextElement).IsAssignableFrom(thisElement.ParentType), "thisElement is expected to be a TextElement"); ITextPointer previousLevel = thisElement.CreatePointer(); previousLevel.MoveToElementEdge(ElementEdge.BeforeStart); // Recurse into the parent element int elementLevel = WriteOpeningTags(range, previousLevel, scope, xmlWriter, xamlTypeMapper, reduceElement, out ignoreWriteHyperlinkEnd, ref ignoreList, preserveTextElements); // After returning from the recursion - when all parent tags have been written, // write the opening tag for this element // Hyperlink open tag will be skipped since the range selection of Hyperlink is the partial // of Hyperlink range or Hyperlink include invalid UIElement except Image. bool ignoreHyperlink = false; bool isPartialNonTypographic = false; if (thisElement.ParentType == typeof(Hyperlink)) { if (TextPointerBase.IsAtNonMergeableInlineStart(range.Start)) { ITextPointer position = thisElement.CreatePointer(); position.MoveToElementEdge(ElementEdge.BeforeStart); ignoreHyperlink = IsHyperlinkInvalid(position, range.End); } else { ignoreHyperlink = true; } } else { // TextElementEditingBehaviorAttribute att = (TextElementEditingBehaviorAttribute)Attribute.GetCustomAttribute(thisElement.ParentType, typeof(TextElementEditingBehaviorAttribute)); if (att != null && !att.IsTypographicOnly) { if (TextPointerBase.IsAtNonMergeableInlineStart(range.Start)) { ITextPointer position = thisElement.CreatePointer(); position.MoveToElementEdge(ElementEdge.BeforeStart); isPartialNonTypographic = IsPartialNonTypographic(position, range.End); } else { isPartialNonTypographic = true; } } } int count; if (ignoreHyperlink) { // Ignore writing Hyperlink opening tag ignoreWriteHyperlinkEnd = true; // Set elementLevel without adding it count = elementLevel; } else if (isPartialNonTypographic) { // Add the end pointer to the list ITextPointer position = thisElement.CreatePointer(); position.MoveToElementEdge(ElementEdge.BeforeEnd); ignoreList.Add(position.Offset); // Set elementLevel without adding to it count = elementLevel; } else { // Write the opening tag WriteStartXamlElement(range, thisElement, xmlWriter, xamlTypeMapper, reduceElement, preserveTextElements); // Each opening tag adds one to the level count count = elementLevel + 1; } // Return the opening tag count return count; }
// Worker implementing IsAtPotentialRunPosition(position) method. // It is used for testing whether an empty Run element is at potential run potision. // For this purpose the method is supposed to be called with // backwardPosition==run.ElementStart and forwardPosition==run.ElementEnd. private static bool IsAtPotentialRunPosition(ITextPointer backwardPosition, ITextPointer forwardPosition) { Invariant.Assert(backwardPosition.HasEqualScope(forwardPosition)); if (TextSchema.IsValidChild(/*position*/backwardPosition, /*childType*/typeof(Run))) { Type forwardType = forwardPosition.GetElementType(LogicalDirection.Forward); Type backwardType = backwardPosition.GetElementType(LogicalDirection.Backward); if (forwardType != null && backwardType != null) { TextPointerContext forwardContext = forwardPosition.GetPointerContext(LogicalDirection.Forward); TextPointerContext backwardContext = backwardPosition.GetPointerContext(LogicalDirection.Backward); if (// Test if the position inside empty Paragraph or Span backwardContext == TextPointerContext.ElementStart && forwardContext == TextPointerContext.ElementEnd || // Test if the position between opening tag and an embedded object backwardContext == TextPointerContext.ElementStart && TextSchema.IsNonFormattingInline(forwardType) && !IsAtNonMergeableInlineStart(backwardPosition) || // Test if the position between an embedded object and a closing tag forwardContext == TextPointerContext.ElementEnd && TextSchema.IsNonFormattingInline(backwardType) && !IsAtNonMergeableInlineEnd(forwardPosition) || // Test if the position between two embedded objects backwardContext == TextPointerContext.ElementEnd && forwardContext == TextPointerContext.ElementStart && TextSchema.IsNonFormattingInline(backwardType) && TextSchema.IsNonFormattingInline(forwardType) || // Test if the position is adjacent to a non-mergeable inline (Hyperlink). backwardContext == TextPointerContext.ElementEnd && typeof(Inline).IsAssignableFrom(backwardType) && !TextSchema.IsMergeableInline(backwardType) && !typeof(Run).IsAssignableFrom(forwardType) && (forwardContext != TextPointerContext.ElementEnd || !IsAtNonMergeableInlineEnd(forwardPosition)) || forwardContext == TextPointerContext.ElementStart && typeof(Inline).IsAssignableFrom(forwardType) && !TextSchema.IsMergeableInline(forwardType) && !typeof(Run).IsAssignableFrom(backwardType) && (backwardContext != TextPointerContext.ElementStart || !IsAtNonMergeableInlineStart(backwardPosition)) ) { return true; } } } return false; }
// Returns true when one or both ends of the error lies at the inner edge of non-mergeable inline // such as Hyperlink. In this case, a TextRange will normalize its ends outside // the scope of the inline, and the corrected text will not be covered by it. // // We work around the common case, when the error is contained within a single // Run. In more complex cases we'll fail and fall back to using a TextRange. private static bool IsErrorAtNonMergeableInlineEdge(SpellingError spellingError, out ITextPointer textStart, out ITextPointer textEnd) { bool result = false; textStart = spellingError.Start.CreatePointer(LogicalDirection.Backward); while (textStart.CompareTo(spellingError.End) < 0 && textStart.GetPointerContext(LogicalDirection.Forward) != TextPointerContext.Text) { textStart.MoveToNextContextPosition(LogicalDirection.Forward); } textEnd = spellingError.End.CreatePointer(); while (textEnd.CompareTo(spellingError.Start) > 0 && textEnd.GetPointerContext(LogicalDirection.Backward) != TextPointerContext.Text) { textEnd.MoveToNextContextPosition(LogicalDirection.Backward); } if (textStart.GetPointerContext(LogicalDirection.Forward) != TextPointerContext.Text || textStart.CompareTo(spellingError.End) == 0) { return false; } Invariant.Assert(textEnd.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.Text && textEnd.CompareTo(spellingError.Start) != 0); if (TextPointerBase.IsAtNonMergeableInlineStart(textStart) || TextPointerBase.IsAtNonMergeableInlineEnd(textEnd)) { if (typeof(Run).IsAssignableFrom(textStart.ParentType) && textStart.HasEqualScope(textEnd)) { result = true; } } return result; }