From 05b00d28783933501ef990544f65b4840a36ea3b Mon Sep 17 00:00:00 2001 From: Edwin Jakobs Date: Fri, 15 Mar 2024 20:38:17 +0100 Subject: [PATCH] [orx-text-writer] Add orx-text-writer --- openrndr-demos/build.gradle.kts | 2 +- orx-jvm/orx-panel/build.gradle.kts | 1 + .../org/openrndr/panel/elements/Button.kt | 7 +- .../panel/elements/ColorpickerButton.kt | 5 +- .../openrndr/panel/elements/DropdownButton.kt | 8 +- .../openrndr/panel/elements/EnvelopeButton.kt | 8 +- .../openrndr/panel/elements/SequenceEditor.kt | 4 +- .../org/openrndr/panel/elements/Slider.kt | 8 +- .../openrndr/panel/elements/TextElements.kt | 7 +- .../org/openrndr/panel/elements/Textfield.kt | 4 +- .../org/openrndr/panel/elements/Toggle.kt | 5 +- .../org/openrndr/panel/elements/XYPad.kt | 5 +- .../org/openrndr/panel/tools/Tooltip.kt | 3 +- orx-text-writer/README.md | 6 + orx-text-writer/build.gradle.kts | 22 ++ .../src/commonMain/kotlin/DrawerExtensions.kt | 16 ++ .../commonMain/kotlin/ProgramExtensions.kt | 14 ++ .../src/commonMain/kotlin/TextWriter.kt | 211 ++++++++++++++++++ settings.gradle.kts | 1 + 19 files changed, 310 insertions(+), 27 deletions(-) create mode 100644 orx-text-writer/README.md create mode 100644 orx-text-writer/build.gradle.kts create mode 100644 orx-text-writer/src/commonMain/kotlin/DrawerExtensions.kt create mode 100644 orx-text-writer/src/commonMain/kotlin/ProgramExtensions.kt create mode 100644 orx-text-writer/src/commonMain/kotlin/TextWriter.kt diff --git a/openrndr-demos/build.gradle.kts b/openrndr-demos/build.gradle.kts index 165cbc35..8c244be6 100644 --- a/openrndr-demos/build.gradle.kts +++ b/openrndr-demos/build.gradle.kts @@ -10,7 +10,7 @@ dependencies { demoImplementation(project(":orx-shader-phrases")) demoImplementation(project(":orx-camera")) demoImplementation(project(":orx-shapes")) + demoImplementation(project(":orx-svg")) demoImplementation(libs.slf4j.simple) demoImplementation(libs.openrndr.ffmpeg) - demoImplementation(libs.openrndr.svg) } \ No newline at end of file diff --git a/orx-jvm/orx-panel/build.gradle.kts b/orx-jvm/orx-panel/build.gradle.kts index c77a904d..0143a74c 100644 --- a/orx-jvm/orx-panel/build.gradle.kts +++ b/orx-jvm/orx-panel/build.gradle.kts @@ -15,6 +15,7 @@ tasks.test { dependencies { implementation(project(":orx-expression-evaluator")) + implementation(project(":orx-text-writer")) implementation(libs.openrndr.application) implementation(libs.openrndr.math) implementation(libs.kotlin.coroutines) diff --git a/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/Button.kt b/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/Button.kt index fcfbd92b..667dceb6 100644 --- a/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/Button.kt +++ b/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/Button.kt @@ -3,9 +3,10 @@ package org.openrndr.panel.elements import org.openrndr.color.ColorRGBa import org.openrndr.draw.Drawer import org.openrndr.draw.FontImageMap -import org.openrndr.draw.Writer + import org.openrndr.draw.isolated import org.openrndr.events.Event +import org.openrndr.extra.textwriter.TextWriter import org.openrndr.panel.style.* import org.openrndr.shape.Rectangle @@ -53,7 +54,7 @@ class Button : Element(ElementType("button")) { val fontSize = (style.fontSize as? LinearDimension.PX)?.value ?: 14.0 val fontMap = FontImageMap.fromUrl(fontUrl, fontSize) - val writer = Writer(null) + val writer = TextWriter(null) writer.box = Rectangle(0.0, 0.0, @@ -84,7 +85,7 @@ class Button : Element(ElementType("button")) { (root() as? Body)?.controlManager?.fontManager?.let { val font = it.font(computedStyle) - val writer = Writer(drawer) + val writer = TextWriter(drawer) drawer.fontMap = (font) val textWidth = writer.textWidth(label) val textHeight = font.ascenderLength diff --git a/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/ColorpickerButton.kt b/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/ColorpickerButton.kt index a545d397..32c39dc4 100644 --- a/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/ColorpickerButton.kt +++ b/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/ColorpickerButton.kt @@ -4,8 +4,9 @@ import kotlinx.coroutines.yield import org.openrndr.color.ColorRGBa import org.openrndr.draw.Drawer import org.openrndr.draw.LineCap -import org.openrndr.draw.Writer + import org.openrndr.events.Event +import org.openrndr.extra.textwriter.TextWriter import org.openrndr.launch import org.openrndr.panel.style.* @@ -62,7 +63,7 @@ class ColorpickerButton : Element(ElementType("colorpicker-button")), Disposable (root() as? Body)?.controlManager?.fontManager?.let { val font = it.font(computedStyle) - val writer = Writer(drawer) + val writer = TextWriter(drawer) drawer.fontMap = (font) val text = "$label" diff --git a/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/DropdownButton.kt b/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/DropdownButton.kt index 63cdfdc3..d98d7f9e 100644 --- a/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/DropdownButton.kt +++ b/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/DropdownButton.kt @@ -5,12 +5,12 @@ import org.openrndr.draw.Drawer import org.openrndr.draw.FontImageMap import org.openrndr.panel.style.* import org.openrndr.shape.Rectangle - +import org.openrndr.extra.textwriter.TextWriter import kotlinx.coroutines.yield import org.openrndr.KEY_ARROW_DOWN import org.openrndr.KEY_ARROW_UP import org.openrndr.KEY_ENTER -import org.openrndr.draw.Writer + import org.openrndr.events.Event import org.openrndr.launch import kotlin.math.max @@ -80,7 +80,7 @@ class DropdownButton : Element(ElementType("dropdown-button")), DisposableElemen val fontUrl = (root() as? Body)?.controlManager?.fontManager?.resolve(style.fontFamily) ?: "broken" val fontSize = (style.fontSize as? LinearDimension.PX)?.value ?: 16.0 val fontMap = FontImageMap.fromUrl(fontUrl, fontSize) - val writer = Writer(null) + val writer = TextWriter(null) writer.box = Rectangle(0.0, 0.0, @@ -116,7 +116,7 @@ class DropdownButton : Element(ElementType("dropdown-button")), DisposableElemen (root() as? Body)?.controlManager?.fontManager?.let { val font = it.font(computedStyle) - val writer = Writer(drawer) + val writer = TextWriter(drawer) drawer.fontMap = (font) val text = (value?.label) ?: "" diff --git a/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/EnvelopeButton.kt b/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/EnvelopeButton.kt index 75231c90..52a80578 100644 --- a/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/EnvelopeButton.kt +++ b/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/EnvelopeButton.kt @@ -1,9 +1,11 @@ package org.openrndr.panel.elements import org.openrndr.color.ColorRGBa -import org.openrndr.draw.Cursor + import org.openrndr.draw.Drawer -import org.openrndr.draw.Writer +import org.openrndr.extra.textwriter.Cursor +import org.openrndr.extra.textwriter.TextWriter + import org.openrndr.math.Vector2 import org.openrndr.panel.style.* @@ -53,7 +55,7 @@ class EnvelopeButton : Element(ElementType("envelope-button")) { (root() as? Body)?.controlManager?.fontManager?.let { val font = it.font(computedStyle) - val writer = Writer(drawer) + val writer = TextWriter(drawer) drawer.fontMap = (font) drawer.fill = (ColorRGBa.BLACK) writer.cursor = Cursor(0.0,layout.screenHeight - 4.0) diff --git a/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/SequenceEditor.kt b/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/SequenceEditor.kt index f5d20005..46e70213 100644 --- a/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/SequenceEditor.kt +++ b/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/SequenceEditor.kt @@ -5,6 +5,8 @@ import org.openrndr.KeyModifier import org.openrndr.color.ColorRGBa import org.openrndr.draw.* import org.openrndr.events.Event +import org.openrndr.extra.textwriter.Cursor +import org.openrndr.extra.textwriter.TextWriter import org.openrndr.math.Vector2 import org.openrndr.math.map import org.openrndr.panel.style.effectiveColor @@ -196,7 +198,7 @@ open class SequenceEditorBase(type: String = "sequence-editor-base") : Element(E drawer.fill = computedStyle.effectiveColor (root() as? Body)?.controlManager?.fontManager?.let { val font = it.font(computedStyle) - val writer = Writer(drawer) + val writer = TextWriter(drawer) drawer.fontMap = (font) drawer.fill = computedStyle.effectiveColor writer.cursor = Cursor(0.0, 4.0) diff --git a/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/Slider.kt b/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/Slider.kt index 36b83348..64d861a6 100644 --- a/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/Slider.kt +++ b/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/Slider.kt @@ -3,11 +3,13 @@ package org.openrndr.panel.elements import io.github.oshai.kotlinlogging.KotlinLogging import kotlinx.coroutines.* import org.openrndr.* -import org.openrndr.draw.Cursor + import org.openrndr.draw.Drawer import org.openrndr.draw.LineCap -import org.openrndr.draw.Writer + import org.openrndr.events.Event +import org.openrndr.extra.textwriter.Cursor +import org.openrndr.extra.textwriter.TextWriter import org.openrndr.math.Vector2 import org.openrndr.panel.style.Color import org.openrndr.panel.style.color @@ -267,7 +269,7 @@ class Slider : Element(ElementType("slider")), DisposableElement { (root() as? Body)?.controlManager?.fontManager?.let { val font = it.font(computedStyle) - val writer = Writer(drawer) + val writer = TextWriter(drawer) drawer.fontMap = (font) drawer.fill = computedStyle.effectiveColor writer.cursor = Cursor(0.0, 8.0) diff --git a/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/TextElements.kt b/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/TextElements.kt index 89d408f8..52c38b1a 100644 --- a/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/TextElements.kt +++ b/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/TextElements.kt @@ -4,7 +4,8 @@ import kotlinx.coroutines.yield import org.openrndr.color.ColorRGBa import org.openrndr.draw.Drawer import org.openrndr.draw.FontImageMap -import org.openrndr.draw.Writer +import org.openrndr.extra.textwriter.TextWriter + import org.openrndr.launch import org.openrndr.math.Vector2 import org.openrndr.panel.style.* @@ -20,7 +21,7 @@ class TextNode(var text: String) : Element(ElementType("text")) { drawer.fill = (fill) } val fontMap = (root() as Body).controlManager.fontManager.font(computedStyle) - val writer = Writer(drawer) + val writer = TextWriter(drawer) drawer.fontMap = (fontMap) writer.box = Rectangle(Vector2(layout.screenX * 0.0, layout.screenY * 0.0), layout.screenWidth, layout.screenHeight) @@ -35,7 +36,7 @@ class TextNode(var text: String) : Element(ElementType("text")) { val fontSize = (style.fontSize as? LinearDimension.PX)?.value?: 14.0 val fontMap = FontImageMap.fromUrl(fontUrl, fontSize) - val writer = Writer(null) + val writer = TextWriter(null) writer.box = Rectangle(layout.screenX, layout.screenY, diff --git a/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/Textfield.kt b/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/Textfield.kt index 900207ad..640d25bc 100644 --- a/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/Textfield.kt +++ b/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/Textfield.kt @@ -7,9 +7,9 @@ import org.openrndr.draw.Drawer import org.openrndr.draw.LineCap import org.openrndr.panel.style.* import org.openrndr.KeyModifier -import org.openrndr.draw.Cursor -import org.openrndr.draw.writer import org.openrndr.events.Event +import org.openrndr.extra.textwriter.Cursor +import org.openrndr.extra.textwriter.writer import org.openrndr.launch import org.openrndr.shape.Rectangle import kotlin.reflect.KMutableProperty0 diff --git a/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/Toggle.kt b/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/Toggle.kt index b29137bc..0e7490de 100644 --- a/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/Toggle.kt +++ b/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/Toggle.kt @@ -7,8 +7,9 @@ import org.openrndr.draw.LineCap import org.openrndr.panel.style.* import org.openrndr.shape.Rectangle -import org.openrndr.draw.Writer + import org.openrndr.events.Event +import org.openrndr.extra.textwriter.TextWriter import org.openrndr.launch import kotlin.reflect.KMutableProperty0 @@ -35,7 +36,7 @@ class Toggle : Element(ElementType("toggle")), DisposableElement { val fontSize = (style.fontSize as? LinearDimension.PX)?.value ?: 14.0 val fontMap = FontImageMap.fromUrl(fontUrl, fontSize) - val writer = Writer(null) + val writer = TextWriter(null) writer.box = Rectangle(0.0, 0.0, diff --git a/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/XYPad.kt b/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/XYPad.kt index 194b5af0..5cd146fd 100644 --- a/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/XYPad.kt +++ b/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/elements/XYPad.kt @@ -4,8 +4,9 @@ import kotlinx.coroutines.yield import org.openrndr.* import org.openrndr.color.ColorRGBa import org.openrndr.draw.Drawer -import org.openrndr.draw.Writer + import org.openrndr.events.Event +import org.openrndr.extra.textwriter.TextWriter import org.openrndr.math.Vector2 import org.openrndr.math.clamp import org.openrndr.math.map @@ -213,7 +214,7 @@ class XYPad : Element(ElementType("xy-pad")) { val valueLabel = "${String.format("%.0${precision}f", value.x)}, ${String.format("%.0${precision}f", value.y)}" (root() as? Body)?.controlManager?.fontManager?.let { - val writer = Writer(drawer) + val writer = TextWriter(drawer) drawer.fontMap = it.font(computedStyle) val textWidth = writer.textWidth(valueLabel) diff --git a/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/tools/Tooltip.kt b/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/tools/Tooltip.kt index 2ced4fc2..7f71cfce 100644 --- a/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/tools/Tooltip.kt +++ b/orx-jvm/orx-panel/src/main/kotlin/org/openrndr/panel/tools/Tooltip.kt @@ -4,7 +4,8 @@ import org.openrndr.color.ColorRGBa import org.openrndr.draw.Drawer import org.openrndr.draw.FontImageMap import org.openrndr.draw.isolated -import org.openrndr.draw.writer +import org.openrndr.extra.textwriter.writer + import org.openrndr.math.Vector2 import org.openrndr.panel.elements.Body import org.openrndr.panel.elements.Element diff --git a/orx-text-writer/README.md b/orx-text-writer/README.md new file mode 100644 index 00000000..8a7716b6 --- /dev/null +++ b/orx-text-writer/README.md @@ -0,0 +1,6 @@ +# orx-text-writer + +Writing texts with layouts + +Code in `orx-text-writer` was previously part of `openrndr-draw`. + diff --git a/orx-text-writer/build.gradle.kts b/orx-text-writer/build.gradle.kts new file mode 100644 index 00000000..260e4db4 --- /dev/null +++ b/orx-text-writer/build.gradle.kts @@ -0,0 +1,22 @@ +plugins { + org.openrndr.extra.convention.`kotlin-multiplatform` + alias(libs.plugins.kotest.multiplatform) +} + +kotlin { + sourceSets { + val commonMain by getting { + dependencies { + implementation(libs.openrndr.shape) + implementation(libs.openrndr.draw) + implementation(libs.openrndr.application) + } + } + + val jvmDemo by getting { + dependencies { + implementation(project(":orx-text-writer")) + } + } + } +} \ No newline at end of file diff --git a/orx-text-writer/src/commonMain/kotlin/DrawerExtensions.kt b/orx-text-writer/src/commonMain/kotlin/DrawerExtensions.kt new file mode 100644 index 00000000..e58851a0 --- /dev/null +++ b/orx-text-writer/src/commonMain/kotlin/DrawerExtensions.kt @@ -0,0 +1,16 @@ +package org.openrndr.extra.textwriter + +import org.openrndr.draw.Drawer +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract +import kotlin.jvm.JvmName + +@OptIn(ExperimentalContracts::class) +@JvmName("drawerWriter") +fun Drawer.writer(f: TextWriter.() -> T): T { + contract { + callsInPlace(f, InvocationKind.EXACTLY_ONCE) + } + return writer(this, f) +} \ No newline at end of file diff --git a/orx-text-writer/src/commonMain/kotlin/ProgramExtensions.kt b/orx-text-writer/src/commonMain/kotlin/ProgramExtensions.kt new file mode 100644 index 00000000..42f90f75 --- /dev/null +++ b/orx-text-writer/src/commonMain/kotlin/ProgramExtensions.kt @@ -0,0 +1,14 @@ +package org.openrndr.extra.textwriter + +import org.openrndr.Program +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +@OptIn(ExperimentalContracts::class) +fun Program.writer(f: TextWriter.() -> T): T { + contract { + callsInPlace(f, InvocationKind.EXACTLY_ONCE) + } + return writer(drawer, f) +} diff --git a/orx-text-writer/src/commonMain/kotlin/TextWriter.kt b/orx-text-writer/src/commonMain/kotlin/TextWriter.kt new file mode 100644 index 00000000..b4825d6a --- /dev/null +++ b/orx-text-writer/src/commonMain/kotlin/TextWriter.kt @@ -0,0 +1,211 @@ +package org.openrndr.extra.textwriter + +import org.openrndr.draw.DrawStyle +import org.openrndr.draw.Drawer +import org.openrndr.draw.FontImageMap +import org.openrndr.math.Vector2 +import org.openrndr.shape.Rectangle + +class Cursor(var x: Double = 0.0, var y: Double = 0.0) { + constructor(cursor: Cursor) : this(cursor.x, cursor.y) +} + +@Suppress("unused") +class TextToken(val token: String, val x: Double, val y: Double, val width: Double, val tracking: Double) + +class WriteStyle { + var leading = 0.0 + var tracking = 0.0 + var ellipsis: String? = "…" +} + +@Suppress("unused", "UNUSED_PARAMETER") +class TextWriter(val drawerRef: Drawer?) { + var cursor = Cursor() + var box = Rectangle( + Vector2.ZERO, drawerRef?.width?.toDouble() ?: Double.POSITIVE_INFINITY, drawerRef?.height?.toDouble() + ?: Double.POSITIVE_INFINITY + ) + set(value) { + field = value + cursor.x = value.corner.x + cursor.y = value.corner.y + } + + var style = WriteStyle() + val styleStack = ArrayDeque() + + var leading + get() = style.leading + set(value) { + style.leading = value + } + + var tracking + get() = style.tracking + set(value) { + style.tracking = value + } + + var ellipsis + get() = style.ellipsis + set(value) { + style.ellipsis = value + } + + var drawStyle: DrawStyle = DrawStyle() + get() { + return drawerRef?.drawStyle ?: field + } + set(value) { + field = drawStyle + } + + fun newLine() { + cursor.x = box.corner.x + cursor.y += (drawStyle.fontMap?.leading ?: 0.0) + style.leading + } + + fun gaplessNewLine() { + cursor.x = box.corner.x + cursor.y += drawStyle.fontMap?.height ?: 0.0 + } + + fun move(x: Double, y: Double) { + cursor.x += x + cursor.y += y + } + + fun textWidth(text: String): Double = + text.sumOf { + ((drawStyle.fontMap as FontImageMap).glyphMetrics[it]?.advanceWidth ?: 0.0) + style.tracking + } - (text.count { it == ' ' } + 1) * style.tracking + + /** + * Draw text + * @param text the text to write, may contain newlines + * @param visible draw the text when set to true, when set to false only type setting is performed + * @return a list of [TextToken] instances + */ + fun text(text: String, visible: Boolean = true): List { + // Triggers loading the default font (if needed) by accessing .fontMap + // otherwise makeRenderTokens() is not aware of the default font. + drawerRef?.fontMap + + val renderTokens = makeTextTokens(text, false) + + if (visible) { + drawTextTokens(renderTokens) + } + return renderTokens + } + + /** + * Draw pre-set text tokens. + * @param tokens a list of [TextToken] instances + * @since 0.4.3 + */ + fun drawTextTokens(tokens: List) { + drawerRef?.let { d -> + val renderer = d.fontImageMapDrawer + val queue = renderer.getQueue(tokens.size) + tokens.forEach { + renderer.queueText( + fontMap = d.drawStyle.fontMap!!, + text = it.token, + x = it.x, + y = it.y, + tracking = style.tracking, + kerning = drawStyle.kerning, + textSetting = drawStyle.textSetting, + queue + ) + } + renderer.flush(d.context, d.drawStyle, queue) + } + } + + private fun makeTextTokens(text: String, mustFit: Boolean = false): List { + drawStyle.fontMap?.let { font -> + + var fits = true + font as FontImageMap + val lines = text.split("((?<=\n)|(?=\n))".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + val tokens = mutableListOf() + lines.forEach { line -> + val lineTokens = line.split(" ") + tokens.addAll(lineTokens) + } + + val localCursor = Cursor(cursor) + + val spaceWidth = (font.glyphMetrics[' ']?.advanceWidth ?: error("no metrics for space")) + val verticalSpace = style.leading + font.leading + + val textTokens = mutableListOf() + + tokenLoop@ for (i in 0 until tokens.size) { + val token = tokens[i] + if (token == "\n") { + localCursor.x = box.corner.x + localCursor.y += verticalSpace + } else { + val tokenWidth = token.sumOf { + (font.glyphMetrics[it]?.advanceWidth ?: 0.0) + } + style.tracking * (token.length - 1).coerceAtLeast(0) + if (localCursor.x + tokenWidth < box.x + box.width && localCursor.y <= box.y + box.height) run { + val textToken = TextToken(token, localCursor.x, localCursor.y, tokenWidth, style.tracking) + emitToken(localCursor, textTokens, textToken) + } else { + if (localCursor.y > box.corner.y + box.height) { + fits = false + } + if (localCursor.y + verticalSpace <= box.y + box.height) { + localCursor.y += verticalSpace + localCursor.x = box.x + + emitToken( + localCursor, + textTokens, + TextToken(token, localCursor.x, localCursor.y, tokenWidth, style.tracking) + ) + } else { + if (!mustFit && style.ellipsis != null && cursor.y <= box.y + box.height) { + emitToken( + localCursor, textTokens, TextToken( + style.ellipsis + ?: "", localCursor.x, localCursor.y, tokenWidth, style.tracking + ) + ) + break@tokenLoop + } else { + fits = false + } + } + } + localCursor.x += tokenWidth + + if (i != tokens.lastIndex) { + localCursor.x += spaceWidth + tracking + } + } + } + if (fits || (!fits && !mustFit)) { + cursor = Cursor(localCursor) + } else { + textTokens.clear() + } + return textTokens + } + return emptyList() + } + + private fun emitToken(cursor: Cursor, textTokens: MutableList, textToken: TextToken) { + textTokens.add(textToken) + } +} + +fun writer(drawer: Drawer, f: TextWriter.() -> T): T { + val textWriter = TextWriter(drawer) + return textWriter.f() +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 95867cf3..17b01f01 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -91,6 +91,7 @@ include( "orx-depth-camera", "orx-jvm:orx-depth-camera-calibrator", "orx-view-box", + "orx-text-writer", "orx-turtle" ) ) \ No newline at end of file