Domanda

I am writing a custom software using avalon edit and I am looking for a way to make the space (height) between lines bigger. At the moment I am forced to add an empty line every time the user has ended writing a line and wants to write another.

I have started looking into the TextView Class where defaultLineHeight seems to be calculated but the only thing I was able to affect is the height of the visual caret but not the content itself.

At the moment I am looking at making every pair line invisible but I am hoping there is an easier way to achieve the simple operation of adding more space between lines.

Here is the method from class TextView I am inspecting at the moment. Any tips or hints would be welcome.

void CalculateDefaultTextMetrics()
{
    if (defaultTextMetricsValid)
    {
        return;
    }

    defaultTextMetricsValid = true;
    if (formatter != null)
    {
        var textRunProperties = CreateGlobalTextRunProperties();
        using (
            var line = formatter.FormatLine(
                new SimpleTextSource("x", textRunProperties),
                0,
                32000,
                new VisualLineTextParagraphProperties { defaultTextRunProperties = textRunProperties },
                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; // bigger value only affects caret height :(**
    }

    // Update heightTree.DefaultLineHeight, if a document is loaded.
    if (heightTree != null)
    {
        heightTree.DefaultLineHeight = defaultLineHeight;
    }
}

Thanks

È stato utile?

Soluzione

The DefaultLineHeight is the height of a line in the default font, which is used as an initial assumption for the each line's height. (e.g. for calculating the scroll bar position)

Whenever a line gets actually measured (TextView.BuildVisualLine), the measured height gets stored in the height tree, overwriting the default height. This is because word wrapping (or a line transformer changing the font size) can cause each line to have a different height.

Inter-line spacing isn't really supported at the moment. If you want to add that, you can try changing the height calculation of the VisualLine, e.g. by changing VisualLine.SetTextLines().

Altri suggerimenti

Including what @Peter Moore said. There's one more step required in order to force the text to render correctly.

At the bottom of VisualLine.cs lies the class VisualLineDrawingVisual which is responsible for drawing the actual text but doesn't have access to the TextView class.

Modify the constructor to include double lineSpacing as a paremeter and multiply all instances of textLine.Height by lineSpacing.

In VisualLine.Render(), pass textView.LineSpacing as the secondary parameter for the now-modified constructor for VisualLineDrawingVisual. After this everything should draw correctly.

Here's a full view of the modified code:

TextView.cs

public static readonly DependencyProperty LineSpacingProperty =
    DependencyProperty.Register("LineSpacing", typeof(double), typeof(TextView),
                                new FrameworkPropertyMetadata(1.0));

public double LineSpacing {
    get { return (double) GetValue(LineSpacingProperty); }
    set { SetValue(LineSpacingProperty, value); }
}

VisualLine.cs

Line 269

internal void SetTextLines(List<TextLine> textLines) {
    this.textLines = textLines.AsReadOnly();
    Height = 0;
    foreach (TextLine line in textLines)
        Height += line.Height * textView.LineSpacing;
}

Line 335

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 * textView.LineSpacing;
            case VisualYPosition.LineBottom:
                return pos + tl.Height * textView.LineSpacing;
            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 * textView.LineSpacing;
        }
    }
    throw new ArgumentException("textLine is not a line in this VisualLine");
}

Line 386

public TextLine GetTextLineByVisualYPosition(double visualTop) {
    const double epsilon = 0.0001;
    double pos = this.VisualTop;
    foreach (TextLine tl in TextLines) {
        pos += tl.Height * textView.LineSpacing;
        if (visualTop + epsilon < pos)
            return tl;
    }
    return TextLines[TextLines.Count - 1];
}

Line 701

internal VisualLineDrawingVisual Render() {
    Debug.Assert(phase == LifetimePhase.Live);
    if (visual == null)
        visual = new VisualLineDrawingVisual(this, textView.LineSpacing);
    return visual;
}

Line 714

public VisualLineDrawingVisual(VisualLine visualLine, double lineSpacing) {
    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 * lineSpacing;
    }
    this.Height = pos;
    drawingContext.Close();
}

I know this is an old post, but I just came across this with the same question. I managed to add line spacing very easily with the following modifications:

  1. Add a new double dependency property to TextView called LineSpacing with a default value of 1.0
  2. In VisualLine.SetTextLines, multiply line.Height by textView.LineSpacing
  3. In VisualLine.GetTextLineVisualYPosition and VisualLine.GetTextLineByVisualYPosition, multiply each occurrence of tl.Height by textView.LineSpacing

Then you can set TextView.LineSpacing directly in code or through a custom XAML style, etc.

That seemed to be all that was necessary.

PS - AvalonEdit is fantastic!

In addition to what @Robert Jordan said, there is a couple more changes i had to make to get a satisfactory result.

First off, to make the text marking overlay to cover the entire height of the line, change the following lines in BackgroundGeometryBuilder.cs

ProcessTextLines method, line 196:

Line 215:

yield return new Rect(pos, y, 1, line.Height * textView.LineSpacing);

Line 259:

lastRect = new Rect(Math.Min(left, right), y, Math.Abs(right - left), line.Height * textView.LineSpacing);

Line 259:

Rect extendSelection = new Rect(Math.Min(left, right), y, Math.Abs(right - left), line.Height * textView.LineSpacing);

The second issue i had was that the text was rendered at the top of the now extended line height, not the bottom. To fix that, the extra space needs to be added before the line is rendered, not after. So make the following changes:

VisualLine.cs, Line 714

public VisualLineDrawingVisual(VisualLine visualLine, double lineSpacing) {
    this.VisualLine = visualLine;
    var drawingContext = RenderOpen();
    double pos = 0;
    foreach (TextLine textLine in visualLine.TextLines) {
        pos +=(textLine.Height * lineSpacing)-textLine.Height
        textLine.Draw(drawingContext, new Point(0, pos), InvertAxes.None);
        pos += textLine.Height;
    }
    this.Height = pos;
    drawingContext.Close();
}

In addition, i found it trivial to convert this to support dynamic line-by-line line spacings. Just move the LineSpacing property to VisualLine, then add a VisualLineTransformer like this:

class LinePaddingTransformer : IVisualLineTransformer
{
    public LinePaddingTransformer()
    {

    }
    public void Transform(ITextRunConstructionContext context, IList<VisualLineElement> elements)
    {
        int index = context.VisualLine.FirstDocumentLine.LineNumber - 1;
       //You need to calculate your padding from the line index somehow.
       //for example create a list of objects with the spacing in them and pass to this transformer.
        double spacing= MyCalculateLinePaddingMethod(index);  
        context.VisualLine.LineSpacing= spacing;
    }
}

If you require the spacing to only be added to the first line in case of text wrapping, you need to go over every single place you previously multiplied with the LineSpacing property and put a check if the TextLine is the first line in the collection.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top