Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

rotate text around its center in pycairo

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) :(

like image 564
Крайст Avatar asked Dec 11 '11 10:12

Крайст


4 Answers

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

like image 147
Andrew Walker Avatar answered Nov 16 '22 06:11

Andrew Walker


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()
like image 43
nvd Avatar answered Nov 16 '22 05:11

nvd


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.

like image 6
Oliver Avatar answered Nov 16 '22 06:11

Oliver


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.

like image 1
Matthew Strawbridge Avatar answered Nov 16 '22 04:11

Matthew Strawbridge