8

I recently discovered the pdfcomment package to annotate my latex-generated PDF files. Here is a minimum working example of what I have been doing:

\documentclass{article}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage{lmodern}
\usepackage[rgb]{xcolor}
\usepackage{pdfcomment}

\begin{document}
A comment appears here: \pdfcomment[icon=Insert,color=red,author={author1}]{comment1_text}
\end{document}

Now the following question arose: I would like to include the comment of someone else and provide an answer to it, which is appearing under the same icon. Much like I could do in Adobe Acrobat: I can add a note, write some text and then hit the reply button to type an answer. I would like to achieve the same, but in latex. Something along the lines of (in pseudo-code)

\begin{document}
A comment appears here: 
\pdfcomment[icon=Insert,color=red,author={author1}]{comment1_text}[author={author2}]{reply_author2}
\end{document}

where now the reply_author2 appears as a reply under the same note. Is something like this possible?


To provide some additional input. When I generate a very simple PDF with:

\documentclass{article}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage{lmodern}
\begin{document}
This is a test document to show annotation in LaTeX vs Adobe Acrobat. The goal is to have the same sort of reply ability as is in Adobe which is very important for collaboration on LaTeX generated documents with people who do not use LaTeX.
\end{document}

I can then open the PDF in Adobe sw and add annotation (insert text type). Then I use this python code:

from PyPDF2 import PdfFileReader
reader = PdfFileReader("test-ad.pdf")
for page in reader.pages:
    try :
        for annot in page["/Annots"] :
            print (annot.getObject())       # (1)
            print ("")
    except : 
        # there are no annotations on this page
        pass

to extract annotations directly from the PDF, I get:

{'/AP': {'/N': IndirectObject(10, 0, 139968916562832)}, '/C': [0, 0, 1], '/Contents': 'insert some text', '/CreationDate': "D:20230828101145-04'00'", '/F': 4, '/M': "D:20230828101153-04'00'", '/NM': '436dc8e6-1207-4d79-91be-a5432a1ce7b9', '/P': IndirectObject(9, 0, 139968916562832), '/Popup': IndirectObject(18, 0, 139968916562832), '/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:22.1.0" xfa:spec="2.0.2" ><p dir="ltr"><span dir="ltr" style="font-size:9.9pt;text-align:left;color:#000000;font-weight:normal;font-style:normal">insert some text</span></p></body>', '/RD': [0.781097, 0.781067, 0.781097, 0.781128], '/Rect': [253.424, 703.434, 264.928, 712.807], '/Subj': 'Inserted Text', '/Subtype': '/Caret', '/T': 'me', '/Type': '/Annot'}

{'/F': 28, '/Open': False, '/Parent': IndirectObject(17, 0, 139968916562832), '/Rect': [595.276, 598.026, 799.276, 712.026], '/Subtype': '/Popup', '/Type': '/Annot'}

When I open the file again and add reply to that annotation comment and again extract the annotations with python, I get:

{'/AP': {'/N': IndirectObject(10, 0, 139968905121360)}, '/C': [0, 0, 1], '/Contents': 'insert some text', '/CreationDate': "D:20230828101145-04'00'", '/F': 4, '/M': "D:20230828101153-04'00'", '/NM': '436dc8e6-1207-4d79-91be-a5432a1ce7b9', '/P': IndirectObject(9, 0, 139968905121360), '/Popup': IndirectObject(18, 0, 139968905121360), '/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:22.1.0" xfa:spec="2.0.2" ><p dir="ltr"><span dir="ltr" style="font-size:9.9pt;text-align:left;color:#000000;font-weight:normal;font-style:normal">insert some text</span></p></body>', '/RD': [0.781097, 0.781067, 0.781097, 0.781128], '/Rect': [253.424, 703.434, 264.928, 712.807], '/Subj': 'Inserted Text', '/Subtype': '/Caret', '/T': 'me', '/Type': '/Annot'}

{'/F': 28, '/Open': False, '/Parent': IndirectObject(17, 0, 139968905121360), '/Rect': [595.276, 598.026, 799.276, 712.026], '/Subtype': '/Popup', '/Type': '/Annot'}

