Is it possible to make text align with a curve rather than a straight line on a screen? [Solved]
Forum rules
This is the right place for Ren'Py help. Please ask one question per thread, use a descriptive subject like 'NotFound error in option.rpy' , and include all the relevant information - especially any relevant code and traceback messages. Use the code tag to format scripts.
This is the right place for Ren'Py help. Please ask one question per thread, use a descriptive subject like 'NotFound error in option.rpy' , and include all the relevant information - especially any relevant code and traceback messages. Use the code tag to format scripts.
-
- Regular
- Posts: 37
- Joined: Sat Nov 23, 2019 7:03 pm
- Projects: Kingmaker
- Contact:
Is it possible to make text align with a curve rather than a straight line on a screen? [Solved]
I don't know if I'm explaining myself. I'm not talking about italics, but rather having the whole text be in a curve. Particularly for a screen.
I was check the text style properties but I could not find anything that could do that, at least to my knowledge.
I was check the text style properties but I could not find anything that could do that, at least to my knowledge.
Last edited by KingmakerVN on Sun Oct 08, 2023 1:03 am, edited 1 time in total.
- PyTom
- Ren'Py Creator
- Posts: 16096
- Joined: Mon Feb 02, 2004 10:58 am
- Completed: Moonlight Walks
- Projects: Ren'Py
- IRC Nick: renpytom
- Github: renpytom
- itch: renpytom
- Location: Kings Park, NY
- Contact:
Re: Is it possible to make text align with a curve rather than a straight line on a screen?
No - at least right now, that's not a feature Ren'Py has.
Supporting creators since 2004
(When was the last time you backed up your game?)
"Do good work." - Virgil Ivan "Gus" Grissom(When was the last time you backed up your game?)
Software > Drama • https://www.patreon.com/renpytom
-
- Regular
- Posts: 37
- Joined: Sat Nov 23, 2019 7:03 pm
- Projects: Kingmaker
- Contact:
- m_from_space
- Eileen-Class Veteran
- Posts: 1011
- Joined: Sun Feb 21, 2021 3:36 am
- Contact:
Re: Is it possible to make text align with a curve rather than a straight line on a screen? [Solved]
In principle you should be able to write yourself a function that does just that:
1. Split the text into single characters
2. Follow a bezier curve using a fixed distance (like if you would plot it).
3. Place the next character at this specific location and then rotate it accordingly.
I have the feeling that it wouldn't be that hard in Python using Renpy, but maybe I am wrong about it.
- m_from_space
- Eileen-Class Veteran
- Posts: 1011
- Joined: Sun Feb 21, 2021 3:36 am
- Contact:
Re: Is it possible to make text align with a curve rather than a straight line on a screen? [Solved]
Haha, okay. Here is what I came up with. It's not perfect but it works.
edit: some things that probably can be improved here:
1) figure out how to plot it to have fixed distance between the characters
2) make sure we only calculate the positions and rotation once and save it in some kind of object, so Renpy doesn't call the function every time it renders the screen
edit: some things that probably can be improved here:
1) figure out how to plot it to have fixed distance between the characters
2) make sure we only calculate the positions and rotation once and save it in some kind of object, so Renpy doesn't call the function every time it renders the screen
Code: Select all
init python:
import math
# return point (x, y) on bezier curve (p0, p1, p2, p3) and rotation r of that point's tangent at t-value
def bezier_info(p0, p1, p2, p3, t):
x = int((1 - t)**3 * p0[0] + 3 * (1 - t)**2 * t * p1[0] + 3 * (1 - t) * t**2 * p2[0] + t**3 * p3[0])
y = int((1 - t)**3 * p0[1] + 3 * (1 - t)**2 * t * p1[1] + 3 * (1 - t) * t**2 * p2[1] + t**3 * p3[1])
u = (1 - t)**2 * (p1[0] - p0[0]) + 2*t * (1 - t) * (p2[0] - p1[0]) + t**2 * (p3[0] - p2[0])
v = (1 - t)**2 * (p1[1] - p0[1]) + 2*t * (1 - t) * (p2[1] - p1[1]) + t**2 * (p3[1] - p2[1])
r = int(math.degrees(math.atan2(v, u)))
return x, y, r
transform atl_bezier(x, y, r):
xcenter x
ycenter y
rotate r
# draw a curved text (change td for different character spacing)
screen curved_text(what, p0, p1, p2, p3, td=0.05):
default l = len(what)
default t = 0.0
for i in range(l):
$ x, y, r = bezier_info(p0, p1, p2, p3, t)
text what[i] color "#f00" at atl_bezier(x, y, r)
$ t += td
label start:
# define a bezier curve's points
$ p0 = (100, 300) # start point
$ p1 = (300, 100) # control point 1
$ p2 = (500, 500) # control point 2
$ p3 = (700, 300) # end point
show screen curved_text("HELLO, I AM A CURVED TEXT!", p0, p1, p2, p3)
"Can you see it?"
- m_from_space
- Eileen-Class Veteran
- Posts: 1011
- Joined: Sun Feb 21, 2021 3:36 am
- Contact:
Re: Is it possible to make text align with a curve rather than a straight line on a screen? [Solved]
Alright, so here is a better version, using a creator-defined displayable. This allows you to create curved texts that you can use like regular images. Feel free to make it better.
Code: Select all
init python:
import math
class CurvedText(renpy.Displayable):
def __init__(self, what, p0, p1, p2, p3, **kwargs):
super(CurvedText, self).__init__(**kwargs)
# a list of arguments we will use for the final composite image
args = []
l = len(what)
# current position along the curve (0.0 = start, 1.0 = end)
t = 0.0
# equal step distance depending on the text length
td = 1.0 / l
for i in range(l):
# the position on the bezier curve at t value
x = int((1 - t)**3 * p0[0] + 3 * (1 - t)**2 * t * p1[0] + 3 * (1 - t) * t**2 * p2[0] + t**3 * p3[0])
y = int((1 - t)**3 * p0[1] + 3 * (1 - t)**2 * t * p1[1] + 3 * (1 - t) * t**2 * p2[1] + t**3 * p3[1])
# the vector of the tangent on that position
u = (1 - t)**2 * (p1[0] - p0[0]) + 2*t * (1 - t) * (p2[0] - p1[0]) + t**2 * (p3[0] - p2[0])
v = (1 - t)**2 * (p1[1] - p0[1]) + 2*t * (1 - t) * (p2[1] - p1[1]) + t**2 * (p3[1] - p2[1])
# the rotation of the vector in relation to the x-axis
r = math.degrees(math.atan2(v, u))
args.append(Transform(Text(what[i], **kwargs), xpos=x, ypos=y, rotate=r))
# move one step along the curve
t += td
self.child = Fixed(*args)
return
def render(self, width, height, st, at):
cr = renpy.render(self.child, width, height, st, at)
self.width, self.height = cr.get_size()
rv = renpy.Render(self.width, self.height)
rv.blit(cr, (0, 0))
return rv
transform atl_blink:
alpha 0.0
linear 1.0 alpha 1.0
linear 1.0 alpha 0.0
repeat
image ct1 = CurvedText("Hello, I'm a curved text!", (100, 100), (400, 0), (500, 400), (800, 300))
image ct2 = CurvedText("I will always be written from start to end.", (100, 100), (400, 0), (500, 400), (800, 300), size=16, color="#00f")
image ct3 = CurvedText("I'm big, bold and I blink.", (200, 300), (600, 400), (400, 200), (900, 500), color="#0f0", bold=True, size=24)
label start:
show ct1
show ct2:
yoffset 100
show ct3 at atl_blink
"You should see curved texts."
- m_from_space
- Eileen-Class Veteran
- Posts: 1011
- Joined: Sun Feb 21, 2021 3:36 am
- Contact:
Re: Is it possible to make text align with a curve rather than a straight line on a screen? [Solved]
So since this was so much fun, I read more about bezier curves and found out that there is this famous "De Casteljau's algorithm" which can be used for any type of curve (two, three, four and more points...). My previous example was only working for 4 points curves. But here you go, put whatever control points you have in there and have fun with it. It should be way more efficient too now.
/Update1: Added subpixel placement and the option to define where to start and end writing on the curve.
/Update2: The last character was not placed on the end of the curve, but one step before. This is fixed now.
/Update3: Only fix the last character's rotation if t_end == 1.0, not in other cases.
/Update4: Skip whitespaces, we neither need the calculation, nor a text object for them
Picture of the following example:
Arguments:
Working example code:
/Update1: Added subpixel placement and the option to define where to start and end writing on the curve.
/Update2: The last character was not placed on the end of the curve, but one step before. This is fixed now.
/Update3: Only fix the last character's rotation if t_end == 1.0, not in other cases.
/Update4: Skip whitespaces, we neither need the calculation, nor a text object for them
Picture of the following example:
Arguments:
Code: Select all
what: The text you want to curve
points: a list or tuple of points that define the curve (points can be a list or a tuple)
rot: whether you want the letters to rotate along the curve (default True)
t_start: where to start writing on the curve (default 0.0)
t_end: where to end writing on the curve (default 1.0)
optional: you can pass keyword arguments to the text object like "font", "color", "size" and so on.
Code: Select all
init python:
import math
class CurvedText(renpy.Displayable):
def __init__(self, what, points, rot=True, t_start=0.0, t_end=1.0, **kwargs):
'''
what: the text we want to curve
points: a list of n > 2 points that define the curve, e.g. [(0, 0), (150, 150), (300, 0)]
rot: whether or not to rotate the letters along the curve
t_start: where to start on the curve (0.0 <= t_start < 1.0)
t_end: where to end on the curve (0.0 < t_end <= 1.0)
optional: pass keyword arguments for the text like "style", "font", "size", "color" etc.
'''
super(CurvedText, self).__init__(**kwargs)
# a list of arguments we will use for the final composite image
args = []
l = len(what)
n = len(points)
# current position along the curve (0.0 = start, 1.0 = end)
t = t_start
# equal step distance depending on the text length
td = (t_end - t_start) / max(1, l - 1)
# loop through all characters of the text
for i in range(l):
# skip whitespaces
if not what[i].strip():
t += td
continue
# use "De Casteljau's algorithm" to find the point (x, y) on the curve at value t
q = [list(p) for p in points]
t0 = 1 - t
for j in range(1, n):
for k in range(n - j):
q[k][0] = q[k][0] * t0 + q[k+1][0] * t
q[k][1] = q[k][1] * t0 + q[k+1][1] * t
# the point on the curve was saved in q[0]
x = int(q[0][0])
y = int(q[0][1])
# if we want to rotate, we need the vector of the tangent through this point (x, y)
if rot:
# the last character's tangent if t_end == 1.0 is not found via the algorithm
# but we know that it equals the line between the last two points
if i == l - 1 and t_end == 1.0:
u = points[-1][0] - points[-2][0]
v = points[-1][1] - points[-2][1]
# in all other cases the algorithm's nature already got it
else:
u = q[1][0] - q[0][0]
v = q[1][1] - q[0][1]
# now just calculate the angle between the vector and the x-axis
r = math.degrees(math.atan2(v, u))
args.append(Transform(Text(what[i], **kwargs), xcenter=x, ycenter=y, rotate=r, subpixel=True))
else:
args.append(Transform(Text(what[i], **kwargs), xcenter=x, ycenter=y, subpixel=True))
# move one step along the curve
t += td
self.child = Fixed(*args)
return
def render(self, width, height, st, at):
cr = renpy.render(self.child, width, height, st, at)
self.width, self.height = cr.get_size()
rv = renpy.Render(self.width, self.height)
rv.blit(cr, (0, 0))
return rv
define curve_3_points = [(50, 50), (250, 150), (450, 50)]
define curve_4_points = [(100, 200), (400, 100), (500, 500), (800, 400)]
define curve_5_points = [(200, 350), (400, 200), (600, 500), (1000, 450), (800, 100)]
image ct1 = CurvedText("Hello, I'm a 3-point curved text!", curve_3_points)
image ct2 = CurvedText("I will be written from start to end by default.", curve_4_points, size=16, color="#f0f")
image ct3 = CurvedText("I am on the same curve, but start at 25%.", curve_4_points, t_start=0.25, size=16, color="#f0f")
image ct4 = CurvedText("Wow, 5 point curves rock...", curve_5_points, color="#0ff")
image ct5 = CurvedText("This one is without letter rotation.", curve_4_points, rot=False, color="#ff0")
label start:
show ct1
show ct2
show ct3:
yoffset 50
show ct4
show ct5:
yoffset 200
"Have fun playing around!"
-
- Regular
- Posts: 37
- Joined: Sat Nov 23, 2019 7:03 pm
- Projects: Kingmaker
- Contact:
Re: Is it possible to make text align with a curve rather than a straight line on a screen? [Solved]
Woah! You really went above and beyond. Thanks a lot! Personally I would never understand the world of high finances when it comes to coding so I wouldn't have been capable of any of that hahahaha. Once again thank you. You are a hero without a cape.m_from_space wrote: ↑Sun Oct 08, 2023 4:02 pmIn principle you should be able to write yourself a function that does just that:
1. Split the text into single characters
2. Follow a bezier curve using a fixed distance (like if you would plot it).
3. Place the next character at this specific location and then rotate it accordingly.
I have the feeling that it wouldn't be that hard in Python using Renpy, but maybe I am wrong about it.
Re: Is it possible to make text align with a curve rather than a straight line on a screen? [Solved]
Wow!m_from_space wrote: ↑Mon Oct 09, 2023 4:16 pm So since this was so much fun, I read more about bezier curves and found out that there is this famous "De Casteljau's algorithm" which can be used for any type of curve (two, three, four and more points...). My previous example was only working for 4 points curves. But here you go, put whatever control points you have in there and have fun with it. It should be way more efficient too now.
/Update1: Added subpixel placement and the option to define where to start and end writing on the curve.
/Update2: The last character was not placed on the end of the curve, but one step before. This is fixed now.
/Update3: Only fix the last character's rotation if t_end == 1.0, not in other cases.
/Update4: Skip whitespaces, we neither need the calculation, nor a text object for them
Picture of the following example:
example.png
Arguments:Working example code:Code: Select all
what: The text you want to curve points: a list or tuple of points that define the curve (points can be a list or a tuple) rot: whether you want the letters to rotate along the curve (default True) t_start: where to start writing on the curve (default 0.0) t_end: where to end writing on the curve (default 1.0) optional: you can pass keyword arguments to the text object like "font", "color", "size" and so on.
Code: Select all
init python: import math class CurvedText(renpy.Displayable): def __init__(self, what, points, rot=True, t_start=0.0, t_end=1.0, **kwargs): ''' what: the text we want to curve points: a list of n > 2 points that define the curve, e.g. [(0, 0), (150, 150), (300, 0)] rot: whether or not to rotate the letters along the curve t_start: where to start on the curve (0.0 <= t_start < 1.0) t_end: where to end on the curve (0.0 < t_end <= 1.0) optional: pass keyword arguments for the text like "style", "font", "size", "color" etc. ''' super(CurvedText, self).__init__(**kwargs) # a list of arguments we will use for the final composite image args = [] l = len(what) n = len(points) # current position along the curve (0.0 = start, 1.0 = end) t = t_start # equal step distance depending on the text length td = (t_end - t_start) / max(1, l - 1) # loop through all characters of the text for i in range(l): # skip whitespaces if not what[i].strip(): t += td continue # use "De Casteljau's algorithm" to find the point (x, y) on the curve at value t q = [list(p) for p in points] t0 = 1 - t for j in range(1, n): for k in range(n - j): q[k][0] = q[k][0] * t0 + q[k+1][0] * t q[k][1] = q[k][1] * t0 + q[k+1][1] * t # the point on the curve was saved in q[0] x = int(q[0][0]) y = int(q[0][1]) # if we want to rotate, we need the vector of the tangent through this point (x, y) if rot: # the last character's tangent if t_end == 1.0 is not found via the algorithm # but we know that it equals the line between the last two points if i == l - 1 and t_end == 1.0: u = points[-1][0] - points[-2][0] v = points[-1][1] - points[-2][1] # in all other cases the algorithm's nature already got it else: u = q[1][0] - q[0][0] v = q[1][1] - q[0][1] # now just calculate the angle between the vector and the x-axis r = math.degrees(math.atan2(v, u)) args.append(Transform(Text(what[i], **kwargs), xcenter=x, ycenter=y, rotate=r, subpixel=True)) else: args.append(Transform(Text(what[i], **kwargs), xcenter=x, ycenter=y, subpixel=True)) # move one step along the curve t += td self.child = Fixed(*args) return def render(self, width, height, st, at): cr = renpy.render(self.child, width, height, st, at) self.width, self.height = cr.get_size() rv = renpy.Render(self.width, self.height) rv.blit(cr, (0, 0)) return rv define curve_3_points = [(50, 50), (250, 150), (450, 50)] define curve_4_points = [(100, 200), (400, 100), (500, 500), (800, 400)] define curve_5_points = [(200, 350), (400, 200), (600, 500), (1000, 450), (800, 100)] image ct1 = CurvedText("Hello, I'm a 3-point curved text!", curve_3_points) image ct2 = CurvedText("I will be written from start to end by default.", curve_4_points, size=16, color="#f0f") image ct3 = CurvedText("I am on the same curve, but start at 25%.", curve_4_points, t_start=0.25, size=16, color="#f0f") image ct4 = CurvedText("Wow, 5 point curves rock...", curve_5_points, color="#0ff") image ct5 = CurvedText("This one is without letter rotation.", curve_4_points, rot=False, color="#ff0") label start: show ct1 show ct2 show ct3: yoffset 50 show ct4 show ct5: yoffset 200 "Have fun playing around!"
Renpy textbook (in Russian). https://disk.yandex.ru/i/httNEajU7iFWHA (all information is out of date) Update 22.06.18
Sawa - a game of the Drow Nation
Honest Critique
Poses in visual novels, or how to hold a character properly in the frame
Help save articles to the webarchive. [/color]
Sawa - a game of the Drow Nation
Honest Critique
Poses in visual novels, or how to hold a character properly in the frame
Help save articles to the webarchive. [/color]
- m_from_space
- Eileen-Class Veteran
- Posts: 1011
- Joined: Sun Feb 21, 2021 3:36 am
- Contact:
Re: Is it possible to make text align with a curve rather than a straight line on a screen? [Solved]
I posted this code here for better visibility by the way: viewtopic.php?t=67350
And I also created another CDD named "CurvedMultiText" that draws multiple lines of text along a curve. It's also inside the post I linked (including an image so you can check it out beforehand).
Who is online
Users browsing this forum: Ahrefs [Bot]