Skip to content

Commit 61a0e78

Browse files
committed
Add SlugTextOnContour.kt
1 parent b67d051 commit 61a0e78

5 files changed

Lines changed: 214 additions & 3 deletions

File tree

orx-shapes/src/commonMain/kotlin/rectify/RectifiedContour.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package org.openrndr.extra.shapes.rectify
22

33
import org.openrndr.math.Matrix44
44
import org.openrndr.math.Vector2
5+
import org.openrndr.math.YPolarity
56
import org.openrndr.shape.ShapeContour
67
import kotlin.math.floor
78

@@ -24,12 +25,12 @@ class RectifiedContour(contour: ShapeContour, distanceTolerance: Double = 0.5, l
2425
}
2526
}
2627

27-
fun pose(t: Double): Matrix44 {
28+
fun pose(t: Double, polarity: YPolarity = (originalPath as ShapeContour).polarity): Matrix44 {
2829
originalPath as ShapeContour
2930
return if (originalPath.empty) {
3031
Matrix44.IDENTITY
3132
} else {
32-
originalPath.pose(rectify(safe(t)))
33+
originalPath.pose(rectify(safe(t)), polarity)
3334
}
3435
}
3536

orx-text-on-contour/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ kotlin {
1515

1616
val jvmDemo by getting {
1717
dependencies {
18+
implementation(openrndr.ffmpeg)
1819
implementation(project(":orx-text-on-contour"))
20+
implementation(project(":orx-camera"))
1921
}
2022
}
2123
}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
package org.openrndr.extra.textoncontour
2+
3+
import org.openrndr.color.ColorRGBa
4+
import org.openrndr.draw.Drawer
5+
import org.openrndr.draw.font.internal.ShapeResult
6+
import org.openrndr.draw.font.internal.TextShapingDriver
7+
import org.openrndr.draw.slug.SlugCommand
8+
import org.openrndr.draw.slug.SlugDrawer
9+
import org.openrndr.draw.slug.SlugGlyphMap
10+
import org.openrndr.draw.slug.StrokeMode
11+
import org.openrndr.draw.slug.TextSpan
12+
import org.openrndr.draw.slug.TextStyle
13+
import org.openrndr.extra.shapes.rectify.RectifiedContour
14+
import org.openrndr.math.Matrix44
15+
import org.openrndr.math.Vector4
16+
import org.openrndr.math.transforms.transform
17+
18+
fun RectifiedContour.positionOnlyPose(t: Double): Matrix44 {
19+
return Matrix44.fromColumnVectors(Vector4.UNIT_X * -1.0, Vector4.UNIT_Y * -1.0, Vector4.UNIT_Z, position(t).xy01)
20+
}
21+
22+
fun slugTextOnContour(
23+
drawer: Drawer,
24+
slugDrawer: SlugDrawer,
25+
slugGlyphMap: SlugGlyphMap,
26+
baseStyle: TextStyle,
27+
spans: List<TextSpan>,
28+
contour: RectifiedContour,
29+
startT: Double = 0.0
30+
) {
31+
val fixOrientation = Matrix44.fromColumnVectors(
32+
Vector4(-1.0, 0.0, 0.0, 0.0),
33+
Vector4(0.0, -1.0, 0.0, 0.0),
34+
Vector4.UNIT_Z,
35+
Vector4.UNIT_W
36+
)
37+
38+
val shaper = TextShapingDriver.instance
39+
40+
var textWidth = 0.0
41+
42+
class WordInfo(val word: String, val width: Double, val shaped: List<ShapeResult>, val style: TextStyle)
43+
44+
val items = mutableListOf<WordInfo>()
45+
46+
47+
for (span in spans) {
48+
val spanStyle = baseStyle.cascade(span.style)
49+
val face = spanStyle.face ?: error("face not set in span")
50+
51+
val words = mutableListOf<String>()
52+
val currentWord = StringBuilder()
53+
54+
for (ch in span.text) {
55+
when (ch) {
56+
' ', '\t' -> {
57+
if (currentWord.isNotEmpty()) {
58+
words.add(currentWord.toString())
59+
currentWord.clear()
60+
}
61+
words.add(" ")
62+
}
63+
64+
'\n' -> {
65+
if (currentWord.isNotEmpty()) {
66+
words.add(currentWord.toString())
67+
currentWord.clear()
68+
}
69+
words.add("\n")
70+
}
71+
72+
else -> {
73+
currentWord.append(ch)
74+
}
75+
}
76+
}
77+
if (currentWord.isNotEmpty()) {
78+
words.add(currentWord.toString())
79+
}
80+
81+
val spaceGlyph = face.glyphForCharacter(' ')
82+
val spaceWidth = spaceGlyph.advanceWidth() * spanStyle.textWidthFactor!! * spanStyle.sizeInEm!!
83+
84+
for (word in words) {
85+
when (word) {
86+
" ", "\t" -> {
87+
items.add(WordInfo(" ", spaceWidth, emptyList(), spanStyle))
88+
textWidth += spaceWidth
89+
}
90+
91+
else -> {
92+
val features = spanStyle.features
93+
94+
val shaped = shaper.shape(face, word, features)
95+
var wordWidth = 0.0
96+
for (sr in shaped) {
97+
wordWidth += sr.advance.x * spanStyle.textWidthFactor!! * spanStyle.sizeInEm!!
98+
}
99+
100+
items.add(WordInfo(word, wordWidth, shaped, spanStyle))
101+
textWidth += wordWidth
102+
}
103+
}
104+
}
105+
var cursorT = startT
106+
107+
val dt = 1.0 / contour.contour.length
108+
109+
val commands = mutableListOf<SlugCommand>()
110+
for (item in items) {
111+
if (item.word == " ") {
112+
cursorT += item.width * dt
113+
} else {
114+
115+
val itemTransform = transform {
116+
scale(item.style.sizeInEm!! * item.style.textWidthFactor!!, item.style.sizeInEm!!)
117+
}
118+
119+
val hscale = item.style.sizeInEm!! * item.style.textWidthFactor!!
120+
for (shape in item.shaped) {
121+
val slugIndex = slugGlyphMap.getSlugForGlyphIndex(item.style.face!!, shape.glyphIndex)
122+
123+
val t =
124+
if (contour.contour.closed) (cursorT + shape.offset.x * hscale * dt).mod(1.0) else cursorT + shape.offset.x * hscale * dt
125+
126+
val pose =
127+
contour.pose(t) * itemTransform * fixOrientation
128+
129+
val cmd = SlugCommand(
130+
slugIndex,
131+
pose,
132+
item.style.fill ?: ColorRGBa.TRANSPARENT,
133+
item.style.stroke ?: ColorRGBa.TRANSPARENT,
134+
strokeWeight = item.style.strokeWeight ?: 0.0,
135+
(item.style.strokeMode ?: StrokeMode.CENTER).mode
136+
)
137+
commands.add(cmd)
138+
cursorT += shape.advance.x * hscale * dt
139+
}
140+
}
141+
}
142+
slugDrawer.prepare(slugGlyphMap.slugMap, commands)
143+
slugDrawer.draw(drawer, slugGlyphMap.slugMap)
144+
}
145+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import org.openrndr.application
2+
import org.openrndr.draw.ColorFormat
3+
import org.openrndr.draw.ColorType
4+
import org.openrndr.draw.MagnifyingFilter
5+
import org.openrndr.draw.MinifyingFilter
6+
import org.openrndr.draw.colorBuffer
7+
import org.openrndr.draw.font.loadFace
8+
import org.openrndr.draw.slug.SlugDrawer
9+
import org.openrndr.draw.slug.SlugGlyphMap
10+
import org.openrndr.draw.slug.SlugMap
11+
import org.openrndr.draw.slug.TextSpan
12+
import org.openrndr.draw.slug.TextStyle
13+
import org.openrndr.extra.camera.Camera2D
14+
import org.openrndr.extra.shapes.rectify.rectified
15+
import org.openrndr.extra.textoncontour.slugTextOnContour
16+
import org.openrndr.ffmpeg.ScreenRecorder
17+
import org.openrndr.shape.Circle
18+
import kotlin.math.PI
19+
import kotlin.math.cos
20+
21+
/**
22+
* Demo Functionality includes:
23+
* - Loading and applying a specific font (`IBMPlexMono-Regular`) with a size of 32.0.
24+
* - Creating a circular contour at the center of the screen with a radius of 200.0.
25+
* - Rendering text along the rectified circle's contour.
26+
* - Offsetting text positions, enabling repeated text rendering along the same contour.
27+
*/
28+
fun main() = application {
29+
configure {
30+
width = 720
31+
height = 720
32+
}
33+
program {
34+
35+
extend(ScreenRecorder()) {
36+
maximumDuration = 10.0
37+
frameRate = 60.0
38+
}
39+
val face = loadFace("demo-data/fonts/IBMPlexMono-Regular.ttf", 32.0, 1.0)
40+
41+
val slugMap = SlugMap(
42+
colorBuffer(4096, 64, type = ColorType.FLOAT32, format = ColorFormat.RG),
43+
colorBuffer(4096, 64, type = ColorType.UINT16_INT, format = ColorFormat.RGBa)
44+
)
45+
46+
slugMap.bands.filter(MinifyingFilter.LINEAR, MagnifyingFilter.LINEAR)
47+
48+
val slugGlyphMap = SlugGlyphMap(slugMap)
49+
val slugDrawer = SlugDrawer()
50+
extend(Camera2D())
51+
extend {
52+
val c = Circle(drawer.bounds.center, 200.0).contour.rectified()
53+
54+
var spans = listOf(TextSpan("The wheels of the bus go round and round.", TextStyle(textWidthFactor = 1.0, sizeInEm = 1.3)))
55+
56+
spans = spans.flatMap { it.text.mapIndexed { index, c -> TextSpan(c.toString(), it.style!!.copy(textWidthFactor = cos(seconds * PI * 2.0 * 0.25 + index*0.1) * 0.4 + 0.6, sizeInEm = cos(seconds * PI * 4.0 * 0.5 + index*0.0465) * 0.4 + 1.4)) } }
57+
58+
slugTextOnContour(drawer, slugDrawer, slugGlyphMap, TextStyle.Defaults.copy(face = face),
59+
spans, c, seconds * 0.5)
60+
61+
}
62+
}
63+
}

orx-text-on-contour/src/jvmDemo/kotlin/DemoTextOnContour01.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ fun main() = application {
2121
drawer.fontMap = loadFont("demo-data/fonts/IBMPlexMono-Regular.ttf", 32.0)
2222
val c = Circle(drawer.bounds.center, 200.0).contour.rectified()
2323
drawer.textOnContour("The wheels of the bus go round and round.", c)
24-
drawer.textOnContour("The wheels of the bus go round and round.", c, c.contour.length / 2.0)
24+
//drawer.textOnContour("The wheels of the bus go round and round.", c, c.contour.length / 2.0)
2525
}
2626
}
2727
}

0 commit comments

Comments
 (0)