Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,8 @@ class CompiledMotd(document: JsonObject, private val alignments: List<String>) {
width +=
(advances[char] ?: 6) +
(if (
char != " " &&
style.decoration(TextDecoration.BOLD) ==
TextDecoration.State.TRUE
style.decoration(TextDecoration.BOLD) ==
TextDecoration.State.TRUE
)
1
else 0)
Expand All @@ -60,19 +59,57 @@ class CompiledMotd(document: JsonObject, private val alignments: List<String>) {
measure(line, Style.empty())
val remaining = (lineWidth - width).coerceAtLeast(0)
val alignment = alignments.getOrNull(index) ?: "left"
val spaces =
val target =
when (alignment) {
"center" -> kotlin.math.floor(remaining / 8.0 + .5).toInt()
"right" -> kotlin.math.floor(remaining / 4.0 + .5).toInt()
else -> 0
"center" -> remaining / 2.0
"right" -> remaining.toDouble()
else -> 0.0
}
val (regular, bold) = padding(target, remaining)
if (index > 0) result = result.append(Component.newline())
result = result.append(Component.text(" ".repeat(spaces))).append(line)
if (regular > 0)
result =
result.append(
Component.text(" ".repeat(regular)).decoration(TextDecoration.BOLD, false)
)
if (bold > 0)
result =
result.append(
Component.text(" ".repeat(bold)).decoration(TextDecoration.BOLD, true)
)
result = result.append(line)
}
return result
}

