font rendering: underline, strikethrough

This commit is contained in:
Bixilon 2023-06-17 19:56:17 +02:00
parent 77b9af81a9
commit 6dc3002528
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
4 changed files with 106 additions and 13 deletions

View File

@ -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)
}

View File

@ -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))
}

View File

@ -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<TextComponent> {
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<TextComponent> {
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<TextComponent> {
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<TextComponent> {
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

View File

@ -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