mirror of
https://gitlab.bixilon.de/bixilon/minosoft.git
synced 2025-09-14 18:05:51 -04:00
improved code point rendering
This commit is contained in:
parent
fa8cd40617
commit
8ef3deacbe
@ -60,16 +60,16 @@ class ChatComponentRendererTest {
|
|||||||
fun `3 chars`() {
|
fun `3 chars`() {
|
||||||
val info = render(TextComponent("bcd"))
|
val info = render(TextComponent("bcd"))
|
||||||
assertEquals(info.lineIndex, 0)
|
assertEquals(info.lineIndex, 0)
|
||||||
assertEquals(info.lines, listOf(TextLineInfo(BaseComponent(TextComponent("bcd")), 4.5f)))
|
assertEquals(info.lines, listOf(TextLineInfo(BaseComponent(TextComponent("bcd")), 5.0f)))
|
||||||
assertEquals(info.size, Vec2(4.5f, 11.0f)) // b + spacing + c + spacing + d
|
assertEquals(info.size, Vec2(5.0f, 11.0f)) // b + spacing + c + spacing + d
|
||||||
assertFalse(info.cutOff)
|
assertFalse(info.cutOff)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun `max line size`() {
|
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.lineIndex, 1)
|
||||||
assertEquals(info.lines, listOf(TextLineInfo(BaseComponent(TextComponent("bcd")), 4.5f), TextLineInfo(BaseComponent(TextComponent("ef")), 5.0f)))
|
assertEquals(info.lines, listOf(TextLineInfo(BaseComponent(TextComponent("bcd")), 5.0f), TextLineInfo(BaseComponent(TextComponent("ef")), 5.5f)))
|
||||||
assertEquals(info.size, Vec2(5.0f, 22.0f)) // b + spacing + c + spacing + d \n e + spacing + f
|
assertEquals(info.size, Vec2(5.5f, 22.0f)) // b + spacing + c + spacing + d \n e + spacing + f
|
||||||
assertFalse(info.cutOff)
|
assertFalse(info.cutOff)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
;
|
||||||
|
}
|
@ -14,8 +14,14 @@
|
|||||||
package de.bixilon.minosoft.gui.rendering.font.renderer.code
|
package de.bixilon.minosoft.gui.rendering.font.renderer.code
|
||||||
|
|
||||||
import de.bixilon.kotlinglm.vec2.Vec2
|
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.data.text.formatting.color.RGBColor
|
||||||
import de.bixilon.minosoft.gui.rendering.font.WorldGUIConsumer
|
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.GUIVertexConsumer
|
||||||
import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexOptions
|
import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexOptions
|
||||||
import de.bixilon.minosoft.gui.rendering.util.vec.vec2.Vec2Util.EMPTY
|
import de.bixilon.minosoft.gui.rendering.util.vec.vec2.Vec2Util.EMPTY
|
||||||
@ -31,4 +37,39 @@ interface CodePointRenderer {
|
|||||||
|
|
||||||
return calculateWidth(scale, shadow)
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,12 +16,12 @@ package de.bixilon.minosoft.gui.rendering.font.renderer.component
|
|||||||
import de.bixilon.kotlinglm.vec2.Vec2i
|
import de.bixilon.kotlinglm.vec2.Vec2i
|
||||||
import de.bixilon.minosoft.data.text.TextComponent
|
import de.bixilon.minosoft.data.text.TextComponent
|
||||||
import de.bixilon.minosoft.data.text.formatting.FormattingCodes
|
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.ChatColors
|
||||||
import de.bixilon.minosoft.data.text.formatting.color.RGBColor
|
import de.bixilon.minosoft.data.text.formatting.color.RGBColor
|
||||||
import de.bixilon.minosoft.gui.rendering.RenderContext
|
import de.bixilon.minosoft.gui.rendering.RenderContext
|
||||||
import de.bixilon.minosoft.gui.rendering.font.WorldGUIConsumer
|
import de.bixilon.minosoft.gui.rendering.font.WorldGUIConsumer
|
||||||
import de.bixilon.minosoft.gui.rendering.font.manager.FontManager
|
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.code.CodePointRenderer
|
||||||
import de.bixilon.minosoft.gui.rendering.font.renderer.element.TextOffset
|
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.TextRenderInfo
|
||||||
@ -45,13 +45,6 @@ object TextComponentRenderer : ChatComponentRenderer<TextComponent> {
|
|||||||
TODO()
|
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() {
|
private fun renderStrikethrough() {
|
||||||
TODO()
|
TODO()
|
||||||
}
|
}
|
||||||
@ -68,6 +61,8 @@ object TextComponentRenderer : ChatComponentRenderer<TextComponent> {
|
|||||||
val formatting = text.formatting
|
val formatting = text.formatting
|
||||||
var skipWhitespaces = false
|
var skipWhitespaces = false
|
||||||
|
|
||||||
|
val line = StringBuilder()
|
||||||
|
|
||||||
for (codePoint in text.message.codePoints()) {
|
for (codePoint in text.message.codePoints()) {
|
||||||
if (codePoint == '\n'.code) {
|
if (codePoint == '\n'.code) {
|
||||||
renderNewline(offset, info)
|
renderNewline(offset, info)
|
||||||
@ -77,10 +72,31 @@ object TextComponentRenderer : ChatComponentRenderer<TextComponent> {
|
|||||||
if (skipWhitespaces && Character.isWhitespace(codePoint)) {
|
if (skipWhitespaces && Character.isWhitespace(codePoint)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
skipWhitespaces = false
|
|
||||||
|
|
||||||
val renderer = getRenderer(codePoint, properties, textFont, fontManager) ?: continue
|
val renderer = getRenderer(codePoint, properties, textFont, fontManager)
|
||||||
renderCodePoint(offset, renderer, color, properties, info, formatting, consumer, options)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,8 +14,15 @@
|
|||||||
package de.bixilon.minosoft.gui.rendering.font.renderer.element
|
package de.bixilon.minosoft.gui.rendering.font.renderer.element
|
||||||
|
|
||||||
import de.bixilon.minosoft.data.text.BaseComponent
|
import de.bixilon.minosoft.data.text.BaseComponent
|
||||||
|
import de.bixilon.minosoft.data.text.TextComponent
|
||||||
|
|
||||||
data class TextLineInfo(
|
data class TextLineInfo(
|
||||||
val text: BaseComponent = BaseComponent(),
|
val text: BaseComponent = BaseComponent(),
|
||||||
var width: Float = 0.0f,
|
var width: Float = 0.0f,
|
||||||
)
|
) {
|
||||||
|
|
||||||
|
fun push(component: TextComponent, builder: StringBuilder) {
|
||||||
|
text += component.copy(message = builder.toString())
|
||||||
|
builder.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -14,10 +14,61 @@
|
|||||||
package de.bixilon.minosoft.gui.rendering.font.renderer.element
|
package de.bixilon.minosoft.gui.rendering.font.renderer.element
|
||||||
|
|
||||||
import de.bixilon.kotlinglm.vec2.Vec2
|
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
|
import de.bixilon.minosoft.gui.rendering.util.vec.vec2.Vec2Util.EMPTY
|
||||||
|
|
||||||
class TextOffset(
|
class TextOffset(
|
||||||
val initial: Vec2 = Vec2.EMPTY,
|
val initial: Vec2 = Vec2.EMPTY,
|
||||||
) {
|
) {
|
||||||
var offset = Vec2(initial)
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,11 +17,30 @@ import de.bixilon.kotlinglm.vec2.Vec2
|
|||||||
import de.bixilon.minosoft.gui.rendering.util.vec.vec2.Vec2Util.EMPTY
|
import de.bixilon.minosoft.gui.rendering.util.vec.vec2.Vec2Util.EMPTY
|
||||||
|
|
||||||
class TextRenderInfo(
|
class TextRenderInfo(
|
||||||
val parentSize: Vec2,
|
val maxSize: Vec2,
|
||||||
) {
|
) {
|
||||||
val lines: MutableList<TextLineInfo> = mutableListOf()
|
val lines: MutableList<TextLineInfo> = mutableListOf()
|
||||||
var lineIndex: Int = 0
|
var lineIndex: Int = 0
|
||||||
|
|
||||||
var size = Vec2.EMPTY
|
var size = Vec2.EMPTY
|
||||||
var cutOff = false
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,11 @@ data class TextRenderProperties(
|
|||||||
val fallbackColor: RGBColor = ChatColors.WHITE,
|
val fallbackColor: RGBColor = ChatColors.WHITE,
|
||||||
val font: FontType? = null,
|
val font: FontType? = null,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
val lineHeight: Float
|
||||||
|
get() = (charSpacing.top + charBaseHeight + charSpacing.bottom) * scale
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val DEFAULT = TextRenderProperties()
|
val DEFAULT = TextRenderProperties()
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user