diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/Caret.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/Caret.cs
index 468f24735b..5c6999c1fd 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/Caret.cs
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Editing/Caret.cs
@@ -351,7 +351,7 @@ namespace ICSharpCode.AvalonEdit.Editing
TextLine textLine = visualLine.GetTextLine(position.VisualColumn);
double xPos = visualLine.GetTextLineVisualXPosition(textLine, position.VisualColumn);
double lineTop = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.TextTop);
- double lineBottom = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.LineBottom);
+ double lineBottom = visualLine.GetTextLineVisualYPosition(textLine, VisualYPosition.TextBottom);
return new Rect(xPos,
lineTop,
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextView.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextView.cs
index a03d344547..920f9f8958 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextView.cs
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextView.cs
@@ -115,7 +115,8 @@ namespace ICSharpCode.AvalonEdit.Rendering
if (newValue != null) {
TextDocumentWeakEventManager.Changing.AddListener(newValue, this);
formatter = TextFormatterFactory.Create(this);
- heightTree = new HeightTree(newValue, DefaultLineHeight); // measuring DefaultLineHeight depends on formatter
+ InvalidateDefaultTextMetrics(); // measuring DefaultLineHeight depends on formatter
+ heightTree = new HeightTree(newValue, DefaultLineHeight);
cachedElements = new TextViewCachedElements();
}
InvalidateMeasure(DispatcherPriority.Normal);
@@ -1369,29 +1370,64 @@ namespace ICSharpCode.AvalonEdit.Rendering
OnScrollChange();
}
+ bool defaultTextMetricsValid;
double wideSpaceWidth; // Width of an 'x'. Used as basis for the tab width, and for scrolling.
double defaultLineHeight; // Height of a line containing 'x'. Used for scrolling.
+ double defaultBaseline; // Baseline of a line containing 'x'. Used for TextTop/TextBottom calculation.
- internal double WideSpaceWidth {
+ ///
+ /// Gets the width of a 'wide space' (the space width used for calculating the tab size).
+ ///
+ ///
+ /// This is the width of an 'x' in the current font.
+ /// We do not measure the width of an actual space as that would lead to tiny tabs in
+ /// some proportional fonts.
+ /// For monospaced fonts, this property will return the expected value, as 'x' and ' ' have the same width.
+ ///
+ public double WideSpaceWidth {
get {
- if (wideSpaceWidth == 0) {
- MeasureWideSpaceWidthAndDefaultLineHeight();
- }
+ CalculateDefaultTextMetrics();
return wideSpaceWidth;
}
}
- double DefaultLineHeight {
+ ///
+ /// Gets the default line height. This is the height of an empty line or a line containing regular text.
+ /// Lines that include formatted text or custom UI elements may have a different line height.
+ ///
+ public double DefaultLineHeight {
get {
- if (defaultLineHeight == 0) {
- MeasureWideSpaceWidthAndDefaultLineHeight();
- }
+ CalculateDefaultTextMetrics();
return defaultLineHeight;
}
}
- void MeasureWideSpaceWidthAndDefaultLineHeight()
+ ///
+ /// Gets the default baseline position. This is the difference between
+ /// and for a line containing regular text.
+ /// Lines that include formatted text or custom UI elements may have a different baseline.
+ ///
+ public double DefaultBaseline {
+ get {
+ CalculateDefaultTextMetrics();
+ return defaultBaseline;
+ }
+ }
+
+ void InvalidateDefaultTextMetrics()
{
+ defaultTextMetricsValid = false;
+ if (heightTree != null) {
+ // calculate immediately so that height tree gets updated
+ CalculateDefaultTextMetrics();
+ }
+ }
+
+ void CalculateDefaultTextMetrics()
+ {
+ if (defaultTextMetricsValid)
+ return;
+ defaultTextMetricsValid = true;
if (formatter != null) {
var textRunProperties = CreateGlobalTextRunProperties();
using (var line = formatter.FormatLine(
@@ -1401,10 +1437,12 @@ namespace ICSharpCode.AvalonEdit.Rendering
null))
{
wideSpaceWidth = Math.Max(1, line.WidthIncludingTrailingWhitespace);
+ defaultBaseline = Math.Max(1, line.Baseline);
defaultLineHeight = Math.Max(1, line.Height);
}
} else {
wideSpaceWidth = FontSize / 2;
+ defaultBaseline = FontSize;
defaultLineHeight = FontSize + 3;
}
// Update heightTree.DefaultLineHeight, if a document is loaded.
@@ -1831,7 +1869,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
// changing text formatter requires recreating the cached elements
RecreateCachedElements();
// and we need to re-measure the font metrics:
- MeasureWideSpaceWidthAndDefaultLineHeight();
+ InvalidateDefaultTextMetrics();
} else if (e.Property == Control.ForegroundProperty
|| e.Property == TextView.NonPrintableCharacterBrushProperty)
{
@@ -1848,7 +1886,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
// changing font properties requires recreating cached elements
RecreateCachedElements();
// and we need to re-measure the font metrics:
- MeasureWideSpaceWidthAndDefaultLineHeight();
+ InvalidateDefaultTextMetrics();
Redraw();
}
}
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLine.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLine.cs
index 7c6ddaa2cb..cf3e080980 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLine.cs
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLine.cs
@@ -1,6 +1,7 @@
// 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.Windows.Controls;
using System.Windows.Media;
using ICSharpCode.AvalonEdit.Utils;
using System;
@@ -268,7 +269,11 @@ namespace ICSharpCode.AvalonEdit.Rendering
case VisualYPosition.LineBottom:
return pos + tl.Height;
case VisualYPosition.TextTop:
- return pos + tl.Height - textView.FontSize;
+ return pos + tl.Baseline - textView.DefaultBaseline;
+ case VisualYPosition.TextBottom:
+ return pos + tl.Baseline - textView.DefaultBaseline + textView.DefaultLineHeight;
+ case VisualYPosition.Baseline:
+ return pos + tl.Baseline;
default:
throw new ArgumentException("Invalid yPositionMode:" + yPositionMode);
}
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualYPosition.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualYPosition.cs
index 4eee266364..536b5fe832 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualYPosition.cs
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualYPosition.cs
@@ -15,18 +15,28 @@ namespace ICSharpCode.AvalonEdit.Rendering
///
LineTop,
///
- /// Returns the top of the text. If the line contains inline UI elements larger than the text, TextTop
- /// will be below LineTop.
+ /// Returns the top of the text.
+ /// If the line contains inline UI elements larger than the text, TextTop may be below LineTop.
+ /// For a line containing regular text (all in the editor's main font), this will be equal to LineTop.
///
TextTop,
///
- /// Returns the bottom of the TextLine. This is the same as the bottom of the text (the text is always
- /// aligned at the bottom border).
+ /// Returns the bottom of the TextLine.
///
LineBottom,
///
/// The middle between LineTop and LineBottom.
///
- LineMiddle
+ LineMiddle,
+ ///
+ /// Returns the bottom of the text.
+ /// If the line contains inline UI elements larger than the text, TextBottom might be above LineBottom.
+ /// For a line containing regular text (all in the editor's main font), this will be equal to LineBottom.
+ ///
+ TextBottom,
+ ///
+ /// Returns the baseline of the text.
+ ///
+ Baseline
}
}