123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237 |
- 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
- {
- /// <summary>
- /// Interface to a run object with a start offset and a length
- /// </summary>
- public interface IRun
- {
- /// <summary>
- /// Offset of the start of this run
- /// </summary>
- int Offset { get; }
- /// <summary>
- /// Length of this run
- /// </summary>
- int Length { get; }
- }
- /// <summary>
- /// Represents a sub-run in a list of runs
- /// </summary>
- [DebuggerDisplay("[{Index}] [{Offset} + {Length} = {Offset + Length}] {Partial}")]
- public struct SubRun
- {
- /// <summary>
- /// Constructs a new sub-run
- /// </summary>
- /// <param name="index">The run index</param>
- /// <param name="offset">The sub-run offset</param>
- /// <param name="length">The sub-run length</param>
- /// <param name="partial">True if the sub-run is partial run</param>
- public SubRun(int index, int offset, int length, bool partial)
- {
- Index = index;
- Offset = offset;
- Length = length;
- Partial = partial;
- }
- /// <summary>
- /// The index of the run in the list of runs
- /// </summary>
- public int Index;
- /// <summary>
- /// Offset of this sub-run in the containing run
- /// </summary>
- public int Offset;
- /// <summary>
- /// Length of this sub-run in the containing run
- /// </summary>
- public int Length;
- /// <summary>
- /// Indicates if this sub-run is partial sub-run
- /// </summary>
- public bool Partial;
- }
- /// <summary>
- /// Helpers for iterating over a set of consecutive runs
- /// </summary>
- public static class RunExtensions
- {
- /// <summary>
- /// Given a list of consecutive runs, a start index and a length
- /// provides a list of sub-runs in the list of runs.
- /// </summary>
- /// <typeparam name="T">The list element type</typeparam>
- /// <param name="list">The list of runs</param>
- /// <param name="offset">The offset of the run</param>
- /// <param name="length">The length of the run</param>
- /// <returns>An enumerable collection of SubRuns</returns>
- public static IEnumerable<SubRun> GetInterectingRuns<T>(this IReadOnlyList<T> 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;
- }
- }
- /// <summary>
- /// 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)
- /// </summary>
- /// <typeparam name="T">The list element type</typeparam>
- /// <param name="list">The list of runs</param>
- /// <param name="offset">The offset of the run</param>
- /// <param name="length">The length of the run</param>
- /// <returns>An enumerable collection of SubRuns</returns>
- public static IEnumerable<SubRun> GetIntersectingRunsReverse<T>(this IReadOnlyList<T> 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;
- }
- }
- /// <summary>
- /// Get the total length of a list of consecutive runs
- /// </summary>
- /// <typeparam name="T">The element type</typeparam>
- /// <param name="list">The list of runs</param>
- /// <returns>The total length</returns>
- public static int TotalLength<T>(this IReadOnlyList<T> 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;
- }
- /// <summary>
- /// Check that a list of runs is valid
- /// </summary>
- /// <typeparam name="T">The element type</typeparam>
- /// <param name="list">The list to be checked</param>
- [Conditional("DEBUG")]
- public static void CheckValid<T>(this IReadOnlyList<T> list) where T : IRun
- {
- CheckValid(list, list.TotalLength());
- }
- /// <summary>
- /// Check that a list of runs is valid
- /// </summary>
- /// <typeparam name="T">The element type</typeparam>
- /// <param name="list">The list to be checked</param>
- /// <param name="totalLength">The expected total length of the list of runs</param>
- [Conditional("DEBUG")]
- public static void CheckValid<T>(this IReadOnlyList<T> 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);
- }
- }
- }
- }
|