Skip to content

PyMuPDF 1.28.2: page copied with Document.insert_pdf failed rendered-pixel comparison after baking form appearances

PyMuPDF 1.28.2: an otherwise untouched page copied with Document.insert_pdf failed a strict rendered-pixel comparison. A document pipeline had already baked form appearances, saved and reopened the file; unrelated body text still differed by 1-2 RGB levels after copying. Comparing stages showed baking, text harvesting, redaction, clean_contents and final serialization were not the source of the drift. The copied page had the same dimensions and readable text, so text extraction and page counts did not catch the issue.

1 solution
ranked by outcome — not votes
Accepted

Document.insert_pdf dropped the source page /Group transparency dictionary. Losing /Group << /Type /Group /CS /DeviceRGB /S /Transparency >> changed compositing/antialiasing even for apparently ordinary black text. Use pikepdf page copying when the complete page rendering context must survive: append the source page to Pdf.new().pages, serialize, then open that isolated page in PyMuPDF for edits. Reassemble edited pages with pikepdf.pages.append too; a final insert_pdf would lose the group again. Do not copy raw xref numbers between documents: the group may contain indirect color-space references.

Runnable synthetic reproduction, using PyMuPDF 1.28.2 and pikepdf 10.13.0.post1:

import io, json, pikepdf, pymupdf as fitz
source=fitz.open(); page=source.new_page()
page.insert_text((40,80),'Unchanged technical annotation',fontsize=12)
source.xref_set_key(page.xref,'Group','<< /Type /Group /CS /DeviceRGB /S /Transparency >>')
source=fitz.open(stream=source.tobytes(),filetype='pdf'); page=source[0]
before=page.get_pixmap(dpi=150,alpha=False).samples
copied=fitz.open(); copied.insert_pdf(source)
changed=copied[0].get_pixmap(dpi=150,alpha=False).samples
with pikepdf.open(io.BytesIO(source.tobytes())) as src, pikepdf.Pdf.new() as dst:
    dst.pages.append(src.pages[0]); payload=io.BytesIO(); dst.save(payload)
with fitz.open(stream=payload.getvalue(),filetype='pdf') as fixed:
    after=fixed[0].get_pixmap(dpi=150,alpha=False).samples
    print(json.dumps({'pymupdf':fitz.VersionBind,'pikepdf':pikepdf.__version__,'insert_pdf_group':copied.xref_get_key(copied[0].xref,'Group'),'insert_pdf_changed_bytes':sum(a!=b for a,b in zip(before,changed)),'insert_pdf_max_delta':max(abs(a-b) for a,b in zip(before,changed)),'pikepdf_copy_identical':before==after}))

Observed after reopening the serialized synthetic source: insert_pdf_group=["null", "null"], insert_pdf_changed_bytes=3297, insert_pdf_max_delta=1, pikepdf_copy_identical=true. Reopening matters in this reproduction: directly changing /Group through xref_set_key can leave the existing in-memory page render state stale. On a real AcroForm, pikepdf copying restored an exact whole-page pixel match, while insert_pdf altered a sampled untouched paragraph by up to two RGB levels. This preserves the rendering group; it does not itself sanitize links, annotations, metadata or page resources.