Skip to content

merge_page() clips content when merging transformed pages #3492

@gnuger

Description

@gnuger

Bug Report: merge_page() clips content when merging transformed pages

Environment

  • Library: pypdf 6.1.1 (also affects PyPDF2)
  • Python: 3.9
  • OS: macOS (ARM64)

Description

When using merge_page() to create a 2-up booklet layout by placing two transformed pages side-by-side on an A4 landscape page, the right-hand page gets clipped at the right edge even though mathematically the page should fit within the bounds.

Expected Behavior

Both left and right pages should be fully visible within the output page boundaries without any clipping.

Actual Behavior

The right-hand page is clipped/cut off on the right edge, despite:

  • The calculated position ending exactly at the page boundary (841.9 pts)
  • Both pages being scaled and positioned to fit within their allocated cells
  • Using proper transformation chains (translate to origin → scale → translate to position)

Minimal Reproducible Example

import io
from pypdf import PdfReader, PdfWriter, Transformation
from pypdf._page import PageObject
from reportlab.lib.pagesizes import A4

def clone_page(page):
    w = PdfWriter()
    w.add_page(page)
    buf = io.BytesIO()
    w.write(buf)
    buf.seek(0)
    return PdfReader(buf).pages[0]

# Read input PDF
reader = PdfReader("input.pdf")
writer = PdfWriter()

# A4 landscape dimensions
a4_w, a4_h = A4[1], A4[0]  # 841.9 x 595.3 pts
gutter = 28.35  # 10mm spacing
cell_w = (a4_w - gutter) / 2.0  # 406.8 pts per cell

# Create output page
output = PageObject.create_blank_page(width=a4_w, height=a4_h)

# Left page - works fine
left = clone_page(reader.pages[0])
lb = left.mediabox
lx0, ly0 = float(lb.lower_left[0]), float(lb.lower_left[1])
lw, lh = float(lb.width), float(lb.height)
scale_l = min(cell_w / lw, a4_h / lh)
lx = (cell_w - lw * scale_l) / 2.0
ly = (a4_h - lh * scale_l) / 2.0

t_left = (
    Transformation()
    .translate(tx=-lx0, ty=-ly0)
    .scale(sx=scale_l, sy=scale_l)
    .translate(tx=lx, ty=ly)
)
left.add_transformation(t_left)
output.merge_page(left)

# Right page - gets clipped on right edge
right = clone_page(reader.pages[1])
rb = right.mediabox
rx0, ry0 = float(rb.lower_left[0]), float(rb.lower_left[1])
rw, rh = float(rb.width), float(rb.height)
scale_r = min(cell_w / rw, a4_h / rh)
rx = cell_w + gutter + (cell_w - rw * scale_r) / 2.0
ry = (a4_h - rh * scale_r) / 2.0

t_right = (
    Transformation()
    .translate(tx=-rx0, ty=-ry0)
    .scale(sx=scale_r, sy=scale_r)
    .translate(tx=rx, ty=ry)
)
right.add_transformation(t_right)
output.merge_page(right)  # This causes clipping

writer.add_page(output)
with open("output.pdf", "wb") as f:
    writer.write(f)

Debug Information

Right page position: x=435.1
Right page width (scaled): 406.8
Right page ends at: 435.1 + 406.8 = 841.9 pts
Output page width: 841.9 pts

Mathematically, the right page should fit exactly, but visually it's clipped.

Workaround

Using PyMuPDF (fitz) with show_pdf_page() works correctly and doesn't have this clipping issue:

import fitz

src = fitz.open("input.pdf")
dst = fitz.open()
out_page = dst.new_page(width=a4_w, height=a4_h)

# Place pages using show_pdf_page
out_page.show_pdf_page(
    fitz.Rect(rx, ry, rx + rw_scaled, ry + rh_scaled),
    src,
    page_number
)

Additional Context

  • Tried reducing scale factor to 95%, 90%, 85% - clipping persists
  • Tried adding explicit cropbox to limit visible area - didn't help
  • Tried different transformation approaches - same issue
  • Left page never has clipping issues, only right page
  • This affects real-world use case: creating print-ready booklets from PDFs

Files

I can provide:

  • Sample input PDF
  • Test script to reproduce
  • Output PDFs showing the clipping

Question

Is this a known limitation of merge_page()? Should there be a different approach for placing multiple transformed pages on a single output page?


Links:

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions