aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLine.cs
diff options
context:
space:
mode:
authorRoy Ben Shabat <Roy.mail.net@gmail.com>2019-04-09 01:47:48 +0300
committerRoy Ben Shabat <Roy.mail.net@gmail.com>2019-04-09 01:47:48 +0300
commit080f1697e97e13461ec6df4d31c8924d01257a1b (patch)
treeb1fe0285de7bc9bc52e9e2195e66fe022bf8f5b3 /Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLine.cs
parent1608e69a417bc5e40a607c3958c4a60f19f66f1a (diff)
downloadTango-080f1697e97e13461ec6df4d31c8924d01257a1b.tar.gz
Tango-080f1697e97e13461ec6df4d31c8924d01257a1b.zip
MERGE
Diffstat (limited to 'Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLine.cs')
-rw-r--r--Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLine.cs681
1 files changed, 681 insertions, 0 deletions
diff --git a/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLine.cs b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLine.cs
new file mode 100644
index 000000000..70727dd07
--- /dev/null
+++ b/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Rendering/VisualLine.cs
@@ -0,0 +1,681 @@
+// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
+// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+
+using System.Linq;
+using System.Windows.Controls;
+using System.Windows.Media;
+using Tango.Scripting.Editors.Utils;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.Windows;
+using System.Windows.Documents;
+using System.Windows.Media.TextFormatting;
+using Tango.Scripting.Editors.Document;
+
+namespace Tango.Scripting.Editors.Rendering
+{
+ /// <summary>
+ /// Represents a visual line in the document.
+ /// A visual line usually corresponds to one DocumentLine, but it can span multiple lines if
+ /// all but the first are collapsed.
+ /// </summary>
+ public sealed class VisualLine
+ {
+ enum LifetimePhase : byte
+ {
+ Generating,
+ Transforming,
+ Live,
+ Disposed
+ }
+
+ TextView textView;
+ List<VisualLineElement> elements;
+ internal bool hasInlineObjects;
+ LifetimePhase phase;
+
+ /// <summary>
+ /// Gets the document to which this VisualLine belongs.
+ /// </summary>
+ public TextDocument Document { get; private set; }
+
+ /// <summary>
+ /// Gets the first document line displayed by this visual line.
+ /// </summary>
+ public DocumentLine FirstDocumentLine { get; private set; }
+
+ /// <summary>
+ /// Gets the last document line displayed by this visual line.
+ /// </summary>
+ public DocumentLine LastDocumentLine { get; private set; }
+
+ /// <summary>
+ /// Gets a read-only collection of line elements.
+ /// </summary>
+ public ReadOnlyCollection<VisualLineElement> Elements { get; private set; }
+
+ ReadOnlyCollection<TextLine> textLines;
+
+ /// <summary>
+ /// Gets a read-only collection of text lines.
+ /// </summary>
+ public ReadOnlyCollection<TextLine> TextLines {
+ get {
+ if (phase < LifetimePhase.Live)
+ throw new InvalidOperationException();
+ return textLines;
+ }
+ }
+
+ /// <summary>
+ /// Gets the start offset of the VisualLine inside the document.
+ /// This is equivalent to <c>FirstDocumentLine.Offset</c>.
+ /// </summary>
+ public int StartOffset {
+ get {
+ return FirstDocumentLine.Offset;
+ }
+ }
+
+ /// <summary>
+ /// Length in visual line coordinates.
+ /// </summary>
+ public int VisualLength { get; private set; }
+
+ /// <summary>
+ /// Length in visual line coordinates including the end of line marker, if TextEditorOptions.ShowEndOfLine is enabled.
+ /// </summary>
+ public int VisualLengthWithEndOfLineMarker {
+ get {
+ int length = VisualLength;
+ if (textView.Options.ShowEndOfLine && LastDocumentLine.NextLine != null) length++;
+ return length;
+ }
+ }
+
+ /// <summary>
+ /// Gets the height of the visual line in device-independent pixels.
+ /// </summary>
+ public double Height { get; private set; }
+
+ /// <summary>
+ /// Gets the Y position of the line. This is measured in device-independent pixels relative to the start of the document.
+ /// </summary>
+ public double VisualTop { get; internal set; }
+
+ internal VisualLine(TextView textView, DocumentLine firstDocumentLine)
+ {
+ Debug.Assert(textView != null);
+ Debug.Assert(firstDocumentLine != null);
+ this.textView = textView;
+ this.Document = textView.Document;
+ this.FirstDocumentLine = firstDocumentLine;
+ }
+
+ internal void ConstructVisualElements(ITextRunConstructionContext context, VisualLineElementGenerator[] generators)
+ {
+ Debug.Assert(phase == LifetimePhase.Generating);
+ foreach (VisualLineElementGenerator g in generators) {
+ g.StartGeneration(context);
+ }
+ elements = new List<VisualLineElement>();
+ PerformVisualElementConstruction(generators);
+ foreach (VisualLineElementGenerator g in generators) {
+ g.FinishGeneration();
+ }
+
+ var globalTextRunProperties = context.GlobalTextRunProperties;
+ foreach (var element in elements) {
+ element.SetTextRunProperties(new VisualLineElementTextRunProperties(globalTextRunProperties));
+ }
+ this.Elements = elements.AsReadOnly();
+ CalculateOffsets();
+ phase = LifetimePhase.Transforming;
+ }
+
+ void PerformVisualElementConstruction(VisualLineElementGenerator[] generators)
+ {
+ TextDocument document = this.Document;
+ int offset = FirstDocumentLine.Offset;
+ int currentLineEnd = offset + FirstDocumentLine.Length;
+ LastDocumentLine = FirstDocumentLine;
+ int askInterestOffset = 0; // 0 or 1
+ while (offset + askInterestOffset <= currentLineEnd) {
+ int textPieceEndOffset = currentLineEnd;
+ foreach (VisualLineElementGenerator g in generators) {
+ g.cachedInterest = g.GetFirstInterestedOffset(offset + askInterestOffset);
+ if (g.cachedInterest != -1) {
+ if (g.cachedInterest < offset)
+ throw new ArgumentOutOfRangeException(g.GetType().Name + ".GetFirstInterestedOffset",
+ g.cachedInterest,
+ "GetFirstInterestedOffset must not return an offset less than startOffset. Return -1 to signal no interest.");
+ if (g.cachedInterest < textPieceEndOffset)
+ textPieceEndOffset = g.cachedInterest;
+ }
+ }
+ Debug.Assert(textPieceEndOffset >= offset);
+ if (textPieceEndOffset > offset) {
+ int textPieceLength = textPieceEndOffset - offset;
+ elements.Add(new VisualLineText(this, textPieceLength));
+ offset = textPieceEndOffset;
+ }
+ // If no elements constructed / only zero-length elements constructed:
+ // do not asking the generators again for the same location (would cause endless loop)
+ askInterestOffset = 1;
+ foreach (VisualLineElementGenerator g in generators) {
+ if (g.cachedInterest == offset) {
+ VisualLineElement element = g.ConstructElement(offset);
+ if (element != null) {
+ elements.Add(element);
+ if (element.DocumentLength > 0) {
+ // a non-zero-length element was constructed
+ askInterestOffset = 0;
+ offset += element.DocumentLength;
+ if (offset > currentLineEnd) {
+ DocumentLine newEndLine = document.GetLineByOffset(offset);
+ currentLineEnd = newEndLine.Offset + newEndLine.Length;
+ this.LastDocumentLine = newEndLine;
+ //if (currentLineEnd < offset) {
+ // throw new InvalidOperationException(
+ // "The VisualLineElementGenerator " + g.GetType().Name +
+ // " produced an element which ends within the line delimiter");
+ //}
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ void CalculateOffsets()
+ {
+ int visualOffset = 0;
+ int textOffset = 0;
+ foreach (VisualLineElement element in elements) {
+ element.VisualColumn = visualOffset;
+ element.RelativeTextOffset = textOffset;
+ visualOffset += element.VisualLength;
+ textOffset += element.DocumentLength;
+ }
+ VisualLength = visualOffset;
+ //Debug.Assert(textOffset == LastDocumentLine.EndOffset - FirstDocumentLine.Offset);
+ }
+
+ internal void RunTransformers(ITextRunConstructionContext context, IVisualLineTransformer[] transformers)
+ {
+ Debug.Assert(phase == LifetimePhase.Transforming);
+ foreach (IVisualLineTransformer transformer in transformers) {
+ transformer.Transform(context, elements);
+ }
+ // For some strange reason, WPF requires that either all or none of the typography properties are set.
+ if (elements.Any(e => e.TextRunProperties.TypographyProperties != null)) {
+ // Fix typographic properties
+ foreach (VisualLineElement element in elements) {
+ if (element.TextRunProperties.TypographyProperties == null) {
+ element.TextRunProperties.SetTypographyProperties(new DefaultTextRunTypographyProperties());
+ }
+ }
+ }
+ phase = LifetimePhase.Live;
+ }
+
+ /// <summary>
+ /// Replaces the single element at <paramref name="elementIndex"/> with the specified elements.
+ /// The replacement operation must preserve the document length, but may change the visual length.
+ /// </summary>
+ /// <remarks>
+ /// This method may only be called by line transformers.
+ /// </remarks>
+ public void ReplaceElement(int elementIndex, params VisualLineElement[] newElements)
+ {
+ ReplaceElement(elementIndex, 1, newElements);
+ }
+
+ /// <summary>
+ /// Replaces <paramref name="count"/> elements starting at <paramref name="elementIndex"/> with the specified elements.
+ /// The replacement operation must preserve the document length, but may change the visual length.
+ /// </summary>
+ /// <remarks>
+ /// This method may only be called by line transformers.
+ /// </remarks>
+ public void ReplaceElement(int elementIndex, int count, params VisualLineElement[] newElements)
+ {
+ if (phase != LifetimePhase.Transforming)
+ throw new InvalidOperationException("This method may only be called by line transformers.");
+ int oldDocumentLength = 0;
+ for (int i = elementIndex; i < elementIndex + count; i++) {
+ oldDocumentLength += elements[i].DocumentLength;
+ }
+ int newDocumentLength = 0;
+ foreach (var newElement in newElements) {
+ newDocumentLength += newElement.DocumentLength;
+ }
+ if (oldDocumentLength != newDocumentLength)
+ throw new InvalidOperationException("Old elements have document length " + oldDocumentLength + ", but new elements have length " + newDocumentLength);
+ elements.RemoveRange(elementIndex, count);
+ elements.InsertRange(elementIndex, newElements);
+ CalculateOffsets();
+ }
+
+ internal void SetTextLines(List<TextLine> textLines)
+ {
+ this.textLines = textLines.AsReadOnly();
+ Height = 0;
+ foreach (TextLine line in textLines)
+ Height += line.Height;
+ }
+
+ /// <summary>
+ /// Gets the visual column from a document offset relative to the first line start.
+ /// </summary>
+ public int GetVisualColumn(int relativeTextOffset)
+ {
+ ThrowUtil.CheckNotNegative(relativeTextOffset, "relativeTextOffset");
+ foreach (VisualLineElement element in elements) {
+ if (element.RelativeTextOffset <= relativeTextOffset
+ && element.RelativeTextOffset + element.DocumentLength >= relativeTextOffset)
+ {
+ return element.GetVisualColumn(relativeTextOffset);
+ }
+ }
+ return VisualLength;
+ }
+
+ /// <summary>
+ /// Gets the document offset (relative to the first line start) from a visual column.
+ /// </summary>
+ public int GetRelativeOffset(int visualColumn)
+ {
+ ThrowUtil.CheckNotNegative(visualColumn, "visualColumn");
+ int documentLength = 0;
+ foreach (VisualLineElement element in elements) {
+ if (element.VisualColumn <= visualColumn
+ && element.VisualColumn + element.VisualLength > visualColumn)
+ {
+ return element.GetRelativeOffset(visualColumn);
+ }
+ documentLength += element.DocumentLength;
+ }
+ return documentLength;
+ }
+
+ /// <summary>
+ /// Gets the text line containing the specified visual column.
+ /// </summary>
+ public TextLine GetTextLine(int visualColumn)
+ {
+ if (visualColumn < 0)
+ throw new ArgumentOutOfRangeException("visualColumn");
+ if (visualColumn >= VisualLengthWithEndOfLineMarker)
+ return TextLines[TextLines.Count - 1];
+ foreach (TextLine line in TextLines) {
+ if (visualColumn < line.Length)
+ return line;
+ else
+ visualColumn -= line.Length;
+ }
+ throw new InvalidOperationException("Shouldn't happen (VisualLength incorrect?)");
+ }
+
+ /// <summary>
+ /// Gets the visual top from the specified text line.
+ /// </summary>
+ /// <returns>Distance in device-independent pixels
+ /// from the top of the document to the top of the specified text line.</returns>
+ public double GetTextLineVisualYPosition(TextLine textLine, VisualYPosition yPositionMode)
+ {
+ if (textLine == null)
+ throw new ArgumentNullException("textLine");
+ double pos = VisualTop;
+ foreach (TextLine tl in TextLines) {
+ if (tl == textLine) {
+ switch (yPositionMode) {
+ case VisualYPosition.LineTop:
+ return pos;
+ case VisualYPosition.LineMiddle:
+ return pos + tl.Height / 2;
+ case VisualYPosition.LineBottom:
+ return pos + tl.Height;
+ case VisualYPosition.TextTop:
+ return pos + tl.Baseline - textView.DefaultBaseline;
+ case VisualYPosition.TextBottom:
+ return pos + tl.Baseline - textView.DefaultBaseline + textView.DefaultLineHeight;
+ case VisualYPosition.TextMiddle:
+ return pos + tl.Baseline - textView.DefaultBaseline + textView.DefaultLineHeight / 2;
+ case VisualYPosition.Baseline:
+ return pos + tl.Baseline;
+ default:
+ throw new ArgumentException("Invalid yPositionMode:" + yPositionMode);
+ }
+ } else {
+ pos += tl.Height;
+ }
+ }
+ throw new ArgumentException("textLine is not a line in this VisualLine");
+ }
+
+ /// <summary>
+ /// Gets the start visual column from the specified text line.
+ /// </summary>
+ public int GetTextLineVisualStartColumn(TextLine textLine)
+ {
+ if (!TextLines.Contains(textLine))
+ throw new ArgumentException("textLine is not a line in this VisualLine");
+ int col = 0;
+ foreach (TextLine tl in TextLines) {
+ if (tl == textLine)
+ break;
+ else
+ col += tl.Length;
+ }
+ return col;
+ }
+
+ /// <summary>
+ /// Gets a TextLine by the visual position.
+ /// </summary>
+ public TextLine GetTextLineByVisualYPosition(double visualTop)
+ {
+ const double epsilon = 0.0001;
+ double pos = this.VisualTop;
+ foreach (TextLine tl in TextLines) {
+ pos += tl.Height;
+ if (visualTop + epsilon < pos)
+ return tl;
+ }
+ return TextLines[TextLines.Count - 1];
+ }
+
+ /// <summary>
+ /// Gets the visual position from the specified visualColumn.
+ /// </summary>
+ /// <returns>Position in device-independent pixels
+ /// relative to the top left of the document.</returns>
+ public Point GetVisualPosition(int visualColumn, VisualYPosition yPositionMode)
+ {
+ TextLine textLine = GetTextLine(visualColumn);
+ double xPos = GetTextLineVisualXPosition(textLine, visualColumn);
+ double yPos = GetTextLineVisualYPosition(textLine, yPositionMode);
+ return new Point(xPos, yPos);
+ }
+
+ /// <summary>
+ /// Gets the distance to the left border of the text area of the specified visual column.
+ /// The visual column must belong to the specified text line.
+ /// </summary>
+ public double GetTextLineVisualXPosition(TextLine textLine, int visualColumn)
+ {
+ if (textLine == null)
+ throw new ArgumentNullException("textLine");
+ double xPos = textLine.GetDistanceFromCharacterHit(
+ new CharacterHit(Math.Min(visualColumn, VisualLengthWithEndOfLineMarker), 0));
+ if (visualColumn > VisualLengthWithEndOfLineMarker) {
+ xPos += (visualColumn - VisualLengthWithEndOfLineMarker) * textView.WideSpaceWidth;
+ }
+ return xPos;
+ }
+
+ /// <summary>
+ /// Gets the visual column from a document position (relative to top left of the document).
+ /// If the user clicks between two visual columns, rounds to the nearest column.
+ /// </summary>
+ public int GetVisualColumn(Point point)
+ {
+ return GetVisualColumn(point, textView.Options.EnableVirtualSpace);
+ }
+
+ /// <summary>
+ /// Gets the visual column from a document position (relative to top left of the document).
+ /// If the user clicks between two visual columns, rounds to the nearest column.
+ /// </summary>
+ public int GetVisualColumn(Point point, bool allowVirtualSpace)
+ {
+ return GetVisualColumn(GetTextLineByVisualYPosition(point.Y), point.X, allowVirtualSpace);
+ }
+
+ /// <summary>
+ /// Gets the visual column from a document position (relative to top left of the document).
+ /// If the user clicks between two visual columns, rounds to the nearest column.
+ /// </summary>
+ public int GetVisualColumn(TextLine textLine, double xPos, bool allowVirtualSpace)
+ {
+ if (xPos > textLine.WidthIncludingTrailingWhitespace) {
+ if (allowVirtualSpace && textLine == TextLines[TextLines.Count - 1]) {
+ int virtualX = (int)Math.Round((xPos - textLine.WidthIncludingTrailingWhitespace) / textView.WideSpaceWidth);
+ return VisualLengthWithEndOfLineMarker + virtualX;
+ }
+ }
+ CharacterHit ch = textLine.GetCharacterHitFromDistance(xPos);
+ return ch.FirstCharacterIndex + ch.TrailingLength;
+ }
+
+ /// <summary>
+ /// Validates the visual column and returns the correct one.
+ /// </summary>
+ public int ValidateVisualColumn(TextViewPosition position, bool allowVirtualSpace)
+ {
+ return ValidateVisualColumn(Document.GetOffset(position.Location), position.VisualColumn, allowVirtualSpace);
+ }
+
+ /// <summary>
+ /// Validates the visual column and returns the correct one.
+ /// </summary>
+ public int ValidateVisualColumn(int offset, int visualColumn, bool allowVirtualSpace)
+ {
+ int firstDocumentLineOffset = this.FirstDocumentLine.Offset;
+ if (visualColumn < 0) {
+ return GetVisualColumn(offset - firstDocumentLineOffset);
+ } else {
+ int offsetFromVisualColumn = GetRelativeOffset(visualColumn);
+ offsetFromVisualColumn += firstDocumentLineOffset;
+ if (offsetFromVisualColumn != offset) {
+ return GetVisualColumn(offset - firstDocumentLineOffset);
+ } else {
+ if (visualColumn > VisualLength && !allowVirtualSpace) {
+ return VisualLength;
+ }
+ }
+ }
+ return visualColumn;
+ }
+
+ /// <summary>
+ /// Gets the visual column from a document position (relative to top left of the document).
+ /// If the user clicks between two visual columns, returns the first of those columns.
+ /// </summary>
+ public int GetVisualColumnFloor(Point point)
+ {
+ return GetVisualColumnFloor(point, textView.Options.EnableVirtualSpace);
+ }
+
+ /// <summary>
+ /// Gets the visual column from a document position (relative to top left of the document).
+ /// If the user clicks between two visual columns, returns the first of those columns.
+ /// </summary>
+ public int GetVisualColumnFloor(Point point, bool allowVirtualSpace)
+ {
+ TextLine textLine = GetTextLineByVisualYPosition(point.Y);
+ if (point.X > textLine.WidthIncludingTrailingWhitespace) {
+ if (allowVirtualSpace && textLine == TextLines[TextLines.Count - 1]) {
+ // clicking virtual space in the last line
+ int virtualX = (int)((point.X - textLine.WidthIncludingTrailingWhitespace) / textView.WideSpaceWidth);
+ return VisualLengthWithEndOfLineMarker + virtualX;
+ } else {
+ // GetCharacterHitFromDistance returns a hit with FirstCharacterIndex=last character in line
+ // and TrailingLength=1 when clicking behind the line, so the floor function needs to handle this case
+ // specially and return the line's end column instead.
+ return GetTextLineVisualStartColumn(textLine) + textLine.Length;
+ }
+ }
+ CharacterHit ch = textLine.GetCharacterHitFromDistance(point.X);
+ return ch.FirstCharacterIndex;
+ }
+
+ /// <summary>
+ /// Gets whether the visual line was disposed.
+ /// </summary>
+ public bool IsDisposed {
+ get { return phase == LifetimePhase.Disposed; }
+ }
+
+ internal void Dispose()
+ {
+ if (phase == LifetimePhase.Disposed)
+ return;
+ Debug.Assert(phase == LifetimePhase.Live);
+ phase = LifetimePhase.Disposed;
+ foreach (TextLine textLine in TextLines) {
+ textLine.Dispose();
+ }
+ }
+
+ /// <summary>
+ /// Gets the next possible caret position after visualColumn, or -1 if there is no caret position.
+ /// </summary>
+ public int GetNextCaretPosition(int visualColumn, LogicalDirection direction, CaretPositioningMode mode, bool allowVirtualSpace)
+ {
+ if (!HasStopsInVirtualSpace(mode))
+ allowVirtualSpace = false;
+
+ if (elements.Count == 0) {
+ // special handling for empty visual lines:
+ if (allowVirtualSpace) {
+ if (direction == LogicalDirection.Forward)
+ return Math.Max(0, visualColumn + 1);
+ else if (visualColumn > 0)
+ return visualColumn - 1;
+ else
+ return -1;
+ } else {
+ // even though we don't have any elements,
+ // there's a single caret stop at visualColumn 0
+ if (visualColumn < 0 && direction == LogicalDirection.Forward)
+ return 0;
+ else if (visualColumn > 0 && direction == LogicalDirection.Backward)
+ return 0;
+ else
+ return -1;
+ }
+ }
+
+ int i;
+ if (direction == LogicalDirection.Backward) {
+ // Search Backwards:
+ // If the last element doesn't handle line borders, return the line end as caret stop
+
+ if (visualColumn > this.VisualLength && !elements[elements.Count-1].HandlesLineBorders && HasImplicitStopAtLineEnd(mode)) {
+ if (allowVirtualSpace)
+ return visualColumn - 1;
+ else
+ return this.VisualLength;
+ }
+ // skip elements that start after or at visualColumn
+ for (i = elements.Count - 1; i >= 0; i--) {
+ if (elements[i].VisualColumn < visualColumn)
+ break;
+ }
+ // search last element that has a caret stop
+ for (; i >= 0; i--) {
+ int pos = elements[i].GetNextCaretPosition(
+ Math.Min(visualColumn, elements[i].VisualColumn + elements[i].VisualLength + 1),
+ direction, mode);
+ if (pos >= 0)
+ return pos;
+ }
+ // If we've found nothing, and the first element doesn't handle line borders,
+ // return the line start as normal caret stop.
+ if (visualColumn > 0 && !elements[0].HandlesLineBorders && HasImplicitStopAtLineStart(mode))
+ return 0;
+ } else {
+ // Search Forwards:
+ // If the first element doesn't handle line borders, return the line start as caret stop
+ if (visualColumn < 0 && !elements[0].HandlesLineBorders && HasImplicitStopAtLineStart(mode))
+ return 0;
+ // skip elements that end before or at visualColumn
+ for (i = 0; i < elements.Count; i++) {
+ if (elements[i].VisualColumn + elements[i].VisualLength > visualColumn)
+ break;
+ }
+ // search first element that has a caret stop
+ for (; i < elements.Count; i++) {
+ int pos = elements[i].GetNextCaretPosition(
+ Math.Max(visualColumn, elements[i].VisualColumn - 1),
+ direction, mode);
+ if (pos >= 0)
+ return pos;
+ }
+ // if we've found nothing, and the last element doesn't handle line borders,
+ // return the line end as caret stop
+ if ((allowVirtualSpace || !elements[elements.Count-1].HandlesLineBorders) && HasImplicitStopAtLineEnd(mode)) {
+ if (visualColumn < this.VisualLength)
+ return this.VisualLength;
+ else if (allowVirtualSpace)
+ return visualColumn + 1;
+ }
+ }
+ // we've found nothing, return -1 and let the caret search continue in the next line
+ return -1;
+ }
+
+ static bool HasStopsInVirtualSpace(CaretPositioningMode mode)
+ {
+ return mode == CaretPositioningMode.Normal;
+ }
+
+ static bool HasImplicitStopAtLineStart(CaretPositioningMode mode)
+ {
+ return mode == CaretPositioningMode.Normal;
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "mode",
+ Justification = "make method consistent with HasImplicitStopAtLineStart; might depend on mode in the future")]
+ static bool HasImplicitStopAtLineEnd(CaretPositioningMode mode)
+ {
+ return true;
+ }
+
+ VisualLineDrawingVisual visual;
+
+ internal VisualLineDrawingVisual Render()
+ {
+ Debug.Assert(phase == LifetimePhase.Live);
+ if (visual == null)
+ visual = new VisualLineDrawingVisual(this);
+ return visual;
+ }
+ }
+
+ sealed class VisualLineDrawingVisual : DrawingVisual
+ {
+ public readonly VisualLine VisualLine;
+ public readonly double Height;
+ internal bool IsAdded;
+
+ public VisualLineDrawingVisual(VisualLine visualLine)
+ {
+ this.VisualLine = visualLine;
+ var drawingContext = RenderOpen();
+ double pos = 0;
+ foreach (TextLine textLine in visualLine.TextLines) {
+ textLine.Draw(drawingContext, new Point(0, pos), InvertAxes.None);
+ pos += textLine.Height;
+ }
+ this.Height = pos;
+ drawingContext.Close();
+ }
+
+ protected override GeometryHitTestResult HitTestCore(GeometryHitTestParameters hitTestParameters)
+ {
+ return null;
+ }
+
+ protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
+ {
+ return null;
+ }
+ }
+}