How to copy annotations so they inherit the destination page coordinate system? (Bluebeam markups jump on color/status change)

Problem:
I’m copying Bluebeam markups (annotations) from one PDF to another using PyMuPDF. I repage/normalize the destination first so page size/origin matches the source. The annotations look correct after copy, but when I change color/status in Bluebeam, some annotations (especially FreeText callouts) jump to a different location. The jump seems to snap to the original coordinate system.

What I’m doing (high‑level):

  1. Repage destination pages to match the original (size/origin/rotation).

  2. Copy annotations from source → destination.

  3. Output looks correct.

Issue:
After copying, changing annotation color/status forces Bluebeam to recompute its appearance, and the annotation moves to old coordinates.

Questions:

  • Is there a PyMuPDF‑supported way to copy annotations so they inherit the destination page’s coordinate system?

  • Are there specific annotation fields that must be updated/reset to “bake in” the new position?

  • For FreeText/Callout annotations, what fields are authoritative for position when the appearance is regenerated? (/Rect, /AP, /CL, /RD, etc.)

  • Should /AP be removed to force a fresh appearance, or is there a better approach?

revised_with_markups.pdf (2.0 MB)

A202.pdf (1.8 MB)

repro_copy_annots.py

Script Example (using Codex)

pip install pymupdf

import fitz

SOURCE = “source.pdf”   # has markups (FreeText/callout)
DEST = “dest.pdf”       # clean PDF with same page count
OUT = “out_with_markups.pdf”

def copy_annots(src_doc, dst_doc):
for i in range(min(len(src_doc), len(dst_doc))):
src_page = src_doc[i]
dst_page = dst_doc[i]

    annot = src_page.first_annot
    while annot:
        # copy annot by xref
        dst_page.add_annot_from(annot)
        annot = annot.next

def main():
src = fitz.open(SOURCE)
dst = fitz.open(DEST)

copy_annots(src, dst)
dst.save(OUT)

src.close()
dst.close()

if name == “main”:
main()

Hi @donovan - welcome to the forum!

I’m not sure if calling annot.update() would help or not?

In any case I couldn’t find any Page.add_annot_from(annot) method in the PyMuPDF API so your code failed for me.

I do this kind of thing when I recreate the annots onto the destination document:

import pymupdf

SOURCE = "source.pdf"   # has markups (FreeText/callout)
DEST = "dest.pdf"       # clean PDF with same page count
OUT = "out_with_markups.pdf"

def copy_annots(src_doc, dst_doc):
    for i in range(min(len(src_doc), len(dst_doc))):
        src_page = src_doc[i]
        dst_page = dst_doc[i]

        annot = src_page.first_annot
        while annot:
            print("found annot!")

            text = ""

            for index, (key, value) in enumerate(annot.info.items()):
                print(f"{index}: {key} = {value}, type:{annot.type}")
                if key == "content":
                    text = value

            # Copy annotation to destination page
            if annot.type[1] == "Text":
                dst_page.add_text_annot(annot.rect.top_left, text)
            elif annot.type[1] == "FreeText":
                dst_page.add_freetext_annot(annot.rect, text)

            annot = annot.next


src = pymupdf.open(SOURCE)
dst = pymupdf.open(DEST)

copy_annots(src, dst)
dst.save(OUT)

src.close()
dst.close()


Hope this helps, please let me know!