{'/AP': {'/N': IndirectObject(28, 0, 139968905121360), '/R': IndirectObject(29, 0, 139968905121360)}, '/C': [1, 0.819611, 0], '/Contents': 'reply to the comment', '/CreationDate': "D:20230828101251-04'00'", '/F': 28, '/IRT': IndirectObject(17, 0, 139968905121360), '/M': "D:20230828101251-04'00'", '/NM': 'dfc46577-b2cb-4666-a159-31fc4cbef078', '/Name': '/Comment', '/P': IndirectObject(9, 0, 139968905121360), '/Popup': IndirectObject(27, 0, 139968905121360), '/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:22.1.0" xfa:spec="2.0.2" ><p dir="ltr"><span dir="ltr" style="font-size:9.8pt;text-align:left;font-weight:normal;font-style:normal">reply to the comment</span></p></body>', '/Rect': [253.424, 688.807, 277.424, 712.807], '/Subj': 'Sticky Note', '/Subtype': '/Text', '/T': 'me', '/Type': '/Annot'}

{'/F': 28, '/Open': False, '/Parent': IndirectObject(26, 0, 139968905121360), '/Rect': [595.276, 598.807, 799.276, 712.807], '/Subtype': '/Popup', '/Type': '/Annot'}

One thing I noticed is that the original comment has tag /C but the comment has in addition also /R. If that is all that is needed or there are some other details, I do not know. I guess one would need to understand the PDF specification.

0

2 Answers 2

7

While the ORIGINAL ANSWER below may serve for some users, the OP indicated an ongoing desire to have all of the conversation contained in a single comment.

Therefore, I have added this

ALTERNATIVE ANSWER

With this answer, the complete conversation occurs within the argument of the original \pdfcomment. Replies within the comment are accomplished with

\addreply{<style-name>} ...

where the style-name can be predefined, to include the author= field. In this example, styles Jim and Sue have been predefined and thus, when used as the argument to \addreply, the full author name ("Jim Jones" or "Sue Smith") is used. If the style is undefined, the style name itself is used as the author.

