aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/Scripting/Tango.Scripting.Editors/Xml/AXmlAttributeCollection.cs
blob: 95fa83cd045c1796d4104932f401f011e383cf60 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
// 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;
using System.Collections.Generic;

namespace Tango.Scripting.Editors.Xml
{
	/// <summary>
	/// Specailized attribute collection with attribute name caching
	/// </summary>
	public class AXmlAttributeCollection: FilteredCollection<AXmlAttribute, AXmlObjectCollection<AXmlObject>>
	{
		/// <summary> Empty unbound collection </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes",
		                                                 Justification = "InsertItem prevents modifying the Empty collection")]
		public static readonly AXmlAttributeCollection Empty = new AXmlAttributeCollection();
		
		/// <summary> Create unbound collection </summary>
		protected AXmlAttributeCollection() {}
		
		/// <summary> Wrap the given collection.  Non-attributes are filtered </summary>
		public AXmlAttributeCollection(AXmlObjectCollection<AXmlObject> source): base(source) {}
		
		/// <summary> Wrap the given collection.  Non-attributes are filtered.  Items not matching the condition are filtered. </summary>
		public AXmlAttributeCollection(AXmlObjectCollection<AXmlObject> source, Predicate<object> condition): base(source, condition) {}
		
		Dictionary<string, List<AXmlAttribute>> hashtable = new Dictionary<string, List<AXmlAttribute>>();
		
		void AddToHashtable(AXmlAttribute attr)
		{
			string localName = attr.LocalName;
			if (!hashtable.ContainsKey(localName)) {
				hashtable[localName] = new List<AXmlAttribute>(1);
			}
			hashtable[localName].Add(attr);
		}
		
		void RemoveFromHashtable(AXmlAttribute attr)
		{
			string localName = attr.LocalName;
			hashtable[localName].Remove(attr);
		}
		
		static List<AXmlAttribute> NoAttributes = new List<AXmlAttribute>();
		
		/// <summary>
		/// Get all attributes with given local name.
		/// Hash table is used for lookup so this is cheap.
		/// </summary>
		public IEnumerable<AXmlAttribute> GetByLocalName(string localName)
		{
			if (hashtable.ContainsKey(localName)) {
				return hashtable[localName];
			} else {
				return NoAttributes;
			}
		}
		
		/// <inheritdoc/>
		protected override void ClearItems()
		{
			foreach(AXmlAttribute item in this) {
				RemoveFromHashtable(item);
				item.Changing -= item_Changing;
				item.Changed  -= item_Changed;
			}
			base.ClearItems();
		}
		
		/// <inheritdoc/>
		protected override void InsertItem(int index, AXmlAttribute item)
		{
			// prevent insertions into the static 'Empty' instance
			if (this == Empty)
				throw new NotSupportedException("Cannot insert into AXmlAttributeCollection.Empty");
			
			AddToHashtable(item);
			item.Changing += item_Changing;
			item.Changed  += item_Changed;
			base.InsertItem(index, item);
		}
		
		/// <inheritdoc/>
		protected override void RemoveItem(int index)
		{
			RemoveFromHashtable(this[index]);
			this[index].Changing -= item_Changing;
			this[index].Changed  -= item_Changed;
			base.RemoveItem(index);
		}
		
		/// <inheritdoc/>
		protected override void SetItem(int index, AXmlAttribute item)
		{
			RemoveFromHashtable(this[index]);
			this[index].Changing -= item_Changing;
			this[index].Changed  -= item_Changed;
			
			AddToHashtable(item);
			item.Changing += item_Changing;
			item.Changed  += item_Changed;
			base.SetItem(index, item);
		}
		
		// Every item in the collection should be registered to these handlers
		// so that we can handle renames
		
		void item_Changing(object sender, AXmlObjectEventArgs e)
		{
			RemoveFromHashtable((AXmlAttribute)e.Object);
		}
		
		void item_Changed(object sender, AXmlObjectEventArgs e)
		{
			AddToHashtable((AXmlAttribute)e.Object);
		}
	}
}
class="nn">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; } } }