diff --git a/src/integration-test/kotlin/de/bixilon/minosoft/gui/rendering/font/renderer/component/ChatComponentRendererTest.kt b/src/integration-test/kotlin/de/bixilon/minosoft/gui/rendering/font/renderer/component/ChatComponentRendererTest.kt
index c3b58a3a5..0f99748e3 100644
--- a/src/integration-test/kotlin/de/bixilon/minosoft/gui/rendering/font/renderer/component/ChatComponentRendererTest.kt
+++ b/src/integration-test/kotlin/de/bixilon/minosoft/gui/rendering/font/renderer/component/ChatComponentRendererTest.kt
@@ -60,16 +60,16 @@ class ChatComponentRendererTest {
fun `3 chars`() {
val info = render(TextComponent("bcd"))
assertEquals(info.lineIndex, 0)
- assertEquals(info.lines, listOf(TextLineInfo(BaseComponent(TextComponent("bcd")), 4.5f)))
- assertEquals(info.size, Vec2(4.5f, 11.0f)) // b + spacing + c + spacing + d
+ assertEquals(info.lines, listOf(TextLineInfo(BaseComponent(TextComponent("bcd")), 5.0f)))
+ assertEquals(info.size, Vec2(5.0f, 11.0f)) // b + spacing + c + spacing + d
assertFalse(info.cutOff)
}
fun `max line size`() {
- val info = render(TextComponent("bcdef"), maxSize = Vec2(5.0f, Float.MAX_VALUE))
+ val info = render(TextComponent("bcdef"), maxSize = Vec2(5.5f, Float.MAX_VALUE))
assertEquals(info.lineIndex, 1)
- assertEquals(info.lines, listOf(TextLineInfo(BaseComponent(TextComponent("bcd")), 4.5f), TextLineInfo(BaseComponent(TextComponent("ef")), 5.0f)))
- assertEquals(info.size, Vec2(5.0f, 22.0f)) // b + spacing + c + spacing + d \n e + spacing + f
+ assertEquals(info.lines, listOf(TextLineInfo(BaseComponent(TextComponent("bcd")), 5.0f), TextLineInfo(BaseComponent(TextComponent("ef")), 5.5f)))
+ assertEquals(info.size, Vec2(5.5f, 22.0f)) // b + spacing + c + spacing + d \n e + spacing + f
assertFalse(info.cutOff)
}
}
diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/font/renderer/CodePointAddResult.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/font/renderer/CodePointAddResult.kt
new file mode 100644
index 000000000..c394b557c
--- /dev/null
+++ b/src/main/java/de/bixilon/minosoft/gui/rendering/font/renderer/CodePointAddResult.kt
@@ -0,0 +1,21 @@
+/*
+ * Minosoft
+ * Copyright (C) 2020-2023 Moritz Zwerger
+ *
+ * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program. If not, see .
+ *
+ * This software is not affiliated with Mojang AB, the original developer of Minecraft.
+ */
+
+package de.bixilon.minosoft.gui.rendering.font.renderer
+
+enum class CodePointAddResult {
+ FINE,
+ NEW_LINE,
+ BREAK,
+ ;
+}
diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/font/renderer/code/CodePointRenderer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/font/renderer/code/CodePointRenderer.kt
index 992f93165..9ff1b7538 100644
--- a/src/main/java/de/bixilon/minosoft/gui/rendering/font/renderer/code/CodePointRenderer.kt
+++ b/src/main/java/de/bixilon/minosoft/gui/rendering/font/renderer/code/CodePointRenderer.kt
@@ -14,8 +14,14 @@
package de.bixilon.minosoft.gui.rendering.font.renderer.code
import de.bixilon.kotlinglm.vec2.Vec2
+import de.bixilon.minosoft.data.text.formatting.FormattingCodes
+import de.bixilon.minosoft.data.text.formatting.TextFormatting
import de.bixilon.minosoft.data.text.formatting.color.RGBColor
import de.bixilon.minosoft.gui.rendering.font.WorldGUIConsumer
+import de.bixilon.minosoft.gui.rendering.font.renderer.CodePointAddResult
+import de.bixilon.minosoft.gui.rendering.font.renderer.element.TextOffset
+import de.bixilon.minosoft.gui.rendering.font.renderer.element.TextRenderInfo
+import de.bixilon.minosoft.gui.rendering.font.renderer.element.TextRenderProperties
import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexConsumer
import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexOptions
import de.bixilon.minosoft.gui.rendering.util.vec.vec2.Vec2Util.EMPTY
@@ -31,4 +37,39 @@ interface CodePointRenderer {
return calculateWidth(scale, shadow)
}
+
+ private fun getVerticalSpacing(offset: TextOffset, properties: TextRenderProperties): Float {
+ if (offset.offset.x == offset.initial.x) return 0.0f
+ // not at line start
+ return properties.charSpacing.vertical * properties.scale
+ }
+
+
+ fun render(offset: TextOffset, color: RGBColor, properties: TextRenderProperties, info: TextRenderInfo, formatting: TextFormatting, codePoint: Int, consumer: GUIVertexConsumer?, options: GUIVertexOptions?): CodePointAddResult {
+ val codePointWidth = calculateWidth(properties.scale, properties.shadow)
+ var width = codePointWidth + getVerticalSpacing(offset, properties)
+ val height = offset.getNextLineHeight(properties)
+
+ val canAdd = offset.canAdd(properties, info, width, height)
+ when (canAdd) {
+ CodePointAddResult.FINE -> Unit
+ CodePointAddResult.NEW_LINE -> {
+ width = codePointWidth // new line, remove vertical spacing
+ info.size.y += height
+ }
+
+ CodePointAddResult.BREAK -> return CodePointAddResult.BREAK
+ }
+
+
+ if (consumer != null) {
+ render(offset.offset, color, properties.shadow, FormattingCodes.BOLD in formatting, FormattingCodes.ITALIC in formatting, properties.scale, consumer, options)
+ } else {
+ info.update(offset, properties, width) // info should only be updated when we determinate text properties, we know all that already when actually rendering it
+ }
+
+ offset.offset.x += width
+
+ return canAdd
+ }
}
diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/font/renderer/component/TextComponentRenderer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/font/renderer/component/TextComponentRenderer.kt
index 76ceb8c55..217f0c718 100644
--- a/src/main/java/de/bixilon/minosoft/gui/rendering/font/renderer/component/TextComponentRenderer.kt
+++ b/src/main/java/de/bixilon/minosoft/gui/rendering/font/renderer/component/TextComponentRenderer.kt
@@ -16,12 +16,12 @@ package de.bixilon.minosoft.gui.rendering.font.renderer.component
import de.bixilon.kotlinglm.vec2.Vec2i
import de.bixilon.minosoft.data.text.TextComponent
import de.bixilon.minosoft.data.text.formatting.FormattingCodes
-import de.bixilon.minosoft.data.text.formatting.TextFormatting
import de.bixilon.minosoft.data.text.formatting.color.ChatColors
import de.bixilon.minosoft.data.text.formatting.color.RGBColor
import de.bixilon.minosoft.gui.rendering.RenderContext
import de.bixilon.minosoft.gui.rendering.font.WorldGUIConsumer
import de.bixilon.minosoft.gui.rendering.font.manager.FontManager
+import de.bixilon.minosoft.gui.rendering.font.renderer.CodePointAddResult
import de.bixilon.minosoft.gui.rendering.font.renderer.code.CodePointRenderer
import de.bixilon.minosoft.gui.rendering.font.renderer.element.TextOffset
import de.bixilon.minosoft.gui.rendering.font.renderer.element.TextRenderInfo
@@ -45,13 +45,6 @@ object TextComponentRenderer : ChatComponentRenderer {
TODO()
}
- private fun renderCodePoint(offset: TextOffset, renderer: CodePointRenderer, color: RGBColor, properties: TextRenderProperties, info: TextRenderInfo, formatting: TextFormatting, consumer: GUIVertexConsumer?, options: GUIVertexOptions?) {
- if (consumer != null) {
- renderer.render(offset.offset, color, properties.shadow, FormattingCodes.BOLD in formatting, FormattingCodes.ITALIC in formatting, properties.scale, consumer, options)
- }
- offset.offset.x += 8
- }
-
private fun renderStrikethrough() {
TODO()
}
@@ -68,6 +61,8 @@ object TextComponentRenderer : ChatComponentRenderer {
val formatting = text.formatting
var skipWhitespaces = false
+ val line = StringBuilder()
+
for (codePoint in text.message.codePoints()) {
if (codePoint == '\n'.code) {
renderNewline(offset, info)
@@ -77,10 +72,31 @@ object TextComponentRenderer : ChatComponentRenderer {
if (skipWhitespaces && Character.isWhitespace(codePoint)) {
continue
}
- skipWhitespaces = false
- val renderer = getRenderer(codePoint, properties, textFont, fontManager) ?: continue
- renderCodePoint(offset, renderer, color, properties, info, formatting, consumer, options)
+ val renderer = getRenderer(codePoint, properties, textFont, fontManager)
+ if (renderer != null && renderer.calculateWidth(properties.scale, properties.shadow) <= 0.0f) {
+ continue
+ }
+ skipWhitespaces = false
+ if (renderer == null) {
+ continue
+ }
+
+ val lineIndex = info.lineIndex
+
+ val lineInfo = renderer.render(offset, color, properties, info, formatting, codePoint, consumer, options)
+ if (consumer != null) continue // already know that information
+ if (lineInfo == CodePointAddResult.BREAK) break
+
+ if (lineIndex != info.lineIndex) {
+ // new line started
+ info.lines[lineIndex].push(text, line) // previous line
+ }
+
+ line.appendCodePoint(codePoint)
+ }
+ if (line.isNotEmpty()) {
+ info.lines[info.lineIndex].push(text, line)
}
}
diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/font/renderer/element/TextLineInfo.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/font/renderer/element/TextLineInfo.kt
index 2d7a0acca..514d41ba1 100644
--- a/src/main/java/de/bixilon/minosoft/gui/rendering/font/renderer/element/TextLineInfo.kt
+++ b/src/main/java/de/bixilon/minosoft/gui/rendering/font/renderer/element/TextLineInfo.kt
@@ -14,8 +14,15 @@
package de.bixilon.minosoft.gui.rendering.font.renderer.element
import de.bixilon.minosoft.data.text.BaseComponent
+import de.bixilon.minosoft.data.text.TextComponent
data class TextLineInfo(
val text: BaseComponent = BaseComponent(),
var width: Float = 0.0f,
-)
+) {
+
+ fun push(component: TextComponent, builder: StringBuilder) {
+ text += component.copy(message = builder.toString())
+ builder.clear()
+ }
+}
diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/font/renderer/element/TextOffset.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/font/renderer/element/TextOffset.kt
index da323c807..008bbb0f0 100644
--- a/src/main/java/de/bixilon/minosoft/gui/rendering/font/renderer/element/TextOffset.kt
+++ b/src/main/java/de/bixilon/minosoft/gui/rendering/font/renderer/element/TextOffset.kt
@@ -14,10 +14,61 @@
package de.bixilon.minosoft.gui.rendering.font.renderer.element
import de.bixilon.kotlinglm.vec2.Vec2
+import de.bixilon.minosoft.gui.rendering.font.renderer.CodePointAddResult
import de.bixilon.minosoft.gui.rendering.util.vec.vec2.Vec2Util.EMPTY
class TextOffset(
val initial: Vec2 = Vec2.EMPTY,
) {
var offset = Vec2(initial)
+
+ private fun fits(offset: Float, initial: Float, max: Float, value: Float): Boolean {
+ val size = offset - initial
+ val remaining = max - size
+
+ return remaining >= value
+ }
+
+
+ fun fitsX(info: TextRenderInfo, width: Float): Boolean {
+ return fits(offset.x, initial.x, info.maxSize.x, width)
+ }
+
+ fun fitsY(info: TextRenderInfo, height: Float): Boolean {
+ return fits(offset.y, initial.y, info.maxSize.y, height)
+ }
+
+ fun fitsInLine(properties: TextRenderProperties, info: TextRenderInfo, width: Float): Boolean {
+ return fitsX(info, width) && fitsY(info, properties.lineHeight)
+ }
+
+ fun getNextLineHeight(properties: TextRenderProperties): Float {
+ var height = properties.lineHeight
+ if (offset.y != initial.y) {
+ // previous line present
+ height += properties.lineSpacing * properties.scale
+ }
+
+ return height
+ }
+
+ fun addLine(properties: TextRenderProperties, info: TextRenderInfo, height: Float): Boolean {
+ if (!fitsY(info, height)) return false
+
+ offset.y += height
+ offset.x = initial.x
+ info.lines += TextLineInfo()
+ info.lineIndex++
+
+ return true
+ }
+
+
+ fun canAdd(properties: TextRenderProperties, info: TextRenderInfo, width: Float, height: Float): CodePointAddResult {
+ if (fitsInLine(properties, info, width)) return CodePointAddResult.FINE
+ if (addLine(properties, info, height) && fitsInLine(properties, info, width)) return CodePointAddResult.NEW_LINE
+
+ info.cutOff = true
+ return CodePointAddResult.BREAK
+ }
}
diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/font/renderer/element/TextRenderInfo.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/font/renderer/element/TextRenderInfo.kt
index 6e4bc626a..5dd9eeadf 100644
--- a/src/main/java/de/bixilon/minosoft/gui/rendering/font/renderer/element/TextRenderInfo.kt
+++ b/src/main/java/de/bixilon/minosoft/gui/rendering/font/renderer/element/TextRenderInfo.kt
@@ -17,11 +17,30 @@ import de.bixilon.kotlinglm.vec2.Vec2
import de.bixilon.minosoft.gui.rendering.util.vec.vec2.Vec2Util.EMPTY
class TextRenderInfo(
- val parentSize: Vec2,
+ val maxSize: Vec2,
) {
val lines: MutableList = mutableListOf()
var lineIndex: Int = 0
var size = Vec2.EMPTY
var cutOff = false
+
+
+ fun update(offset: TextOffset, properties: TextRenderProperties, width: Float): TextLineInfo {
+ size.x = maxOf(offset.offset.x - offset.initial.x + width, size.x)
+
+ val line: TextLineInfo
+ if (lineIndex == 0 && lines.isEmpty()) {
+ // first char of all lines
+ line = TextLineInfo()
+ lines += line
+ size.y = properties.lineHeight
+ } else {
+ line = lines[lineIndex]
+ }
+
+ line.width += width
+
+ return line
+ }
}
diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/font/renderer/element/TextRenderProperties.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/font/renderer/element/TextRenderProperties.kt
index de3bc1aee..627e9416c 100644
--- a/src/main/java/de/bixilon/minosoft/gui/rendering/font/renderer/element/TextRenderProperties.kt
+++ b/src/main/java/de/bixilon/minosoft/gui/rendering/font/renderer/element/TextRenderProperties.kt
@@ -30,6 +30,11 @@ data class TextRenderProperties(
val fallbackColor: RGBColor = ChatColors.WHITE,
val font: FontType? = null,
) {
+
+ val lineHeight: Float
+ get() = (charSpacing.top + charBaseHeight + charSpacing.bottom) * scale
+
+
companion object {
val DEFAULT = TextRenderProperties()
}