pypdf icon indicating copy to clipboard operation
pypdf copied to clipboard

ENH: Add PDF annotation /IRT ("in reply to")

Open C-monC opened this issue 10 months ago • 2 comments

I am working on getting the "in reply to" working for annotations using pypdf but I think there is something that I am misunderstanding. Here's a basic script I thought should've worked.

from pypdf import PageObject, PdfWriter
from pypdf.generic import (
    DictionaryObject,
    NameObject,
    RectangleObject,
    ArrayObject,
    TextStringObject,
    NumberObject,
    FloatObject,
)

writer = PdfWriter()
writer.add_page(PageObject.create_blank_page(writer, 1000, 1000))


test_anot = DictionaryObject(
    {
        NameObject("/Type"): NameObject("/Annot"),
        NameObject("/Subtype"): NameObject("/Text"),
        NameObject("/Rect"): RectangleObject(
            [
                FloatObject(100.0),
                FloatObject(100.0),
                FloatObject(125.0),
                FloatObject(125.0),
            ]
        ),
        NameObject("/Contents"): TextStringObject("Testcomment"),
        NameObject("/P"): NumberObject(0),
        NameObject("/C"): ArrayObject(
            [FloatObject(0.97), FloatObject(0.98), FloatObject(0.31)]
        ),
        NameObject("/Name"): TextStringObject("Help"),
        NameObject("/T"): TextStringObject("Me"),
        NameObject("/RC"): TextStringObject("Testcomment"),
    }
)
added_annot = writer.add_annotation(page_number=0, annotation=test_anot)
test_anot2 = DictionaryObject(
    {
        NameObject("/Type"): NameObject("/Annot"),
        NameObject("/Subtype"): NameObject("/Text"),
        NameObject("/Contents"): TextStringObject("Reply"),
        NameObject("/P"): NumberObject(0),
        NameObject("/C"): ArrayObject(
            [FloatObject(0.97), FloatObject(0.98), FloatObject(0.31)]
        ),
        NameObject("/Name"): TextStringObject("Help"),
        NameObject("/T"): TextStringObject("Me"),
        NameObject("/IRT"): test_anot.indirect_reference,
        NameObject("/RT"): NameObject("/R"),
        NameObject("/RC"): TextStringObject("Reply"),
    }
)


writer.add_annotation(page_number=0, annotation=test_anot2)
with open("annotated-pdf.pdf", "wb") as fp:
    writer.write(fp)

Only the parent annotation is visible in the pdf. image

If I compare annotations and annotation replies in Adobe Acrobat to the script I can see the IRT gets populated correctly.

Snippet of adobe annotation with /IRT:

<</AP<</N 200 0 R/R 201 0 R>>/C[1.0 0.819611 0.0]/Contents(Comment 2)/CreationDate(D:20230803120428+02'00')/F 28/IRT 196 0 R/M

script annotation:

5 0 obj << /Type /Annot /Subtype /Text /Rect [ 100 100 125 125 ] /Contents (Testcomment) /P 4 0 R /C [ 0.97 0.98 0.31 ] /Name (Help) /T (Me) /RC (Testcomment)

endobj 6 0 obj << /Type /Annot /Subtype /Text /Contents (Reply) /P 4 0 R /C [ 0.97 0.98 0.31 ] /Name (Help) /T (Me) /IRT 5 0 R /RT /R /RC (Reply)

endobj

Could anyone provide guidance on getting this work. I'd be happy to work on adding it to pypdf if it does.

C-monC avatar Aug 05 '23 18:08 C-monC

Let's first check the specs:

Table 170 – Additional entries specific to markup annotations

IRT -- dictionary -- (Required if an RT entry is present, otherwise optional; PDF 1.5) A reference to the annotation that this annotation is “in reply to.” Both annotations shall be on the same page of the document. The relationship between the two annotations shall be specified by the RT entry. If this entry is present in an FDF file (see 12.7.7, “Forms Data Format”), its type shall not be a dictionary but a text string containing the contents of the NM entry of the annotation being replied to, to allow for a situation where the annotation being replied to is not in the same FDF file.

