diff --git a/orx-text-writer/src/commonMain/kotlin/TextWriter.kt b/orx-text-writer/src/commonMain/kotlin/TextWriter.kt index b061d980..be03936f 100644 --- a/orx-text-writer/src/commonMain/kotlin/TextWriter.kt +++ b/orx-text-writer/src/commonMain/kotlin/TextWriter.kt @@ -3,6 +3,7 @@ package org.openrndr.extra.textwriter import org.openrndr.draw.DrawStyle import org.openrndr.draw.Drawer import org.openrndr.draw.FontImageMap +import org.openrndr.internal.GlyphOutput import org.openrndr.math.Vector2 import org.openrndr.shape.Rectangle import kotlin.contracts.ExperimentalContracts @@ -182,6 +183,20 @@ class TextWriter(val drawerRef: Drawer?) { var style = WriteStyle() val styleStack = ArrayDeque() + /** + * Stores the output of glyph positioning and rendering processes within the text drawing operations. + * + * This variable is used internally to maintain a collection of calculated glyph positions and associated + * bounding rectangles. It consists of two mutable lists: + * - One for glyph characters, represented by their positions. + * - One for glyph bounding rectangles, which define the area occupied by each glyph. + * + * The `glyphOutput` is primarily utilized in text rendering pipelines to calculate, store, and later + * process glyph information for visual rendering or additional transformations such as custom effects. + */ + val glyphOutput = GlyphOutput(mutableListOf(), mutableListOf()) + + var leading get() = style.leading set(value) { @@ -380,7 +395,7 @@ class TextWriter(val drawerRef: Drawer?) { val first = renderTokens.filter { it != TextToken.END_OF_LINE }.first() val last = renderTokens.last() renderTokens.split().flatMap { - val sy = first.y - (fontMap?.ascenderLength ?: 0.0) + val sy = first.y - (fontMap?.ascenderLength ?: 0.0) val ey = last.y + (fontMap?.descenderLength ?: 0.0) val th = ey - sy @@ -390,9 +405,7 @@ class TextWriter(val drawerRef: Drawer?) { } - if (visible) { - drawTextTokens(renderTokens) - } + drawTextTokens(renderTokens, visible) return renderTokens } @@ -401,7 +414,9 @@ class TextWriter(val drawerRef: Drawer?) { * @param tokens a list of [TextToken] instances * @since 0.4.3 */ - fun drawTextTokens(tokens: List) { + fun drawTextTokens(tokens: List, visible: Boolean) { + glyphOutput.characters.clear() + glyphOutput.rectangles.clear() drawerRef?.let { d -> val renderer = d.fontImageMapDrawer val queue = renderer.getQueue(tokens.sumOf { it.token.length }) @@ -414,10 +429,14 @@ class TextWriter(val drawerRef: Drawer?) { tracking = style.tracking, kerning = drawStyle.kerning, textSetting = drawStyle.textSetting, - queue + queue, + visible, + glyphOutput ) } - renderer.flush(d.context, d.drawStyle, queue) + if (visible) { + renderer.flush(d.context, d.drawStyle, queue) + } } } @@ -496,8 +515,6 @@ class TextWriter(val drawerRef: Drawer?) { } return emptyList() } - - } /** diff --git a/orx-text-writer/src/jvmDemo/kotlin/DemoGlyphOutput01.kt b/orx-text-writer/src/jvmDemo/kotlin/DemoGlyphOutput01.kt new file mode 100644 index 00000000..20d7af9d --- /dev/null +++ b/orx-text-writer/src/jvmDemo/kotlin/DemoGlyphOutput01.kt @@ -0,0 +1,68 @@ +import org.openrndr.application +import org.openrndr.color.ColorRGBa +import org.openrndr.draw.FontImageMap +import org.openrndr.draw.TextSettingMode +import org.openrndr.draw.isolated +import org.openrndr.draw.loadFont +import org.openrndr.draw.shadeStyle +import org.openrndr.extra.textwriter.TextWriter +import org.openrndr.extra.textwriter.writer +import org.openrndr.math.Vector2 +import org.openrndr.shape.Rectangle +import kotlin.math.cos + +/** + * This demo implements a drawing program utilizing custom text rendering with a wave-like animation effect. + * It allows for manipulating text position and scaling over time. + * + * Key elements of the program: + * - A centered rectangle on the drawing canvas. + * - Text rendering with properties such as horizontal alignment, vertical alignment, and tracking, + * dynamically changing over time. + * - Custom text animation implementing wave-like movement and scaling. + */ +fun main() = application { + configure { + width = 720 + height = 720 + } + program { + extend { + val r = Rectangle.fromCenter(drawer.bounds.center, 600.0, 600.0) + drawer.isolated { + drawer.fill = null + drawer.stroke = ColorRGBa.WHITE + drawer.rectangle(r) + } + drawer.fontMap = loadFont("demo-data/fonts/IBMPlexMono-Regular.ttf", 24.0) + + + fun TextWriter.wavyWrite(text: String) { + text(text, visible = false) + drawer.shadeStyle = shadeStyle { + fragmentTransform = """ + float o = x_fill.r; + x_fill = u_fill; + x_fill.a *= o; + """.trimIndent() + } + + drawer.image((drawer.fontMap as FontImageMap).texture, glyphOutput.rectangles.mapIndexed { index, it -> + Pair( + it.first, it.second + .movedBy(Vector2(0.0, 20.0 * cos(index * 0.5 + seconds * 10.0))) + .scaledBy(cos(index * 0.1 + seconds * 0.5) * 0.5 + 1.0) + ) + }) + } + writer { + drawer.drawStyle.textSetting = TextSettingMode.SUBPIXEL + style.horizontalAlign = cos(seconds) * 0.5 + 0.5 + style.verticalAlign = 0.5 + style.tracking = (cos(seconds * 0.1) * 0.5 + 0.5) * 20.0 + box = r.offsetEdges(-10.0) + wavyWrite("hello world\nthis is a test\ncentered") + } + } + } +}