// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System.Linq;
using System.Windows.Controls;
using System.Windows.Media;
using ICSharpCode.AvalonEdit.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 ICSharpCode.AvalonEdit.Document;
namespace ICSharpCode.AvalonEdit.Rendering
{
///
/// 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.
///
public sealed class VisualLine
{
enum LifetimePhase : byte
{
Generating,
Transforming,
Live,
Disposed
}
TextView textView;
List elements;
internal bool hasInlineObjects;
LifetimePhase phase;
///
/// Gets the document to which this VisualLine belongs.
///
public TextDocument Document { get; private set; }
///
/// Gets the first document line displayed by this visual line.
///
public DocumentLine FirstDocumentLine { get; private set; }
///
/// Gets the last document line displayed by this visual line.
///
public DocumentLine LastDocumentLine { get; private set; }
///
/// Gets a read-only collection of line elements.
///
public ReadOnlyCollection Elements { get; private set; }
ReadOnlyCollection textLines;
///
/// Gets a read-only collection of text lines.
///
public ReadOnlyCollection TextLines {
get {
if (phase < LifetimePhase.Live)
throw new InvalidOperationException();
return textLines;
}
}
///
/// Gets the start offset of the VisualLine inside the document.
/// This is equivalent to FirstDocumentLine.Offset.
///
public int StartOffset {
get {
return FirstDocumentLine.Offset;
}
}
///
/// Length in visual line coordinates.
///
public int VisualLength { get; private set; }
///
/// Length in visual line coordinates including the end of line marker, if TextEditorOptions.ShowEndOfLine is enabled.
///
public int VisualLengthWithEndOfLineMarker {
get {
int length = VisualLength;
if (textView.Options.ShowEndOfLine && LastDocumentLine.NextLine != null) length++;
return length;
}
}
///
/// Gets the height of the visual line in device-independent pixels.
///
public double Height { get; private set; }
///
/// Gets the Y position of the line. This is measured in device-independent pixels relative to the start of the document.
///
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();
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;
}
///
/// Replaces the single element at with the specified elements.
/// The replacement operation must preserve the document length, but may change the visual length.
///
///
/// This method may only be called by line transformers.
///
public void ReplaceElement(int elementIndex, params VisualLineElement[] newElements)
{
ReplaceElement(elementIndex, 1, newElements);
}
///
/// Replaces elements starting at with the specified elements.
/// The replacement operation must preserve the document length, but may change the visual length.
///
///
/// This method may only be called by line transformers.
///
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 textLines)
{
this.textLines = textLines.AsReadOnly();
Height = 0;
foreach (TextLine line in textLines)
Height += line.Height;
}
///
/// Gets the visual column from a document offset relative to the first line start.
///
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;
}
///
/// Gets the document offset (relative to the first line start) from a visual column.
///
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;
}
///
/// Gets the text line containing the specified visual column.
///
public TextLine GetTextLine(int visualColumn)
{
return GetTextLine(visualColumn, false);
}
///
/// Gets the text line containing the specified visual column.
///
public TextLine GetTextLine(int visualColumn, bool isAtEndOfLine)
{
if (visualColumn < 0)
throw new ArgumentOutOfRangeException("visualColumn");
if (visualColumn >= VisualLengthWithEndOfLineMarker)
return TextLines[TextLines.Count - 1];
foreach (TextLine line in TextLines) {
if (isAtEndOfLine ? visualColumn <= line.Length : visualColumn < line.Length)
return line;
else
visualColumn -= line.Length;
}
throw new InvalidOperationException("Shouldn't happen (VisualLength incorrect?)");
}
///
/// Gets the visual top from the specified text line.
///
/// Distance in device-independent pixels
/// from the top of the document to the top of the specified text line.
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");
}
///
/// Gets the start visual column from the specified text line.
///
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;
}
///
/// Gets a TextLine by the visual position.
///
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];
}
///
/// Gets the visual position from the specified visualColumn.
///
/// Position in device-independent pixels
/// relative to the top left of the document.
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);
}
internal Point GetVisualPosition(int visualColumn, bool isAtEndOfLine, VisualYPosition yPositionMode)
{
TextLine textLine = GetTextLine(visualColumn, isAtEndOfLine);
double xPos = GetTextLineVisualXPosition(textLine, visualColumn);
double yPos = GetTextLineVisualYPosition(textLine, yPositionMode);
return new Point(xPos, yPos);
}
///
/// 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.
///
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;
}
///
/// 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.
///
public int GetVisualColumn(Point point)
{
return GetVisualColumn(point, textView.Options.EnableVirtualSpace);
}
///
/// 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.
///
public int GetVisualColumn(Point point, bool allowVirtualSpace)
{
return GetVisualColumn(GetTextLineByVisualYPosition(point.Y), point.X, allowVirtualSpace);
}
internal int GetVisualColumn(Point point, bool allowVirtualSpace, out bool isAtEndOfLine)
{
var textLine = GetTextLineByVisualYPosition(point.Y);
int vc = GetVisualColumn(textLine, point.X, allowVirtualSpace);
isAtEndOfLine = (vc >= GetTextLineVisualStartColumn(textLine) + textLine.Length);
return vc;
}
///
/// 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.
///
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;
}
///
/// Validates the visual column and returns the correct one.
///
public int ValidateVisualColumn(TextViewPosition position, bool allowVirtualSpace)
{
return ValidateVisualColumn(Document.GetOffset(position.Location), position.VisualColumn, allowVirtualSpace);
}
///
/// Validates the visual column and returns the correct one.
///
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;
}
///
/// 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.
///
public int GetVisualColumnFloor(Point point)
{
return GetVisualColumnFloor(point, textView.Options.EnableVirtualSpace);
}
///
/// 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.
///
public int GetVisualColumnFloor(Point point, bool allowVirtualSpace)
{
bool tmp;
return GetVisualColumnFloor(point, allowVirtualSpace, out tmp);
}
internal int GetVisualColumnFloor(Point point, bool allowVirtualSpace, out bool isAtEndOfLine)
{
TextLine textLine = GetTextLineByVisualYPosition(point.Y);
if (point.X > textLine.WidthIncludingTrailingWhitespace) {
isAtEndOfLine = true;
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;
}
} else {
isAtEndOfLine = false;
}
CharacterHit ch = textLine.GetCharacterHitFromDistance(point.X);
return ch.FirstCharacterIndex;
}
///
/// Gets the text view position from the specified visual column.
///
public TextViewPosition GetTextViewPosition(int visualColumn)
{
int documentOffset = GetRelativeOffset(visualColumn) + this.FirstDocumentLine.Offset;
return new TextViewPosition(this.Document.GetLocation(documentOffset), visualColumn);
}
///
/// Gets the text view position from the specified visual position.
/// If the position is within a character, it is rounded to the next character boundary.
///
/// The position in WPF device-independent pixels relative
/// to the top left corner of the document.
/// Controls whether positions in virtual space may be returned.
public TextViewPosition GetTextViewPosition(Point visualPosition, bool allowVirtualSpace)
{
bool isAtEndOfLine;
int visualColumn = GetVisualColumn(visualPosition, allowVirtualSpace, out isAtEndOfLine);
int documentOffset = GetRelativeOffset(visualColumn) + this.FirstDocumentLine.Offset;
TextViewPosition pos = new TextViewPosition(this.Document.GetLocation(documentOffset), visualColumn);
pos.IsAtEndOfLine = isAtEndOfLine;
return pos;
}
///
/// Gets the text view position from the specified visual position.
/// If the position is inside a character, the position in front of the character is returned.
///
/// The position in WPF device-independent pixels relative
/// to the top left corner of the document.
/// Controls whether positions in virtual space may be returned.
public TextViewPosition GetTextViewPositionFloor(Point visualPosition, bool allowVirtualSpace)
{
bool isAtEndOfLine;
int visualColumn = GetVisualColumnFloor(visualPosition, allowVirtualSpace, out isAtEndOfLine);
int documentOffset = GetRelativeOffset(visualColumn) + this.FirstDocumentLine.Offset;
TextViewPosition pos = new TextViewPosition(this.Document.GetLocation(documentOffset), visualColumn);
pos.IsAtEndOfLine = isAtEndOfLine;
return pos;
}
///
/// Gets whether the visual line was disposed.
///
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();
}
}
///
/// Gets the next possible caret position after visualColumn, or -1 if there is no caret position.
///
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 || mode == CaretPositioningMode.EveryCodepoint;
}
static bool HasImplicitStopAtLineStart(CaretPositioningMode mode)
{
return mode == CaretPositioningMode.Normal || mode == CaretPositioningMode.EveryCodepoint;
}
[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;
}
}
}