pythonreportlab

how can I rotate at the center of the reportlab.graphics.shapes.Drawing


it seems I cannot rotate Drawing at the centre.

I have a svg Drawing: drawing.getBounds()=(14.173228346456694, 14.173228346456654, 581.1023622047245, 581.1023622047245), drawing.width=595.2755905511812, drawing.height=595.2755905511812, drawing.transform=(1, 0, 0, 1, 0, 0), drawing.hAlign='LEFT', drawing.vAlign='BOTTOM'

I rotate and put several drawing in row*col array:

            r = i // col
            c_idx = i % col
            x = c_idx * cell_w + padding
            y = page_height - ((r + 1) * cell_h) + padding

            drawing = svg2rlg(svg_path)
            if drawing is None:
                 raise ValueError("Could not convert SVG to drawing object.")
            # Scale to fit within cell
            print(f"{drawing.getBounds()=}, {drawing.width=}, {drawing.height=}, {drawing.transform=}, {drawing.hAlign=}, {drawing.vAlign=}")
            if rotate_x_y[0] > 0:
                drawing.rotate(rotate_x_y[0], rotate_x_y[1]*drawing.width, rotate_x_y[2]*drawing.height)

            max_w = cell_w - 2 * padding
            max_h = cell_h - 2 * padding
            scale_x = max_w / drawing.width
            scale_y = max_h / drawing.height
            scale = min(scale_x, scale_y)
            drawing.width *= scale
            drawing.height *= scale
            drawing.scale(scale, scale)
            # Center in cell
            dx = x + (max_w - drawing.width) / 2
            dy = y + (max_h - drawing.height) / 2
            renderPDF.draw(drawing, c, dx, dy)

but I cannot get the drawing.rotate() to work I tried call drawing.rotate(90), it seems the same as drawing.rotate(90, 0, 0) the rotation centre also not at w/2, h/2 I always end up very strange results not matter what I tried. the original pdf looks like:

orig

rotated results:

90 at 0,0 90 at 0,h/2 90 at w/2, 0 90 at w/2, h/2


Solution

  • TLTR

    this is a fundamental bug of the transform module in the project probably since the very beginning I verified it at least start from 2010. I think this project probably not worth using, it will waste you lots of time. https://hg.reportlab.com/hg-public/reportlab/file/a57692215f7c/src/reportlab/graphics/shapes.py

    detail

    they use column major mmul (mmul(A, B) means v_new=A*B*v, so apply transform B then apply transform A), but throughout the entire project, they treat it as row major mmul, did self.transform=mmul(self.transform, new_transform).

    simple verification

    # T = (1,0, 0,1, -100, -100) (Translate by -100, -100)
    # R = (0,-1,1,0, 0, 0)      (Rotate 90 degrees CCW)
    
    # Test 1: mmult(T, R) (Apply R, then T)
    print(transform.mmult((1,0, 0,1, -100, -100), (0,-1,1,0, 0, 0))) 
    # Result: (0, -1, 1, 0, -100, -100) 
    # The translation (-100, -100) was NOT rotated. This means R was applied first.
    
    # Test 2: mmult(R, T) (Apply T, then R)
    print(transform.mmult((0,-1,1,0, 0, 0), (1,0, 0,1, -100, -100))) 
    # Result: (0, -1, 1, 0, -100, 100)
    # The translation (-100, -100) was rotated to (-100, 100). This means T was applied first.
    

    verify by calling the rotate and translate function

    print(f"Origin: {drawing.transform=}")
    drawing.translate(
        -drawing.width/2, -drawing.height/2
    )
    print(f"translate: {drawing.transform=}")
    drawing.rotate(
        90
    )
    print(f"Rotate: {drawing.transform=}")
    

    output show it is rotate then translate:

    drawing.getBounds()=(6.803149606299213, 6.803149606299229, 246.6141732283465, 246.61417322834652), drawing.width=253.41732283464572, drawing.height=253.41732283464572, drawing.transform=(1, 0, 0, 1, 0, 0), drawing.hAlign='LEFT', drawing.vAlign='BOTTOM'
    Origin: drawing.transform=(1, 0, 0, 1, 0, 0)
    translate: drawing.transform=(1, 0, 0, 1, -126.70866141732286, -126.70866141732286)
    Rotate: drawing.transform=(6.123233995736766e-17, 1.0, -1.0, 6.123233995736766e-17, -126.70866141732286, -126.70866141732286)
    
    
    print(f"Origin: {drawing.transform=}")
    drawing.rotate(
        90
    )
    print(f"Rotate: {drawing.transform=}")
    drawing.translate(
        -drawing.width/2, -drawing.height/2
    )
    print(f"translate: {drawing.transform=}")
    

    output show it is translate then rotate

    drawing.getBounds()=(6.803149606299213, 6.803149606299229, 246.6141732283465, 246.61417322834652), drawing.width=253.41732283464572, drawing.height=253.41732283464572, drawing.transform=(1, 0, 0, 1, 0, 0), drawing.hAlign='LEFT', drawing.vAlign='BOTTOM'
    Origin: drawing.transform=(1, 0, 0, 1, 0, 0)
    Rotate: drawing.transform=(6.123233995736766e-17, 1.0, -1.0, 6.123233995736766e-17, 0, 0)
    translate: drawing.transform=(6.123233995736766e-17, 1.0, -1.0, 6.123233995736766e-17, 126.70866141732284, -126.70866141732287)