RT -- name (Default value: R) -- (Optional; meaningful only if IRT is present; PDF 1.6) A name specifying the relationship (the “reply type”) between this annotation and one specified by IRT. Valid values are:

  • R --- The annotation shall be considered a reply to the annotation specified by IRT. Conforming readers shall not display replies to an annotation individually but together in the form of threaded comments.
  • Group --- The annotation shall be grouped with the annotation specified by IRT; see the discussion following this Table

[...] Table 164 – Entries common to all annotation dictionaries

NM -- text string -- (Optional; PDF 1.4) The annotation name, a text string uniquely identifying it among all the annotations on its page.

Please note that we don't support FDF so far.

Assuming your PDF does not contain an FDF, I don't know why this doesn't work. Have you tried adding the Reply-to with another tool + inspecting the result?

MartinThoma avatar Aug 06 '23 06:08 MartinThoma

Here are two annotations that are linked. image

Here's the formatted pdf contents:

195 0 obj
<</AP 202 0 R>>
endobj
196 0 obj
<<	/AP	<<	/N 200 0 R
		/R 201 0 R
		>>
	/C[1.0 0.819611 0.0]
	/Contents(Comment 1)
	/CreationDate(D:20230803120415+02'00')
	/F 28
	/M(D:20230803120419+02'00')
	/NM(755dd31f-57df-46d5-91e7-eb52a97954c7)
	/Name/Comment
	/P 6 0 R
	/Popup 197 0 R
	/RC(<?xml version="1.0"?><body xmlns="http://www.w3.org/1999/xhtml" xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/" xfa:APIVersion="Acrobat:23.3.0" xfa:spec="2.0.2" ><p dir="ltr"><span dir="ltr" style="font-size:10.2pt;text-align:left;font-weight:normal;font-style:normal">Comment 1</span></p></body>)/Rect[591.451 511.575 615.451 535.575]
	/Subj(Sticky Note)
	/Subtype/Text
	/T(Simon)
	/Type/Annot>>
endobj
197 0 obj
<<	/F 28
	/Open false
	/Parent 196 0 R
	/Rect[2474.35 413.81 2658.48 535.685]
	/Subtype/Popup
	/Type/Annot
	>>
endobj
198 0 obj
<<	/AP	<<	/N 200 0 R
			/R 201 0 R
			>>
	/C[1.0 0.819611 0.0]
	/Contents(Comment 2)
	/CreationDate(D:20230803120428+02'00')
	/F 28
	/IRT 196 0 R
	/M(D:20230803120432+02'00')
	/NM(299bdaf6-4550-476a-b0ec-29a3e1162dec)
	/Name/Comment
	/P 6 0 R
	/Popup 199 0 R
	/RC(<?xml version="1.0"?><body xmlns="http://www.w3.org/1999/xhtml" xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/" xfa:APIVersion="Acrobat:23.3.0" xfa:spec="2.0.2" ><p dir="ltr"><span dir="ltr" style="font-size:10.2pt;text-align:left;font-weight:normal;font-style:normal">Comment 2</span></p></body>)/Rect[591.451 511.575 615.451 535.575]
	/Subj(Sticky Note)
	/Subtype/Text
	/T(Simon)
	/Type/Annot>>
endobj
199 0 obj
<<	/F 28
	/Open false
	/Parent 198 0 R
	/Rect[3465.39 443.575 3649.39 535.575]
	/Subtype/Popup
	/Type/Annot
	>>
endobj

and at the top of the pdf <</Annots[184 0 R 196 0 R 197 0 R 198 0 R 199 0 R]/Contents 181 0 R/Group<</CS/DeviceRGB/S/Transparency/Type/Group>>/MediaBox[0 0 3465.3945 792]/Parent 3 0 R/Resources 182 0 R/Type/Page>>

I'm posting this for reference. I'll try with 4 annotations like in the snippet above later this week.

C-monC avatar Aug 08 '23 17:08 C-monC