|
| 1 | +package io.wdsj.asw.bukkit.listener |
| 2 | + |
| 3 | +internal data class SignReplacementSpan( |
| 4 | + val start: Int, |
| 5 | + val end: Int, |
| 6 | + val replacement: String, |
| 7 | +) |
| 8 | + |
| 9 | +internal object SignTextLayout { |
| 10 | + fun replaceSegments( |
| 11 | + segmentLengths: List<Int>, |
| 12 | + content: String, |
| 13 | + spans: List<SignReplacementSpan>, |
| 14 | + ): List<String> { |
| 15 | + if (segmentLengths.isEmpty()) return emptyList() |
| 16 | + |
| 17 | + val lengths = segmentLengths.toIntArray() |
| 18 | + require(lengths.all { it > 0 }) { "Segment lengths must be positive" } |
| 19 | + require(lengths.sum() == content.length) { "Segment lengths must cover the content exactly" } |
| 20 | + |
| 21 | + val starts = segmentStarts(lengths) |
| 22 | + val replacements = Array(lengths.size) { StringBuilder() } |
| 23 | + var cursor = 0 |
| 24 | + for (span in spans.sortedWith(compareBy<SignReplacementSpan> { it.start }.thenByDescending { it.end })) { |
| 25 | + val start = span.start.coerceIn(0, content.length) |
| 26 | + val end = span.end.coerceIn(start, content.length) |
| 27 | + if (start < cursor || start == end) continue |
| 28 | + |
| 29 | + appendUnchanged(replacements, lengths, starts, content, cursor, start) |
| 30 | + appendReplacement(replacements, lengths, starts, start, end, span.replacement) |
| 31 | + cursor = end |
| 32 | + } |
| 33 | + appendUnchanged(replacements, lengths, starts, content, cursor, content.length) |
| 34 | + return replacements.map(StringBuilder::toString) |
| 35 | + } |
| 36 | + |
| 37 | + private fun segmentStarts(lengths: IntArray): IntArray { |
| 38 | + val starts = IntArray(lengths.size) |
| 39 | + for (index in 1 until lengths.size) { |
| 40 | + starts[index] = starts[index - 1] + lengths[index - 1] |
| 41 | + } |
| 42 | + return starts |
| 43 | + } |
| 44 | + |
| 45 | + private fun appendUnchanged( |
| 46 | + replacements: Array<StringBuilder>, |
| 47 | + lengths: IntArray, |
| 48 | + starts: IntArray, |
| 49 | + content: String, |
| 50 | + start: Int, |
| 51 | + end: Int, |
| 52 | + ) { |
| 53 | + var cursor = start |
| 54 | + while (cursor < end) { |
| 55 | + val segmentIndex = segmentIndexAt(starts, cursor) |
| 56 | + val segmentEnd = starts[segmentIndex] + lengths[segmentIndex] |
| 57 | + val copyEnd = minOf(end, segmentEnd) |
| 58 | + replacements[segmentIndex].append(content, cursor, copyEnd) |
| 59 | + cursor = copyEnd |
| 60 | + } |
| 61 | + } |
| 62 | + |
| 63 | + private fun appendReplacement( |
| 64 | + replacements: Array<StringBuilder>, |
| 65 | + lengths: IntArray, |
| 66 | + starts: IntArray, |
| 67 | + start: Int, |
| 68 | + end: Int, |
| 69 | + replacement: String, |
| 70 | + ) { |
| 71 | + if (replacement.length != end - start) { |
| 72 | + replacements[segmentIndexAt(starts, start)].append(replacement) |
| 73 | + return |
| 74 | + } |
| 75 | + |
| 76 | + var contentOffset = start |
| 77 | + var replacementOffset = 0 |
| 78 | + while (contentOffset < end) { |
| 79 | + val segmentIndex = segmentIndexAt(starts, contentOffset) |
| 80 | + val segmentEnd = starts[segmentIndex] + lengths[segmentIndex] |
| 81 | + val copyEnd = minOf(end, segmentEnd) |
| 82 | + val copyLength = copyEnd - contentOffset |
| 83 | + replacements[segmentIndex].append( |
| 84 | + replacement, |
| 85 | + replacementOffset, |
| 86 | + replacementOffset + copyLength, |
| 87 | + ) |
| 88 | + contentOffset = copyEnd |
| 89 | + replacementOffset += copyLength |
| 90 | + } |
| 91 | + } |
| 92 | + |
| 93 | + private fun segmentIndexAt(starts: IntArray, index: Int): Int { |
| 94 | + for (segmentIndex in starts.indices.reversed()) { |
| 95 | + if (index >= starts[segmentIndex]) return segmentIndex |
| 96 | + } |
| 97 | + return 0 |
| 98 | + } |
| 99 | +} |
0 commit comments