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 f7fa455d8..dc114ddb1 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 @@ -382,5 +382,54 @@ class ChatComponentRendererTest { ) } - // TODO: shadow, underline, strikethrough, formatting (just basic, that is code point renderer's job) + + fun `single strikethrough rendering`() { + val consumer = DummyComponentConsumer() + render(TextComponent("bcd").strikethrough(), fontManager = FontManager(consumer.Font()), consumer = consumer) + + consumer.assert( + DummyComponentConsumer.RendererdQuad(Vec2(10.0f, 14.0f), Vec2(15.0f, 15.0f)), + ) + } + + fun `multiline strikethrough rendering`() { + val consumer = DummyComponentConsumer() + render(TextComponent("bcd\ncde").strikethrough(), fontManager = FontManager(consumer.Font()), consumer = consumer) + + consumer.assert( + DummyComponentConsumer.RendererdQuad(Vec2(10.0f, 14.0f), Vec2(15.0f, 15.0f)), + DummyComponentConsumer.RendererdQuad(Vec2(10.0f, 25.0f), Vec2(16.5f, 26.0f)), + ) + } + + fun `single underline rendering`() { + val consumer = DummyComponentConsumer() + render(TextComponent("bcd").underline(), fontManager = FontManager(consumer.Font()), consumer = consumer) + + consumer.assert( + DummyComponentConsumer.RendererdQuad(Vec2(10.0f, 19.0f), Vec2(15.0f, 20.0f)), + ) + } + + fun `multiline underline rendering`() { + val consumer = DummyComponentConsumer() + render(TextComponent("bcd\ncde").underline(), fontManager = FontManager(consumer.Font()), consumer = consumer) + + consumer.assert( + DummyComponentConsumer.RendererdQuad(Vec2(10.0f, 19.0f), Vec2(15.0f, 20.0f)), + DummyComponentConsumer.RendererdQuad(Vec2(10.0f, 30.0f), Vec2(16.5f, 31.0f)), + ) + } + + + fun `mixed text strikethrough rendering`() { + val consumer = DummyComponentConsumer() + render(BaseComponent(TextComponent("bcd").strikethrough(), TextComponent("bcd")), fontManager = FontManager(consumer.Font()), consumer = consumer) + + consumer.assert( + DummyComponentConsumer.RendererdQuad(Vec2(10.0f, 14.0f), Vec2(15.0f, 15.0f)), + ) + } + + // TODO: shadow, formatting (just basic, that is code point renderer's job) } diff --git a/src/integration-test/kotlin/de/bixilon/minosoft/gui/rendering/font/renderer/component/DummyComponentConsumer.kt b/src/integration-test/kotlin/de/bixilon/minosoft/gui/rendering/font/renderer/component/DummyComponentConsumer.kt index fb9e4dd95..22f3e4fa0 100644 --- a/src/integration-test/kotlin/de/bixilon/minosoft/gui/rendering/font/renderer/component/DummyComponentConsumer.kt +++ b/src/integration-test/kotlin/de/bixilon/minosoft/gui/rendering/font/renderer/component/DummyComponentConsumer.kt @@ -19,7 +19,6 @@ import de.bixilon.minosoft.data.text.formatting.color.RGBColor import de.bixilon.minosoft.gui.rendering.font.renderer.code.CodePointRenderer import de.bixilon.minosoft.gui.rendering.font.renderer.element.TextRenderProperties import de.bixilon.minosoft.gui.rendering.font.types.FontType -import de.bixilon.minosoft.gui.rendering.gui.atlas.TexturePart import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIMeshCache import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexConsumer import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexOptions @@ -35,7 +34,7 @@ class DummyComponentConsumer : GUIVertexConsumer { override fun addCache(cache: GUIMeshCache) = Broken() override fun ensureSize(size: Int) = Unit - override fun addQuad(start: Vec2, end: Vec2, texture: TexturePart, tint: RGBColor, options: GUIVertexOptions?) { + override fun addQuad(start: Vec2, end: Vec2, texture: ShaderIdentifiable?, uvStart: Vec2, uvEnd: Vec2, tint: RGBColor, options: GUIVertexOptions?) { quads += RendererdQuad(Vec2(start), Vec2(end)) } 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 f0845570c..eb4098bc9 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 @@ -13,12 +13,14 @@ package de.bixilon.minosoft.gui.rendering.font.renderer.component +import de.bixilon.kotlinglm.vec2.Vec2 import de.bixilon.minosoft.data.text.TextComponent import de.bixilon.minosoft.data.text.formatting.FormattingCodes import de.bixilon.minosoft.data.text.formatting.color.RGBColor 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.LineRenderInfo 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 @@ -51,14 +53,40 @@ object TextComponentRenderer : ChatComponentRenderer { return false } - private fun renderStrikethrough() { - TODO() + private fun renderStrikethrough(offset: Vec2, width: Float, italic: Boolean, color: RGBColor, properties: TextRenderProperties, consumer: GUIVertexConsumer, options: GUIVertexOptions?) { + val y = offset.y + properties.charSpacing.top + properties.charBaseHeight / 2.0f - 1.0f + + // TODO: italic + consumer.addQuad(Vec2(offset.x, y), Vec2(offset.x + width, y + 1.0f), color, options) } - private fun renderUnderline() { - TODO() + private fun renderUnderline(offset: Vec2, width: Float, italic: Boolean, color: RGBColor, properties: TextRenderProperties, consumer: GUIVertexConsumer, options: GUIVertexOptions?) { + val y = offset.y + properties.charSpacing.top + properties.charBaseHeight + + // TODO: italic + consumer.addQuad(Vec2(offset.x, y), Vec2(offset.x + width, y + 1.0f), color, options) } + private fun renderFormatting(offset: Vec2, text: TextComponent, width: Float, color: RGBColor, properties: TextRenderProperties, consumer: GUIVertexConsumer, options: GUIVertexOptions?) { + if (width <= 0.0f) return + val italic = FormattingCodes.ITALIC in text.formatting + if (FormattingCodes.UNDERLINED in text.formatting) { + renderUnderline(offset, width, italic, color, properties, consumer, options) + } + if (FormattingCodes.STRIKETHROUGH in text.formatting) { + renderStrikethrough(offset, width, italic, color, properties, consumer, options) + } + } + + + private fun LineRenderInfo.pushAndRender(offset: Vec2, text: TextComponent, line: StringBuilder, width: Float, color: RGBColor, properties: TextRenderProperties, consumer: GUIVertexConsumer?, options: GUIVertexOptions?) { + push(text, line) + if (consumer != null) { + renderFormatting(offset, text, width, color, properties, consumer, options) + } + } + + override fun render(offset: TextOffset, fontManager: FontManager, properties: TextRenderProperties, info: TextRenderInfo, consumer: GUIVertexConsumer?, options: GUIVertexOptions?, text: TextComponent): Boolean { if (text.message.isEmpty()) return false @@ -75,6 +103,8 @@ object TextComponentRenderer : ChatComponentRenderer { val line = StringBuilder() var filled = false + val lineStart = Vec2(offset.offset) + val stream = text.message.codePoints().iterator() while (stream.hasNext()) { @@ -82,10 +112,12 @@ object TextComponentRenderer : ChatComponentRenderer { if (codePoint == '\n'.code) { if (!properties.allowNewLine) continue val lineIndex = info.lineIndex + val width = offset.offset.x - lineStart.x filled = renderNewline(properties, offset, info, consumer != null) if (line.isNotEmpty()) { - info.lines[lineIndex].push(text, line) + info.lines[lineIndex].pushAndRender(lineStart, text, line, width, color, properties, consumer, options) } + lineStart(offset.offset) skipWhitespaces = true if (filled) break else continue } @@ -104,22 +136,31 @@ object TextComponentRenderer : ChatComponentRenderer { val lineIndex = info.lineIndex + val width = offset.offset.x - lineStart.x + val lineInfo = renderer.render(offset, color, properties, info, formatting, codePoint, consumer, options) + if (lineIndex != info.lineIndex && info.lines.isNotEmpty() && consumer != null) { + renderFormatting(lineStart, text, width, color, properties, consumer, options) + lineStart(offset.offset) + } if (lineInfo == CodePointAddResult.BREAK) { filled = true break } - if (consumer != null) continue // already know that information if (lineIndex != info.lineIndex) { // new line started - info.lines[lineIndex].push(text, line) // previous line + if (consumer == null) { + info.lines[lineIndex].push(text, line) // previous line + } else { + line.clear() + } } - line.appendCodePoint(codePoint) } + if (line.isNotEmpty()) { - info.lines[info.lineIndex].push(text, line) + info.lines[info.lineIndex].pushAndRender(lineStart, text, line, offset.offset.x - lineStart.x, color, properties, consumer, options) } return filled diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/mesh/GUIVertexConsumer.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/mesh/GUIVertexConsumer.kt index 971faf315..adefd67ca 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/mesh/GUIVertexConsumer.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/mesh/GUIVertexConsumer.kt @@ -30,7 +30,7 @@ interface GUIVertexConsumer { addVertex(Vec2(position), texture, uv, tint, options) } - fun addQuad(start: Vec2, end: Vec2, texture: ShaderIdentifiable, uvStart: Vec2 = UV_START, uvEnd: Vec2 = UV_END, tint: RGBColor, options: GUIVertexOptions?) { + fun addQuad(start: Vec2, end: Vec2, texture: ShaderIdentifiable?, uvStart: Vec2 = UV_START, uvEnd: Vec2 = UV_END, tint: RGBColor, options: GUIVertexOptions?) { val positions = arrayOf( start, Vec2(end.x, start.y), @@ -53,6 +53,10 @@ interface GUIVertexConsumer { addQuad(start, end, texture.texture, texture.uvStart, texture.uvEnd, tint, options) } + fun addQuad(start: Vec2, end: Vec2, tint: RGBColor, options: GUIVertexOptions?) { + addQuad(start, end, null, tint = tint, options = options) + } + fun addChar(start: Vec2, end: Vec2, texture: Texture?, uvStart: Vec2, uvEnd: Vec2, italic: Boolean, tint: RGBColor, options: GUIVertexOptions?) { val topOffset = if (italic) (end.y - start.y) / FontProperties.CHAR_BASE_HEIGHT * FormattingProperties.ITALIC_OFFSET else 0.0f