diff options
Diffstat (limited to 'src/RunStyles.h')
-rw-r--r-- | src/RunStyles.h | 293 |
1 files changed, 270 insertions, 23 deletions
diff --git a/src/RunStyles.h b/src/RunStyles.h index 44367adf7..98c50f224 100644 --- a/src/RunStyles.h +++ b/src/RunStyles.h @@ -27,30 +27,277 @@ class RunStyles { private: Partitioning<DISTANCE> starts; SplitVector<STYLE> styles; - DISTANCE RunFromPosition(DISTANCE position) const noexcept; - DISTANCE SplitRun(DISTANCE position); - void RemoveRun(DISTANCE run); - void RemoveRunIfEmpty(DISTANCE run); - void RemoveRunIfSameAsPrevious(DISTANCE run); + + // Find the first run at a position + DISTANCE RunFromPosition(DISTANCE position) const noexcept { + DISTANCE run = starts.PartitionFromPosition(position); + // Go to first element with this position + while ((run > 0) && (position == starts.PositionFromPartition(run-1))) { + run--; + } + return run; + } + + // If there is no run boundary at position, insert one continuing style. + DISTANCE SplitRun(DISTANCE position) { + DISTANCE run = RunFromPosition(position); + const DISTANCE posRun = starts.PositionFromPartition(run); + if (posRun < position) { + STYLE runStyle = ValueAt(position); + run++; + starts.InsertPartition(run, position); + styles.InsertValue(run, 1, runStyle); + } + return run; + } + + void RemoveRun(DISTANCE run) { + starts.RemovePartition(run); + styles.DeleteRange(run, 1); + } + + void RemoveRunIfEmpty(DISTANCE run) { + if ((run < starts.Partitions()) && (starts.Partitions() > 1)) { + if (starts.PositionFromPartition(run) == starts.PositionFromPartition(run+1)) { + RemoveRun(run); + } + } + } + + void RemoveRunIfSameAsPrevious(DISTANCE run) { + if ((run > 0) && (run < starts.Partitions())) { + const DISTANCE runBefore = run - 1; + if (styles.ValueAt(runBefore) == styles.ValueAt(run)) { + RemoveRun(run); + } + } + } + public: - RunStyles(); - DISTANCE Length() const noexcept; - STYLE ValueAt(DISTANCE position) const noexcept; - DISTANCE FindNextChange(DISTANCE position, DISTANCE end) const noexcept; - DISTANCE StartRun(DISTANCE position) const noexcept; - DISTANCE EndRun(DISTANCE position) const noexcept; - // Returns changed=true if some values may have changed - FillResult<DISTANCE> FillRange(DISTANCE position, STYLE value, DISTANCE fillLength); - void SetValueAt(DISTANCE position, STYLE value); - void InsertSpace(DISTANCE position, DISTANCE insertLength); - void DeleteAll(); - void DeleteRange(DISTANCE position, DISTANCE deleteLength); - DISTANCE Runs() const noexcept; - bool AllSame() const noexcept; - bool AllSameAs(STYLE value) const noexcept; - DISTANCE Find(STYLE value, DISTANCE start) const noexcept; - - void Check() const; + RunStyles() { + styles.InsertValue(0, 2, 0); + } + + DISTANCE Length() const noexcept { + return starts.PositionFromPartition(starts.Partitions()); + } + + STYLE ValueAt(DISTANCE position) const noexcept { + return styles.ValueAt(starts.PartitionFromPosition(position)); + } + + DISTANCE FindNextChange(DISTANCE position, DISTANCE end) const noexcept { + const DISTANCE run = starts.PartitionFromPosition(position); + if (run < starts.Partitions()) { + const DISTANCE runChange = starts.PositionFromPartition(run); + if (runChange > position) + return runChange; + const DISTANCE nextChange = starts.PositionFromPartition(run + 1); + if (nextChange > position) { + return nextChange; + } else if (position < end) { + return end; + } else { + return end + 1; + } + } else { + return end + 1; + } + } + + DISTANCE StartRun(DISTANCE position) const noexcept { + return starts.PositionFromPartition(starts.PartitionFromPosition(position)); + } + + DISTANCE EndRun(DISTANCE position) const noexcept { + return starts.PositionFromPartition(starts.PartitionFromPosition(position) + 1); + } + + FillResult<DISTANCE> FillRange(DISTANCE position, STYLE value, DISTANCE fillLength) { + const FillResult<DISTANCE> resultNoChange{false, position, fillLength}; + if (fillLength <= 0) { + return resultNoChange; + } + DISTANCE end = position + fillLength; + if (end > Length()) { + return resultNoChange; + } + DISTANCE runEnd = RunFromPosition(end); + const STYLE valueCurrent = styles.ValueAt(runEnd); + if (valueCurrent == value) { + // End already has value so trim range. + end = starts.PositionFromPartition(runEnd); + if (position >= end) { + // Whole range is already same as value so no action + return resultNoChange; + } + fillLength = end - position; + } else { + const DISTANCE startRun = starts.PositionFromPartition(runEnd); + if (position > startRun) { + const DISTANCE runNext = runEnd + 1; + const DISTANCE endRun = starts.PositionFromPartition(runNext); + if (end < endRun) { + // New piece is completely inside a run with a different value so its a simple + // insertion of two points [ (position, value), (end, valueCurrent) ] + const DISTANCE range[] { position, end}; + starts.InsertPartitions(runEnd + 1, range, 2); + // Temporary runEndIndex silences non-useful arithmetic overflow warnings + const ptrdiff_t runEndIndex = runEnd; + styles.Insert(runEndIndex + 1, value); + styles.Insert(runEndIndex + 2, valueCurrent); + return { true, position, fillLength }; + } + } + runEnd = SplitRun(end); + } + DISTANCE runStart = RunFromPosition(position); + if (styles.ValueAt(runStart) == value) { + // Start is in expected value so trim range. + runStart++; + position = starts.PositionFromPartition(runStart); + fillLength = end - position; + } else { + if (starts.PositionFromPartition(runStart) < position) { + runStart = SplitRun(position); + runEnd++; + } + } + if (runStart < runEnd) { + const FillResult<DISTANCE> result{ true, position, fillLength }; + styles.SetValueAt(runStart, value); + // Remove each old run over the range + for (DISTANCE run=runStart+1; run<runEnd; run++) { + RemoveRun(runStart+1); + } + runEnd = RunFromPosition(end); + RemoveRunIfSameAsPrevious(runEnd); + RemoveRunIfSameAsPrevious(runStart); + runEnd = RunFromPosition(end); + RemoveRunIfEmpty(runEnd); + return result; + } + return resultNoChange; + } + + void SetValueAt(DISTANCE position, STYLE value) { + FillRange(position, value, 1); + } + + void InsertSpace(DISTANCE position, DISTANCE insertLength) { + DISTANCE runStart = RunFromPosition(position); + if (starts.PositionFromPartition(runStart) == position) { + STYLE runStyle = ValueAt(position); + // Inserting at start of run so make previous longer + if (runStart == 0) { + // Inserting at start of document so ensure 0 + if (runStyle) { + styles.SetValueAt(0, STYLE()); + starts.InsertPartition(1, 0); + styles.InsertValue(1, 1, runStyle); + starts.InsertText(0, insertLength); + } else { + starts.InsertText(runStart, insertLength); + } + } else { + if (runStyle) { + starts.InsertText(runStart-1, insertLength); + } else { + // Insert at end of run so do not extend style + starts.InsertText(runStart, insertLength); + } + } + } else { + starts.InsertText(runStart, insertLength); + } + } + + void DeleteAll() { + starts = Partitioning<DISTANCE>(); + styles = SplitVector<STYLE>(); + styles.InsertValue(0, 2, 0); + } + + void DeleteRange(DISTANCE position, DISTANCE deleteLength) { + DISTANCE end = position + deleteLength; + DISTANCE runStart = RunFromPosition(position); + DISTANCE runEnd = RunFromPosition(end); + if (runStart == runEnd) { + // Deleting from inside one run + starts.InsertText(runStart, -deleteLength); + RemoveRunIfEmpty(runStart); + } else { + runStart = SplitRun(position); + runEnd = SplitRun(end); + starts.InsertText(runStart, -deleteLength); + // Remove each old run over the range + for (DISTANCE run=runStart; run<runEnd; run++) { + RemoveRun(runStart); + } + RemoveRunIfEmpty(runStart); + RemoveRunIfSameAsPrevious(runStart); + } + } + + DISTANCE Runs() const noexcept { + return starts.Partitions(); + } + + bool AllSame() const noexcept { + for (DISTANCE run = 1; run < starts.Partitions(); run++) { + const DISTANCE runBefore = run - 1; + if (styles.ValueAt(run) != styles.ValueAt(runBefore)) + return false; + } + return true; + } + + bool AllSameAs(STYLE value) const noexcept { + return AllSame() && (styles.ValueAt(0) == value); + } + + DISTANCE Find(STYLE value, DISTANCE start) const noexcept { + if (start < Length()) { + DISTANCE run = start ? RunFromPosition(start) : 0; + if (styles.ValueAt(run) == value) + return start; + run++; + while (run < starts.Partitions()) { + if (styles.ValueAt(run) == value) + return starts.PositionFromPartition(run); + run++; + } + } + return -1; + } + + void Check() const { + if (Length() < 0) { + throw std::runtime_error("RunStyles: Length can not be negative."); + } + if (starts.Partitions() < 1) { + throw std::runtime_error("RunStyles: Must always have 1 or more partitions."); + } + if (starts.Partitions() != styles.Length()-1) { + throw std::runtime_error("RunStyles: Partitions and styles different lengths."); + } + DISTANCE start=0; + while (start < Length()) { + const DISTANCE end = EndRun(start); + if (start >= end) { + throw std::runtime_error("RunStyles: Partition is 0 length."); + } + start = end; + } + if (styles.ValueAt(styles.Length()-1) != 0) { + throw std::runtime_error("RunStyles: Unused style at end changed."); + } + for (ptrdiff_t j=1; j<styles.Length()-1; j++) { + if (styles.ValueAt(j) == styles.ValueAt(j-1)) { + throw std::runtime_error("RunStyles: Style of a partition same as previous."); + } + } + } }; } |