// Candidate for replacing MoveToInsertionPosition for immutable TextPointer model ITextPointer ITextPointer.GetInsertionPosition(LogicalDirection direction) { ITextPointer pointer = ((ITextPointer)this).CreatePointer(); pointer.MoveToInsertionPosition(direction); pointer.Freeze(); return(pointer); }
private ITextPointer _SnapToText(Point point) { ITextPointer itp = null; FixedNode[] fixedNodes = Container.FixedTextBuilder.GetLine(this.PageIndex, point); if (fixedNodes != null && fixedNodes.Length > 0) { double closestDistance = Double.MaxValue; double xoffset = 0; Glyphs closestGlyphs = null; FixedNode closestNode = fixedNodes[0]; foreach (FixedNode node in fixedNodes) { Glyphs startGlyphs = this.FixedPage.GetGlyphsElement(node); GeneralTransform tranToGlyphs = this.FixedPage.TransformToDescendant(startGlyphs); Point transformedPt = point; if (tranToGlyphs != null) { tranToGlyphs.TryTransform(transformedPt, out transformedPt); } GlyphRun run = startGlyphs.ToGlyphRun(); Rect alignmentRect = run.ComputeAlignmentBox(); alignmentRect.Offset(startGlyphs.OriginX, startGlyphs.OriginY); double horizontalDistance = Math.Max(0, (transformedPt.X > alignmentRect.X) ? (transformedPt.X - alignmentRect.Right) : (alignmentRect.X - transformedPt.X)); double verticalDistance = Math.Max(0, (transformedPt.Y > alignmentRect.Y) ? (transformedPt.Y - alignmentRect.Bottom) : (alignmentRect.Y - transformedPt.Y)); double manhattanDistance = horizontalDistance + verticalDistance; if (closestGlyphs == null || manhattanDistance < closestDistance) { closestDistance = manhattanDistance; closestGlyphs = startGlyphs; closestNode = node; xoffset = transformedPt.X; } } int index; LogicalDirection dir; _GlyphRunHitTest(closestGlyphs, xoffset, out index, out dir); FixedPosition fixedp = new FixedPosition(closestNode, index); itp = _CreateTextPointer(fixedp, dir); Debug.Assert(itp != null); } else { // // That condition is only possible when there is no line in the page // if (point.Y < this.FixedPage.Height / 2) { itp = ((ITextPointer)this.Start).CreatePointer(LogicalDirection.Forward); itp.MoveToInsertionPosition(LogicalDirection.Forward); } else { itp = ((ITextPointer)this.End).CreatePointer(LogicalDirection.Backward); itp.MoveToInsertionPosition(LogicalDirection.Backward); } } return(itp); }
/// <summary> /// </summary> void ITextSelection.SetCaretToPosition(ITextPointer caretPosition, LogicalDirection direction, bool allowStopAtLineEnd, bool allowStopNearSpace) { // We need a pointer with appropriate direction, // becasue it will be used in textRangeBase.Select method for // pointer normalization. caretPosition = caretPosition.CreatePointer(direction); // Normalize the position in its logical direction - to get to text content over there. caretPosition.MoveToInsertionPosition(direction); // We need a pointer with the reverse direction to confirm // the line wrapping position. So we can ensure Bidi caret navigation. // Bidi can have the different caret position by setting the // logical direction, so we have to only set the logical direction // as the forward for the real line wrapping position. ITextPointer reversePosition = caretPosition.CreatePointer(direction == LogicalDirection.Forward ? LogicalDirection.Backward : LogicalDirection.Forward); // Check line wrapping condition if (!allowStopAtLineEnd && ((TextPointerBase.IsAtLineWrappingPosition(caretPosition, this.TextView) && TextPointerBase.IsAtLineWrappingPosition(reversePosition, this.TextView)) || TextPointerBase.IsNextToPlainLineBreak(caretPosition, LogicalDirection.Backward) || TextSchema.IsBreak(caretPosition.GetElementType(LogicalDirection.Backward)))) { // Caret is at wrapping position, and we are not allowed to stay at end of line, // so we choose forward direction to appear in the begiinning of a second line caretPosition.SetLogicalDirection(LogicalDirection.Forward); } else { if (caretPosition.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.Text && caretPosition.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text) { // This is statistically most typical case. No "smartness" needed // to choose standard Forward orientation for the caret. // NOTE: By using caretPosition's direction we solve BiDi caret orientation case: // The orietnation reflects a direction from where caret has been moved // or orientation where mouse clicked. So we will stick with appropriate // character. // Nothing to do. The caretPosition is good to go. } else if (!allowStopNearSpace) { // There are some tags around, and we are not allowed to choose a side near to space. // So we need to perform some content analysis. char[] charBuffer = new char[1]; if (caretPosition.GetPointerContext(direction) == TextPointerContext.Text && caretPosition.GetTextInRun(direction, charBuffer, 0, 1) == 1 && Char.IsWhiteSpace(charBuffer[0])) { LogicalDirection oppositeDirection = direction == LogicalDirection.Forward ? LogicalDirection.Backward : LogicalDirection.Forward; // Check formatting switch condition at this position FlowDirection initialFlowDirection = (FlowDirection)caretPosition.GetValue(FrameworkElement.FlowDirectionProperty); bool moved = caretPosition.MoveToInsertionPosition(oppositeDirection); if (moved && initialFlowDirection == (FlowDirection)caretPosition.GetValue(FrameworkElement.FlowDirectionProperty) && (caretPosition.GetPointerContext(oppositeDirection) != TextPointerContext.Text || caretPosition.GetTextInRun(oppositeDirection, charBuffer, 0, 1) != 1 || !Char.IsWhiteSpace(charBuffer[0]))) { // In the opposite direction we have a non-space // character. So we choose that direction direction = oppositeDirection; caretPosition.SetLogicalDirection(direction); } } } } // Now that orientation of a caretPosition is identified, // build an empty selection at this position TextRangeBase.BeginChange(this); try { TextRangeBase.Select(this, caretPosition, caretPosition); // Note how Select method works for the case of empty range: // It creates a single instance TextPointer normalized and oriented // in a direction taken from caretPosition: ITextSelection thisSelection = this; Invariant.Assert(thisSelection.Start.LogicalDirection == caretPosition.LogicalDirection); // orientation must be as passed Invariant.Assert(this.IsEmpty); //Invariant.Assert((object)thisSelection.Start == (object)thisSelection.End); // it must be the same instance of TextPointer //Invariant.Assert(TextPointerBase.IsAtInsertionPosition(thisSelection.Start, caretPosition.LogicalDirection)); // normalization must be done in the same diredction as orientation // Clear active positions when selection is empty SetActivePositions(null, null); } finally { TextRangeBase.EndChange(this); } }
/// <summary> /// This wrapper is to cover up a shortcoming in MoveToInsertionPosition code. /// If the position is inside a Hyperlink, it is being moved out of the hyperlink and to the previous /// insertion position which can be on the previous line (or unit) and this is creating problems /// This is a temporary solution until we find a better solution to handle the case /// There is also a related bug in MoveToLineBoundary: PS#1742102 /// </summary> internal static bool MoveToInsertionPosition(ITextPointer position, LogicalDirection direction) { if (!position.TextContainer.IsReadOnly || (!TextPointerBase.IsAtNonMergeableInlineStart(position) && !TextPointerBase.IsAtNonMergeableInlineEnd(position))) { return position.MoveToInsertionPosition(direction); } return false; }