Search code examples
pythonpdfrotationpypdf

How to rotate+scale PDF pages around the center with pypdf?


I would like to rotate PDF pages around the center (other than just multiples of 90°) in a PDF document and optionally scale them to fit into the original page.

Here on StackOverflow, I found a few similar questions, however, mostly about the outdated PyPDF2. And in the latest pypdf documentation, I could not find (or overlooked) a recipe to rotate pages around the center, e.g. for slightly tilted scanned documents, which require rotation of a few degrees.

I know that there is the Transformation Class, but the standard rotation is around the lower left corner and documentation is not explaining in detail what the matrix elements actually are.

How to rotate a PDF page around the center and optionally scale it that it fits into the original page?


Solution

  • It took me some time to figure out how to rotate a page around the center and scale it to fit the original page. Although, it just requires a matrix with the correct elements at the right place and some trigonometric functions, it was not too obvious for me. Hence, I post the script I ended up with for my own memories and maybe it is helpful to others not having to re-invent the wheel.

    After all, you can also achieve it somehow via a combination of rotate(), translate(), and scale().

    If anybody has ideas to simplify or improve the following script, please let me know.

    Script:

    ### rotate around center (and optional scale) pdf pages
    from pypdf import PdfReader, PdfWriter, Transformation
    from math import sin, cos, atan, sqrt, radians, pi
    
    pdf_input  = PdfReader("Test.pdf")
    pdf_output = PdfWriter()
    
    rotation_angle = 7.3    # in degrees
    shrink_page    = True
    
    for page in pdf_input.pages:
        x0 = (page.mediabox.right - page.mediabox.left)/2
        y0 = (page.mediabox.top   - page.mediabox.bottom)/2
        a  = radians(rotation_angle)
        a0 = atan(max(x0,y0)/min(x0,y0))
        s0 = min(x0,y0)/cos(a0 - abs((a-pi/2)%pi - pi/2))/sqrt(x0**2 + y0**2) if shrink_page else 1
        rotate_center = Transformation((
            s0*cos(a), s0*sin(a), -s0*sin(a), s0*cos(a), 
            s0*(-x0*cos(a)+y0*sin(a))+x0, s0*(-x0*sin(a)-y0*cos(a))+y0))
        pdf_output.add_page(page).add_transformation(rotate_center)
    
    pdf_output.write("Test_out.pdf")
    ### end of script
    

    Alternatively, as @MartinThoma commented, instead of using the transformation matrix (or tuple) use translate(), rotate() and again translate() and optionally scale(), but in the right order with the proper numbers. This means, replace the line rotate_center = ... with the following:

    rotate_center = Transformation().translate(tx=-x0,ty=-y0).scale(sx=s0,sy=s0).rotate(rotation_angle).translate(tx=x0,ty=y0)
    

    Result: (screenshot from Test_out.pdf)

    enter image description here