File: Android\Extensions\TextViewExtensions.cs
Web Access
Project: src\src\Compatibility\Core\src\Compatibility.csproj (Microsoft.Maui.Controls.Compatibility)
using System;
using System.Collections.Generic;
using Android.Text;
using Android.Widget;
using Microsoft.Maui.Controls.Internals;
 
namespace Microsoft.Maui.Controls.Compatibility.Platform.Android
{
	internal static class TextViewExtensions
	{
		[PortHandler]
		public static void SetMaxLines(this TextView textView, Label label)
		{
			var maxLines = label.MaxLines;
 
			if (maxLines == (int)Label.MaxLinesProperty.DefaultValue)
			{
				// MaxLines is not explicitly set, so just let it be whatever gets set by LineBreakMode
				textView.SetLineBreakMode(label);
				return;
			}
 
			textView.SetMaxLines(maxLines);
		}
 
		public static void SetLineBreakMode(this TextView textView, Label label)
		{
			var maxLines = SetLineBreak(textView, label.LineBreakMode);
			textView.SetMaxLines(maxLines);
		}
 
		public static void SetLineBreakMode(this TextView textView, Button button) =>
			SetLineBreak(textView, button.LineBreakMode);
 
		[PortHandler]
		public static int SetLineBreak(TextView textView, LineBreakMode lineBreakMode)
		{
			int maxLines = Int32.MaxValue;
			bool singleLine = false;
 
			switch (lineBreakMode)
			{
				case LineBreakMode.NoWrap:
					maxLines = 1;
					singleLine = true;
					textView.Ellipsize = null;
					break;
				case LineBreakMode.WordWrap:
					textView.Ellipsize = null;
					break;
				case LineBreakMode.CharacterWrap:
					textView.Ellipsize = null;
					break;
				case LineBreakMode.HeadTruncation:
					maxLines = 1;
					singleLine = true; // Workaround for bug in older Android API versions (https://bugzilla.xamarin.com/show_bug.cgi?id=49069)
					textView.Ellipsize = TextUtils.TruncateAt.Start;
					break;
				case LineBreakMode.TailTruncation:
					maxLines = 1;
					singleLine = true;
					textView.Ellipsize = TextUtils.TruncateAt.End;
					break;
				case LineBreakMode.MiddleTruncation:
					maxLines = 1;
					singleLine = true; // Workaround for bug in older Android API versions (https://bugzilla.xamarin.com/show_bug.cgi?id=49069)
					textView.Ellipsize = TextUtils.TruncateAt.Middle;
					break;
			}
 
			textView.SetSingleLine(singleLine);
			return maxLines;
		}
 
		public static void RecalculateSpanPositions(this TextView textView, Label element, SpannableString spannableString, SizeRequest finalSize)
		{
			if (element?.FormattedText?.Spans == null || element.FormattedText.Spans.Count == 0)
				return;
 
			var labelWidth = finalSize.Request.Width;
			if (labelWidth <= 0 || finalSize.Request.Height <= 0)
				return;
 
			if (spannableString == null || spannableString.IsDisposed())
				return;
 
			var layout = textView.Layout;
			if (layout == null)
				return;
 
			int next = 0;
			int count = 0;
			IList<int> totalLineHeights = new List<int>();
 
#pragma warning disable CA1416 // https://github.com/xamarin/xamarin-android/issues/6962
			for (int i = 0; i < spannableString.Length(); i = next)
			{
				var type = Java.Lang.Class.FromType(typeof(Java.Lang.Object));
 
				var span = element.FormattedText.Spans[count];
 
				count++;
 
				if (string.IsNullOrEmpty(span.Text))
					continue;
 
				// find the next span
				next = spannableString.NextSpanTransition(i, spannableString.Length(), type);
#pragma warning restore CA1416
 
				// get all spans in the range - Android can have overlapping spans				
				var spans = spannableString.GetSpans(i, next, type);
 
				var startSpan = spans[0];
				var endSpan = spans[spans.Length - 1];
 
				var startSpanOffset = spannableString.GetSpanStart(startSpan);
				var endSpanOffset = spannableString.GetSpanEnd(endSpan);
 
				var thisLine = layout.GetLineForOffset(endSpanOffset);
				var lineStart = layout.GetLineStart(thisLine);
				var lineEnd = layout.GetLineEnd(thisLine);
 
				//If this is true, endSpanOffset has the value for another line that belong to the next span and not it self. 
				//So it should be rearranged to value not pass the lineEnd.
				if (endSpanOffset > (lineEnd - lineStart))
					endSpanOffset = lineEnd;
 
				var startX = layout.GetPrimaryHorizontal(startSpanOffset);
				var endX = layout.GetPrimaryHorizontal(endSpanOffset);
 
				var startLine = layout.GetLineForOffset(startSpanOffset);
				var endLine = layout.GetLineForOffset(endSpanOffset);
 
				double[] lineHeights = new double[endLine - startLine + 1];
 
				// calculate all the different line heights
				for (var lineCount = startLine; lineCount <= endLine; lineCount++)
				{
					var lineHeight = layout.GetLineBottom(lineCount) - layout.GetLineTop(lineCount);
					lineHeights[lineCount - startLine] = lineHeight;
 
					if (totalLineHeights.Count <= lineCount)
						totalLineHeights.Add(lineHeight);
				}
 
				var yaxis = 0.0;
 
 
				for (var line = startLine; line > 0; line--)
					yaxis += totalLineHeights[line];
 
				((ISpatialElement)span).Region = Region.FromLines(lineHeights, labelWidth, startX, endX, yaxis).Inflate(10);
			}
		}
	}
}