Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,13 @@ public static void RecalculateSpanPositions(this TextView textView, Label elemen
if (layout == null)
return;

// Fix for https://github.com/dotnet/maui/issues/35755: skip spans in the
// ellipsized tail to avoid IndexOutOfBoundsException.
var lastLayoutLine = layout.LineCount - 1;
if (lastLayoutLine < 0)
return;
var layoutEndOffset = layout.GetLineEnd(lastLayoutLine);

int next = 0;
int count = 0;

Expand Down Expand Up @@ -163,8 +170,16 @@ public static void RecalculateSpanPositions(this TextView textView, Label elemen
var spanStartOffset = spannableString.GetSpanStart(startSpan);
var spanEndOffset = spannableString.GetSpanEnd(endSpan);

// Safe for TailTruncation only: both offsets share the same string prefix.
if (spanStartOffset >= layoutEndOffset)
Comment thread
Shalini-Ashokan marked this conversation as resolved.
continue;

var spanStartLine = layout.GetLineForOffset(spanStartOffset);
var spanEndLine = layout.GetLineForOffset(spanEndOffset);
var spanEndLine = layout.GetLineForOffset(System.Math.Min(spanEndOffset, layoutEndOffset - 1));

// OEM guard: some Layout subclasses don't cap GetLineForOffset at lineCount-1.
// Not dead code — see https://github.com/dotnet/maui/issues/35755
spanEndLine = System.Math.Min(spanEndLine, lastLayoutLine);
Comment thread
Shalini-Ashokan marked this conversation as resolved.

// Go through all lines that are affected by the span and calculate a rectangle for each
List<Graphics.Rect> spanRectangles = new List<Graphics.Rect>();
Expand Down
70 changes: 70 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue35755.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
namespace Maui.Controls.Sample.Issues;

[Issue(IssueTracker.Github, 35755, "IndexOutOfBoundsException in RecalculateSpanPositions when a Label uses FormattedText, MaxLines, and TailTruncation", PlatformAffected.Android)]
public class Issue35755 : ContentPage
{
readonly string _paragraphA = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum eleifend, augue nec aliquam interdum, massa nisl viverra orci, non interdum risus arcu id lorem. Curabitur accumsan, urna eu tempor tincidunt, purus neque feugiat tortor, sed tristique nibh nunc et augue.";
readonly string _paragraphB = "Aliquam erat volutpat. Quisque a mi lacus. Integer vitae malesuada sem. Nunc id dui nec lacus feugiat volutpat. Morbi et sollicitudin erat. Sed varius felis id dignissim facilisis. Vivamus vulputate, augue sed finibus laoreet, enim neque tristique odio, id rhoncus elit purus a turpis.";
readonly string _paragraphC = "Praesent in lectus non mauris mattis ultrices. Donec non justo ac nunc porta pellentesque. Integer euismod, velit in posuere iaculis, lorem nunc commodo libero, nec interdum lorem nibh ut turpis. Phasellus gravida tristique tortor, id posuere turpis sodales in.";

Label _crashTargetLabel;

public Issue35755()
{
_crashTargetLabel = new Label
{
AutomationId = "CrashTargetLabel",
MaxLines = 4,
LineBreakMode = LineBreakMode.TailTruncation,
FontSize = 14
};

var resultLabel = new Label
{
AutomationId = "ResultLabel",
Text = "Waiting for trigger..."
};

var triggerButton = new Button
{
AutomationId = "TriggerButton",
Text = "Trigger FormattedText"
};

triggerButton.Clicked += (s, e) =>
{
_crashTargetLabel.FormattedText = BuildFormattedText();
resultLabel.Text = "Success";
};

Content = new ScrollView
{
Content = new VerticalStackLayout
{
Padding = new Thickness(24),
Spacing = 16,
Children =
{
triggerButton,
resultLabel,
_crashTargetLabel
}
}
};
}

FormattedString BuildFormattedText()
{
var fs = new FormattedString();
fs.Spans.Add(new Span
{
Text = "Content: ",
FontAttributes = FontAttributes.Bold,
TextColor = Colors.DarkRed
});
fs.Spans.Add(new Span { Text = _paragraphA + "\n\n", TextColor = Colors.Black });
fs.Spans.Add(new Span { Text = _paragraphB + "\n\n", TextColor = Colors.DarkBlue });
fs.Spans.Add(new Span { Text = _paragraphC, TextColor = Colors.DarkGreen });
return fs;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues;

public class Issue35755 : _IssuesUITest
{
public Issue35755(TestDevice device) : base(device)
{
}

public override string Issue => "IndexOutOfBoundsException in RecalculateSpanPositions when a Label uses FormattedText, MaxLines, and TailTruncation";

[Test]
[Category(UITestCategories.Label)]
public void FormattedTextWithMaxLinesAndTailTruncationShouldNotCrash()
{
App.WaitForElement("TriggerButton");
App.Tap("TriggerButton");

App.WaitForElement("ResultLabel");
var resultText = App.FindElement("ResultLabel").GetText();
Assert.That(resultText, Is.EqualTo("Success"), "Label should display truncated FormattedText without crashing.");
App.WaitForElement("CrashTargetLabel");
Comment thread
Shalini-Ashokan marked this conversation as resolved.
}
}
Loading