File: Text\AttributedTextRunExtensions.cs
Web Access
Project: src\src\Graphics\src\Graphics\Graphics.csproj (Microsoft.Maui.Graphics)
using System;
using System.Collections.Generic;
using Microsoft.Maui.Graphics.Text;
 
namespace Microsoft.Maui.Graphics.Text
{
	public static class AttributedTextRunExtensions
	{
		public static int GetEnd(this IAttributedTextRun run)
		{
			if (run == null)
				return 0;
 
			return run.Start + run.Length;
		}
 
		public static bool Intersects(
			this IAttributedTextRun first,
			IAttributedTextRun second)
		{
			if (first == null || second == null)
				return false;
 
			if (first.Start < second.GetEnd())
				return first.GetEnd() > second.Start;
 
			return false;
		}
 
		public static bool Intersects(
			this IAttributedTextRun first,
			int start,
			int length)
		{
			if (first == null)
				return false;
 
			var end = start + length;
			if (first.Start < end)
				return first.GetEnd() > start;
 
			return false;
		}
 
		public static bool IntersectsExactly(
			this IAttributedTextRun first,
			IAttributedTextRun second)
		{
			if (first == null || second == null)
				return false;
 
			return first.Start == second.Start && first.Length == second.Length;
		}
 
		public static bool IntersectsExactly(
			this IAttributedTextRun first,
			int start,
			int length)
		{
			if (first == null)
				return false;
 
			return first.Start == start && first.Length == length;
		}
 
		public static IList<IAttributedTextRun> CalculatedIntersections(
			this IAttributedTextRun first,
			IAttributedTextRun second)
		{
			List<IAttributedTextRun> intersections = new List<IAttributedTextRun>();
 
			var combined = first.Attributes.Union(second.Attributes);
			if (first.Start == second.Start)
			{
				if (first.Length == second.Length)
				{
					intersections.Add(new AttributedTextRun(first.Start, first.Length, combined));
				}
				else if (first.Length > second.Length)
				{
					var start1 = first.Start;
					var length1 = Math.Min(first.Length, second.Length);
					var start2 = start1 + length1;
					var length2 = Math.Max(first.Length, second.Length) - length1;
 
					intersections.Add(new AttributedTextRun(start1, length1, combined));
					intersections.Add(new AttributedTextRun(start2, length2, first.Attributes));
				}
				else if (first.Length < second.Length)
				{
					var start1 = first.Start;
					var length1 = Math.Min(first.Length, second.Length);
					var start2 = start1 + length1;
					var length2 = Math.Max(first.Length, second.Length) - length1;
 
					intersections.Add(new AttributedTextRun(start1, length1, combined));
					intersections.Add(new AttributedTextRun(start2, length2, second.Attributes));
				}
			}
			else if (first.GetEnd() == second.GetEnd())
			{
				if (first.Start < second.Start)
				{
					var start1 = first.Start;
					var length1 = second.Start - first.Start;
					var start2 = start1 + length1;
					var length2 = Math.Max(first.Length, second.Length) - length1;
 
					intersections.Add(new AttributedTextRun(start1, length1, first.Attributes));
					intersections.Add(new AttributedTextRun(start2, length2, combined));
				}
				else
				{
					var start1 = second.Start;
					var length1 = first.Start - second.Start;
					var start2 = start1 + length1;
					var length2 = Math.Max(first.Length, second.Length) - length1;
 
					intersections.Add(new AttributedTextRun(start1, length1, second.Attributes));
					intersections.Add(new AttributedTextRun(start2, length2, combined));
				}
			}
			else
			{
				if (first.Start < second.Start)
				{
					var start1 = first.Start;
					var length1 = second.Start - first.Start;
					var start2 = start1 + length1;
					var length2 = second.Length;
					var start3 = start2 + length2;
					var length3 = Math.Max(first.Length, second.Length) - (length1 + length2);
 
					intersections.Add(new AttributedTextRun(start1, length1, first.Attributes));
					intersections.Add(new AttributedTextRun(start2, length2, combined));
					intersections.Add(new AttributedTextRun(start3, length3, first.GetEnd() > second.GetEnd() ? first.Attributes : second.Attributes));
				}
				else
				{
					var start1 = second.Start;
					var length1 = first.Start - second.Start;
					var start2 = start1 + length1;
					var length2 = first.Length;
					var start3 = start2 + length2;
					var length3 = Math.Max(first.Length, second.Length) - (length1 + length2);
 
					intersections.Add(new AttributedTextRun(start1, length1, second.Attributes));
					intersections.Add(new AttributedTextRun(start2, length2, combined));
					intersections.Add(new AttributedTextRun(start3, length3, first.GetEnd() > second.GetEnd() ? first.Attributes : second.Attributes));
				}
			}
 
			return intersections;
		}
 
		public static void Optimize(this List<IAttributedTextRun> runs, int textLength)
		{
			// Loop through the runs and make sure that they don't extend beyond the bounds of the text.
			for (int i = 0; i < runs.Count; i++)
			{
				var run = runs[i];
				var end = run.GetEnd();
 
				if (run.Start < 0 || end > textLength)
				{
					var start = Math.Max(run.Start, 0);
					var maxLength = textLength - start;
					var length = Math.Min(run.Length, maxLength);
					if (length > 0)
						runs[i] = new AttributedTextRun(start, length, run.Attributes);
					else
						runs.RemoveAt(i--);
				}
			}
 
			runs.Sort(AttributedTextRunComparer.Instance);
 
			// Loop through the runs and join the ones that overlap.
			IAttributedTextRun previous = null;
			for (int i = 0; i < runs.Count; i++)
			{
				var run = runs[i];
 
				if (previous != null)
				{
					if (previous.IntersectsExactly(run))
					{
						var combined = previous.Attributes.Union(run.Attributes);
						run = runs[i - 1] = new AttributedTextRun(run.Start, run.Length, combined);
						runs.RemoveAt(i--);
					}
					else if (previous.Intersects(run))
					{
						var intersections = previous.CalculatedIntersections(run);
						runs.RemoveAt(i--);
						runs.RemoveAt(i);
						runs.InsertRange(i++, intersections);
						run = runs[i];
						runs.Sort(AttributedTextRunComparer.Instance);
					}
				}
 
				previous = run;
			}
		}
	}
}