From 8ef3deacbe16fa13a90b600f83a5773e694ed65e Mon Sep 17 00:00:00 2001 From: Bixilon Date: Tue, 13 Jun 2023 20:28:50 +0200 Subject: [PATCH] improved code point rendering --- .../component/ChatComponentRendererTest.kt | 10 ++-- .../font/renderer/CodePointAddResult.kt | 21 ++++++++ .../font/renderer/code/CodePointRenderer.kt | 41 +++++++++++++++ .../component/TextComponentRenderer.kt | 38 ++++++++++---- .../font/renderer/element/TextLineInfo.kt | 9 +++- .../font/renderer/element/TextOffset.kt | 51 +++++++++++++++++++ .../font/renderer/element/TextRenderInfo.kt | 21 +++++++- .../renderer/element/TextRenderProperties.kt | 5 ++ 8 files changed, 178 insertions(+), 18 deletions(-) create mode 100644 src/main/java/de/bixilon/minosoft/gui/rendering/font/renderer/CodePointAddResult.kt 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() }