How to perform text auto wrapping with predefined width?
Calling
text.setTextBounds(Rectangle(0.0, 0.0, 300.0, 200.0))
doesn't seem to solve the issue.
Thanks
How to perform text auto wrapping with predefined width?
Calling
text.setTextBounds(Rectangle(0.0, 0.0, 300.0, 200.0))
doesn't seem to solve the issue.
Thanks
I ended up doing this.
In KorGE version 2 we can pass custom TextRenderer when creating Text.
This is my "quick" implementation in case anyone want to use it.
package view
import com.soywiz.korge.view.Container
import com.soywiz.korge.view.Text
import com.soywiz.korge.view.ViewDslMarker
import com.soywiz.korge.view.addTo
import com.soywiz.korim.color.Colors
import com.soywiz.korim.color.RGBA
import com.soywiz.korim.font.DefaultTtfFont
import com.soywiz.korim.font.Font
import com.soywiz.korim.font.getTextBounds
import com.soywiz.korim.text.TextAlignment
import com.soywiz.korim.text.TextRenderer
import com.soywiz.korim.text.TextRendererActions
import com.soywiz.korio.resources.Resourceable
enum class Gravity {
LEFT, CENTER, RIGHT
}
inline fun Container.wrappableText(
text: String,
wrapWidth: Double,
textSize: Double = 16.0,
color: RGBA = Colors.WHITE,
font: Resourceable<out Font> = DefaultTtfFont,
gravity: Gravity = Gravity.CENTER,
callback: @ViewDslMarker WrappableText.() -> Unit = {}
) = WrappableText(
text,
textSize = textSize,
color = color,
font = font,
wrapWidth = wrapWidth,
renderer = WrappableTextRenderer(wrapWidth, gravity)
).addTo(this, callback)
class WrappableText(
text: String, textSize: Double = DEFAULT_TEXT_SIZE,
color: RGBA = Colors.WHITE, font: Resourceable<out Font> = DefaultTtfFont,
alignment: TextAlignment = TextAlignment.TOP_LEFT,
wrapWidth: Double,
renderer: TextRenderer<String>,
autoScaling: Boolean = true
) : Text(text, textSize, color, font, alignment, renderer, autoScaling) {
init {
val f = this.font.getOrNull()
if (f is Font) {
val metrics = f.getTextBounds(textSize, text, renderer = renderer)
metrics.bounds.x = 0.0
metrics.bounds.y = 0.0
metrics.bounds.width = wrapWidth
setTextBounds(metrics.bounds)
}
}
}
class WrappableTextRenderer(val wrapWidth: Double, val gravity: Gravity) : TextRenderer<String> {
override fun TextRendererActions.run(text: String, size: Double, defaultFont: Font) {
reset()
setFont(defaultFont, size)
val lines = mutableListOf(Line())
var curX = 0.0
val spaceWidth = getGlyphMetrics(' '.toInt()).xadvance + getKerning(' '.toInt(), 'A'.toInt())
for (word in text.split(' ')) {
var wordWidth = 0.0
var curWord = ""
for (n in word.indices) {
val c = word[n].toInt()
val c1 = word.getOrElse(n + 1) { '\u0000' }.toInt()
val g = getGlyphMetrics(c)
val kerning = getKerning(c, c1)
if (c == '\n'.toInt()) {
curX = 0.0
lines.last().words.add(Word(curWord, wordWidth))
curWord = ""
wordWidth = 0.0
lines.add(Line())
}
else {
wordWidth += g.xadvance + kerning
curWord += c.toChar()
}
}
curX += wordWidth + spaceWidth
if (curX > wrapWidth) {
lines.add(Line())
curX = wordWidth + spaceWidth
}
lines.last().words.add(Word(curWord, wordWidth))
}
for (line in lines) {
var start =
when (gravity) {
Gravity.LEFT -> 0.0
Gravity.CENTER -> (wrapWidth - line.calculateWidth(spaceWidth)) / 2
Gravity.RIGHT -> wrapWidth - line.calculateWidth(spaceWidth)
}
for (word in line.words) {
x = start
for (n in word.text.indices) {
val c = word.text[n].toInt()
val c1 = word.text.getOrElse(n + 1) { '\u0000' }.toInt()
val g = getGlyphMetrics(c)
transform.identity()
val advance = g.xadvance + getKerning(c, c1)
put(c)
advance(advance)
}
start += word.width + spaceWidth
}
newLine(lineHeight)
}
put(0)
}
data class Line(
val words: MutableList<Word> = mutableListOf()
) {
fun calculateWidth(spaceWidth: Double): Double {
return words.sumByDouble { it.width } + (words.size - 1) * spaceWidth
}
}
data class Word(val text: String, val width: Double)
}
I ended up doing this.
In KorGE version 2 we can pass custom TextRenderer when creating Text.
This is my "quick" implementation in case anyone want to use it.
package view
import com.soywiz.korge.view.Container
import com.soywiz.korge.view.Text
import com.soywiz.korge.view.ViewDslMarker
import com.soywiz.korge.view.addTo
import com.soywiz.korim.color.Colors
import com.soywiz.korim.color.RGBA
import com.soywiz.korim.font.DefaultTtfFont
import com.soywiz.korim.font.Font
import com.soywiz.korim.font.getTextBounds
import com.soywiz.korim.text.TextAlignment
import com.soywiz.korim.text.TextRenderer
import com.soywiz.korim.text.TextRendererActions
import com.soywiz.korio.resources.Resourceable
enum class Gravity {
LEFT, CENTER, RIGHT
}
inline fun Container.wrappableText(
text: String,
wrapWidth: Double,
textSize: Double = 16.0,
color: RGBA = Colors.WHITE,
font: Resourceable<out Font> = DefaultTtfFont,
gravity: Gravity = Gravity.CENTER,
callback: @ViewDslMarker WrappableText.() -> Unit = {}
) = WrappableText(
text,
textSize = textSize,
color = color,
font = font,
wrapWidth = wrapWidth,
renderer = WrappableTextRenderer(wrapWidth, gravity)
).addTo(this, callback)
class WrappableText(
text: String, textSize: Double = DEFAULT_TEXT_SIZE,
color: RGBA = Colors.WHITE, font: Resourceable<out Font> = DefaultTtfFont,
alignment: TextAlignment = TextAlignment.TOP_LEFT,
wrapWidth: Double,
renderer: TextRenderer<String>,
autoScaling: Boolean = true
) : Text(text, textSize, color, font, alignment, renderer, autoScaling) {
init {
val f = this.font.getOrNull()
if (f is Font) {
val metrics = f.getTextBounds(textSize, text, renderer = renderer)
metrics.bounds.x = 0.0
metrics.bounds.y = 0.0
metrics.bounds.width = wrapWidth
setTextBounds(metrics.bounds)
}
}
}
class WrappableTextRenderer(val wrapWidth: Double, val gravity: Gravity) : TextRenderer<String> {
override fun TextRendererActions.run(text: String, size: Double, defaultFont: Font) {
reset()
setFont(defaultFont, size)
val lines = mutableListOf(Line())
var curX = 0.0
val spaceWidth = getGlyphMetrics(' '.toInt()).xadvance + getKerning(' '.toInt(), 'A'.toInt())
for (word in text.split(' ')) {
var wordWidth = 0.0
var curWord = ""
for (n in word.indices) {
val c = word[n].toInt()
val c1 = word.getOrElse(n + 1) { '\u0000' }.toInt()
val g = getGlyphMetrics(c)
val kerning = getKerning(c, c1)
if (c == '\n'.toInt()) {
curX = 0.0
lines.last().words.add(Word(curWord, wordWidth))
curWord = ""
wordWidth = 0.0
lines.add(Line())
}
else {
wordWidth += g.xadvance + kerning
curWord += c.toChar()
}
}
curX += wordWidth + spaceWidth
if (curX > wrapWidth) {
lines.add(Line())
curX = wordWidth + spaceWidth
}
lines.last().words.add(Word(curWord, wordWidth))
}
for (line in lines) {
var start =
when (gravity) {
Gravity.LEFT -> 0.0
Gravity.CENTER -> (wrapWidth - line.calculateWidth(spaceWidth)) / 2
Gravity.RIGHT -> wrapWidth - line.calculateWidth(spaceWidth)
}
for (word in line.words) {
x = start
for (n in word.text.indices) {
val c = word.text[n].toInt()
val c1 = word.text.getOrElse(n + 1) { '\u0000' }.toInt()
val g = getGlyphMetrics(c)
transform.identity()
val advance = g.xadvance + getKerning(c, c1)
put(c)
advance(advance)
}
start += word.width + spaceWidth
}
newLine(lineHeight)
}
put(0)
}
data class Line(
val words: MutableList<Word> = mutableListOf()
) {
fun calculateWidth(spaceWidth: Double): Double {
return words.sumByDouble { it.width } + (words.size - 1) * spaceWidth
}
}
data class Word(val text: String, val width: Double)
}
How to perform text auto wrapping with predefined width?
Calling
text.setTextBounds(Rectangle(0.0, 0.0, 300.0, 200.0))
doesn't seem to solve the issue.
Thanks