rotate text around its center in pycairo
Asked Answered
T

4

12

community.

I know that there are many answers here, manuals, tutorials and references over the internets and amny more about this question. Also I know that knowledge of linear algebra is required. But when I think about time to figuring out all the theory and solving exercises in practice - my head is blowing off and I can't do the simplest things :(

Please, if you know a little fast solution how to make rotation of text over its center before rendering it - tell me, pleeease.

For now I have:

#...
cr.move_to(*text_center)
myX, myY = text_center[0] - (width / 2), text_center[1] + (height / 2)

cr.save()
cr.translate(myX, myY)
cr.rotate(radians(text_angle))
cr.show_text(letter)
cr.restore()
#...

But my letter isn't rotating around itself. It's just like falling down to the right side :( I know that my code isn't right. Maybe I miss transformation but I don't know how to make it right.

UPDATE: Unfortunately, text are not affected by translations, so

cr.translate(10000, 10000)
cr.rotate(radians(15))
cr.show_text("hello")

will be exactly the same as

cr.rotate(radians(15))
cr.show_text("hello")

And I don't know how to make text rotation over its center without making new surface or something (like new layer in graphic processor) :(

Ternate answered 11/12, 2011 at 10:20 Comment(0)
C
15

At least on the version of cairo available on my machine (1.8.8), the following approach works for me:

def text(ctx, string, pos, theta = 0.0, face = 'Georgia', font_size = 18):
    ctx.save()

    # build up an appropriate font
    ctx.select_font_face(face , cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
    ctx.set_font_size(font_size)
    fascent, fdescent, fheight, fxadvance, fyadvance = ctx.font_extents()
    x_off, y_off, tw, th = ctx.text_extents(string)[:4]
    nx = -tw/2.0
    ny = fheight/2

    ctx.translate(pos[0], pos[1])
    ctx.rotate(theta)
    ctx.translate(nx, ny)
    ctx.move_to(0,0)
    ctx.show_text(string)
    ctx.restore()

Which can be used in the following way:

width = 500
height = 500
surface = cairo.ImageSurface(cairo.FORMAT_RGB24, width, height)
ctx = cairo.Context(surface)
ctx.set_source_rgb(1,1,1)
rect(ctx, (0,0), (width, height), stroke=False)
ctx.set_source_rgb(0,0,0)
for i in xrange(5):
    for j in xrange(5):
        x = 100 * i + 20
        y = 100 * j + 20
        theta = math.pi*0.25*(5*i+j)
        text(ctx, 'hello world', (x, y), theta, font_size=15)
surface.write_to_png('text-demo.png')

text-demo.png

Chaudfroid answered 26/6, 2013 at 13:29 Comment(5)
Genius and perfect! Oh, since I asked that question I freezed my project and opened math books till now. I know that your solution is what I wanted and I'll simply paste it my code if you don't mind and will write your name in comments. But math is great and interesting, so I'll continue learning something new. Can you tell me where to find your solution in math books: how it names and what is it? Thank you for your help.Ternate
@Schollii was correct, but it turns out that there are some intricacies of the Cairo API that make this a bit interesting. The key point is that rotations always happen about the origin. This means you need to translate the origin to where you want to draw the text. Then you can do your rotation. Finally, to make sure that the rendered text passes through your point, you need to do one final translation (in the rotated co-ordinate frame). The move_to command ensures the text will then be rendered from the origin.Chaudfroid
This kind of mathematics can probably be best described using the term Transformation Matrices, which are really just a way to express co-ordinate transforms. In a very broad sense this is a linear algebra problem. This task is particularly important in mechanics (which in turn is important for software developers involved in physics, robotics and game development).Chaudfroid
Sure, Schollii was right but trigonometry functions in python lowers precision a lot. On the first sight your method much precise and maybe faster. I will use this code in web app and it's important for dynamic image rendering and floating point operations. Thank you one more time. Linear algebra is on my way :)Ternate
Doesn't seem to work for me. My special case maybe that I'm using single characters only. Any ideas ?Ebb
L
6

OK so cairo allows for text move_to and rotate. This means that what you want is to figure out (x,y) for move_to (T), such that when you rotate (R), the center point of your text is at your desired location, c=(cx,cy):

enter image description here

So you have to solve the equation Mv = c, where v is the text center relative to the text origin:

M = T*R

T = (1 0 x)
    (0 1 y)
    (0 0 1)

R =  (cos r    -sin r   0)
     (sin r     cos r   0)
     (0            0    1)

v = (w/2, h', 1)

c = (cx, cy, 1)

h' = h/2 - (h - y_bearing)

Sanity checks:

  • when r is 0 (no rotation), you get x=cx-w/2, y=cy-h', which you know is the correct answer
  • when r=-90 (text sideways, with "up" towards the right), you get what you expect, ie x = cx - h' and y = cy + w/2

For python code, you will have to rewrite the above equation so you end up with A*t=b, where t=(x,y), and you will compute t = inv(A)*b. Then, you will simply do

cr.move_to(x, y)
cr.rotate(r)
cr.show_text(yourtext)

Note that the coordinate system in cairo has +y going down so there will be a couple signs to fix, and maybe y_bearing is not correct, but you get the idea.

Lalla answered 12/12, 2011 at 4:35 Comment(6)
"place text at text_center" - how can I make this? I only have separated function cr.move_to(x, y) and separated cr.show_text(text). When must I make move_to() - before translation and rotation or after and before placing text?Ternate
my text_center means coordinates of origin's x, y to place text exactly to overlap preffered point by its (text) center (w/2, h/2).Ternate
these codes isn't and will not working because rotation happens always around current_point. If I write: cr.move_to(a, b); cr.rotate(rad); cr.show_text(); - it'll be rotated only around (a, b) and translation will not affect rotation. But I know that there is some formulas like [here][1] to calculate new coords where to move to. [1]: codeproject.com/KB/GDI/textrotation.aspx?display=PrintAllTernate
thank you very much for your expanded answer. I can't understand a few things in that but I'll try to figure it out cause it's very important to me to understand all of this in my own head =) Thank you so much!Ternate
@Крайст it would be nice if you could let everyone know if this is the answer (mark as answer), or at least useful (vote up, with comment about what you find).Lalla
please, can you explain your answer for newbie? I can only feel that it's the right answer or very helpful but my knowledge isn't enough to figure it out :(Ternate
V
2

Class function based on above input with multi-line text support.

def text(self, text, x, y, rotation=0, fontName="Arial", fontSize=10, verticalPadding=0):

    rotation = rotation * math.pi / 180

    self.ctx.select_font_face(fontName, cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
    self.ctx.set_font_size(fontSize)

    fascent, fdescent, fheight, fxadvance, fyadvance = self.ctx.font_extents()

    self.ctx.save()
    self.ctx.translate(x, y)
    self.ctx.rotate(rotation)

    lines = text.split("\n")

    for i in xrange(len(lines)):
        line = lines[i]
        xoff, yoff, textWidth, textHeight = self.ctx.text_extents(line)[:4]

        offx = -textWidth / 2.0
        offy = (fheight / 2.0) + (fheight + verticalPadding) * i

        self.ctx.move_to(offx, offy)
        self.ctx.show_text(line)

    self.ctx.restore()
Volsci answered 21/3, 2017 at 18:18 Comment(3)
Has anything changed in the past 5 years? This was my pet-project and I abandoned it long ago, starting to study math anew. I have not been back to this project since. However, I'm curious to try everything again.Ternate
12 changed to 17.Volsci
i meant cairo/pycairoTernate
D
1

Should

myX, myY = text_center[0] + (height / 2), text_center[1] - (width / 2)

be

myX, myY = text_center[0] - (width / 2), text_center[1] + (height / 2)

?

That might explain why it's falling down to the right side.

Dragonhead answered 11/12, 2011 at 11:6 Comment(2)
oh, please, it's my fault. I've wrote this code from my head on my phone. on real code it's quiet like in your example. but when I go home I'll check. you think that translating - is the only transformation that I need?Ternate
this isn't error. result is the same. maybe something complex will fix that situation? maybe new transformation matrix or different code order?Ternate

© 2022 - 2024 — McMap. All rights reserved.