companion object {
/** Same bounded 4/5-pixel native space search as library-motd. */
private fun padding(target: Double, available: Int): Pair<Int, Int> {
var regular = 0
var bold = 0
var advance = 0
var error = kotlin.math.abs(target)
for (b in 0..3) {
val near = (target - b * 5) / 4
for (r in listOf(kotlin.math.floor(near).toInt(), kotlin.math.ceil(near).toInt())) {
val candidate = r * 4 + b * 5
if (r < 0 || candidate > available) continue
val distance = kotlin.math.abs(candidate - target)
if (
distance < error ||
(distance == error &&
(candidate > advance || (candidate == advance && b < bold)))
) {
regular = r
bold = b
advance = candidate
error = distance
}
}
}
return regular to bold
}

private val advances: Map<String, Int> =
CompiledMotd::class
.java
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package gg.grounds.proxy.velocity.motd

import com.google.gson.JsonParser
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.TextComponent
import net.kyori.adventure.text.format.Style
import net.kyori.adventure.text.format.TextDecoration
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test

class NativeMotdAlignmentTest {
private fun characters(component: Component): List<Pair<String, Boolean>> {
val chars = mutableListOf<Pair<String, Boolean>>()
fun walk(node: Component, inherited: Style) {
val style = node.style().merge(inherited, Style.Merge.Strategy.IF_ABSENT_ON_TARGET)
if (node is TextComponent)
node.content().codePoints().forEach {
chars.add(
String(Character.toChars(it)) to
(style.decoration(TextDecoration.BOLD) == TextDecoration.State.TRUE)
)
}
node.children().forEach { walk(it, style) }
}
walk(component, Style.empty())
return chars
}

@Test
fun `matches the shared native padding and resolved placeholder fixtures`() {
val samples =
JsonParser.parseReader(
javaClass.getResourceAsStream("/server-list/native-padding.json")!!.reader()
)
.asJsonArray
val advances =
JsonParser.parseReader(
javaClass.getResourceAsStream("/motd/java-1.21.1-advances.json")!!.reader()
)
.asJsonObject
for (sample in samples) {
val s = sample.asJsonObject
val source = s.get("source").asString
val document =
JsonParser.parseString(
"""{"nodes":[{"type":"opaque","format":"minimessage","raw":${s.get("source")}}]}"""
)
.asJsonObject
val values =
s.getAsJsonObject("values").entrySet().associate { it.key to it.value.asString }
val result =
CompiledMotd(document, s.getAsJsonArray("alignments").map { it.asString })
.render(values, s.get("width").asInt)
val lines = componentLines(result)
assertEquals(s.getAsJsonArray("expected").size(), lines.size, s.get("id").asString)
for ((index, line) in lines.withIndex()) {
val expected = s.getAsJsonArray("expected")[index].asJsonObject
val chars = characters(line)
val count = expected.get("regular").asInt + expected.get("bold").asInt
val prefix = chars.take(count)
assertTrue(prefix.all { it.first == " " }, source)
assertEquals(expected.get("bold").asInt, prefix.count { it.second }, source)
assertEquals(
expected.get("padding").asInt,
prefix.sumOf { if (it.second) 5 else 4 },
source,
)
assertEquals(
expected.get("width").asInt,
chars.drop(count).sumOf {
(advances[it.first]?.asInt ?: 6) + if (it.second) 1 else 0
},
source,
)
assertFalse(chars.joinToString("") { it.first }.contains("{{"), source)
}
}
}

@Test
fun `uses the closest representable padding for short and full-width lines`() {
val document =
JsonParser.parseString("""{"nodes":[{"type":"text","value":"A"}]}""").asJsonObject
for (alignment in listOf("center", "right")) for (remaining in 0..280) {
val target = if (alignment == "center") remaining / 2.0 else remaining.toDouble()
val candidates =
(0..3)
.flatMap { b -> (0..(remaining / 4)).map { r -> r * 4 + b * 5 } }
.filter { it <= remaining }
val expected =
candidates.minWith(
compareBy<Int> { kotlin.math.abs(it - target) }.thenByDescending { it }
)
val chars =
characters(
CompiledMotd(document, listOf(alignment)).render(emptyMap(), remaining + 6)
)
assertEquals("A", chars.last().first)
assertFalse(chars.last().second, "padding bold must not affect the MOTD")
assertEquals(
expected,
chars.dropLast(1).sumOf { if (it.second) 5 else 4 },
"$alignment $remaining",
)
}
}
}
10 changes: 10 additions & 0 deletions velocity/src/test/resources/server-list/native-padding.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[
{"id":"modern-user-motd","width":267,"source":"<b>GROUNDS NETWORK</b><newline>Stage System | {{region}}","values":{"region":"eu"},"alignments":["center","center"],"expected":[{"width":103,"padding":82,"regular":18,"bold":2},{"width":88,"padding":90,"regular":20,"bold":2}]},
{"id":"legacy-user-motd","width":271,"source":"<b>GROUNDS NETWORK</b><newline>Stage System | {{region}}","values":{"region":"eu"},"alignments":["center","center"],"expected":[{"width":103,"padding":84,"regular":21,"bold":0},{"width":88,"padding":92,"regular":23,"bold":0}]},
{"id":"modern-right","width":267,"source":"A","values":{},"alignments":["right"],"expected":[{"width":6,"padding":261,"regular":64,"bold":1}]},
{"id":"legacy-right","width":271,"source":"A","values":{},"alignments":["right"],"expected":[{"width":6,"padding":265,"regular":65,"bold":1}]},
{"id":"bold-space-measurement","width":40,"source":"<b>A A</b>","values":{},"alignments":["center"],"expected":[{"width":19,"padding":10,"regular":0,"bold":2}]},
{"id":"short-unrepresentable-center","width":8,"source":"A","values":{},"alignments":["center"],"expected":[{"width":6,"padding":0,"regular":0,"bold":0}]},
{"id":"short-right","width":13,"source":"A","values":{},"alignments":["right"],"expected":[{"width":6,"padding":5,"regular":0,"bold":1}]},
{"id":"left-preserves-literal-indentation","width":267,"source":" <b>A</b>","values":{},"alignments":["left"],"expected":[{"width":15,"padding":0,"regular":0,"bold":0}]}
]
Loading