improved code point rendering

This commit is contained in:
Bixilon 2023-06-13 20:28:50 +02:00
parent fa8cd40617
commit 8ef3deacbe
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
8 changed files with 178 additions and 18 deletions

View File

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

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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,
;
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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<TextLineInfo> = 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
}
}

View File

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