Text Wrapping



  • 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 have the same question... how to wrap text?



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


  • @Saiful-Anwar Thank you for the contribution!
    I only had to fix it a bit 🙂 and add wrapping for e.g. Chinese texts,
    and it works well for my Help screen.
    https://github.com/andstatus/game2048/blob/master/src/commonMain/kotlin/org/andstatus/game2048/view/WrappableText.kt
    help_zh.png


Log in to reply