\documentclass{article}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage{lmodern}
\usepackage[rgb]{xcolor}
\usepackage{pdfcomment}
\usepackage{listofitems}
\let\svdefinestyle\definestyle
\renewcommand\definestyle[2]{%
  \defineauthor{#1}{#2}%
  \svdefinestyle{#1}{#2}%
}
\def\authorstring{author}
\newcommand\defineauthor[2]{%
  \setsepchar{,/=}%
  \readlist*\styleoption{#2}%
  \foreachitem\z\in\styleoption[]{%
    \itemtomacro\styleoption[\zcnt,1]\ztmp
    \ifx\ztmp\authorstring
      \expandafter\xdef\csname author#1\endcsname
        {\styleoption[\zcnt,2]}%
    \fi
  }%
}
\newcommand\theauthor[1]{\ifcsname author#1\endcsname
  \csname author#1\endcsname\else#1\fi}
\newcommand\addreply[1]{\textCR\textLF\textCR\textLF Reply
  \theauthor{#1}:\textCR\textLF\ignorespaces}
\definestyle{Jim}{color=red,author={Jim Jones}}
\definestyle{Sue}{color=blue,author={Sue Smith}}
\begin{document}
Jim's comment appears here:\pdfcomment[style=Jim]{%
Do I really need a comment here?
\addreply{Sue} 
Yes, Jim, I think you do.
\addreply{Jim} 
Very well, I guess I will leave it here.
}

An now for Sue's turn.\pdfcomment[style=Sue]{%
Jim, can you hear me now?
\addreply{Jim}
I sure can.
\addreply{Sam}
Hey, I'm just interloping into this conversation
}
\end{document}

As can be seen in the following image, the complete conversation now occurs in the context of a single comment. Each reply is prepended on its own line with "Reply author-name:". If a reply style is not associated with an author= field, the style name itself is used as the author, as in the case of Sam in the second conversation.

enter image description here

ORIGINAL ANSWER

This uses a series of horizontally connected \pdfsquarecomments to denote a comment/reply chain. The initial call is made with

\startcomment[<defaults for comment/reply chain>][<comment options>]{comment}

with subsequent replies, located sequentially in the source code, as

\addreply[<comment options>]{comment}

Here is the MWE, in which the styles Jim and Sue have been defined. Note that \addreply options can override the comment/reply-chain defaults. The size/shift of the comment boxes is defined by \mycommentsize, which here is set to .8\baselineskip. The header Reply: is added to the reply header by way of the author field, as in author={\commentmode Jim Jones}

\documentclass{article}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage{lmodern}
\usepackage[rgb]{xcolor}
\usepackage{pdfcomment}
\newlength\myhoffset
\def\mycommentsize{.8\baselineskip}% <--Can be changed
\newcommand\startcomment[1][]{%
  \catcode`_=12
  \gdef\commentdefault{#1}%
  \gdef\commentmode{}%
  \setlength\myhoffset{0pt}%
  \makecommentnext}
\newcommand\addreply{%
  \catcode`_=12
  \gdef\commentmode{Reply: }%
  \addtolength\myhoffset{\mycommentsize}%
  \makecommentnext}
\newcommand\makecommentnext[2][]{%
  \expandafter\pdfsquarecomment\expandafter
    [\commentdefault,hoffset=\myhoffset,
     open=true,opacity=.4,
     height=\mycommentsize,width=\mycommentsize,#1]{#2}%
  \catcode`_=8 }
\definestyle{Jim}{color=red,author={\commentmode Jim Jones}}
\definestyle{Sue}{color=blue,author={\commentmode Sue Smith}}
\begin{document}A comment appears here: 
\startcomment
  [borderstyle=dashed]% Global option for comment/reply chain
  [style={Jim}]
  {Does this require comment?}
\addreply
  [style={Sue},borderstyle=solid]% Can override global default
  {Yes, I think it does.}
\addreply
  [style={Jim}]
  {OK, I will leave it}
And we can contine the line since the comments will appear with opacity so that the underlying text can be read.

$a_b$ shows underscore is restored.
\end{document}

enter image description here

6
  • That is a good attempt but it really only adds more comments but it does not really add reply to comments in the sense that Adobe Acrobat would interpret it as such. If you add a reply to a comment in adobe acrobat, it will start a tree of comments but it will still be part of one comment.
    – atapaka
    Aug 25, 2023 at 13:22
  • @atapaka Yes, I understand the deficiency. It was more of a workaround attempt, rather than a direct answer to your request. Aug 25, 2023 at 14:00
  • @atapaka Please see my edit. Aug 25, 2023 at 16:02
  • This is better but still does not work as a reply. I tried to modify the original question to add more info that I have discovered but it is pending community review. In short, extracting the annotations from PDF I have found learnt that adobe inserts additional tags \R and \IRT to replies. I realize this is becoming quite complex. Is this even attainable without completely rewriting the pdfcomment package? It would, however, resolve collaboration issues and lack of track changes. The current solutions are insufficient to collaborate with people who do not use LaTeX.
    – atapaka
    Aug 28, 2023 at 14:31
  • @atapaka I see. It wasn't clear when I added my answers that you were collaborating with non-LaTeX users. My answers assume otherwise, as you have realized. Aug 28, 2023 at 15:08
4
+200

Well actually the package contains an (undocumented) command \pdfreply that sets the needed /IRT reference. The implementation is a bit buggy as it errors on the first compilation but by setting an arbitrary default it seems to work (you must compile twice).

\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{lmodern}
\usepackage[rgb]{xcolor}
\usepackage{pdfcomment}
\makeatletter
\renewcommand*{\pc@get@PDFOBJID}[1]%
 {%
  \zref@extractdefault{#1}{PCPDFOBJID}{S,0}%
 }%
\makeatother
\begin{document}
A comment appears here: 
\pdfcomment[id=A,icon=Insert,color=red,author={author1}]{comment1_text}
\pdfreply[replyto=A,author={author2}]{comment1_text}

\end{document}

enter image description here

1
  • That is perfect! Thank you. Just as a followup, isn't there by chance also hidden the ability to change the comment status (in acrobat, it is set status, with options {None, Accepted, Cancelled, Completed, Rejected}?
    – atapaka
    Aug 29, 2023 at 21:57

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .