Aligning text in drawer.writer

Doing manual calculations for when I’d like to center to right align text can be frustrating, especially for longer paragraphs. Having it built into drawer.writer would be useful for text presentation and manipulation.
For example, p5.js has a textAlign function that allows you to place text to the left, center, or right of a given start point. They even go as far as vertical align, but I dont think thats nearly as useful as center and right align.

Hi! I took a first attempt at doing this. The idea is to pass uv coordinates to drawer.text() as a third argument. For instance Vector2(0.5, 0.5) would be used for aligning a text to the center. The TextAlign class contains some frequent locations (the typical 9 grid points).

tests.TextAlign-2023-04-18-16.16.40

import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.Drawer
import org.openrndr.draw.Writer
import org.openrndr.draw.isolated
import org.openrndr.draw.loadFont
import org.openrndr.math.Vector2

fun main() = application {
    program {
        val word = "H|j"
        val font = loadFont("data/fonts/default.otf", 100.0, word.toSet())

        fun pos(u: Double, v: Double) = drawer.bounds.position(u, v)

        extend {
            drawer.clear(ColorRGBa.WHITE)
            drawer.fill = ColorRGBa.BLACK
            drawer.fontMap = font
            drawer.text(word, pos(1.0, 0.0), TextAlign.TOP_RIGHT)
            drawer.text(word, pos(0.0, 1.0), TextAlign.BOTTOM_LEFT)
            drawer.text(word, pos(1.0, 1.0), TextAlign.BOTTOM_RIGHT)
            drawer.text(word, pos(0.0, 0.0), TextAlign.TOP_LEFT)

            drawer.text(word, pos(0.5, 0.0), TextAlign.TOP)
            drawer.text(word, pos(0.5, 1.0), TextAlign.BOTTOM)
            drawer.text(word, pos(1.0, 0.5), TextAlign.RIGHT)
            drawer.text(word, pos(0.0, 0.5), TextAlign.LEFT)

            drawer.text(word, pos(0.5, 0.5), TextAlign.CENTER)

            drawer.stroke = ColorRGBa.BLACK.opacify(0.3)
            drawer.lineSegment(pos(0.5, 0.0), pos(0.5, 1.0))
            drawer.lineSegment(pos(0.0, 0.5), pos(1.0, 0.5))
        }
    }
}

/**
 * Text alignment presets
 */
class TextAlign {
    companion object {
        val CENTER = Vector2(0.5, 0.5)
        val LEFT = Vector2(0.0, 0.5)
        val RIGHT = Vector2(1.0, 0.5)
        val TOP = Vector2(0.5, 0.0)
        val BOTTOM = Vector2(0.5, 1.0)
        val TOP_LEFT = Vector2(0.0, 0.0)
        val TOP_RIGHT = Vector2(1.0, 0.0)
        val BOTTOM_LEFT = Vector2(0.0, 1.0)
        val BOTTOM_RIGHT = Vector2(1.0, 1.0)
    }
}

/**
 * A version of `Drawer.text()` accepting an [align] argument.
 */
private fun Drawer.text(txt: String, pos: Vector2, align: Vector2) {
    fontMap?.let { fm ->
        val writer = Writer(this)
        val off = Vector2(
            -writer.textWidth(txt) * align.x,
            fm.height * (1 - align.y)
        )
        text(txt, pos + off)
    }
}

I will think more about it. Also about multiline text.

2 Likes

Thats a pretty good idea. I was playing around with overloading the Writer.text function and using a similar thought process as you, but it doesnt apply to all of the lines. Heres what I wrote before I stopped.

fun Writer.text(text: String, align: Vector2){                                      
    drawerRef?.fontMap?.let { fm ->                                                 
        val writer = Writer(drawerRef)                                              
        val off = Vector2(                                                          
            -writer.textWidth(text) * align.x,                                      
            fm.height * (1 - align.y)                                               
        )                                                                           
       box = Rectangle(box.x + off.x, box.y + off.y, box.width, box.height)         
                                                                                    
                                                                                    
       text(text)                                                                   
    }                                                                               
}                                                                                   

this is so cool and solves the problem in so clever way that should find its way to the guide

1 Like