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 ); } } } }