using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Text; using System.Xml.Schema; namespace Topten.RichTextKit.Utils { /// /// Interface to a run object with a start offset and a length /// public interface IRun { /// /// Offset of the start of this run /// int Offset { get; } /// /// Length of this run /// int Length { get; } } /// /// Represents a sub-run in a list of runs /// [DebuggerDisplay("[{Index}] [{Offset} + {Length} = {Offset + Length}] {Partial}")] public struct SubRun { /// /// Constructs a new sub-run /// /// The run index /// The sub-run offset /// The sub-run length /// True if the sub-run is partial run public SubRun(int index, int offset, int length, bool partial) { Index = index; Offset = offset; Length = length; Partial = partial; } /// /// The index of the run in the list of runs /// public int Index; /// /// Offset of this sub-run in the containing run /// public int Offset; /// /// Length of this sub-run in the containing run /// public int Length; /// /// Indicates if this sub-run is partial sub-run /// public bool Partial; } /// /// Helpers for iterating over a set of consecutive runs /// public static class RunExtensions { /// /// Given a list of consecutive runs, a start index and a length /// provides a list of sub-runs in the list of runs. /// /// The list element type /// The list of runs /// The offset of the run /// The length of the run /// An enumerable collection of SubRuns public static IEnumerable GetInterectingRuns(this IReadOnlyList list, int offset, int length) where T : IRun { // Check list is consistent list.CheckValid(); // Calculate end position int to = offset + length; // Find the start run int startRunIndex = list.BinarySearch(offset, (r, a) => { if (r.Offset > a) return 1; if (r.Offset + r.Length <= a) return -1; return 0; }); Debug.Assert(startRunIndex >= 0); Debug.Assert(startRunIndex < list.Count); // Iterate over all runs for (int i = startRunIndex; i < list.Count; i++) { // Get the run var r = list[i]; // Quit if past requested run if (r.Offset >= to) break; // Yield sub-run var sr = new SubRun(); sr.Index = i; sr.Offset = i == startRunIndex ? offset - r.Offset : 0; sr.Length = Math.Min(r.Offset + r.Length, to) - r.Offset - sr.Offset; sr.Partial = r.Length != sr.Length; yield return sr; } } /// /// Given a list of consecutive runs, a start index and a length /// provides a list of sub-runs in the list of runs (in reverse order) /// /// The list element type /// The list of runs /// The offset of the run /// The length of the run /// An enumerable collection of SubRuns public static IEnumerable GetIntersectingRunsReverse(this IReadOnlyList list, int offset, int length) where T : IRun { // Check list is consistent list.CheckValid(); // Calculate end position int to = offset + length; // Find the start run int endRunIndex = list.BinarySearch(to, (r, a) => { if (r.Offset >= a) return 1; if (r.Offset + r.Length < a) return -1; return 0; }); Debug.Assert(endRunIndex >= 0); Debug.Assert(endRunIndex < list.Count); // Iterate over all runs for (int i = endRunIndex; i >= 0; i--) { // Get the run var r = list[i]; // Quit if past requested run if (r.Offset + r.Length <= offset) break; // Yield sub-run var sr = new SubRun(); sr.Index = i; sr.Offset = r.Offset > offset ? 0 : offset - r.Offset; sr.Length = Math.Min(r.Offset + r.Length, to) - r.Offset - sr.Offset; sr.Partial = r.Length != sr.Length; yield return sr; } } /// /// Get the total length of a list of consecutive runs /// /// The element type /// The list of runs /// The total length public static int TotalLength(this IReadOnlyList list) where T : IRun { // Empty list? if (list.Count == 0) return 0; // Get length from last element var last = list[list.Count - 1]; return last.Offset + last.Length; } /// /// Check that a list of runs is valid /// /// The element type /// The list to be checked [Conditional("DEBUG")] public static void CheckValid(this IReadOnlyList list) where T : IRun { CheckValid(list, list.TotalLength()); } /// /// Check that a list of runs is valid /// /// The element type /// The list to be checked /// The expected total length of the list of runs [Conditional("DEBUG")] public static void CheckValid(this IReadOnlyList list, int totalLength) where T : IRun { if (list.Count > 0) { // Must start at zero Debug.Assert(list[0].Offset == 0); // Must cover entire code point buffer Debug.Assert(list[list.Count - 1].Offset + list[list.Count - 1].Length == totalLength); var prev = list[0]; for (int i = 1; i < list.Count; i++) { // All runs must have a length Debug.Assert(list[i].Length > 0); // All runs must be consecutive and joined end to end Debug.Assert(list[i].Offset == prev.Offset + prev.Length); prev = list[i]; } } else { // If no style runs then mustn't have any code points either Debug.Assert(totalLength == 0); } } } }